abtars 0.1.0-alpha.1

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 (312) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +84 -0
  3. package/bundle/_registry.generated-M4WY2MMI.js +35 -0
  4. package/bundle/_registry.generated-M4WY2MMI.js.map +7 -0
  5. package/bundle/abtars-browser.js +162 -0
  6. package/bundle/abtars-browser.js.map +7 -0
  7. package/bundle/abtars-cli.js +1438 -0
  8. package/bundle/abtars-cli.js.map +7 -0
  9. package/bundle/abtars-restart.js +12 -0
  10. package/bundle/abtars-restart.js.map +7 -0
  11. package/bundle/abtars-rss.js +165 -0
  12. package/bundle/abtars-rss.js.map +7 -0
  13. package/bundle/abtars-task.js +258 -0
  14. package/bundle/abtars-task.js.map +7 -0
  15. package/bundle/abtars.js +4072 -0
  16. package/bundle/abtars.js.map +7 -0
  17. package/bundle/agent-api-rate-limit-OQNFMXTZ.js +38 -0
  18. package/bundle/agent-api-rate-limit-OQNFMXTZ.js.map +7 -0
  19. package/bundle/agent-registry-LT4JNQH6.js +18 -0
  20. package/bundle/agent-registry-LT4JNQH6.js.map +7 -0
  21. package/bundle/agents/default.md +29 -0
  22. package/bundle/anthropic-adapter-2APTH3LA.js +40 -0
  23. package/bundle/anthropic-adapter-2APTH3LA.js.map +7 -0
  24. package/bundle/bridge-lock-transport-4AC2G5G6.js +39 -0
  25. package/bundle/bridge-lock-transport-4AC2G5G6.js.map +7 -0
  26. package/bundle/browse-delivery-JXBY36GK.js +17 -0
  27. package/bundle/browse-delivery-JXBY36GK.js.map +7 -0
  28. package/bundle/browser-ELNDVPLC.js +18 -0
  29. package/bundle/browser-ELNDVPLC.js.map +7 -0
  30. package/bundle/capability-CIL3G4FI.js +17 -0
  31. package/bundle/capability-CIL3G4FI.js.map +7 -0
  32. package/bundle/chunk-265TPOPC.js +289 -0
  33. package/bundle/chunk-265TPOPC.js.map +7 -0
  34. package/bundle/chunk-2UENBO6M.js +223 -0
  35. package/bundle/chunk-2UENBO6M.js.map +7 -0
  36. package/bundle/chunk-2UPU3OW6.js +67 -0
  37. package/bundle/chunk-2UPU3OW6.js.map +7 -0
  38. package/bundle/chunk-2XU2X4OI.js +125 -0
  39. package/bundle/chunk-2XU2X4OI.js.map +7 -0
  40. package/bundle/chunk-3B7BBE4F.js +758 -0
  41. package/bundle/chunk-3B7BBE4F.js.map +7 -0
  42. package/bundle/chunk-3E545J66.js +69 -0
  43. package/bundle/chunk-3E545J66.js.map +7 -0
  44. package/bundle/chunk-5R2ANXQ7.js +510 -0
  45. package/bundle/chunk-5R2ANXQ7.js.map +7 -0
  46. package/bundle/chunk-6CPN4IGS.js +507 -0
  47. package/bundle/chunk-6CPN4IGS.js.map +7 -0
  48. package/bundle/chunk-6NR3OHEW.js +88 -0
  49. package/bundle/chunk-6NR3OHEW.js.map +7 -0
  50. package/bundle/chunk-6SETMHNN.js +206 -0
  51. package/bundle/chunk-6SETMHNN.js.map +7 -0
  52. package/bundle/chunk-6UCRKRWR.js +644 -0
  53. package/bundle/chunk-6UCRKRWR.js.map +7 -0
  54. package/bundle/chunk-AR6GO6YC.js +83 -0
  55. package/bundle/chunk-AR6GO6YC.js.map +7 -0
  56. package/bundle/chunk-AZJIODTQ.js +54 -0
  57. package/bundle/chunk-AZJIODTQ.js.map +7 -0
  58. package/bundle/chunk-BHMZ4RCC.js +3706 -0
  59. package/bundle/chunk-BHMZ4RCC.js.map +7 -0
  60. package/bundle/chunk-BQ2L4GMG.js +9175 -0
  61. package/bundle/chunk-BQ2L4GMG.js.map +7 -0
  62. package/bundle/chunk-BSSBCSCL.js +159 -0
  63. package/bundle/chunk-BSSBCSCL.js.map +7 -0
  64. package/bundle/chunk-BUUVFUPO.js +157 -0
  65. package/bundle/chunk-BUUVFUPO.js.map +7 -0
  66. package/bundle/chunk-CEVRHKJY.js +131 -0
  67. package/bundle/chunk-CEVRHKJY.js.map +7 -0
  68. package/bundle/chunk-CWOHNFUV.js +39 -0
  69. package/bundle/chunk-CWOHNFUV.js.map +7 -0
  70. package/bundle/chunk-D2DCBO6M.js +228 -0
  71. package/bundle/chunk-D2DCBO6M.js.map +7 -0
  72. package/bundle/chunk-FMWKEPM7.js +31 -0
  73. package/bundle/chunk-FMWKEPM7.js.map +7 -0
  74. package/bundle/chunk-GRNENTPA.js +145 -0
  75. package/bundle/chunk-GRNENTPA.js.map +7 -0
  76. package/bundle/chunk-GST5T3WZ.js +93 -0
  77. package/bundle/chunk-GST5T3WZ.js.map +7 -0
  78. package/bundle/chunk-GUQVJC3U.js +299 -0
  79. package/bundle/chunk-GUQVJC3U.js.map +7 -0
  80. package/bundle/chunk-HX7Y7EYP.js +3659 -0
  81. package/bundle/chunk-HX7Y7EYP.js.map +7 -0
  82. package/bundle/chunk-JCJS4ZIB.js +296 -0
  83. package/bundle/chunk-JCJS4ZIB.js.map +7 -0
  84. package/bundle/chunk-JW6RU47G.js +184 -0
  85. package/bundle/chunk-JW6RU47G.js.map +7 -0
  86. package/bundle/chunk-LSPKJQCI.js +24 -0
  87. package/bundle/chunk-LSPKJQCI.js.map +7 -0
  88. package/bundle/chunk-M6VBAPNT.js +16 -0
  89. package/bundle/chunk-M6VBAPNT.js.map +7 -0
  90. package/bundle/chunk-MPX525QO.js +129 -0
  91. package/bundle/chunk-MPX525QO.js.map +7 -0
  92. package/bundle/chunk-MW6WDLU7.js +130 -0
  93. package/bundle/chunk-MW6WDLU7.js.map +7 -0
  94. package/bundle/chunk-NT3OBORC.js +215 -0
  95. package/bundle/chunk-NT3OBORC.js.map +7 -0
  96. package/bundle/chunk-NWDBD4PA.js +50 -0
  97. package/bundle/chunk-NWDBD4PA.js.map +7 -0
  98. package/bundle/chunk-OP7BTAWY.js +29 -0
  99. package/bundle/chunk-OP7BTAWY.js.map +7 -0
  100. package/bundle/chunk-PLCY3GFH.js +77 -0
  101. package/bundle/chunk-PLCY3GFH.js.map +7 -0
  102. package/bundle/chunk-PNEDC45Y.js +97 -0
  103. package/bundle/chunk-PNEDC45Y.js.map +7 -0
  104. package/bundle/chunk-QBGBT5QS.js +81 -0
  105. package/bundle/chunk-QBGBT5QS.js.map +7 -0
  106. package/bundle/chunk-RVE2N7FA.js +70 -0
  107. package/bundle/chunk-RVE2N7FA.js.map +7 -0
  108. package/bundle/chunk-TZHIDLDS.js +71910 -0
  109. package/bundle/chunk-TZHIDLDS.js.map +7 -0
  110. package/bundle/chunk-UCQ2WC3B.js +126 -0
  111. package/bundle/chunk-UCQ2WC3B.js.map +7 -0
  112. package/bundle/chunk-UHRP745J.js +214 -0
  113. package/bundle/chunk-UHRP745J.js.map +7 -0
  114. package/bundle/chunk-V76TVMCM.js +58 -0
  115. package/bundle/chunk-V76TVMCM.js.map +7 -0
  116. package/bundle/chunk-VVEDVGCR.js +981 -0
  117. package/bundle/chunk-VVEDVGCR.js.map +7 -0
  118. package/bundle/chunk-W6FAL35D.js +102 -0
  119. package/bundle/chunk-W6FAL35D.js.map +7 -0
  120. package/bundle/chunk-X6TERNVJ.js +15902 -0
  121. package/bundle/chunk-X6TERNVJ.js.map +7 -0
  122. package/bundle/chunk-X76UX47U.js +47 -0
  123. package/bundle/chunk-X76UX47U.js.map +7 -0
  124. package/bundle/chunk-XREWVCUO.js +518 -0
  125. package/bundle/chunk-XREWVCUO.js.map +7 -0
  126. package/bundle/chunk-Y6XAEX2Q.js +408 -0
  127. package/bundle/chunk-Y6XAEX2Q.js.map +7 -0
  128. package/bundle/chunk-YOCTDKKL.js +28 -0
  129. package/bundle/chunk-YOCTDKKL.js.map +7 -0
  130. package/bundle/chunk-ZXPXCDA6.js +160 -0
  131. package/bundle/chunk-ZXPXCDA6.js.map +7 -0
  132. package/bundle/commands-BHVUOU3V.js +31 -0
  133. package/bundle/commands-BHVUOU3V.js.map +7 -0
  134. package/bundle/completion-buffer-P253ONKF.js +13 -0
  135. package/bundle/completion-buffer-P253ONKF.js.map +7 -0
  136. package/bundle/config-RGSDAPZN.js +19 -0
  137. package/bundle/config-RGSDAPZN.js.map +7 -0
  138. package/bundle/config-show-ERTATR6E.js +40 -0
  139. package/bundle/config-show-ERTATR6E.js.map +7 -0
  140. package/bundle/context-HCEGZNDC.js +72 -0
  141. package/bundle/context-HCEGZNDC.js.map +7 -0
  142. package/bundle/delegation-tools-GYTS2D6A.js +27 -0
  143. package/bundle/delegation-tools-GYTS2D6A.js.map +7 -0
  144. package/bundle/deploy-lib-import-32ZFKHWP.js +49 -0
  145. package/bundle/deploy-lib-import-32ZFKHWP.js.map +7 -0
  146. package/bundle/digital-signature-OFCGSHWO.js +13 -0
  147. package/bundle/digital-signature-OFCGSHWO.js.map +7 -0
  148. package/bundle/direct-api-transport-YR7SXXNN.js +860 -0
  149. package/bundle/direct-api-transport-YR7SXXNN.js.map +7 -0
  150. package/bundle/discord-adapter-YYWVMPPU.js +584 -0
  151. package/bundle/discord-adapter-YYWVMPPU.js.map +7 -0
  152. package/bundle/dist-MTMKARCP.js +1969 -0
  153. package/bundle/dist-MTMKARCP.js.map +7 -0
  154. package/bundle/dns-wakeup-27M7D2MR.js +107 -0
  155. package/bundle/dns-wakeup-27M7D2MR.js.map +7 -0
  156. package/bundle/doctor-QNUSDY73.js +248 -0
  157. package/bundle/doctor-QNUSDY73.js.map +7 -0
  158. package/bundle/ensure-invariants-NMXNS476.js +49 -0
  159. package/bundle/ensure-invariants-NMXNS476.js.map +7 -0
  160. package/bundle/env-schema-2KBHBDGN.js +19 -0
  161. package/bundle/env-schema-2KBHBDGN.js.map +7 -0
  162. package/bundle/esm-DDP6NCZG.js +100663 -0
  163. package/bundle/esm-DDP6NCZG.js.map +7 -0
  164. package/bundle/fallback-policy-L4QV2PEJ.js +46 -0
  165. package/bundle/fallback-policy-L4QV2PEJ.js.map +7 -0
  166. package/bundle/health-check-SPA7NT6N.js +56 -0
  167. package/bundle/health-check-SPA7NT6N.js.map +7 -0
  168. package/bundle/hook-system-6Q5YTR53.js +17 -0
  169. package/bundle/hook-system-6Q5YTR53.js.map +7 -0
  170. package/bundle/hotskills-K7BM4YLB.js +12 -0
  171. package/bundle/hotskills-K7BM4YLB.js.map +7 -0
  172. package/bundle/install-6HRZVKUM.js +15 -0
  173. package/bundle/install-6HRZVKUM.js.map +7 -0
  174. package/bundle/install-log-IAPHYKD4.js +28 -0
  175. package/bundle/install-log-IAPHYKD4.js.map +7 -0
  176. package/bundle/install-manifest-SPQRUNXL.js +102 -0
  177. package/bundle/install-manifest-SPQRUNXL.js.map +7 -0
  178. package/bundle/install-validate-PVLZXYLQ.js +53 -0
  179. package/bundle/install-validate-PVLZXYLQ.js.map +7 -0
  180. package/bundle/irc-adapter-OI5UZSQF.js +293 -0
  181. package/bundle/irc-adapter-OI5UZSQF.js.map +7 -0
  182. package/bundle/irc-config-55YO6EGB.js +88 -0
  183. package/bundle/irc-config-55YO6EGB.js.map +7 -0
  184. package/bundle/logs-ZNYXX5PA.js +19 -0
  185. package/bundle/logs-ZNYXX5PA.js.map +7 -0
  186. package/bundle/media-utils-XNNDTYFI.js +4662 -0
  187. package/bundle/media-utils-XNNDTYFI.js.map +7 -0
  188. package/bundle/message-pipeline-LLH5SYMO.js +33 -0
  189. package/bundle/message-pipeline-LLH5SYMO.js.map +7 -0
  190. package/bundle/meta.json +41304 -0
  191. package/bundle/model-health-registry-35LQNVQR.js +11 -0
  192. package/bundle/model-health-registry-35LQNVQR.js.map +7 -0
  193. package/bundle/notification-Y5S5MMLV.js +13 -0
  194. package/bundle/notification-Y5S5MMLV.js.map +7 -0
  195. package/bundle/openrouter-credits-EDY7ETAU.js +32 -0
  196. package/bundle/openrouter-credits-EDY7ETAU.js.map +7 -0
  197. package/bundle/passwd-RRFV4CC5.js +133 -0
  198. package/bundle/passwd-RRFV4CC5.js.map +7 -0
  199. package/bundle/paths-G33RZWZ7.js +17 -0
  200. package/bundle/paths-G33RZWZ7.js.map +7 -0
  201. package/bundle/peer-client-52XYMNI7.js +156 -0
  202. package/bundle/peer-client-52XYMNI7.js.map +7 -0
  203. package/bundle/peer-config-VK6EDLN5.js +16 -0
  204. package/bundle/peer-config-VK6EDLN5.js.map +7 -0
  205. package/bundle/peer-sessions-EAXTNQ36.js +49 -0
  206. package/bundle/peer-sessions-EAXTNQ36.js.map +7 -0
  207. package/bundle/pending-callback-RIMQZ7FJ.js +40 -0
  208. package/bundle/pending-callback-RIMQZ7FJ.js.map +7 -0
  209. package/bundle/phase-transport-KYERDL2O.js +22 -0
  210. package/bundle/phase-transport-KYERDL2O.js.map +7 -0
  211. package/bundle/public/css/dashboard.css +542 -0
  212. package/bundle/public/index.html +180 -0
  213. package/bundle/public/js/app.js +437 -0
  214. package/bundle/public/memory-universe.js +384 -0
  215. package/bundle/responses-adapter-AAQTY3K4.js +30 -0
  216. package/bundle/responses-adapter-AAQTY3K4.js.map +7 -0
  217. package/bundle/restore-ZE3SEPSS.js +46 -0
  218. package/bundle/restore-ZE3SEPSS.js.map +7 -0
  219. package/bundle/self-healer-utils-DMUUXC47.js +43 -0
  220. package/bundle/self-healer-utils-DMUUXC47.js.map +7 -0
  221. package/bundle/skill-stats-LLEXEXLR.js +22 -0
  222. package/bundle/skill-stats-LLEXEXLR.js.map +7 -0
  223. package/bundle/sleep-OYIUOVQD.js +19 -0
  224. package/bundle/sleep-OYIUOVQD.js.map +7 -0
  225. package/bundle/soul-loader-54WCVNLJ.js +16 -0
  226. package/bundle/soul-loader-54WCVNLJ.js.map +7 -0
  227. package/bundle/src-JL4PVO23.js +8 -0
  228. package/bundle/src-JL4PVO23.js.map +7 -0
  229. package/bundle/sse-parser-anthropic-P7CE2MH2.js +72 -0
  230. package/bundle/sse-parser-anthropic-P7CE2MH2.js.map +7 -0
  231. package/bundle/sse-parser-responses-EQQA5FWN.js +63 -0
  232. package/bundle/sse-parser-responses-EQQA5FWN.js.map +7 -0
  233. package/bundle/ssrf-guard-FZCBYIVW.js +64 -0
  234. package/bundle/ssrf-guard-FZCBYIVW.js.map +7 -0
  235. package/bundle/start-FH3GRMJ4.js +35 -0
  236. package/bundle/start-FH3GRMJ4.js.map +7 -0
  237. package/bundle/stream-single-WSG4D53C.js +33 -0
  238. package/bundle/stream-single-WSG4D53C.js.map +7 -0
  239. package/bundle/stt-2UH3RITX.js +14 -0
  240. package/bundle/stt-2UH3RITX.js.map +7 -0
  241. package/bundle/subagent-runtime-LE2ZXH3G.js +12 -0
  242. package/bundle/subagent-runtime-LE2ZXH3G.js.map +7 -0
  243. package/bundle/system-message-T5R3EYYN.js +30 -0
  244. package/bundle/system-message-T5R3EYYN.js.map +7 -0
  245. package/bundle/system-status-KQ6KHFJ6.js +189 -0
  246. package/bundle/system-status-KQ6KHFJ6.js.map +7 -0
  247. package/bundle/task-store-K7CQDEPI.js +22 -0
  248. package/bundle/task-store-K7CQDEPI.js.map +7 -0
  249. package/bundle/telegram-adapter-2V3XUMT5.js +1060 -0
  250. package/bundle/telegram-adapter-2V3XUMT5.js.map +7 -0
  251. package/bundle/tool-registry-MU3OX4UI.js +38 -0
  252. package/bundle/tool-registry-MU3OX4UI.js.map +7 -0
  253. package/bundle/tool-sandbox-VYOK4ZOA.js +20 -0
  254. package/bundle/tool-sandbox-VYOK4ZOA.js.map +7 -0
  255. package/bundle/transport-config-YLXU33RO.js +57 -0
  256. package/bundle/transport-config-YLXU33RO.js.map +7 -0
  257. package/bundle/update-QCW5LXRN.js +13 -0
  258. package/bundle/update-QCW5LXRN.js.map +7 -0
  259. package/bundle/update-check-27KZSAP6.js +12 -0
  260. package/bundle/update-check-27KZSAP6.js.map +7 -0
  261. package/bundle/usage-tracker-OVVEVMOY.js +17 -0
  262. package/bundle/usage-tracker-OVVEVMOY.js.map +7 -0
  263. package/bundle/user-registry-D4SD73UV.js +16 -0
  264. package/bundle/user-registry-D4SD73UV.js.map +7 -0
  265. package/core/professor.json +14 -0
  266. package/core/prompts/browsing_prompt.md +39 -0
  267. package/core/prompts/compaction.md +32 -0
  268. package/core/skills/memory/classification/SKILL.md +37 -0
  269. package/core/skills/memory/memory-anomalies/SKILL.md +39 -0
  270. package/core/skills/memory/memory-search/SKILL.md +48 -0
  271. package/core/skills/memory/topic-save/SKILL.md +44 -0
  272. package/core/skills/ops/cron/SKILL.md +51 -0
  273. package/core/skills/ops/gdrive-backup/SKILL.md +15 -0
  274. package/core/skills/ops/session-start/SKILL.md +11 -0
  275. package/core/skills/ops/skill-authoring/SKILL.md +54 -0
  276. package/core/skills/ops/system-health/SKILL.md +104 -0
  277. package/core/skills/ops/troubleshooting/SKILL.md +48 -0
  278. package/core/skills/ops/trust-gating/SKILL.md +30 -0
  279. package/core/skills/tools/a2a-communication/SKILL.md +68 -0
  280. package/core/skills/tools/browse-delegate/SKILL.md +27 -0
  281. package/core/skills/tools/browser/SKILL.md +36 -0
  282. package/core/skills/tools/clawhub/SKILL.md +44 -0
  283. package/core/skills/tools/delegation/SKILL.md +48 -0
  284. package/core/skills/tools/fxtwitter/SKILL.md +52 -0
  285. package/core/skills/tools/gmail/SKILL.md +44 -0
  286. package/core/skills/tools/irc-chat/SKILL.md +84 -0
  287. package/core/skills/tools/linear/SKILL.md +90 -0
  288. package/core/skills/tools/mcporter/SKILL.md +46 -0
  289. package/core/skills/tools/model-scout/SKILL.md +132 -0
  290. package/core/skills/tools/model-scout/scout-add-model.py +67 -0
  291. package/core/skills/tools/model-scout/scout-ollama.py +116 -0
  292. package/core/skills/tools/model-scout/scout-openrouter.py +85 -0
  293. package/core/skills/tools/nlm/SKILL.md +40 -0
  294. package/core/skills/tools/todo/SKILL.md +30 -0
  295. package/core/skills/tools/twitterX/SKILL.md +52 -0
  296. package/core/skills/tools/twitterX/scripts/abtars-tweet.js +532 -0
  297. package/core/skills/tools/twitterX/scripts/package.json +1 -0
  298. package/core/skills/tools/web-fetch/SKILL.md +29 -0
  299. package/package.json +59 -0
  300. package/scripts/abtars-daemon.service +23 -0
  301. package/scripts/abtars-fetch.sh +42 -0
  302. package/scripts/abtars-watchdog.service +13 -0
  303. package/scripts/abtars.sh +14 -0
  304. package/scripts/abtars@.service +21 -0
  305. package/scripts/browser-patchright.sh +79 -0
  306. package/scripts/com.abtars.daemon.plist +24 -0
  307. package/scripts/com.abtars.watchdog.plist +27 -0
  308. package/scripts/daily-backup.sh +62 -0
  309. package/scripts/doctor.sh +553 -0
  310. package/scripts/hooks/audit-logger.sh +22 -0
  311. package/scripts/upgrade-deps.sh +64 -0
  312. package/scripts/watchdog.sh +309 -0
@@ -0,0 +1,223 @@
1
+ import { createRequire as __bundleCreateRequire } from 'node:module'; import { fileURLToPath as __bundleFileURLToPath } from 'node:url'; import { dirname as __bundleDirname } from 'node:path'; const require = __bundleCreateRequire(import.meta.url); const __chunk_filename = __bundleFileURLToPath(import.meta.url); const __chunk_dirname = __bundleDirname(__chunk_filename);
2
+ import {
3
+ loadUsers
4
+ } from "./chunk-GST5T3WZ.js";
5
+ import {
6
+ readEnv
7
+ } from "./chunk-YOCTDKKL.js";
8
+ import {
9
+ getEnv,
10
+ init_env_schema
11
+ } from "./chunk-JCJS4ZIB.js";
12
+ import {
13
+ init_logger,
14
+ logError,
15
+ logWarn
16
+ } from "./chunk-BUUVFUPO.js";
17
+ import {
18
+ init_paths
19
+ } from "./chunk-X76UX47U.js";
20
+
21
+ // src/components/config.ts
22
+ import { access, stat, constants } from "node:fs/promises";
23
+ import { resolve } from "node:path";
24
+ import { homedir } from "node:os";
25
+
26
+ // src/types/config.ts
27
+ var CONFIG_DEFAULTS = {
28
+ transport: {
29
+ agentCliPath: "kiro-cli",
30
+ workingDir: "~/.abtars/workspace",
31
+ trustMode: false,
32
+ permissionTimeoutMs: 6e4,
33
+ tmuxSession: "kiro-bridge",
34
+ tmuxCaptureDelaySec: 3,
35
+ tmuxMaxWaitSec: 300
36
+ },
37
+ telegram: {
38
+ pollTimeoutS: 30
39
+ },
40
+ discord: {
41
+ enabled: false
42
+ },
43
+ voice: {
44
+ sttEnabled: false,
45
+ groqApiKey: "",
46
+ sttModel: "whisper-large-v3",
47
+ ttsEnabled: true,
48
+ ttsVoice: "en-US-AndrewMultilingualNeural"
49
+ },
50
+ logLevel: "low",
51
+ mcpDaemon: false
52
+ };
53
+
54
+ // src/components/env-utils.ts
55
+ function parseBoolEnv(key, fallback) {
56
+ const raw = process.env[key];
57
+ if (raw === void 0 || raw === "") return fallback;
58
+ return raw === "true" || raw === "1";
59
+ }
60
+ function parseNumberEnv(key, fallback) {
61
+ const raw = process.env[key];
62
+ if (raw === void 0 || raw === "") return fallback;
63
+ const n = Number(raw);
64
+ if (!Number.isFinite(n)) return fallback;
65
+ return n;
66
+ }
67
+
68
+ // src/components/config.ts
69
+ init_env_schema();
70
+ init_logger();
71
+ init_paths();
72
+ var BOT_TOKEN_REGEX = /^\d+:[A-Za-z0-9_-]+$/;
73
+ var SNOWFLAKE_REGEX = /^\d{17,20}$/;
74
+ function isValidSnowflake(value) {
75
+ return SNOWFLAKE_REGEX.test(value);
76
+ }
77
+ async function validateCliPath(cliPath) {
78
+ const isWindows = process.platform === "win32";
79
+ const isBareCommand = !cliPath.includes("/") && !cliPath.includes("\\");
80
+ if (isBareCommand) return;
81
+ if (isWindows) {
82
+ await access(cliPath, constants.F_OK);
83
+ } else {
84
+ await access(cliPath, constants.X_OK);
85
+ }
86
+ }
87
+ async function loadAndValidateConfig() {
88
+ const { loadTransport, validateAtStartup } = await import("./transport-config-YLXU33RO.js");
89
+ const tc = loadTransport();
90
+ if (tc) {
91
+ validateAtStartup();
92
+ } else {
93
+ logWarn("config", "transport.json not loaded \u2014 using .env defaults");
94
+ }
95
+ const token = getEnv().telegramBotToken ?? "";
96
+ const hasTelegramToken = BOT_TOKEN_REGEX.test(token);
97
+ const discordToken = getEnv().discordBotToken ?? "";
98
+ const hasDiscordToken = discordToken.length > 20;
99
+ if (!hasTelegramToken && !hasDiscordToken) {
100
+ logError("config", "No platform configured \u2014 set TELEGRAM_BOT_TOKEN or DISCORD_BOT_TOKEN in config/.env (run abtars onboard)");
101
+ }
102
+ const registry = loadUsers();
103
+ const telegramIds = registry.users.filter((u) => u.platforms.telegram).map((u) => u.platforms.telegram);
104
+ const mainChatId = getEnv().mainChatId;
105
+ if (mainChatId && hasTelegramToken) telegramIds.push(parseInt(mainChatId, 10));
106
+ const allowedUserIds = new Set(telegramIds.filter((id) => id > 0));
107
+ const hasDiscordUsers = registry.users.some((u) => u.platforms.discord);
108
+ if (allowedUserIds.size === 0 && !hasDiscordUsers) {
109
+ logError("config", "No users configured \u2014 create config/users.json or set MAIN_CHAT_ID");
110
+ }
111
+ const defaultCliPath = "kiro-cli";
112
+ const agentCliPath = getEnv().agentCliPath || defaultCliPath;
113
+ try {
114
+ await validateCliPath(agentCliPath);
115
+ } catch {
116
+ logError("config", `CLI binary "${agentCliPath}" is not accessible or not executable \u2014 will fail at prompt time`);
117
+ }
118
+ let workingDir = getEnv().workingDir;
119
+ if (workingDir.startsWith("~")) {
120
+ workingDir = resolve(homedir(), workingDir.slice(1).replace(/^[/\\]/, ""));
121
+ }
122
+ try {
123
+ const info = await stat(workingDir);
124
+ if (!info.isDirectory()) {
125
+ throw new Error(
126
+ `WORKING_DIR "${workingDir}" exists but is not a directory`
127
+ );
128
+ }
129
+ } catch (err) {
130
+ if (err instanceof Error && err.message.startsWith("WORKING_DIR")) {
131
+ throw err;
132
+ }
133
+ const { mkdirSync } = await import("node:fs");
134
+ mkdirSync(workingDir, { recursive: true });
135
+ }
136
+ const trustMode = parseBoolEnv(
137
+ "TRUST_MODE",
138
+ CONFIG_DEFAULTS.transport.trustMode
139
+ );
140
+ const permissionTimeoutMs = getEnv().permissionTimeoutMs;
141
+ const pollTimeoutS = parseNumberEnv(
142
+ "POLL_TIMEOUT_S",
143
+ CONFIG_DEFAULTS.telegram.pollTimeoutS
144
+ );
145
+ const tmuxSession = getEnv().tmuxSession;
146
+ const tmuxCaptureDelaySec = parseNumberEnv(
147
+ "TMUX_CAPTURE_DELAY_SEC",
148
+ CONFIG_DEFAULTS.transport.tmuxCaptureDelaySec
149
+ );
150
+ const tmuxMaxWaitSec = parseNumberEnv(
151
+ "TMUX_MAX_WAIT_SEC",
152
+ CONFIG_DEFAULTS.transport.tmuxMaxWaitSec
153
+ );
154
+ const rawLogLevel = getEnv().logLevel;
155
+ if (rawLogLevel !== "off" && rawLogLevel !== "low" && rawLogLevel !== "debug" && rawLogLevel !== "trace") {
156
+ logError("config", `LOG_LEVEL must be "off", "low", "debug", or "trace", got "${rawLogLevel}" \u2014 defaulting to "low"`);
157
+ }
158
+ const logLevel = rawLogLevel === "off" || rawLogLevel === "low" || rawLogLevel === "debug" || rawLogLevel === "trace" ? rawLogLevel : "low";
159
+ const groqApiKey = readEnv("GROQ_API_KEY", "STT/voice notes disabled") ?? "";
160
+ const sttEnabled = parseBoolEnv("STT_ENABLED", groqApiKey.length > 0);
161
+ const sttModel = getEnv().sttModel;
162
+ const ttsEnabled = parseBoolEnv("TTS_ENABLED", CONFIG_DEFAULTS.voice.ttsEnabled);
163
+ const ttsVoice = getEnv().ttsVoice;
164
+ const discordBotToken = getEnv().discordBotToken;
165
+ const discordEnabled = !!discordBotToken;
166
+ let discordAppId;
167
+ let discordAllowedUserIds;
168
+ if (discordEnabled) {
169
+ const rawAppId = getEnv().discordAppId;
170
+ if (!rawAppId || !isValidSnowflake(rawAppId)) {
171
+ throw new Error(
172
+ "DISCORD_APP_ID is required and must be a valid Discord snowflake ID (17\u201320 digits) when DISCORD_BOT_TOKEN is set"
173
+ );
174
+ }
175
+ discordAppId = rawAppId;
176
+ const discordUsers = registry.users.filter((u) => u.platforms.discord);
177
+ discordAllowedUserIds = new Set(discordUsers.map((u) => u.platforms.discord));
178
+ if (discordAllowedUserIds.size === 0) {
179
+ throw new Error(
180
+ "No Discord users in users.json \u2014 add at least one user with platforms.discord"
181
+ );
182
+ }
183
+ }
184
+ const resolvedMainChatId = mainChatId ?? String([...allowedUserIds][0] ?? "");
185
+ return {
186
+ mainChatId: resolvedMainChatId,
187
+ telegram: {
188
+ botToken: token,
189
+ allowedUserIds,
190
+ pollTimeoutS
191
+ },
192
+ discord: {
193
+ enabled: discordEnabled,
194
+ botToken: discordBotToken,
195
+ appId: discordAppId,
196
+ allowedUserIds: discordAllowedUserIds
197
+ },
198
+ transport: {
199
+ agentCliPath,
200
+ workingDir,
201
+ trustMode,
202
+ permissionTimeoutMs,
203
+ tmuxSession,
204
+ tmuxCaptureDelaySec,
205
+ tmuxMaxWaitSec
206
+ },
207
+ voice: {
208
+ sttEnabled,
209
+ groqApiKey,
210
+ sttModel,
211
+ ttsEnabled,
212
+ ttsVoice
213
+ },
214
+ logLevel,
215
+ mcpDaemon: parseBoolEnv("MCPORTER_DAEMON", CONFIG_DEFAULTS.mcpDaemon)
216
+ };
217
+ }
218
+
219
+ export {
220
+ isValidSnowflake,
221
+ loadAndValidateConfig
222
+ };
223
+ //# sourceMappingURL=chunk-2UENBO6M.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/components/config.ts", "../src/types/config.ts", "../src/components/env-utils.ts"],
4
+ "sourcesContent": ["import { access, stat, constants } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { type Config, CONFIG_DEFAULTS } from \"../types/index.js\";\nimport { loadUsers } from \"./user-registry.js\";\nimport { parseBoolEnv, parseNumberEnv } from \"./env-utils.js\";\nimport { readEnv } from \"./env.js\";\nimport { getEnv } from \"./env-schema.js\";\nimport { logWarn, logError } from \"./logger.js\";\nimport type { LogLevel } from \"./logger.js\";\nexport { abtarsHome } from \"../paths.js\";\n\nconst BOT_TOKEN_REGEX = /^\\d+:[A-Za-z0-9_-]+$/;\nconst SNOWFLAKE_REGEX = /^\\d{17,20}$/;\n\n/**\n * Validate that a string is a valid Discord snowflake ID (17\u201320 digits).\n */\nexport function isValidSnowflake(value: string): boolean {\n return SNOWFLAKE_REGEX.test(value);\n}\n\n/**\n * Parse a comma-separated string of Discord snowflake IDs into a Set.\n * Trims whitespace and ignores empty segments.\n * Throws if any non-empty segment is not a valid snowflake.\n */\n\n/**\n * Parse a comma-separated string of user IDs into a Set of numbers.\n * Trims whitespace, ignores empty segments and non-numeric values.\n */\n\n/**\n * Check whether the kiro-cli path is executable.\n * On Windows, skip the X_OK check \u2014 just verify the path exists or treat\n * bare command names (no path separator) as valid.\n */\nasync function validateCliPath(cliPath: string): Promise<void> {\n const isWindows = process.platform === \"win32\";\n const isBareCommand = !cliPath.includes(\"/\") && !cliPath.includes(\"\\\\\");\n\n // Bare command names (e.g. \"kiro-cli\") rely on PATH resolution at runtime.\n // We can't reliably check those with fs.access, so accept them as-is.\n if (isBareCommand) return;\n\n if (isWindows) {\n // On Windows just verify the file exists (no X_OK support).\n await access(cliPath, constants.F_OK);\n } else {\n await access(cliPath, constants.X_OK);\n }\n}\n\n/**\n * Load configuration from `process.env`, validate every field, and return a\n * typed `Config` object. Throws with a descriptive message on any failure.\n *\n * Env-file loading is the caller's responsibility (dotenv or `--env-file`).\n */\nexport async function loadAndValidateConfig(): Promise<Config> {\n // Env is loaded by src/boot/env.ts (imported first in main.ts). By the time\n // this function runs, `.env` + `.env.skills` + `./.env` are already merged\n // into process.env with process.env precedence preserved.\n\n // Load transport.json + models.json (replaces transport profile .env files)\n const { loadTransport, validateAtStartup } = await import(\"./transport-config.js\");\n const tc = loadTransport();\n if (tc) {\n validateAtStartup();\n } else {\n logWarn(\"config\", \"transport.json not loaded \u2014 using .env defaults\");\n }\n\n // --- TELEGRAM_BOT_TOKEN (optional \u2014 only required if TG is the desired platform) ---\n const token = getEnv().telegramBotToken ?? \"\";\n const hasTelegramToken = BOT_TOKEN_REGEX.test(token);\n\n // --- DISCORD_BOT_TOKEN (optional) ---\n const discordToken = getEnv().discordBotToken ?? \"\";\n const hasDiscordToken = discordToken.length > 20;\n\n if (!hasTelegramToken && !hasDiscordToken) {\n logError(\"config\", \"No platform configured \u2014 set TELEGRAM_BOT_TOKEN or DISCORD_BOT_TOKEN in config/.env (run abtars onboard)\");\n }\n\n // --- User IDs (from users.json, fallback to MAIN_CHAT_ID for TG only) ---\n const registry = loadUsers();\n const telegramIds = registry.users\n .filter(u => u.platforms.telegram)\n .map(u => u.platforms.telegram!);\n const mainChatId = getEnv().mainChatId;\n if (mainChatId && hasTelegramToken) telegramIds.push(parseInt(mainChatId, 10));\n const allowedUserIds = new Set(telegramIds.filter(id => id > 0));\n const hasDiscordUsers = registry.users.some(u => u.platforms.discord);\n if (allowedUserIds.size === 0 && !hasDiscordUsers) {\n logError(\"config\", \"No users configured \u2014 create config/users.json or set MAIN_CHAT_ID\");\n }\n\n // --- AGENT_CLI_PATH ---\n const defaultCliPath = \"kiro-cli\";\n const agentCliPath = getEnv().agentCliPath || defaultCliPath;\n try {\n await validateCliPath(agentCliPath);\n } catch {\n logError(\"config\", `CLI binary \"${agentCliPath}\" is not accessible or not executable \u2014 will fail at prompt time`);\n }\n\n // --- WORKING_DIR (optional, default cwd) ---\n let workingDir = getEnv().workingDir;\n // Expand ~ to home directory (Node doesn't do this automatically)\n if (workingDir.startsWith(\"~\")) {\n workingDir = resolve(homedir(), workingDir.slice(1).replace(/^[/\\\\]/, \"\"));\n }\n try {\n const info = await stat(workingDir);\n if (!info.isDirectory()) {\n throw new Error(\n `WORKING_DIR \"${workingDir}\" exists but is not a directory`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"WORKING_DIR\")) {\n throw err;\n }\n // Create if missing (lazy dir)\n const { mkdirSync } = await import(\"node:fs\");\n mkdirSync(workingDir, { recursive: true });\n }\n\n // --- TRUST_MODE (optional boolean, default false) ---\n const trustMode = parseBoolEnv(\n \"TRUST_MODE\",\n CONFIG_DEFAULTS.transport.trustMode,\n );\n\n // --- PERMISSION_TIMEOUT (from env-schema: PERMISSION_TIMEOUT_SEC * 1000) ---\n const permissionTimeoutMs = getEnv().permissionTimeoutMs;\n\n // --- POLL_TIMEOUT_S (optional number, default 30) ---\n const pollTimeoutS = parseNumberEnv(\n \"POLL_TIMEOUT_S\",\n CONFIG_DEFAULTS.telegram.pollTimeoutS,\n );\n\n\n // --- TMUX_SESSION (optional, default \"kiro-bridge\") ---\n const tmuxSession = getEnv().tmuxSession;\n\n // --- TMUX_CAPTURE_DELAY_SEC (optional, default 3) ---\n const tmuxCaptureDelaySec = parseNumberEnv(\n \"TMUX_CAPTURE_DELAY_SEC\",\n CONFIG_DEFAULTS.transport.tmuxCaptureDelaySec,\n );\n\n // --- TMUX_MAX_WAIT_SEC (optional, default 300) ---\n const tmuxMaxWaitSec = parseNumberEnv(\n \"TMUX_MAX_WAIT_SEC\",\n CONFIG_DEFAULTS.transport.tmuxMaxWaitSec,\n );\n\n // --- LOG_LEVEL (optional, default \"low\") ---\n const rawLogLevel = getEnv().logLevel;\n if (rawLogLevel !== \"off\" && rawLogLevel !== \"low\" && rawLogLevel !== \"debug\" && rawLogLevel !== \"trace\") {\n logError(\"config\", `LOG_LEVEL must be \"off\", \"low\", \"debug\", or \"trace\", got \"${rawLogLevel}\" \u2014 defaulting to \"low\"`);\n }\n const logLevel = (rawLogLevel === \"off\" || rawLogLevel === \"low\" || rawLogLevel === \"debug\" || rawLogLevel === \"trace\" ? rawLogLevel : \"low\") as LogLevel;\n\n // --- GROQ_API_KEY (optional, enables STT) ---\n const groqApiKey = readEnv(\"GROQ_API_KEY\", \"STT/voice notes disabled\") ?? \"\";\n const sttEnabled = parseBoolEnv(\"STT_ENABLED\", groqApiKey.length > 0);\n const sttModel = getEnv().sttModel;\n\n // --- TTS (optional, default enabled) ---\n const ttsEnabled = parseBoolEnv(\"TTS_ENABLED\", CONFIG_DEFAULTS.voice.ttsEnabled);\n const ttsVoice = getEnv().ttsVoice;\n\n // --- DISCORD_BOT_TOKEN (optional \u2014 Discord disabled if absent) ---\n const discordBotToken = getEnv().discordBotToken;\n const discordEnabled = !!discordBotToken;\n\n let discordAppId: string | undefined;\n let discordAllowedUserIds: Set<string> | undefined;\n\n if (discordEnabled) {\n // --- DISCORD_APP_ID (required when Discord enabled) ---\n const rawAppId = getEnv().discordAppId;\n if (!rawAppId || !isValidSnowflake(rawAppId)) {\n throw new Error(\n \"DISCORD_APP_ID is required and must be a valid Discord snowflake ID (17\u201320 digits) when DISCORD_BOT_TOKEN is set\",\n );\n }\n discordAppId = rawAppId;\n // Discord user IDs from users.json\n const discordUsers = registry.users.filter(u => u.platforms.discord);\n discordAllowedUserIds = new Set(discordUsers.map(u => u.platforms.discord!));\n if (discordAllowedUserIds.size === 0) {\n throw new Error(\n \"No Discord users in users.json \u2014 add at least one user with platforms.discord\",\n );\n }\n }\n\n const resolvedMainChatId = mainChatId ?? String([...allowedUserIds][0] ?? \"\");\n\n return {\n mainChatId: resolvedMainChatId,\n telegram: {\n botToken: token,\n allowedUserIds,\n pollTimeoutS,\n },\n discord: {\n enabled: discordEnabled,\n botToken: discordBotToken,\n appId: discordAppId,\n allowedUserIds: discordAllowedUserIds,\n },\n transport: {\n agentCliPath,\n workingDir,\n trustMode,\n permissionTimeoutMs,\n tmuxSession,\n tmuxCaptureDelaySec,\n tmuxMaxWaitSec,\n },\n voice: {\n sttEnabled,\n groqApiKey,\n sttModel,\n ttsEnabled,\n ttsVoice,\n },\n logLevel,\n mcpDaemon: parseBoolEnv(\"MCPORTER_DAEMON\", CONFIG_DEFAULTS.mcpDaemon),\n };\n}\n", "import type { LogLevel } from \"../components/logger.js\";\n\n/** Transport method for communicating with the agent CLI. */\nexport type AgentTransport = \"tmux\" | \"acp\" | \"api\";\n\nexport type TelegramConfig = {\n botToken: string;\n allowedUserIds: Set<number>;\n pollTimeoutS: number;\n};\n\nexport type DiscordConfig = {\n enabled: boolean;\n botToken?: string;\n appId?: string;\n allowedUserIds?: Set<string>;\n};\n\nexport type TransportConfig = {\n agentCliPath: string;\n workingDir: string;\n trustMode: boolean;\n permissionTimeoutMs: number;\n tmuxSession: string;\n tmuxCaptureDelaySec: number;\n tmuxMaxWaitSec: number;\n};\n\nexport type VoiceConfig = {\n sttEnabled: boolean;\n groqApiKey: string;\n sttModel: string;\n ttsEnabled: boolean;\n ttsVoice: string;\n};\n\n/** Bridge configuration loaded from .env and validated at startup. */\nexport type Config = {\n mainChatId: string;\n telegram: TelegramConfig;\n discord: DiscordConfig;\n transport: TransportConfig;\n voice: VoiceConfig;\n logLevel: LogLevel;\n mcpDaemon: boolean;\n};\n\n/** Default values for optional config fields. */\nexport const CONFIG_DEFAULTS = {\n transport: {\n agentCliPath: \"kiro-cli\",\n workingDir: \"~/.abtars/workspace\",\n trustMode: false,\n permissionTimeoutMs: 60_000,\n tmuxSession: \"kiro-bridge\",\n tmuxCaptureDelaySec: 3,\n tmuxMaxWaitSec: 300,\n },\n telegram: {\n pollTimeoutS: 30,\n },\n discord: {\n enabled: false,\n },\n voice: {\n sttEnabled: false,\n groqApiKey: \"\",\n sttModel: \"whisper-large-v3\",\n ttsEnabled: true,\n ttsVoice: \"en-US-AndrewMultilingualNeural\",\n },\n logLevel: \"low\" as LogLevel,\n mcpDaemon: false,\n} as const;\n", "/** Shared env-parsing utilities. */\n\nexport function parseBoolEnv(key: string, fallback: boolean): boolean {\n const raw = process.env[key];\n if (raw === undefined || raw === \"\") return fallback;\n return raw === \"true\" || raw === \"1\";\n}\n\nexport function parsePositiveIntEnv(key: string, fallback: number): number {\n const raw = process.env[key];\n if (raw === undefined || raw === \"\") return fallback;\n const n = Number(raw);\n if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {\n console.warn(`[env] Invalid ${key}=\"${raw}\", using default ${fallback}`);\n return fallback;\n }\n return n;\n}\n\nexport function parseNumberEnv(key: string, fallback: number): number {\n const raw = process.env[key];\n if (raw === undefined || raw === \"\") return fallback;\n const n = Number(raw);\n if (!Number.isFinite(n)) return fallback;\n return n;\n}\n\nexport function parseStringEnv(key: string, fallback: string): string {\n const raw = process.env[key];\n if (raw === undefined || raw.trim() === \"\") return fallback;\n return raw;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,67 @@
1
+ import { createRequire as __bundleCreateRequire } from 'node:module'; import { fileURLToPath as __bundleFileURLToPath } from 'node:url'; import { dirname as __bundleDirname } from 'node:path'; const require = __bundleCreateRequire(import.meta.url); const __chunk_filename = __bundleFileURLToPath(import.meta.url); const __chunk_dirname = __bundleDirname(__chunk_filename);
2
+ import {
3
+ init_logger,
4
+ logWarn
5
+ } from "./chunk-BUUVFUPO.js";
6
+ import {
7
+ __esm
8
+ } from "./chunk-NWDBD4PA.js";
9
+
10
+ // src/components/config-validator.ts
11
+ function getPath(obj, path) {
12
+ let current = obj;
13
+ for (const key of path.split(".")) {
14
+ if (current == null || typeof current !== "object") return void 0;
15
+ current = current[key];
16
+ }
17
+ return current;
18
+ }
19
+ function checkType(val, expected) {
20
+ if (expected === "array") return Array.isArray(val);
21
+ if (expected === "object") return val !== null && typeof val === "object" && !Array.isArray(val);
22
+ return typeof val === expected;
23
+ }
24
+ function validateShape(config, specs, filename) {
25
+ const errors = [];
26
+ for (const spec of specs) {
27
+ const val = getPath(config, spec.path);
28
+ if (val === void 0 || val === null) {
29
+ if (spec.required) errors.push(`${spec.path} is required`);
30
+ } else if (!checkType(val, spec.type)) {
31
+ errors.push(`${spec.path} must be ${spec.type}, got ${Array.isArray(val) ? "array" : typeof val}`);
32
+ }
33
+ }
34
+ if (errors.length > 0) {
35
+ for (const e of errors) logWarn(TAG, `${filename}: ${e}`);
36
+ }
37
+ return errors;
38
+ }
39
+ var TAG, TRANSPORT_SCHEMA, IRC_SCHEMA, PEERS_SCHEMA;
40
+ var init_config_validator = __esm({
41
+ "src/components/config-validator.ts"() {
42
+ "use strict";
43
+ init_logger();
44
+ TAG = "config";
45
+ TRANSPORT_SCHEMA = [
46
+ { path: "agents", type: "object", required: true },
47
+ { path: "providers", type: "object", required: true }
48
+ ];
49
+ IRC_SCHEMA = [
50
+ { path: "servers", type: "array", required: true }
51
+ ];
52
+ PEERS_SCHEMA = [
53
+ { path: "self", type: "object", required: true },
54
+ { path: "self.name", type: "string", required: true },
55
+ { path: "peers", type: "object", required: true }
56
+ ];
57
+ }
58
+ });
59
+
60
+ export {
61
+ validateShape,
62
+ TRANSPORT_SCHEMA,
63
+ IRC_SCHEMA,
64
+ PEERS_SCHEMA,
65
+ init_config_validator
66
+ };
67
+ //# sourceMappingURL=chunk-2UPU3OW6.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/components/config-validator.ts"],
4
+ "sourcesContent": ["/**\n * config-validator.ts \u2014 lightweight shape validation for JSON config files (#434).\n * No external deps. Logs warnings, never throws. Boot continues with best-effort.\n */\n\nimport { logWarn } from \"./logger.js\";\n\nconst TAG = \"config\";\n\ntype FieldType = \"string\" | \"number\" | \"boolean\" | \"object\" | \"array\";\n\nexport interface FieldSpec {\n path: string;\n type: FieldType;\n required?: boolean;\n}\n\nfunction getPath(obj: unknown, path: string): unknown {\n let current: unknown = obj;\n for (const key of path.split(\".\")) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction checkType(val: unknown, expected: FieldType): boolean {\n if (expected === \"array\") return Array.isArray(val);\n if (expected === \"object\") return val !== null && typeof val === \"object\" && !Array.isArray(val);\n return typeof val === expected;\n}\n\nexport function validateShape(config: unknown, specs: FieldSpec[], filename: string): string[] {\n const errors: string[] = [];\n for (const spec of specs) {\n const val = getPath(config, spec.path);\n if (val === undefined || val === null) {\n if (spec.required) errors.push(`${spec.path} is required`);\n } else if (!checkType(val, spec.type)) {\n errors.push(`${spec.path} must be ${spec.type}, got ${Array.isArray(val) ? \"array\" : typeof val}`);\n }\n }\n if (errors.length > 0) {\n for (const e of errors) logWarn(TAG, `${filename}: ${e}`);\n }\n return errors;\n}\n\n// \u2500\u2500 Schema specs for each config file \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const TRANSPORT_SCHEMA: FieldSpec[] = [\n { path: \"agents\", type: \"object\", required: true },\n { path: \"providers\", type: \"object\", required: true },\n];\n\nexport const MODELS_SCHEMA: FieldSpec[] = [\n // models.json is a Record<string, ModelEntry> \u2014 just check it's an object\n];\n\nexport const IRC_SCHEMA: FieldSpec[] = [\n { path: \"servers\", type: \"array\", required: true },\n];\n\nexport const PEERS_SCHEMA: FieldSpec[] = [\n { path: \"self\", type: \"object\", required: true },\n { path: \"self.name\", type: \"string\", required: true },\n { path: \"peers\", type: \"object\", required: true },\n];\n\nexport const USERS_SCHEMA: FieldSpec[] = [\n { path: \"users\", type: \"array\", required: true },\n];\n"],
5
+ "mappings": ";;;;;;;;;;AAiBA,SAAS,QAAQ,KAAc,MAAuB;AACpD,MAAI,UAAmB;AACvB,aAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AACjC,QAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,cAAW,QAAoC,GAAG;AAAA,EACpD;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAAc,UAA8B;AAC7D,MAAI,aAAa,QAAS,QAAO,MAAM,QAAQ,GAAG;AAClD,MAAI,aAAa,SAAU,QAAO,QAAQ,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG;AAC/F,SAAO,OAAO,QAAQ;AACxB;AAEO,SAAS,cAAc,QAAiB,OAAoB,UAA4B;AAC7F,QAAM,SAAmB,CAAC;AAC1B,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,QAAQ,QAAQ,KAAK,IAAI;AACrC,QAAI,QAAQ,UAAa,QAAQ,MAAM;AACrC,UAAI,KAAK,SAAU,QAAO,KAAK,GAAG,KAAK,IAAI,cAAc;AAAA,IAC3D,WAAW,CAAC,UAAU,KAAK,KAAK,IAAI,GAAG;AACrC,aAAO,KAAK,GAAG,KAAK,IAAI,YAAY,KAAK,IAAI,SAAS,MAAM,QAAQ,GAAG,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IACnG;AAAA,EACF;AACA,MAAI,OAAO,SAAS,GAAG;AACrB,eAAW,KAAK,OAAQ,SAAQ,KAAK,GAAG,QAAQ,KAAK,CAAC,EAAE;AAAA,EAC1D;AACA,SAAO;AACT;AA9CA,IAOM,KA2CO,kBASA,YAIA;AA/Db;AAAA;AAAA;AAKA;AAEA,IAAM,MAAM;AA2CL,IAAM,mBAAgC;AAAA,MAC3C,EAAE,MAAM,UAAU,MAAM,UAAU,UAAU,KAAK;AAAA,MACjD,EAAE,MAAM,aAAa,MAAM,UAAU,UAAU,KAAK;AAAA,IACtD;AAMO,IAAM,aAA0B;AAAA,MACrC,EAAE,MAAM,WAAW,MAAM,SAAS,UAAU,KAAK;AAAA,IACnD;AAEO,IAAM,eAA4B;AAAA,MACvC,EAAE,MAAM,QAAQ,MAAM,UAAU,UAAU,KAAK;AAAA,MAC/C,EAAE,MAAM,aAAa,MAAM,UAAU,UAAU,KAAK;AAAA,MACpD,EAAE,MAAM,SAAS,MAAM,UAAU,UAAU,KAAK;AAAA,IAClD;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -0,0 +1,125 @@
1
+ import { createRequire as __bundleCreateRequire } from 'node:module'; import { fileURLToPath as __bundleFileURLToPath } from 'node:url'; import { dirname as __bundleDirname } from 'node:path'; const require = __bundleCreateRequire(import.meta.url); const __chunk_filename = __bundleFileURLToPath(import.meta.url); const __chunk_dirname = __bundleDirname(__chunk_filename);
2
+
3
+ // src/components/transport/model-health-registry.ts
4
+ var D_SKIP_THRESHOLD = 0.7;
5
+ var D_LEAK_PER_MIN = 0.03;
6
+ var D_AUTH_STICKY = true;
7
+ var D_RATE_LIMIT_FILL = 0.5;
8
+ var D_WEAK_FILL = 0.35;
9
+ var D_TRANSIENT_PROGRESSIVE = [0.1, 0.2, 0.4, 0.8];
10
+ var D_TRANSIENT_COOLDOWN_AFTER = 3;
11
+ var D_TRANSIENT_MAX_COOLDOWN_SEC = 300;
12
+ var ModelHealthRegistry = class {
13
+ buckets = /* @__PURE__ */ new Map();
14
+ skipThreshold;
15
+ leakPerMs;
16
+ authSticky;
17
+ rateLimitFill;
18
+ weakFill;
19
+ transientProgressive;
20
+ transientCooldownAfter;
21
+ transientMaxCooldownMs;
22
+ /** Fired when a model crosses the demotion threshold. Wired at boot. */
23
+ onDemote;
24
+ constructor(config) {
25
+ this.skipThreshold = config?.skipThreshold ?? D_SKIP_THRESHOLD;
26
+ this.leakPerMs = (config?.leakPerMinute ?? D_LEAK_PER_MIN) / 6e4;
27
+ this.authSticky = config?.authSticky ?? D_AUTH_STICKY;
28
+ this.rateLimitFill = config?.rateLimitFill ?? D_RATE_LIMIT_FILL;
29
+ this.weakFill = config?.weakFill ?? D_WEAK_FILL;
30
+ this.transientProgressive = config?.transientProgressive ?? D_TRANSIENT_PROGRESSIVE;
31
+ this.transientCooldownAfter = config?.transientCooldownAfter ?? D_TRANSIENT_COOLDOWN_AFTER;
32
+ this.transientMaxCooldownMs = (config?.transientMaxCooldownSec ?? D_TRANSIENT_MAX_COOLDOWN_SEC) * 1e3;
33
+ }
34
+ drain(b, now) {
35
+ if (this.authSticky && b.authFailed) return;
36
+ const elapsed = now - b.lastUpdate;
37
+ b.level = Math.max(0, b.level - elapsed * this.leakPerMs);
38
+ b.lastUpdate = now;
39
+ }
40
+ shouldSkip(model, endpoint) {
41
+ const b = this.buckets.get(`${endpoint}|${model}`);
42
+ if (!b) return false;
43
+ const now = Date.now();
44
+ if (b.cooldownUntil && now < b.cooldownUntil) return true;
45
+ if (b.cooldownUntil && now >= b.cooldownUntil) b.cooldownUntil = void 0;
46
+ this.drain(b, now);
47
+ return b.level > this.skipThreshold;
48
+ }
49
+ recordSuccess(model, endpoint) {
50
+ const b = this.buckets.get(`${endpoint}|${model}`);
51
+ if (!b) return;
52
+ b.consecutiveErrors = 0;
53
+ b.cooldownUntil = void 0;
54
+ b.authFailed = false;
55
+ }
56
+ recordError(model, endpoint, kind, retryAfterMs) {
57
+ const key = `${endpoint}|${model}`;
58
+ const now = Date.now();
59
+ const b = this.buckets.get(key) ?? { level: 0, lastUpdate: now, consecutiveErrors: 0 };
60
+ this.drain(b, now);
61
+ switch (kind) {
62
+ case "auth":
63
+ b.level = 1;
64
+ b.authFailed = true;
65
+ if (this.onDemote && !b.demoted) {
66
+ b.demoted = true;
67
+ this.onDemote(model, endpoint, "auth");
68
+ }
69
+ break;
70
+ case "transient": {
71
+ const idx = Math.min(b.consecutiveErrors, this.transientProgressive.length - 1);
72
+ b.level = Math.min(1, b.level + this.transientProgressive[idx]);
73
+ if (b.consecutiveErrors >= this.transientCooldownAfter) {
74
+ b.cooldownUntil = now + Math.min((b.consecutiveErrors + 1) * 6e4, this.transientMaxCooldownMs);
75
+ }
76
+ break;
77
+ }
78
+ case "rate_limit":
79
+ b.level = Math.min(1, b.level + this.rateLimitFill);
80
+ if (retryAfterMs && retryAfterMs > 0) b.cooldownUntil = now + retryAfterMs;
81
+ break;
82
+ case "weak":
83
+ b.level = Math.min(1, b.level + this.weakFill);
84
+ break;
85
+ }
86
+ b.consecutiveErrors++;
87
+ b.lastUpdate = now;
88
+ this.buckets.set(key, b);
89
+ }
90
+ getHealth() {
91
+ const now = Date.now();
92
+ const result = /* @__PURE__ */ new Map();
93
+ for (const [key, b] of this.buckets) {
94
+ this.drain(b, now);
95
+ let status = "healthy";
96
+ if (b.authFailed) status = "auth_failed";
97
+ else if (b.level > this.skipThreshold) status = "exhausted";
98
+ else if (b.level > 0.3) status = "degraded";
99
+ result.set(key, { level: Math.round(b.level * 100), consecutiveErrors: b.consecutiveErrors, cooldownUntil: b.cooldownUntil, status });
100
+ }
101
+ return result;
102
+ }
103
+ /** Get bucket level for a specific model (0-100%). */
104
+ getBucketLevel(model, endpoint) {
105
+ const b = this.buckets.get(`${endpoint}|${model}`);
106
+ if (!b) return 0;
107
+ this.drain(b, Date.now());
108
+ return Math.round(b.level * 100);
109
+ }
110
+ resetAll() {
111
+ this.buckets.clear();
112
+ }
113
+ };
114
+ function classifyError(status, message) {
115
+ if (status === 429 || status === 402) return "rate_limit";
116
+ if (status === 404 && message && /image input|No endpoints found/i.test(message)) return "transient";
117
+ if (status === 401 || status === 403 || status === 404) return "auth";
118
+ return "transient";
119
+ }
120
+
121
+ export {
122
+ ModelHealthRegistry,
123
+ classifyError
124
+ };
125
+ //# sourceMappingURL=chunk-2XU2X4OI.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/components/transport/model-health-registry.ts"],
4
+ "sourcesContent": ["/**\n * ModelHealthRegistry \u2014 shared per-model error tracking.\n * Leaky-bucket algorithm: fills on errors, drains over time. Full = skip.\n * One instance per bridge process, shared across all FallbackPolicy instances.\n *\n * All tuning constants configurable via transport.json `healthPolicy` key.\n * Missing fields fall back to hardcoded defaults.\n */\n\nexport type ErrorKind = \"rate_limit\" | \"auth\" | \"transient\" | \"weak\";\nexport type ModelStatus = \"healthy\" | \"degraded\" | \"exhausted\" | \"auth_failed\";\n\nexport interface HealthPolicyConfig {\n /** Bucket level (0-1) above which model is skipped. Default: 0.7 */\n skipThreshold?: number;\n /** Drain rate per minute (0-1). Default: 0.03 */\n leakPerMinute?: number;\n /** Auth error: fill amount (0-1). Default: 1.0 (instant full) */\n authFill?: number;\n /** Auth errors are sticky (never auto-drain). Default: true */\n authSticky?: boolean;\n /** Rate-limit fill per hit. Default: 0.5 */\n rateLimitFill?: number;\n /** Weak-model fill per hit. Default: 0.05 */\n weakFill?: number;\n /** Transient progressive fill array. Default: [0.1, 0.2, 0.4, 0.8] */\n transientProgressive?: number[];\n /** Consecutive errors before cooldown kicks in. Default: 3 */\n transientCooldownAfter?: number;\n /** Max cooldown seconds for transient errors. Default: 300 */\n transientMaxCooldownSec?: number;\n}\n\ninterface Bucket {\n level: number;\n lastUpdate: number;\n consecutiveErrors: number;\n cooldownUntil?: number;\n authFailed?: boolean;\n demoted?: boolean;\n}\n\n// Defaults (unchanged from pre-config behavior)\nconst D_SKIP_THRESHOLD = 0.7;\nconst D_LEAK_PER_MIN = 0.03;\nconst D_AUTH_STICKY = true;\nconst D_RATE_LIMIT_FILL = 0.5;\nconst D_WEAK_FILL = 0.35;\nconst D_TRANSIENT_PROGRESSIVE = [0.1, 0.2, 0.4, 0.8];\nconst D_TRANSIENT_COOLDOWN_AFTER = 3;\nconst D_TRANSIENT_MAX_COOLDOWN_SEC = 300;\n\nexport class ModelHealthRegistry {\n private readonly buckets = new Map<string, Bucket>();\n private readonly skipThreshold: number;\n private readonly leakPerMs: number;\n private readonly authSticky: boolean;\n private readonly rateLimitFill: number;\n private readonly weakFill: number;\n private readonly transientProgressive: number[];\n private readonly transientCooldownAfter: number;\n private readonly transientMaxCooldownMs: number;\n\n /** Fired when a model crosses the demotion threshold. Wired at boot. */\n onDemote?: (model: string, endpoint: string, reason: \"auth\" | \"timeout\") => void;\n\n constructor(config?: HealthPolicyConfig) {\n this.skipThreshold = config?.skipThreshold ?? D_SKIP_THRESHOLD;\n this.leakPerMs = (config?.leakPerMinute ?? D_LEAK_PER_MIN) / 60000;\n this.authSticky = config?.authSticky ?? D_AUTH_STICKY;\n this.rateLimitFill = config?.rateLimitFill ?? D_RATE_LIMIT_FILL;\n this.weakFill = config?.weakFill ?? D_WEAK_FILL;\n this.transientProgressive = config?.transientProgressive ?? D_TRANSIENT_PROGRESSIVE;\n this.transientCooldownAfter = config?.transientCooldownAfter ?? D_TRANSIENT_COOLDOWN_AFTER;\n this.transientMaxCooldownMs = (config?.transientMaxCooldownSec ?? D_TRANSIENT_MAX_COOLDOWN_SEC) * 1000;\n }\n\n private drain(b: Bucket, now: number): void {\n if (this.authSticky && b.authFailed) return;\n const elapsed = now - b.lastUpdate;\n b.level = Math.max(0, b.level - elapsed * this.leakPerMs);\n b.lastUpdate = now;\n }\n\n shouldSkip(model: string, endpoint: string): boolean {\n const b = this.buckets.get(`${endpoint}|${model}`);\n if (!b) return false;\n const now = Date.now();\n if (b.cooldownUntil && now < b.cooldownUntil) return true;\n if (b.cooldownUntil && now >= b.cooldownUntil) b.cooldownUntil = undefined;\n this.drain(b, now);\n return b.level > this.skipThreshold;\n }\n\n recordSuccess(model: string, endpoint: string): void {\n const b = this.buckets.get(`${endpoint}|${model}`);\n if (!b) return;\n b.consecutiveErrors = 0;\n b.cooldownUntil = undefined;\n b.authFailed = false;\n }\n\n recordError(model: string, endpoint: string, kind: ErrorKind, retryAfterMs?: number): void {\n const key = `${endpoint}|${model}`;\n const now = Date.now();\n const b = this.buckets.get(key) ?? { level: 0, lastUpdate: now, consecutiveErrors: 0 };\n this.drain(b, now);\n\n switch (kind) {\n case \"auth\":\n b.level = 1.0;\n b.authFailed = true;\n if (this.onDemote && !b.demoted) {\n b.demoted = true;\n this.onDemote(model, endpoint, \"auth\");\n }\n break;\n case \"transient\": {\n const idx = Math.min(b.consecutiveErrors, this.transientProgressive.length - 1);\n b.level = Math.min(1.0, b.level + this.transientProgressive[idx]!);\n if (b.consecutiveErrors >= this.transientCooldownAfter) {\n b.cooldownUntil = now + Math.min((b.consecutiveErrors + 1) * 60_000, this.transientMaxCooldownMs);\n }\n break;\n }\n case \"rate_limit\":\n b.level = Math.min(1.0, b.level + this.rateLimitFill);\n if (retryAfterMs && retryAfterMs > 0) b.cooldownUntil = now + retryAfterMs;\n break;\n case \"weak\":\n b.level = Math.min(1.0, b.level + this.weakFill);\n break;\n }\n\n b.consecutiveErrors++;\n b.lastUpdate = now;\n this.buckets.set(key, b);\n }\n\n getHealth(): Map<string, { level: number; consecutiveErrors: number; cooldownUntil?: number; status: ModelStatus }> {\n const now = Date.now();\n const result = new Map<string, { level: number; consecutiveErrors: number; cooldownUntil?: number; status: ModelStatus }>();\n for (const [key, b] of this.buckets) {\n this.drain(b, now);\n let status: ModelStatus = \"healthy\";\n if (b.authFailed) status = \"auth_failed\";\n else if (b.level > this.skipThreshold) status = \"exhausted\";\n else if (b.level > 0.3) status = \"degraded\";\n result.set(key, { level: Math.round(b.level * 100), consecutiveErrors: b.consecutiveErrors, cooldownUntil: b.cooldownUntil, status });\n }\n return result;\n }\n\n /** Get bucket level for a specific model (0-100%). */\n getBucketLevel(model: string, endpoint: string): number {\n const b = this.buckets.get(`${endpoint}|${model}`);\n if (!b) return 0;\n this.drain(b, Date.now());\n return Math.round(b.level * 100);\n }\n\n resetAll(): void {\n this.buckets.clear();\n }\n}\n\n/** Classify HTTP status to error kind. */\nexport function classifyError(status: number, message?: string): ErrorKind {\n if (status === 429 || status === 402) return \"rate_limit\";\n if (status === 404 && message && /image input|No endpoints found/i.test(message)) return \"transient\";\n if (status === 401 || status === 403 || status === 404) return \"auth\";\n return \"transient\";\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }