agentix-cli 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/dist/{App-BPNHIGUX.js → App-U52IPUK2.js} +2 -2
  2. package/dist/{ChatApp-6XSSZB4U.js → ChatApp-XP4OMHPL.js} +2 -2
  3. package/dist/agent-V2CA4TXD.js +2 -0
  4. package/dist/article-fields-L4D2BOGY.js +2 -0
  5. package/dist/article-quality-44ONVGLN.js +3 -0
  6. package/dist/article-quality-44ONVGLN.js.map +1 -0
  7. package/dist/{audit-JU3HWISB.js → audit-4245CX6P.js} +2 -2
  8. package/dist/backfill-MTD2B62I.js +6 -0
  9. package/dist/backfill-MTD2B62I.js.map +1 -0
  10. package/dist/{board-dashboard-M3372FLO.js → board-dashboard-TTMAYRLG.js} +1407 -447
  11. package/dist/board-dashboard-TTMAYRLG.js.map +1 -0
  12. package/dist/{builtin-FRKAQMFS.js → builtin-B67DMJ3M.js} +2 -2
  13. package/dist/calibration-ILS5OI44.js +2 -0
  14. package/dist/chunk-2T5W3ETS.js +5 -0
  15. package/dist/{chunk-K2CAPYES.js.map → chunk-2T5W3ETS.js.map} +1 -1
  16. package/dist/chunk-3SCPRYJB.js +5 -0
  17. package/dist/chunk-3SCPRYJB.js.map +1 -0
  18. package/dist/chunk-4BR7D2OU.js +36 -0
  19. package/dist/chunk-4BR7D2OU.js.map +1 -0
  20. package/dist/{chunk-P4VWIVKN.js → chunk-52ZPELD2.js} +403 -29
  21. package/dist/chunk-52ZPELD2.js.map +1 -0
  22. package/dist/{chunk-5U35WAZF.js → chunk-77OV7346.js} +6 -6
  23. package/dist/chunk-77OV7346.js.map +1 -0
  24. package/dist/chunk-AJ5RMU7X.js +2 -0
  25. package/dist/chunk-AJ5RMU7X.js.map +1 -0
  26. package/dist/chunk-AJONZWLT.js +2 -0
  27. package/dist/chunk-AJONZWLT.js.map +1 -0
  28. package/dist/chunk-AX2PWSRT.js +114 -0
  29. package/dist/chunk-AX2PWSRT.js.map +1 -0
  30. package/dist/chunk-BH3GTCZ6.js +2 -0
  31. package/dist/chunk-BH3GTCZ6.js.map +1 -0
  32. package/dist/chunk-BPIRO4YD.js +126 -0
  33. package/dist/chunk-BPIRO4YD.js.map +1 -0
  34. package/dist/chunk-CJQQIRPD.js +13 -0
  35. package/dist/chunk-CJQQIRPD.js.map +1 -0
  36. package/dist/chunk-DCDQZR7I.js +24 -0
  37. package/dist/chunk-DCDQZR7I.js.map +1 -0
  38. package/dist/chunk-EVWEG3VD.js +3 -0
  39. package/dist/chunk-EVWEG3VD.js.map +1 -0
  40. package/dist/chunk-F4HZTZHW.js +2 -0
  41. package/dist/chunk-F4HZTZHW.js.map +1 -0
  42. package/dist/chunk-F6CNF7V3.js +25 -0
  43. package/dist/chunk-F6CNF7V3.js.map +1 -0
  44. package/dist/chunk-FIEJDZVC.js +121 -0
  45. package/dist/chunk-FIEJDZVC.js.map +1 -0
  46. package/dist/chunk-FIOB3MEX.js +2 -0
  47. package/dist/{chunk-H5D7ATXS.js → chunk-FKBGJYWQ.js} +5 -5
  48. package/dist/chunk-FKBGJYWQ.js.map +1 -0
  49. package/dist/chunk-GDWJUGB5.js +9 -0
  50. package/dist/chunk-GDWJUGB5.js.map +1 -0
  51. package/dist/{chunk-BVGJSPW4.js → chunk-IB3F7VBE.js} +7 -7
  52. package/dist/{chunk-BVGJSPW4.js.map → chunk-IB3F7VBE.js.map} +1 -1
  53. package/dist/chunk-IBUSGCLQ.js +109 -0
  54. package/dist/chunk-IBUSGCLQ.js.map +1 -0
  55. package/dist/{chunk-LDHPMJ7M.js → chunk-IWMW5432.js} +3 -3
  56. package/dist/chunk-IWMW5432.js.map +1 -0
  57. package/dist/chunk-K33GIBSE.js +2 -0
  58. package/dist/chunk-K33GIBSE.js.map +1 -0
  59. package/dist/chunk-L2IZPR6P.js +45 -0
  60. package/dist/chunk-L2IZPR6P.js.map +1 -0
  61. package/dist/chunk-L2WSPY6S.js +2 -0
  62. package/dist/chunk-L2WSPY6S.js.map +1 -0
  63. package/dist/chunk-LCIREYTD.js +54 -0
  64. package/dist/chunk-LCIREYTD.js.map +1 -0
  65. package/dist/chunk-LNRB5MCU.js +530 -0
  66. package/dist/chunk-LNRB5MCU.js.map +1 -0
  67. package/dist/chunk-OPHIXXGI.js +157 -0
  68. package/dist/chunk-OPHIXXGI.js.map +1 -0
  69. package/dist/{chunk-JTJIPNXY.js → chunk-PNWNVOEZ.js} +1 -1
  70. package/dist/chunk-PNWNVOEZ.js.map +1 -0
  71. package/dist/{chunk-V3U4OF7L.js → chunk-UFAGYX73.js} +45 -47
  72. package/dist/chunk-UFAGYX73.js.map +1 -0
  73. package/dist/{chunk-DJEZ2HVU.js → chunk-VDMYBPYB.js} +1 -1
  74. package/dist/chunk-VDMYBPYB.js.map +1 -0
  75. package/dist/chunk-VTE7L6LO.js +5 -0
  76. package/dist/{chunk-O6YPEOQF.js.map → chunk-VTE7L6LO.js.map} +1 -1
  77. package/dist/chunk-W5C2GDNC.js +32 -0
  78. package/dist/{chunk-JP5IEXR7.js.map → chunk-W5C2GDNC.js.map} +1 -1
  79. package/dist/chunk-WPMX3VGI.js +3 -0
  80. package/dist/chunk-WPMX3VGI.js.map +1 -0
  81. package/dist/chunk-WZ72HX5C.js +1182 -0
  82. package/dist/chunk-WZ72HX5C.js.map +1 -0
  83. package/dist/chunk-ZTEU5JVQ.js +2 -0
  84. package/dist/chunk-ZTEU5JVQ.js.map +1 -0
  85. package/dist/{chunk-2WVX3LEC.js → chunk-ZVP6GB4G.js} +1 -1
  86. package/dist/chunk-ZVP6GB4G.js.map +1 -0
  87. package/dist/cli.js +1 -1
  88. package/dist/cli.js.map +1 -1
  89. package/dist/client-PX25MD47.js +2 -0
  90. package/dist/{compaction-NWEMOSHA.js → compaction-FD4R23HZ.js} +4 -4
  91. package/dist/config-HL5F36V4.js +2 -0
  92. package/dist/config-mutator-HBX53OMJ.js +2 -0
  93. package/dist/{conflict-detector-D7VIYPDI.js → conflict-detector-ZPOT3M2C.js} +2 -2
  94. package/dist/conflict-detector-ZPOT3M2C.js.map +1 -0
  95. package/dist/context-planner-AQHJMVY2.js +17 -0
  96. package/dist/context-planner-AQHJMVY2.js.map +1 -0
  97. package/dist/debug-3MEUO2P2.js +2 -0
  98. package/dist/{discover-XJXQXXIO.js → discover-D4VVRNAI.js} +2 -2
  99. package/dist/discover-D4VVRNAI.js.map +1 -0
  100. package/dist/doctor-PBAH6XL3.js +2 -0
  101. package/dist/editor-chat-OB7IQOB5.js +137 -0
  102. package/dist/editor-chat-OB7IQOB5.js.map +1 -0
  103. package/dist/entry-triage-LK34TSQD.js +2 -0
  104. package/dist/entry-triage-LK34TSQD.js.map +1 -0
  105. package/dist/facts-YVEJOM7O.js +22 -0
  106. package/dist/facts-YVEJOM7O.js.map +1 -0
  107. package/dist/heal-FVLBHYP7.js +2 -0
  108. package/dist/index.d.ts +1016 -65
  109. package/dist/index.js +13 -13
  110. package/dist/index.js.map +1 -1
  111. package/dist/{ingest-whatsapp-LZE62INA.js → ingest-whatsapp-NS4OWDOU.js} +2 -2
  112. package/dist/loader-4ZGTK7LD.js +2 -0
  113. package/dist/loader-BOKE5M5S.js +2 -0
  114. package/dist/memory-extract-F5ZHWY5E.js +2 -0
  115. package/dist/{migrate-v2-QO7SLKIM.js → migrate-v2-V4WN2VVM.js} +2 -2
  116. package/dist/ntfy-X7EPB3TG.js +4 -0
  117. package/dist/ntfy-X7EPB3TG.js.map +1 -0
  118. package/dist/processes-WU23U2OJ.js +2 -0
  119. package/dist/program-YGXVCV2Y.js +2 -0
  120. package/dist/{projects-api-KRME2YKZ.js → projects-api-QE6L65SK.js} +2 -2
  121. package/dist/{projects-mutate-3NHVG437.js → projects-mutate-MY7JTLSZ.js} +2 -2
  122. package/dist/providers-KO2DODIO.js +2 -0
  123. package/dist/query-ZKTFJMNE.js +2 -0
  124. package/dist/questions-4B4KSPXG.js +3 -0
  125. package/dist/questions-4B4KSPXG.js.map +1 -0
  126. package/dist/recipes-7S4MC5OJ.js +2 -0
  127. package/dist/registry-PSZDPFZK.js +2 -0
  128. package/dist/review-candidates-AEUXRQQC.js +4 -0
  129. package/dist/review-candidates-AEUXRQQC.js.map +1 -0
  130. package/dist/review-store-B4YE3EFK.js +4 -0
  131. package/dist/review-store-B4YE3EFK.js.map +1 -0
  132. package/dist/{rotation-memo-B3EXXSEH.js → rotation-memo-7T75BLEX.js} +2 -2
  133. package/dist/routing-4PV66BK6.js +2 -0
  134. package/dist/routing-4PV66BK6.js.map +1 -0
  135. package/dist/runner-YRBVNYTY.js +2 -0
  136. package/dist/scheduler-GZZAN6ZG.js +2 -0
  137. package/dist/seat-VZ7AVO27.js +2 -0
  138. package/dist/select-F5I5PO2O.js +2 -0
  139. package/dist/select-F5I5PO2O.js.map +1 -0
  140. package/dist/store-MCOA6N4F.js +2 -0
  141. package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
  142. package/dist/{surface-usage-PRL6Z4U3.js → surface-usage-NS74AXSE.js} +2 -2
  143. package/dist/timers-PCB4ZIBD.js +2 -0
  144. package/dist/triage-backtest-JNO7RKX7.js +2 -0
  145. package/dist/triage-backtest-JNO7RKX7.js.map +1 -0
  146. package/dist/types-H5ENIUIT.js +2 -0
  147. package/dist/types-OIFU3KSQ.js +2 -0
  148. package/dist/web/workflow-editor.global.js +1542 -0
  149. package/dist/web/workflow-editor.global.js.map +1 -0
  150. package/dist/{whatsapp-state-XMTCSRXU.js → whatsapp-state-6D6FHHD2.js} +2 -2
  151. package/dist/wiki-N5SMP3DT.js +2 -0
  152. package/dist/wiki-N5SMP3DT.js.map +1 -0
  153. package/dist/workflows/templates/branching.yaml +1 -1
  154. package/dist/{workspace-setup-TBQAHYHJ.js → workspace-setup-B3F3TUDE.js} +2 -2
  155. package/dist/workspace-setup-B3F3TUDE.js.map +1 -0
  156. package/package.json +1 -1
  157. package/dist/agent-5QMITZT6.js +0 -2
  158. package/dist/architect-SEPWMWS4.js +0 -2
  159. package/dist/board-dashboard-M3372FLO.js.map +0 -1
  160. package/dist/chunk-2WVX3LEC.js.map +0 -1
  161. package/dist/chunk-3WQFCIB6.js +0 -24
  162. package/dist/chunk-3WQFCIB6.js.map +0 -1
  163. package/dist/chunk-4YCH6IZV.js +0 -2
  164. package/dist/chunk-53NRX2NA.js +0 -2
  165. package/dist/chunk-53NRX2NA.js.map +0 -1
  166. package/dist/chunk-5U35WAZF.js.map +0 -1
  167. package/dist/chunk-BFXONUUN.js +0 -108
  168. package/dist/chunk-BFXONUUN.js.map +0 -1
  169. package/dist/chunk-BFZUG66X.js +0 -45
  170. package/dist/chunk-BFZUG66X.js.map +0 -1
  171. package/dist/chunk-BTHHSY7D.js +0 -13
  172. package/dist/chunk-BTHHSY7D.js.map +0 -1
  173. package/dist/chunk-D46VZ7K7.js +0 -116
  174. package/dist/chunk-D46VZ7K7.js.map +0 -1
  175. package/dist/chunk-DJEZ2HVU.js.map +0 -1
  176. package/dist/chunk-E5CXCI33.js +0 -155
  177. package/dist/chunk-E5CXCI33.js.map +0 -1
  178. package/dist/chunk-FDWOECJY.js +0 -54
  179. package/dist/chunk-FDWOECJY.js.map +0 -1
  180. package/dist/chunk-H5D7ATXS.js.map +0 -1
  181. package/dist/chunk-HR4JSRZS.js +0 -3
  182. package/dist/chunk-HR4JSRZS.js.map +0 -1
  183. package/dist/chunk-ISEBLHWX.js +0 -118
  184. package/dist/chunk-ISEBLHWX.js.map +0 -1
  185. package/dist/chunk-JP5IEXR7.js +0 -32
  186. package/dist/chunk-JTJIPNXY.js.map +0 -1
  187. package/dist/chunk-K2CAPYES.js +0 -5
  188. package/dist/chunk-LDHPMJ7M.js.map +0 -1
  189. package/dist/chunk-METYYZKQ.js +0 -1115
  190. package/dist/chunk-METYYZKQ.js.map +0 -1
  191. package/dist/chunk-MTAPOCGA.js +0 -47
  192. package/dist/chunk-MTAPOCGA.js.map +0 -1
  193. package/dist/chunk-O6YPEOQF.js +0 -5
  194. package/dist/chunk-P4VWIVKN.js.map +0 -1
  195. package/dist/chunk-SR3X25TN.js +0 -3
  196. package/dist/chunk-SR3X25TN.js.map +0 -1
  197. package/dist/chunk-UHE5FCPB.js +0 -431
  198. package/dist/chunk-UHE5FCPB.js.map +0 -1
  199. package/dist/chunk-V3U4OF7L.js.map +0 -1
  200. package/dist/chunk-VJUTQAPJ.js +0 -2
  201. package/dist/chunk-VJUTQAPJ.js.map +0 -1
  202. package/dist/chunk-VT5XYFDD.js +0 -126
  203. package/dist/chunk-VT5XYFDD.js.map +0 -1
  204. package/dist/client-HGKAC6VP.js +0 -2
  205. package/dist/config-A4RWEJHV.js +0 -2
  206. package/dist/config-mutator-MNSFUN7D.js +0 -2
  207. package/dist/conflict-detector-D7VIYPDI.js.map +0 -1
  208. package/dist/context-planner-45GEWKGL.js +0 -17
  209. package/dist/context-planner-45GEWKGL.js.map +0 -1
  210. package/dist/debug-N3B5NVJU.js +0 -2
  211. package/dist/discover-XJXQXXIO.js.map +0 -1
  212. package/dist/doctor-3Q36ZFE6.js +0 -2
  213. package/dist/heal-HZ3EQD52.js +0 -2
  214. package/dist/loader-5CA3O5GM.js +0 -2
  215. package/dist/loader-6IHUCPVO.js +0 -2
  216. package/dist/memory-extract-AENFVVO5.js +0 -2
  217. package/dist/processes-UIC4XXA6.js +0 -2
  218. package/dist/program-SAUNRCCD.js +0 -2
  219. package/dist/providers-L27BJ5A6.js +0 -2
  220. package/dist/query-HZYYRCLU.js +0 -2
  221. package/dist/recipes-EPLE2SNT.js +0 -2
  222. package/dist/registry-HK3WQ7BS.js +0 -2
  223. package/dist/runner-BFYDWPUO.js +0 -2
  224. package/dist/scheduler-6T2SU5ST.js +0 -2
  225. package/dist/store-I3HEWVLQ.js +0 -2
  226. package/dist/timers-5LPZN55J.js +0 -2
  227. package/dist/types-VECTMYTO.js +0 -2
  228. package/dist/types-Y6ZCZ52Z.js +0 -2
  229. package/dist/wiki-M6YNRFC4.js +0 -2
  230. /package/dist/{App-BPNHIGUX.js.map → App-U52IPUK2.js.map} +0 -0
  231. /package/dist/{ChatApp-6XSSZB4U.js.map → ChatApp-XP4OMHPL.js.map} +0 -0
  232. /package/dist/{agent-5QMITZT6.js.map → agent-V2CA4TXD.js.map} +0 -0
  233. /package/dist/{architect-SEPWMWS4.js.map → article-fields-L4D2BOGY.js.map} +0 -0
  234. /package/dist/{audit-JU3HWISB.js.map → audit-4245CX6P.js.map} +0 -0
  235. /package/dist/{builtin-FRKAQMFS.js.map → builtin-B67DMJ3M.js.map} +0 -0
  236. /package/dist/{chunk-4YCH6IZV.js.map → calibration-ILS5OI44.js.map} +0 -0
  237. /package/dist/{client-HGKAC6VP.js.map → chunk-FIOB3MEX.js.map} +0 -0
  238. /package/dist/{config-A4RWEJHV.js.map → client-PX25MD47.js.map} +0 -0
  239. /package/dist/{compaction-NWEMOSHA.js.map → compaction-FD4R23HZ.js.map} +0 -0
  240. /package/dist/{config-mutator-MNSFUN7D.js.map → config-HL5F36V4.js.map} +0 -0
  241. /package/dist/{debug-N3B5NVJU.js.map → config-mutator-HBX53OMJ.js.map} +0 -0
  242. /package/dist/{doctor-3Q36ZFE6.js.map → debug-3MEUO2P2.js.map} +0 -0
  243. /package/dist/{heal-HZ3EQD52.js.map → doctor-PBAH6XL3.js.map} +0 -0
  244. /package/dist/{loader-5CA3O5GM.js.map → heal-FVLBHYP7.js.map} +0 -0
  245. /package/dist/{ingest-whatsapp-LZE62INA.js.map → ingest-whatsapp-NS4OWDOU.js.map} +0 -0
  246. /package/dist/{loader-6IHUCPVO.js.map → loader-4ZGTK7LD.js.map} +0 -0
  247. /package/dist/{memory-extract-AENFVVO5.js.map → loader-BOKE5M5S.js.map} +0 -0
  248. /package/dist/{processes-UIC4XXA6.js.map → memory-extract-F5ZHWY5E.js.map} +0 -0
  249. /package/dist/{migrate-v2-QO7SLKIM.js.map → migrate-v2-V4WN2VVM.js.map} +0 -0
  250. /package/dist/{program-SAUNRCCD.js.map → processes-WU23U2OJ.js.map} +0 -0
  251. /package/dist/{providers-L27BJ5A6.js.map → program-YGXVCV2Y.js.map} +0 -0
  252. /package/dist/{projects-api-KRME2YKZ.js.map → projects-api-QE6L65SK.js.map} +0 -0
  253. /package/dist/{projects-mutate-3NHVG437.js.map → projects-mutate-MY7JTLSZ.js.map} +0 -0
  254. /package/dist/{query-HZYYRCLU.js.map → providers-KO2DODIO.js.map} +0 -0
  255. /package/dist/{recipes-EPLE2SNT.js.map → query-ZKTFJMNE.js.map} +0 -0
  256. /package/dist/{registry-HK3WQ7BS.js.map → recipes-7S4MC5OJ.js.map} +0 -0
  257. /package/dist/{runner-BFYDWPUO.js.map → registry-PSZDPFZK.js.map} +0 -0
  258. /package/dist/{rotation-memo-B3EXXSEH.js.map → rotation-memo-7T75BLEX.js.map} +0 -0
  259. /package/dist/{scheduler-6T2SU5ST.js.map → runner-YRBVNYTY.js.map} +0 -0
  260. /package/dist/{store-I3HEWVLQ.js.map → scheduler-GZZAN6ZG.js.map} +0 -0
  261. /package/dist/{surface-usage-PRL6Z4U3.js.map → seat-VZ7AVO27.js.map} +0 -0
  262. /package/dist/{timers-5LPZN55J.js.map → store-MCOA6N4F.js.map} +0 -0
  263. /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
  264. /package/dist/{types-VECTMYTO.js.map → surface-usage-NS74AXSE.js.map} +0 -0
  265. /package/dist/{types-Y6ZCZ52Z.js.map → timers-PCB4ZIBD.js.map} +0 -0
  266. /package/dist/{wiki-M6YNRFC4.js.map → types-H5ENIUIT.js.map} +0 -0
  267. /package/dist/{workspace-setup-TBQAHYHJ.js.map → types-OIFU3KSQ.js.map} +0 -0
  268. /package/dist/{whatsapp-state-XMTCSRXU.js.map → whatsapp-state-6D6FHHD2.js.map} +0 -0
@@ -0,0 +1,2 @@
1
+ import{b as i}from"./chunk-FIOB3MEX.js";import{randomBytes as c}from"crypto";function f(t){let n="";for(let e=u-1;e>=0;e--)n=r[t&31]+n,t=Math.floor(t/32);return n}function s(){let t=c(o),n="";for(let e=0;e<o;e++)n+=r[t[e]&31];return n}function l(t=Date.now()){return f(t)+s()}var r,u,o,a=i(()=>{"use strict";r="0123456789ABCDEFGHJKMNPQRSTVWXYZ",u=10,o=16});export{l as a,a as b};
2
+ //# sourceMappingURL=chunk-ZTEU5JVQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/intent/ulid.ts"],"sourcesContent":["import { randomBytes } from \"crypto\"\n\n// In-tree ULID generator — Phase 1 of the architectural rescue.\n//\n// The ledger demands monotonically-sortable ids so chronological queries\n// can use ID ranges and the audit trail reads coherently. ULIDs give us\n// that (10-char Crockford-base32 timestamp + 16-char randomness, total\n// 26 chars). The full `ulid` npm package is overkill for the few hundred\n// lines of generation logic we need, and adding a dep means another\n// `pnpm install --prod` step on peer-server. So we inline.\n//\n// This is not a cryptographic primitive — its job is to be a unique,\n// time-sortable identifier. Random bytes come from `crypto.randomBytes`\n// purely because it's the right tool for \"give me bytes\" and there's no\n// reason to use weaker randomness.\n\nconst ENCODING = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\" // Crockford base32\n\nconst TIME_LEN = 10\nconst RAND_LEN = 16\n\nfunction encodeTime(ms: number): string {\n let out = \"\"\n for (let i = TIME_LEN - 1; i >= 0; i--) {\n out = ENCODING[ms & 31] + out\n ms = Math.floor(ms / 32)\n }\n return out\n}\n\nfunction encodeRandom(): string {\n const bytes = randomBytes(RAND_LEN)\n let out = \"\"\n for (let i = 0; i < RAND_LEN; i++) {\n out += ENCODING[bytes[i] & 31]\n }\n return out\n}\n\n/** Generate a new ULID. Pass `now` for deterministic tests. */\nexport function newEventId(now: number = Date.now()): string {\n return encodeTime(now) + encodeRandom()\n}\n\n/** Extract the timestamp portion from a ULID. Inverse of `newEventId`'s\n * time prefix. Useful for sanity checks; not load-bearing. */\nexport function decodeTime(id: string): number {\n let ms = 0\n for (let i = 0; i < TIME_LEN; i++) {\n const v = ENCODING.indexOf(id[i])\n if (v < 0) throw new Error(`invalid ULID character at position ${i}: ${id[i]}`)\n ms = ms * 32 + v\n }\n return ms\n}\n"],"mappings":"wCAAA,OAAS,eAAAA,MAAmB,SAqB5B,SAASC,EAAWC,EAAoB,CACtC,IAAIC,EAAM,GACV,QAASC,EAAIC,EAAW,EAAGD,GAAK,EAAGA,IACjCD,EAAMG,EAASJ,EAAK,EAAE,EAAIC,EAC1BD,EAAK,KAAK,MAAMA,EAAK,EAAE,EAEzB,OAAOC,CACT,CAEA,SAASI,GAAuB,CAC9B,IAAMC,EAAQR,EAAYS,CAAQ,EAC9BN,EAAM,GACV,QAASC,EAAI,EAAGA,EAAIK,EAAUL,IAC5BD,GAAOG,EAASE,EAAMJ,CAAC,EAAI,EAAE,EAE/B,OAAOD,CACT,CAGO,SAASO,EAAWC,EAAc,KAAK,IAAI,EAAW,CAC3D,OAAOV,EAAWU,CAAG,EAAIJ,EAAa,CACxC,CA1CA,IAgBMD,EAEAD,EACAI,EAnBNG,EAAAC,EAAA,kBAgBMP,EAAW,mCAEXD,EAAW,GACXI,EAAW","names":["randomBytes","encodeTime","ms","out","i","TIME_LEN","ENCODING","encodeRandom","bytes","RAND_LEN","newEventId","now","init_ulid","__esmMin"]}
@@ -1,2 +1,2 @@
1
1
  import{existsSync as u,readdirSync as g,readFileSync as h,statSync as f,watch as d}from"fs";import{extname as b,join as p,resolve as m}from"path";import y from"js-yaml";var c=class{byProject=new Map;dir;log;watcher;debounce;loadErrors=[];constructor(e,t=console.error.bind(console,"[projects]")){this.dir=e,this.log=t}load(){if(this.byProject.clear(),this.loadErrors=[],!u(this.dir))return{count:0,errors:0};for(let e of this.walk(this.dir))try{let t=h(e,"utf-8"),r=y.load(t),s=this.validate(r,e);if(!s)continue;if(s._path=e,this.byProject.has(s.project)){this.log(`duplicate project "${s.project}" \u2014 keeping ${this.byProject.get(s.project)._path}, skipping ${e}`);continue}this.byProject.set(s.project,s)}catch(t){this.loadErrors.push({file:e,error:t?.message??String(t)}),this.log(`failed to load ${e}: ${t?.message??t}`)}return{count:this.byProject.size,errors:this.loadErrors.length}}startWatching(e){if(u(this.dir))try{this.watcher=d(this.dir,{recursive:!0},()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout(()=>{let t=this.load();this.log(`rules reloaded: ${t.count} project(s), ${t.errors} error(s)`),e?.()},200)})}catch(t){this.log(`watcher unavailable (${t.message}) \u2014 rules will not hot-reload`)}}stop(){this.watcher?.close(),this.debounce&&clearTimeout(this.debounce)}find(e){return this.byProject.get(e)}list(){return Array.from(this.byProject.values())}static inferKind(e){return e.kind?e.kind:e.gitlab?"gitlab":e.github?"github":"other"}health(){return{count:this.byProject.size,errors:[...this.loadErrors]}}shouldFireGitlabIssue(e,t){let r=this.find(e)?.gitlab?.issue;return r?a(r,t,"gitlab"):{allow:!0}}shouldFireGitlabNote(e,t,r){let s=this.find(e)?.gitlab?.note;return s?w(s,t,r?.knownAgentMentions):{allow:!0}}shouldFireGitlabMR(e,t){let r=this.find(e)?.gitlab?.merge_request;return r?a(r,t,"gitlab"):{allow:!0}}shouldFireGitlabPipeline(e,t){let r=this.find(e)?.gitlab?.pipeline;return r?r.actions&&r.actions.length>0&&t.status&&!r.actions.includes(t.status)?{allow:!1,reason:`pipeline status="${t.status}" not in actions=${JSON.stringify(r.actions)}`}:{allow:!0}:{allow:!0}}shouldFireGithubIssue(e,t){let r=this.find(e)?.github?.issues;return r?a(r,t,"github"):{allow:!0}}shouldFireGithubPR(e,t){let r=this.find(e)?.github?.pull_request;return r?a(r,t,"github"):{allow:!0}}*walk(e){let t;try{t=g(e)}catch{return}for(let r of t){let s=p(e,r),o;try{o=f(s)}catch{continue}if(o.isDirectory()){yield*this.walk(s);continue}let n=b(r).toLowerCase();(n===".yaml"||n===".yml")&&(yield s)}}validate(e,t){if(!e||typeof e!="object")throw new Error(`${t}: top-level must be an object`);let r=e;if(typeof r.project!="string"||!r.project.includes("/"))throw new Error(`${t}: \`project\` must be a string of the form "org/repo"`);if(r.runbook!==void 0&&typeof r.runbook!="string")throw new Error(`${t}: \`runbook\` must be a string path`);return r.runbook&&m(r.runbook),e}};function a(i,e,t){let r=e.labels??[],s=e.authorUsername??"";if(i.actions&&i.actions.length>0&&(!e.action||!i.actions.includes(e.action)))return{allow:!1,reason:`${t} issue action="${e.action}" not in actions=${JSON.stringify(i.actions)}`};if(i.excludeStates&&e.state&&i.excludeStates.includes(e.state))return{allow:!1,reason:`${t} issue state="${e.state}" in excludeStates`};if(i.requireLabels&&i.requireLabels.length>0&&!i.requireLabels.some(n=>r.includes(n)))return{allow:!1,reason:`${t} issue missing requireLabels=${JSON.stringify(i.requireLabels)} (have ${JSON.stringify(r)})`};if(i.excludeLabels&&i.excludeLabels.length>0){let o=i.excludeLabels.find(n=>r.includes(n));if(o)return{allow:!1,reason:`${t} issue carries excluded label "${o}"`}}if(i.excludeAuthors&&i.excludeAuthors.length>0){let o=i.excludeAuthors.find(n=>s.toLowerCase().includes(n.toLowerCase()));if(o)return{allow:!1,reason:`${t} issue authored by "${s}" matched excludeAuthors entry "${o}"`}}return{allow:!0}}function w(i,e,t){let r=(e.text??"").toLowerCase(),s=e.authorUsername??"";if(i.onlyOn&&i.onlyOn.length>0&&(!e.noteableType||!i.onlyOn.includes(e.noteableType)))return{allow:!1,reason:`note noteableType="${e.noteableType}" not in onlyOn=${JSON.stringify(i.onlyOn)}`};if(i.excludeAuthors&&i.excludeAuthors.length>0){let o=i.excludeAuthors.find(n=>s.toLowerCase().includes(n.toLowerCase()));if(o)return{allow:!1,reason:`note authored by "${s}" matched excludeAuthors entry "${o}"`}}return i.triggers&&i.triggers.length>0&&!i.triggers.some(n=>{if(n==="auto")return!t||t.length===0?!1:t.some(l=>r.includes(l.toLowerCase()));if(n.mention){let l=n.mention.toLowerCase();if(r.includes(l))return!0}return!!(n.keyword&&r.includes(n.keyword.toLowerCase()))})?{allow:!1,reason:"note text matched no trigger"}:{allow:!0}}export{c as a};
2
- //# sourceMappingURL=chunk-2WVX3LEC.js.map
2
+ //# sourceMappingURL=chunk-ZVP6GB4G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/projects/rules.ts"],"sourcesContent":["// --- Per-project webhook rules + runbook reference ---\n//\n// Project rules live in `.agentx/projects/<org>/<project>.yaml` (or any\n// nested path; the loader walks the tree). Each file declares which\n// channel events for that project may reach an agent and where the\n// project's runbook (CLAUDE.md / AGENTS.md / DEPLOY.md / RUNBOOK.md)\n// lives so the registry can inject it into the agent's system prefix.\n//\n// The store is loaded at daemon boot and watched for changes — write a\n// file, save, and the next webhook event picks up the new rules without\n// a daemon restart. Validation errors are logged and the offending file\n// is skipped (other rules keep working).\n//\n// Why per-file: agencies grow projects faster than they touch agentx\n// internals; a single project.yaml would force coordination on every\n// edit. One file per project lets each project owner edit their own\n// rules without merge conflicts.\n//\n// Schema is intentionally narrow:\n// - issue.actions / pull_request.actions / issues.actions: action allowlist\n// - requireLabels: at least one must be present (OR-of). Empty/absent = no\n// label requirement.\n// - excludeLabels: any present blocks the event.\n// - excludeStates: states that always block (e.g. \"closed\").\n// - excludeAuthors: handle prefixes that always block (bots).\n// - note.triggers: at least one must match for a comment to fire.\n// - runbook: filesystem path; the registry reads CLAUDE.md / AGENTS.md /\n// DEPLOY.md / RUNBOOK.md from this path (extensible via `runbookFiles`).\n//\n// Anything not listed by a rule passes through with the legacy default\n// (existing adapter behaviour). Rules opt INTO filtering, never opt out\n// of an event the adapter already drops.\n\nimport { existsSync, readdirSync, readFileSync, statSync, watch } from \"fs\"\nimport type { FSWatcher } from \"fs\"\nimport { extname, join, resolve } from \"path\"\nimport yaml from \"js-yaml\"\n\nexport type Channel = \"gitlab\" | \"github\"\n\n/** Declarative source of a project. Drives the Projects-page UI badge\n * and indicates which channel adapters are expected to carry events\n * for it. `gitlab` and `github` have rule clauses + live adapters;\n * `jira` and `linear` are accepted as values for tracking purposes\n * even though no adapter exists yet (events from those sources can't\n * fire workflows today). `other` is the catch-all for projects that\n * exist organisationally but aren't wired to any source. */\nexport type ProjectKind = \"gitlab\" | \"github\" | \"jira\" | \"linear\" | \"other\"\n\n/** Default runbook filenames read into the agent system prefix. Order\n * preserved so CLAUDE.md (claude convention) lands before AGENTS.md\n * (codex convention) — agents see Claude rules first. Operator can\n * override per-project via `runbookFiles`. */\nexport const DEFAULT_RUNBOOK_FILES = [\"CLAUDE.md\", \"AGENTS.md\", \"DEPLOY.md\", \"RUNBOOK.md\"] as const\n\nexport interface IssueRule {\n /** Allowed actions. Empty/absent = no action restriction. */\n actions?: string[]\n /** Issue must carry at least one of these labels. Empty/absent = no req. */\n requireLabels?: string[]\n /** Any of these labels present blocks the event. */\n excludeLabels?: string[]\n /** States that always block (gitlab: opened|closed|reopened, github: open|closed). */\n excludeStates?: string[]\n /** Username substrings/equals that always block — used for bot exclusion. */\n excludeAuthors?: string[]\n}\n\nexport interface NoteTrigger {\n /** Comment must contain this @mention to fire. Case-insensitive. */\n mention?: string\n /** Comment must contain this keyword (case-insensitive substring). */\n keyword?: string\n}\n\n/** Sentinel string that tells the matcher to accept any @-mention that\n * resolves to a configured agent (per `channels.gitlab.agentMappings`).\n * Lets a project rule say \"fire on any agent mention\" without manually\n * enumerating every handle — the canonical list lives in agentMappings.\n * Use it as a string entry in `triggers`:\n * triggers: [auto, { keyword: \"merge and deploy\" }]\n * When the channel adapter doesn't supply `knownAgentMentions`, an\n * `auto` entry is treated as a no-op (it does not block the rule). */\nexport type NoteTriggerEntry = NoteTrigger | \"auto\"\n\nexport interface NoteRule {\n /** Restrict to certain noteable types — gitlab: issue|merge_request|commit|snippet. */\n onlyOn?: string[]\n /** At least one trigger must match the comment text for the event to fire.\n * Mix explicit objects with the `\"auto\"` sentinel to delegate mention\n * enumeration to the channel's agentMappings. */\n triggers?: NoteTriggerEntry[]\n /** Username substrings/equals that always block. */\n excludeAuthors?: string[]\n}\n\nexport interface PipelineRule {\n /** Allowed pipeline statuses (success|failed|canceled|...). */\n actions?: string[]\n}\n\nexport interface ProjectRule {\n /** Project path. GitLab: `org/repo`. GitHub: `owner/repo`. Required. */\n project: string\n channel?: Channel\n /** Declarative source of this project. Optional. When unset, the\n * loader infers from clauses (presence of `gitlab:` → \"gitlab\",\n * `github:` → \"github\", neither → \"other\"). Set explicitly to track\n * jira / linear / other-source projects that don't have rule\n * clauses yet. */\n kind?: ProjectKind\n /** Human-friendly project name shown on the Projects page header. */\n displayName?: string\n /** External URL — opens the project's home (GitLab repo, GitHub repo,\n * Jira board, etc.). Rendered as a link on the project header. */\n homeUrl?: string\n /** Contact ids from `.agentx/contacts.json` associated with this\n * project. The Projects page uses these to populate the Contacts\n * section in addition to contacts that carry their own `project`\n * field. Either side may declare the link — both render. */\n contacts?: string[]\n /** Default agent for this project. Overrides channel-level resolveAgent\n * when set. Optional — leaving unset preserves existing behaviour. */\n agent?: string\n /** Filesystem path to the project root (where CLAUDE.md/AGENTS.md live).\n * When set, the registry injects those files into the agent system\n * prefix for tasks scoped to this project. */\n runbook?: string\n /** Override the default runbook file set (DEFAULT_RUNBOOK_FILES). */\n runbookFiles?: string[]\n gitlab?: {\n issue?: IssueRule\n /** Same matcher shape as issue — actions/requireLabels/excludeLabels/\n * excludeStates/excludeAuthors. GitLab MR `action` values: open,\n * reopen, update, close, approved, unapproved, approval, unapproval,\n * merge. Most W3-style review automations want `[open, reopen, update]`\n * only, with `excludeAuthors: [bot, acme-]` to ignore agent-driven\n * MR updates. */\n merge_request?: IssueRule\n note?: NoteRule\n pipeline?: PipelineRule\n }\n github?: {\n /** GitHub uses `issues` for issue events and `pull_request` for PRs. */\n issues?: IssueRule\n pull_request?: IssueRule\n push?: { branches?: string[] }\n }\n /** Free-form notes for the operator. Ignored by the loader. */\n description?: string\n /** Internal: file path the rule was loaded from, for diagnostics. */\n _path?: string\n}\n\nexport interface FilterDecision {\n allow: boolean\n /** Reason for the decision — surfaced in logs so operators can see why\n * an event was dropped. Always present when allow=false. */\n reason?: string\n}\n\nexport class ProjectRulesStore {\n private byProject = new Map<string, ProjectRule>()\n private dir: string\n private log: (...args: unknown[]) => void\n private watcher?: FSWatcher\n private debounce?: ReturnType<typeof setTimeout>\n private loadErrors: Array<{ file: string; error: string }> = []\n\n constructor(dir: string, log: (...args: unknown[]) => void = console.error.bind(console, \"[projects]\")) {\n this.dir = dir\n this.log = log\n }\n\n load(): { count: number; errors: number } {\n this.byProject.clear()\n this.loadErrors = []\n if (!existsSync(this.dir)) {\n return { count: 0, errors: 0 }\n }\n for (const file of this.walk(this.dir)) {\n try {\n const raw = readFileSync(file, \"utf-8\")\n const parsed = yaml.load(raw) as unknown\n const rule = this.validate(parsed, file)\n if (!rule) continue\n rule._path = file\n if (this.byProject.has(rule.project)) {\n this.log(`duplicate project \"${rule.project}\" — keeping ${this.byProject.get(rule.project)!._path}, skipping ${file}`)\n continue\n }\n this.byProject.set(rule.project, rule)\n } catch (e: any) {\n this.loadErrors.push({ file, error: e?.message ?? String(e) })\n this.log(`failed to load ${file}: ${e?.message ?? e}`)\n }\n }\n return { count: this.byProject.size, errors: this.loadErrors.length }\n }\n\n startWatching(onReload?: () => void): void {\n if (!existsSync(this.dir)) return\n try {\n this.watcher = watch(this.dir, { recursive: true }, () => {\n if (this.debounce) clearTimeout(this.debounce)\n this.debounce = setTimeout(() => {\n const result = this.load()\n this.log(`rules reloaded: ${result.count} project(s), ${result.errors} error(s)`)\n onReload?.()\n }, 200)\n })\n } catch (e: any) {\n this.log(`watcher unavailable (${e.message}) — rules will not hot-reload`)\n }\n }\n\n stop(): void {\n this.watcher?.close()\n if (this.debounce) clearTimeout(this.debounce)\n }\n\n /** Look up a rule by project path. Returns undefined if no rule\n * exists — callers must treat that as \"no filter, legacy behaviour\". */\n find(project: string): ProjectRule | undefined {\n return this.byProject.get(project)\n }\n\n list(): ProjectRule[] {\n return Array.from(this.byProject.values())\n }\n\n /** Resolve a project's `kind` even when not set on the rule file. The\n * loader-side default is intentionally absent — we infer at read time\n * so an operator who flips a `gitlab:` clause to `github:` doesn't\n * also have to remember to update `kind:`. */\n static inferKind(rule: ProjectRule): ProjectKind {\n if (rule.kind) return rule.kind\n if (rule.gitlab) return \"gitlab\"\n if (rule.github) return \"github\"\n return \"other\"\n }\n\n health(): { count: number; errors: Array<{ file: string; error: string }> } {\n return { count: this.byProject.size, errors: [...this.loadErrors] }\n }\n\n // --- Filter decisions ---\n\n /** Should a GitLab issue event reach an agent? */\n shouldFireGitlabIssue(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.gitlab?.issue\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"gitlab\")\n }\n\n /** Should a GitLab MR-or-issue note event reach an agent?\n * `opts.knownAgentMentions` carries the @-handle list resolved by the\n * channel adapter from `agentMappings.gitlabUsernames`. When the rule\n * has an `\"auto\"` trigger entry, the matcher accepts any of these\n * mentions found in the comment text — single source of truth, no\n * manual sync required between rule files and agentMappings. */\n shouldFireGitlabNote(project: string, payload: {\n noteableType?: string\n text?: string\n authorUsername?: string\n }, opts?: { knownAgentMentions?: string[] }): FilterDecision {\n const rule = this.find(project)?.gitlab?.note\n if (!rule) return { allow: true }\n return matchNoteRule(rule, payload, opts?.knownAgentMentions)\n }\n\n /** Should a GitLab MR event reach an agent? Same matcher as issue —\n * filter on action / labels / state / author. */\n shouldFireGitlabMR(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.gitlab?.merge_request\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"gitlab\")\n }\n\n /** Should a GitLab pipeline event reach an agent? */\n shouldFireGitlabPipeline(project: string, payload: { status?: string }): FilterDecision {\n const rule = this.find(project)?.gitlab?.pipeline\n if (!rule) return { allow: true }\n if (rule.actions && rule.actions.length > 0 && payload.status && !rule.actions.includes(payload.status)) {\n return { allow: false, reason: `pipeline status=\"${payload.status}\" not in actions=${JSON.stringify(rule.actions)}` }\n }\n return { allow: true }\n }\n\n /** Should a GitHub issues event reach an agent? */\n shouldFireGithubIssue(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.github?.issues\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"github\")\n }\n\n /** Should a GitHub pull_request event reach an agent? */\n shouldFireGithubPR(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.github?.pull_request\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"github\")\n }\n\n // --- Internals ---\n\n private *walk(dir: string): Generator<string> {\n let entries: string[]\n try { entries = readdirSync(dir) } catch { return }\n for (const name of entries) {\n const full = join(dir, name)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (stat.isDirectory()) {\n yield* this.walk(full)\n continue\n }\n const ext = extname(name).toLowerCase()\n if (ext === \".yaml\" || ext === \".yml\") yield full\n }\n }\n\n private validate(raw: unknown, file: string): ProjectRule | null {\n if (!raw || typeof raw !== \"object\") {\n throw new Error(`${file}: top-level must be an object`)\n }\n const r = raw as Record<string, unknown>\n if (typeof r.project !== \"string\" || !r.project.includes(\"/\")) {\n throw new Error(`${file}: \\`project\\` must be a string of the form \"org/repo\"`)\n }\n if (r.runbook !== undefined && typeof r.runbook !== \"string\") {\n throw new Error(`${file}: \\`runbook\\` must be a string path`)\n }\n if (r.runbook && !resolve(r.runbook as string)) {\n // resolve never returns falsy — kept for symmetry / future expansion.\n }\n return raw as ProjectRule\n }\n}\n\nfunction matchIssueRule(rule: IssueRule, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n}, kind: Channel): FilterDecision {\n const labels = payload.labels ?? []\n const author = payload.authorUsername ?? \"\"\n if (rule.actions && rule.actions.length > 0) {\n if (!payload.action || !rule.actions.includes(payload.action)) {\n return { allow: false, reason: `${kind} issue action=\"${payload.action}\" not in actions=${JSON.stringify(rule.actions)}` }\n }\n }\n if (rule.excludeStates && payload.state && rule.excludeStates.includes(payload.state)) {\n return { allow: false, reason: `${kind} issue state=\"${payload.state}\" in excludeStates` }\n }\n if (rule.requireLabels && rule.requireLabels.length > 0) {\n const has = rule.requireLabels.some((req) => labels.includes(req))\n if (!has) {\n return { allow: false, reason: `${kind} issue missing requireLabels=${JSON.stringify(rule.requireLabels)} (have ${JSON.stringify(labels)})` }\n }\n }\n if (rule.excludeLabels && rule.excludeLabels.length > 0) {\n const hit = rule.excludeLabels.find((bad) => labels.includes(bad))\n if (hit) {\n return { allow: false, reason: `${kind} issue carries excluded label \"${hit}\"` }\n }\n }\n if (rule.excludeAuthors && rule.excludeAuthors.length > 0) {\n const hit = rule.excludeAuthors.find((bad) => author.toLowerCase().includes(bad.toLowerCase()))\n if (hit) {\n return { allow: false, reason: `${kind} issue authored by \"${author}\" matched excludeAuthors entry \"${hit}\"` }\n }\n }\n return { allow: true }\n}\n\nfunction matchNoteRule(rule: NoteRule, payload: {\n noteableType?: string\n text?: string\n authorUsername?: string\n}, knownAgentMentions?: string[]): FilterDecision {\n const text = (payload.text ?? \"\").toLowerCase()\n const author = payload.authorUsername ?? \"\"\n if (rule.onlyOn && rule.onlyOn.length > 0) {\n if (!payload.noteableType || !rule.onlyOn.includes(payload.noteableType)) {\n return { allow: false, reason: `note noteableType=\"${payload.noteableType}\" not in onlyOn=${JSON.stringify(rule.onlyOn)}` }\n }\n }\n if (rule.excludeAuthors && rule.excludeAuthors.length > 0) {\n const hit = rule.excludeAuthors.find((bad) => author.toLowerCase().includes(bad.toLowerCase()))\n if (hit) {\n return { allow: false, reason: `note authored by \"${author}\" matched excludeAuthors entry \"${hit}\"` }\n }\n }\n if (rule.triggers && rule.triggers.length > 0) {\n const matched = rule.triggers.some((t) => {\n // \"auto\" sentinel — matches any of the agent mentions resolved by the\n // adapter from agentMappings.gitlabUsernames. When the adapter doesn't\n // supply the list (or it's empty), `auto` neither matches nor blocks\n // — fall through to the next trigger entry.\n if (t === \"auto\") {\n if (!knownAgentMentions || knownAgentMentions.length === 0) return false\n return knownAgentMentions.some((m) => text.includes(m.toLowerCase()))\n }\n if (t.mention) {\n const m = t.mention.toLowerCase()\n if (text.includes(m)) return true\n }\n if (t.keyword) {\n if (text.includes(t.keyword.toLowerCase())) return true\n }\n return false\n })\n if (!matched) {\n return { allow: false, reason: `note text matched no trigger` }\n }\n }\n return { allow: true }\n}\n"],"mappings":"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","names":["existsSync","readdirSync","readFileSync","statSync","watch","extname","join","resolve","yaml","ProjectRulesStore","dir","log","existsSync","file","raw","readFileSync","parsed","yaml","rule","e","onReload","watch","result","project","payload","matchIssueRule","opts","matchNoteRule","entries","readdirSync","name","full","join","stat","statSync","ext","extname","resolve","kind","labels","author","req","hit","bad","knownAgentMentions","text","t","m"]}
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as s}from"./chunk-UHE5FCPB.js";import"./chunk-O6YPEOQF.js";import"./chunk-BVGJSPW4.js";import"./chunk-V3U4OF7L.js";import"./chunk-UCQ2N3KO.js";import"./chunk-P4VWIVKN.js";import"./chunk-GV7S5BAX.js";import"./chunk-GSX4NK2N.js";import"./chunk-SZDACK6O.js";import"./chunk-METYYZKQ.js";import"./chunk-3WQFCIB6.js";import"./chunk-E5CXCI33.js";import"./chunk-MTAPOCGA.js";import"./chunk-M4AMWP3H.js";import"./chunk-VT5XYFDD.js";import"./chunk-SFQUP3BP.js";import"./chunk-I3SIXTJO.js";import"./chunk-67Q4DCPS.js";import"./chunk-LDHPMJ7M.js";import"./chunk-2WVX3LEC.js";import"./chunk-76Z4VHDX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import"./chunk-D46VZ7K7.js";import"./chunk-K2CAPYES.js";import"./chunk-BTHHSY7D.js";import"./chunk-ISEBLHWX.js";import"./chunk-HR4JSRZS.js";import"./chunk-VWTBHLMX.js";import"./chunk-BFXONUUN.js";import"./chunk-JTJIPNXY.js";import"./chunk-64P4QHSZ.js";import"./chunk-BFZUG66X.js";import"./chunk-SR3X25TN.js";import"./chunk-53NRX2NA.js";import"./chunk-FDWOECJY.js";import"./chunk-VJUTQAPJ.js";import"./chunk-DJEZ2HVU.js";import"./chunk-DSYKYZMT.js";import"./chunk-5U35WAZF.js";import"./chunk-QOYAX2FT.js";import"./chunk-4YCH6IZV.js";process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));async function o(){let r=await s();if(process.argv.slice(2).length===0){r.outputHelp();return}r.parse()}o();
2
+ import{a as s}from"./chunk-LNRB5MCU.js";import"./chunk-VTE7L6LO.js";import"./chunk-UFAGYX73.js";import"./chunk-UCQ2N3KO.js";import"./chunk-52ZPELD2.js";import"./chunk-IB3F7VBE.js";import"./chunk-GV7S5BAX.js";import"./chunk-GSX4NK2N.js";import"./chunk-SZDACK6O.js";import"./chunk-WZ72HX5C.js";import"./chunk-F6CNF7V3.js";import"./chunk-DCDQZR7I.js";import"./chunk-IWMW5432.js";import"./chunk-ZVP6GB4G.js";import"./chunk-OPHIXXGI.js";import"./chunk-GDWJUGB5.js";import"./chunk-M4AMWP3H.js";import"./chunk-4BR7D2OU.js";import"./chunk-BPIRO4YD.js";import"./chunk-SFQUP3BP.js";import"./chunk-3SCPRYJB.js";import"./chunk-I3SIXTJO.js";import"./chunk-67Q4DCPS.js";import"./chunk-76Z4VHDX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import"./chunk-FIEJDZVC.js";import"./chunk-2T5W3ETS.js";import"./chunk-77OV7346.js";import"./chunk-VWTBHLMX.js";import"./chunk-IBUSGCLQ.js";import"./chunk-PNWNVOEZ.js";import"./chunk-64P4QHSZ.js";import"./chunk-L2IZPR6P.js";import"./chunk-AJ5RMU7X.js";import"./chunk-EVWEG3VD.js";import"./chunk-AX2PWSRT.js";import"./chunk-AJONZWLT.js";import"./chunk-F4HZTZHW.js";import"./chunk-ZTEU5JVQ.js";import"./chunk-QOYAX2FT.js";import"./chunk-WPMX3VGI.js";import"./chunk-K33GIBSE.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-CJQQIRPD.js";import"./chunk-BH3GTCZ6.js";import"./chunk-FIOB3MEX.js";process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));async function o(){let r=await s();if(process.argv.slice(2).length===0){r.outputHelp();return}r.parse()}o();
3
3
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { buildProgram } from \"@/program\"\n\nprocess.on(\"SIGINT\", () => process.exit(0))\nprocess.on(\"SIGTERM\", () => process.exit(0))\n\nasync function main() {\n const program = await buildProgram()\n\n const args = process.argv.slice(2)\n if (args.length === 0) {\n program.outputHelp()\n return\n }\n\n program.parse()\n}\n\nmain()\n"],"mappings":";oqCAGA,QAAQ,GAAG,SAAU,IAAM,QAAQ,KAAK,CAAC,CAAC,EAC1C,QAAQ,GAAG,UAAW,IAAM,QAAQ,KAAK,CAAC,CAAC,EAE3C,eAAeA,GAAO,CACpB,IAAMC,EAAU,MAAMC,EAAa,EAGnC,GADa,QAAQ,KAAK,MAAM,CAAC,EACxB,SAAW,EAAG,CACrBD,EAAQ,WAAW,EACnB,OAGFA,EAAQ,MAAM,CAChB,CAEAD,EAAK","names":["main","program","buildProgram"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { buildProgram } from \"@/program\"\n\nprocess.on(\"SIGINT\", () => process.exit(0))\nprocess.on(\"SIGTERM\", () => process.exit(0))\n\nasync function main() {\n const program = await buildProgram()\n\n const args = process.argv.slice(2)\n if (args.length === 0) {\n program.outputHelp()\n return\n }\n\n program.parse()\n}\n\nmain()\n"],"mappings":";o4CAGA,QAAQ,GAAG,SAAU,IAAM,QAAQ,KAAK,CAAC,CAAC,EAC1C,QAAQ,GAAG,UAAW,IAAM,QAAQ,KAAK,CAAC,CAAC,EAE3C,eAAeA,GAAO,CACpB,IAAMC,EAAU,MAAMC,EAAa,EAGnC,GADa,QAAQ,KAAK,MAAM,CAAC,EACxB,SAAW,EAAG,CACrBD,EAAQ,WAAW,EACnB,OAGFA,EAAQ,MAAM,CAChB,CAEAD,EAAK","names":["main","program","buildProgram"]}
@@ -0,0 +1,2 @@
1
+ import{a,b,c,d,e,f,g}from"./chunk-VTE7L6LO.js";import"./chunk-CJQQIRPD.js";import"./chunk-FIOB3MEX.js";export{b as fetchAgents,d as fetchCrons,c as fetchProcesses,e as killProcess,a as resolveConn,f as sendTask,g as streamTask};
2
+ //# sourceMappingURL=client-PX25MD47.js.map
@@ -1,4 +1,4 @@
1
- import{a as h}from"./chunk-QOYAX2FT.js";import"./chunk-4YCH6IZV.js";import{readFileSync as O,writeFileSync as k,existsSync as S,mkdirSync as E}from"fs";import{resolve as g}from"path";function f(t,o){let n=new Map;for(let r of t){if(r.role!=="agent")continue;let s=h(r.content);for(let a of s)n.set(a,(n.get(a)||0)+1)}let e=[...n.entries()].sort((r,s)=>s[1]-r[1]).slice(0,50).map(([r])=>r);return{lexicon:n,toolCalls:o||[],topKeywords:e,capturedAt:new Date().toISOString(),messageCount:t.length}}function y(t,o){let n=D(t,o),{distance:e,newTools:r,missingTools:s}=T(t,o),a=b(t,o),c=Math.min(1,n*.4+e*.3+a*.3),m=t.topKeywords.filter(l=>!o.lexicon.has(l)||(o.lexicon.get(l)||0)===0).slice(0,10);return{overallScore:Math.round(c*100)/100,lexiconDecay:Math.round(n*100)/100,toolShift:Math.round(e*100)/100,semanticDrift:Math.round(a*100)/100,lostWords:m,newTools:r,missingTools:s}}function D(t,o){if(t.topKeywords.length===0)return 0;let n=0;for(let e of t.topKeywords)o.lexicon.has(e)||n++;return n/t.topKeywords.length}function T(t,o){let n=new Set(t.toolCalls),e=new Set(o.toolCalls);if(n.size===0&&e.size===0)return{distance:0,newTools:[],missingTools:[]};let r=[...n].filter(u=>e.has(u)),s=new Set([...n,...e]),c=1-r.length/s.size,m=[...e].filter(u=>!n.has(u)),l=[...n].filter(u=>!e.has(u));return{distance:c,newTools:m,missingTools:l}}function b(t,o){if(t.topKeywords.length===0)return 0;let n=new Set(o.topKeywords),e=0;for(let r of t.topKeywords)n.has(r)&&e++;return 1-e/t.topKeywords.length}var w=".agentx/drift";function C(t,o,n=process.cwd()){let e=g(n,w);S(e)||E(e,{recursive:!0});let r={...o,lexicon:Object.fromEntries(o.lexicon)};k(g(e,`${t}.json`),JSON.stringify(r,null,2))}function M(t,o=process.cwd()){let n=g(o,w,`${t}.json`);if(!S(n))return null;try{let e=JSON.parse(O(n,"utf-8"));return{...e,lexicon:new Map(Object.entries(e.lexicon))}}catch{return null}}var I="claude-haiku-4-20250514";var R=5e4,A=6,v=8,K=`You are a conversation summarizer. Given a conversation history between a user and an AI agent, produce a concise summary that preserves:
1
+ import{a as h}from"./chunk-QOYAX2FT.js";import"./chunk-FIOB3MEX.js";import{readFileSync as O,writeFileSync as k,existsSync as S,mkdirSync as E}from"fs";import{resolve as g}from"path";function f(t,o){let n=new Map;for(let r of t){if(r.role!=="agent")continue;let s=h(r.content);for(let a of s)n.set(a,(n.get(a)||0)+1)}let e=[...n.entries()].sort((r,s)=>s[1]-r[1]).slice(0,50).map(([r])=>r);return{lexicon:n,toolCalls:o||[],topKeywords:e,capturedAt:new Date().toISOString(),messageCount:t.length}}function y(t,o){let n=D(t,o),{distance:e,newTools:r,missingTools:s}=T(t,o),a=b(t,o),c=Math.min(1,n*.4+e*.3+a*.3),m=t.topKeywords.filter(l=>!o.lexicon.has(l)||(o.lexicon.get(l)||0)===0).slice(0,10);return{overallScore:Math.round(c*100)/100,lexiconDecay:Math.round(n*100)/100,toolShift:Math.round(e*100)/100,semanticDrift:Math.round(a*100)/100,lostWords:m,newTools:r,missingTools:s}}function D(t,o){if(t.topKeywords.length===0)return 0;let n=0;for(let e of t.topKeywords)o.lexicon.has(e)||n++;return n/t.topKeywords.length}function T(t,o){let n=new Set(t.toolCalls),e=new Set(o.toolCalls);if(n.size===0&&e.size===0)return{distance:0,newTools:[],missingTools:[]};let r=[...n].filter(u=>e.has(u)),s=new Set([...n,...e]),c=1-r.length/s.size,m=[...e].filter(u=>!n.has(u)),l=[...n].filter(u=>!e.has(u));return{distance:c,newTools:m,missingTools:l}}function b(t,o){if(t.topKeywords.length===0)return 0;let n=new Set(o.topKeywords),e=0;for(let r of t.topKeywords)n.has(r)&&e++;return 1-e/t.topKeywords.length}var w=".agentx/drift";function C(t,o,n=process.cwd()){let e=g(n,w);S(e)||E(e,{recursive:!0});let r={...o,lexicon:Object.fromEntries(o.lexicon)};k(g(e,`${t}.json`),JSON.stringify(r,null,2))}function M(t,o=process.cwd()){let n=g(o,w,`${t}.json`);if(!S(n))return null;try{let e=JSON.parse(O(n,"utf-8"));return{...e,lexicon:new Map(Object.entries(e.lexicon))}}catch{return null}}var I="claude-haiku-4-20250514";var R=5e4,A=6,v=8,K=`You are a conversation summarizer. Given a conversation history between a user and an AI agent, produce a concise summary that preserves:
2
2
 
3
3
  1. Key decisions made and their reasoning
4
4
  2. Important facts learned (names, IDs, paths, URLs, credentials shared)
@@ -17,12 +17,12 @@ Format as a structured summary:
17
17
  Keep it under 500 words. Prioritize actionable information over conversational flow.
18
18
  Do NOT include greetings, acknowledgments, or meta-commentary about the conversation.`;function U(t){return t.length<v?!1:t.reduce((n,e)=>n+e.content.length,0)>R}async function G(t,o,n){if(t.length<v)return{summary:"",compactedCount:0,keptCount:t.length,memoryFlushed:!1};let e=Math.max(0,t.length-A),r=t.slice(0,e),s=t.slice(e);if(r.length===0)return{summary:"",compactedCount:0,keptCount:s.length,memoryFlushed:!1};let a=f(r);C(o,a);let c=!1;if(n)try{await _(o,r,n),c=!0}catch{}let m=$(r),l=await j(m),{qualityScore:u,lostEntities:i}=P(m,l),p,F=[{role:"agent",content:l,timestamp:new Date().toISOString()}],x=f(F),d=M(o);return d&&(p=y(d,x)),{summary:l,compactedCount:r.length,keptCount:s.length,memoryFlushed:c,qualityScore:u,lostEntities:i.length>0?i:void 0,drift:p&&p.overallScore>.1?p:void 0}}function P(t,o){let n=B(t);if(n.length===0)return{qualityScore:100,lostEntities:[]};let e=o.toLowerCase(),r=[],s=[];for(let c of n)e.includes(c.toLowerCase())?r.push(c):s.push(c);return{qualityScore:Math.round(r.length/n.length*100),lostEntities:s}}function B(t){let o=[],n=new Set,e=i=>{let p=i.toLowerCase().trim();p.length<3||n.has(p)||(n.add(p),o.push(i.trim()))},r=t.match(/https?:\/\/[^\s<>)"]+/g)||[];for(let i of r)e(i.slice(0,80));let s=t.match(/(?:\/[\w.-]+){2,}/g)||[];for(let i of s)e(i);let a=t.match(/[#!]\d{2,}/g)||[];for(let i of a)e(i);let c=t.match(/[\w.-]+@[\w.-]+\.\w+/g)||[];for(let i of c)e(i);let m=t.match(/"([^"]{3,40})"/g)||[];for(let i of m)e(i.slice(1,-1));let l=t.match(/[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+/g)||[];for(let i of l)e(i);let u=t.match(/[a-f0-9]{8,}/gi)||[];for(let i of u.slice(0,5))e(i);return o.slice(0,30)}function J(t,o){if(o.compactedCount===0)return t;let n=t.slice(-o.keptCount);return[{role:"agent",name:"system",content:`[Compacted conversation summary \u2014 ${o.compactedCount} earlier messages]
19
19
 
20
- ${o.summary}`,timestamp:new Date().toISOString()},...n]}async function _(t,o,n){let e=[],r=[];for(let a of o)a.role==="user"?e.push(`${a.name||"User"}: ${a.content}`):r.push(a.content);if(e.length===0||r.length===0)return;let{extractMemories:s}=await import("./memory-extract-AENFVVO5.js");await s(t,e.join(`
20
+ ${o.summary}`,timestamp:new Date().toISOString()},...n]}async function _(t,o,n){let e=[],r=[];for(let a of o)a.role==="user"?e.push(`${a.name||"User"}: ${a.content}`):r.push(a.content);if(e.length===0||r.length===0)return;let{extractMemories:s}=await import("./memory-extract-F5ZHWY5E.js");await s(t,e.join(`
21
21
  `),r.join(`
22
22
  `),{channel:"compaction",chatId:"flush",sender:"compaction"},n)}function $(t){return t.map(o=>{let n=o.timestamp.slice(11,16),e=o.role==="user"?o.name||"User":o.name||"Agent";return`[${n}] ${e}: ${o.content}`}).join(`
23
23
 
24
- `)}async function j(t){let n=t.length>2e4?t.slice(-2e4):t;try{let{createProvider:e}=await import("./providers-L27BJ5A6.js");return(await e("claude").generate([{role:"system",content:K},{role:"user",content:n}],{model:I,maxTokens:1024})).content.trim()}catch{let s=t.split(`
24
+ `)}async function j(t){let n=t.length>2e4?t.slice(-2e4):t;try{let{createProvider:e}=await import("./providers-KO2DODIO.js");return(await e("claude").generate([{role:"system",content:K},{role:"user",content:n}],{model:I,maxTokens:1024})).content.trim()}catch{let s=t.split(`
25
25
  `).slice(-20);return`[Summary unavailable \u2014 keeping last ${s.length} lines]
26
26
  ${s.join(`
27
27
  `)}`}}export{J as applyCompaction,G as compactSession,U as needsCompaction};
28
- //# sourceMappingURL=compaction-NWEMOSHA.js.map
28
+ //# sourceMappingURL=compaction-FD4R23HZ.js.map
@@ -0,0 +1,2 @@
1
+ import{d as a,e as b,f as c,g as d,h as e,j as f}from"./chunk-CJQQIRPD.js";import"./chunk-FIOB3MEX.js";f();export{a as INTEGRATION_KINDS,b as daemonConfigSchema,c as expandEnvVars,d as loadDaemonConfig,e as validateWorkspaces};
2
+ //# sourceMappingURL=config-HL5F36V4.js.map
@@ -0,0 +1,2 @@
1
+ import{a,b,c,d}from"./chunk-2T5W3ETS.js";import"./chunk-CJQQIRPD.js";import"./chunk-FIOB3MEX.js";export{a as applyConfigMutation,c as getAtPath,b as setAtPath,d as unsetAtPath};
2
+ //# sourceMappingURL=config-mutator-HBX53OMJ.js.map
@@ -1,2 +1,2 @@
1
- import"./chunk-4YCH6IZV.js";function u(r,g){let n=[];for(let o of r)n.push(...c(o,g));return n}function c(r,g){let n=[],o=g.channels.gitlab;if(!o?.enabled||!o.agentMappings?.length)return n;let l=new Set(["on:gitlab-issue","on:gitlab-mr","on:gitlab-note"]);for(let i of r.nodes){let s,t;if(i.type==="trigger.channel"){let e=i.config;if(e?.source!=="gitlab-issue")continue;t=e.filter?.project,s=`trigger.channel(gitlab-issue, project=${t??"*"})`}else if(i.type==="trigger.hook"){let e=i.config;if(!e?.event||!l.has(e.event))continue;t=void 0,s=`trigger.hook(${e.event})`}else continue;let a=o.agentMappings.filter(e=>!t||t==="*"?!0:o.routes.find(f=>f.project===t)?.agent===e.agentId);a.length!==0&&n.push({kind:"workflow-vs-gitlab-agentmapping",severity:"warn",workflowId:r.id,summary:`workflow "${r.id}" ${s} overlaps with gitlab agentMapping(s) ${a.map(e=>e.agentId).join(", ")}`,details:{triggerNodeId:i.id,triggerType:i.type,project:t??"*",overlappingAgents:a.map(e=>e.agentId)},suggestion:"set `hookBlocked: true` on the gitlab trigger so the channel router's default-target dispatch defers to the workflow",autoFix:!0})}return n}export{u as detectConflicts};
2
- //# sourceMappingURL=conflict-detector-D7VIYPDI.js.map
1
+ import"./chunk-FIOB3MEX.js";function u(r,g){let n=[];for(let o of r)n.push(...c(o,g));return n}function c(r,g){let n=[],o=g.channels.gitlab;if(!o?.enabled||!o.agentMappings?.length)return n;let l=new Set(["on:gitlab-issue","on:gitlab-mr","on:gitlab-note"]);for(let i of r.nodes){let s,t;if(i.type==="trigger.channel"){let e=i.config;if(e?.source!=="gitlab-issue")continue;t=e.filter?.project,s=`trigger.channel(gitlab-issue, project=${t??"*"})`}else if(i.type==="trigger.hook"){let e=i.config;if(!e?.event||!l.has(e.event))continue;t=void 0,s=`trigger.hook(${e.event})`}else continue;let a=o.agentMappings.filter(e=>!t||t==="*"?!0:o.routes.find(f=>f.project===t)?.agent===e.agentId);a.length!==0&&n.push({kind:"workflow-vs-gitlab-agentmapping",severity:"warn",workflowId:r.id,summary:`workflow "${r.id}" ${s} overlaps with gitlab agentMapping(s) ${a.map(e=>e.agentId).join(", ")}`,details:{triggerNodeId:i.id,triggerType:i.type,project:t??"*",overlappingAgents:a.map(e=>e.agentId)},suggestion:"set `hookBlocked: true` on the gitlab trigger so the channel router's default-target dispatch defers to the workflow",autoFix:!0})}return n}export{u as detectConflicts};
2
+ //# sourceMappingURL=conflict-detector-ZPOT3M2C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workflows/conflict-detector.ts"],"sourcesContent":["// Static analyzer for the dispatch graph.\n//\n// A \"conflict\" is two dispatch paths that can fire for the same external\n// event. The classic case (incident 2026-04-29 issue globex/globex-system-v2#709):\n// a workflow `gitlab-sdlc-loop` triggers on `gitlab-issue` for project\n// globex/globex-system-v2 AND the gitlab channel router has an agentMapping for\n// `globex-v2` on the same project. Both fire on issue.create — two parallel\n// agent runs on one issue.\n//\n// This module runs at workflow load and on config reload. It returns a list\n// of detected conflicts. v0 covers only the trigger.channel(gitlab-issue) ↔\n// gitlab.agentMappings case (the one that produced #709). The detector is\n// designed to grow: each conflict kind is a single function, all wired into\n// detectConflicts().\n//\n// The dispatcher and trigger registrar do NOT consult this module directly\n// today — they honor `workflow.state`, which is set by the operator (manual\n// `disabled`) or by an upcoming auto-quarantine layer (`quarantined`). This\n// stub exists so that layer has a stable contract to build on.\n\nimport type { Workflow } from \"./types\"\nimport type { DaemonConfig } from \"@/daemon/config\"\n\nexport type ConflictSeverity = \"warn\" | \"block\"\nexport type ConflictKind = \"workflow-vs-gitlab-agentmapping\"\n\nexport interface Conflict {\n kind: ConflictKind\n severity: ConflictSeverity\n workflowId: string\n /** Brief one-liner suitable for log + admin panel summary. */\n summary: string\n /** What overlaps. Free-form so each conflict kind can carry its own data. */\n details: Record<string, unknown>\n /** Suggested resolution shown to the operator. Auto-applicable fixes set\n * `autoFix: true` so an upcoming auto-fix layer can act without prompt. */\n suggestion: string\n autoFix: boolean\n}\n\n/**\n * Run all conflict checks for the current workflow set against the daemon\n * config. Pure function — does not mutate state, does not log. Callers\n * decide what to do with the result (log, surface, quarantine, auto-fix).\n */\nexport function detectConflicts(workflows: Workflow[], config: DaemonConfig): Conflict[] {\n const out: Conflict[] = []\n for (const wf of workflows) {\n out.push(...detectGitlabAgentMappingOverlap(wf, config))\n }\n return out\n}\n\n// --- Individual conflict checks ---------------------------------------------\n\n/**\n * trigger.channel(source: gitlab-issue, filter.project: P) overlaps with\n * channels.gitlab.agentMappings entries that route the same project. Both\n * paths reach an agent on issue events for project P; the channel router's\n * default-target dispatch (`src/channels/gitlab.ts:712`) will fire alongside\n * the workflow's own dispatch.\n *\n * Auto-fix: set the workflow trigger's `hookBlocked: true` (or the matching\n * channel-router opt-out) so the channel router's default dispatch defers to\n * the workflow. Not applied here — the auto-fix engine consumes this result.\n */\nfunction detectGitlabAgentMappingOverlap(wf: Workflow, config: DaemonConfig): Conflict[] {\n const out: Conflict[] = []\n const gitlab = config.channels.gitlab\n if (!gitlab?.enabled || !gitlab.agentMappings?.length) return out\n\n // Hook events whose source is the gitlab channel router. trigger.hook\n // workflows on these events race against gitlab.agentMappings dispatched\n // from the same webhook (gitlab.ts handleIssue/handleMR/handleNote).\n const GITLAB_HOOK_EVENTS = new Set([\"on:gitlab-issue\", \"on:gitlab-mr\", \"on:gitlab-note\"])\n\n for (const node of wf.nodes) {\n let triggerLabel: string | undefined\n let project: string | undefined\n\n if (node.type === \"trigger.channel\") {\n const cfg = node.config as { source?: string; filter?: { project?: string } } | undefined\n if (cfg?.source !== \"gitlab-issue\") continue\n project = cfg.filter?.project\n triggerLabel = `trigger.channel(gitlab-issue, project=${project ?? \"*\"})`\n } else if (node.type === \"trigger.hook\") {\n // trigger.hook fires on a HookRegistry event; project filter is not\n // part of the hook config (the event IS the filter), so any matching\n // hook races for every project the gitlab adapter dispatches to.\n const cfg = node.config as { event?: string } | undefined\n if (!cfg?.event || !GITLAB_HOOK_EVENTS.has(cfg.event)) continue\n project = undefined // hook fires for all projects\n triggerLabel = `trigger.hook(${cfg.event})`\n } else {\n continue\n }\n\n const overlaps = gitlab.agentMappings.filter((m) => {\n if (!project || project === \"*\") return true\n // The mapping itself doesn't carry a project field — gitlab routing is\n // by username + project routes. We approximate \"this mapping handles\n // this project\" by looking at gitlab.routes too.\n const projectRoute = gitlab.routes.find((r) => r.project === project)\n return projectRoute?.agent === m.agentId\n })\n if (overlaps.length === 0) continue\n\n out.push({\n kind: \"workflow-vs-gitlab-agentmapping\",\n severity: \"warn\",\n workflowId: wf.id,\n summary: `workflow \"${wf.id}\" ${triggerLabel} overlaps with gitlab agentMapping(s) ${overlaps.map((m) => m.agentId).join(\", \")}`,\n details: {\n triggerNodeId: node.id,\n triggerType: node.type,\n project: project ?? \"*\",\n overlappingAgents: overlaps.map((m) => m.agentId),\n },\n suggestion: \"set `hookBlocked: true` on the gitlab trigger so the channel router's default-target dispatch defers to the workflow\",\n autoFix: true,\n })\n }\n return out\n}\n"],"mappings":"4BA6CO,SAASA,EAAgBC,EAAuBC,EAAkC,CACvF,IAAMC,EAAkB,CAAC,EACzB,QAAWC,KAAMH,EACfE,EAAI,KAAK,GAAGE,EAAgCD,EAAIF,CAAM,CAAC,EAEzD,OAAOC,CACT,CAeA,SAASE,EAAgCD,EAAcF,EAAkC,CACvF,IAAMC,EAAkB,CAAC,EACnBG,EAASJ,EAAO,SAAS,OAC/B,GAAI,CAACI,GAAQ,SAAW,CAACA,EAAO,eAAe,OAAQ,OAAOH,EAK9D,IAAMI,EAAqB,IAAI,IAAI,CAAC,kBAAmB,eAAgB,gBAAgB,CAAC,EAExF,QAAWC,KAAQJ,EAAG,MAAO,CAC3B,IAAIK,EACAC,EAEJ,GAAIF,EAAK,OAAS,kBAAmB,CACnC,IAAMG,EAAMH,EAAK,OACjB,GAAIG,GAAK,SAAW,eAAgB,SACpCD,EAAUC,EAAI,QAAQ,QACtBF,EAAe,yCAAyCC,GAAW,eAC1DF,EAAK,OAAS,eAAgB,CAIvC,IAAMG,EAAMH,EAAK,OACjB,GAAI,CAACG,GAAK,OAAS,CAACJ,EAAmB,IAAII,EAAI,KAAK,EAAG,SACvDD,EAAU,OACVD,EAAe,gBAAgBE,EAAI,aAEnC,UAGF,IAAMC,EAAWN,EAAO,cAAc,OAAQO,GACxC,CAACH,GAAWA,IAAY,IAAY,GAInBJ,EAAO,OAAO,KAAMQ,GAAMA,EAAE,UAAYJ,CAAO,GAC/C,QAAUG,EAAE,OAClC,EACGD,EAAS,SAAW,GAExBT,EAAI,KAAK,CACP,KAAM,kCACN,SAAU,OACV,WAAYC,EAAG,GACf,QAAS,aAAaA,EAAG,OAAOK,0CAAqDG,EAAS,IAAKC,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,IAC7H,QAAS,CACP,cAAeL,EAAK,GACpB,YAAaA,EAAK,KAClB,QAASE,GAAW,IACpB,kBAAmBE,EAAS,IAAKC,GAAMA,EAAE,OAAO,CAClD,EACA,WAAY,uHACZ,QAAS,EACX,CAAC,EAEH,OAAOV,CACT","names":["detectConflicts","workflows","config","out","wf","detectGitlabAgentMappingOverlap","gitlab","GITLAB_HOOK_EVENTS","node","triggerLabel","project","cfg","overlaps","m","r"]}
@@ -0,0 +1,17 @@
1
+ import{a as d,b as C}from"./chunk-AJONZWLT.js";import"./chunk-FIOB3MEX.js";C();var S="claude-haiku-4-5-20251001",I=`You are a context planner for an AI agent. Given the user's current message and recent conversation tail, decide what historical context the agent needs.
2
+
3
+ Output ONE JSON object on one line, no prose, no fences:
4
+ { "recentTurns": number, "memory": { "include": boolean, "query": string }, "crossChat": boolean, "reasoning": string }
5
+
6
+ Rules:
7
+ - "recentTurns" in [0,6]. Short acknowledgments ("ok", "yes", "do it", "continue") need 2-3 turns to know what they refer to. New topics need 0. Technical follow-ups need 3-5.
8
+ - "memory.include" = true only when the message likely references a person, past event, decision, or project the agent should recall. "memory.query" = 3-8 words distilling what to search for (leave "" when include=false).
9
+ - "crossChat" = true only when the message references another chat ("DM", "the group", "earlier conversation", a peer agent's name). Most messages = false.
10
+ - "reasoning" = one short phrase explaining the plan.
11
+
12
+ Be conservative \u2014 unneeded context costs tokens. When in doubt, exclude.`;async function N(e){let c=Date.now(),i=e.timeoutMs??8e3,r=M(e.sessions,e.agentId,e.channel,e.chatId,4),o=[`Agent: ${e.agentId}`,`Channel: ${e.channel}`,r?`Recent conversation:
13
+ ${r}`:"Recent conversation: (none \u2014 first message)","",`Current message: ${e.message.slice(0,1500)}`].join(`
14
+ `),n;try{let{createProvider:l}=await import("./providers-KO2DODIO.js"),f=l("claude"),b=new AbortController,p=setTimeout(()=>b.abort(),i);try{let x=await f.generate([{role:"system",content:I},{role:"user",content:o}],{model:S,maxTokens:256});n=d(x.content)}finally{clearTimeout(p)}}catch(l){return process.env.AGENTX_PLANNER_DEBUG&&console.error(`[planner] error: ${l?.message??l}`),null}if(!n||typeof n!="object")return null;let m=T(n.recentTurns,0,6,3),t=!!n.memory?.include,s=typeof n.memory?.query=="string"?n.memory.query:"",a=!!n.crossChat,g=typeof n.reasoning=="string"?n.reasoning:void 0,y=w(e.sessions,e.agentId,e.channel,e.chatId,m),u="";if(t&&s)try{let l=e.memoryStore.findRelevant(s,e.agentId,5);u=e.memoryStore.buildContext(l)}catch{}let h="";return a&&(h=e.sessions.getCrossSessionSummary(e.agentId,e.channel,e.chatId,"")),{sessionHistory:y,memoryContext:u,crossChatContext:h,debug:{recentTurns:m,memoryIncluded:t,memoryQuery:s||void 0,crossChatIncluded:a,reasoning:g,planLatencyMs:Date.now()-c}}}function w(e,c,i,r,o){if(o<=0)return"";let n=e.getSession(c,i,r);if(n.messages.length===0)return"";let m=Math.min(n.messages.length,o*2),t=n.messages.slice(-m),s=[`[Recent exchange \u2014 last ${t.length} messages]`];for(let a of t){let g=a.name||(a.role==="user"?"User":"Agent");s.push(`${g}: ${a.content}`)}return s.push("[End recent exchange \u2014 respond to the latest message above]"),s.push(""),s.join(`
15
+ `)}function M(e,c,i,r,o){let n=e.getSession(c,i,r);return n.messages.length===0?"":n.messages.slice(-o).map(t=>{let s=t.name||(t.role==="user"?"User":"Agent"),a=t.content.length>200?t.content.slice(0,200)+"\u2026":t.content;return` ${s}: ${a}`}).join(`
16
+ `)}function T(e,c,i,r){let o=typeof e=="number"?Math.floor(e):Number.parseInt(String(e??""),10);return Number.isFinite(o)?Math.min(i,Math.max(c,o)):r}export{N as planContext};
17
+ //# sourceMappingURL=context-planner-AQHJMVY2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agents/context-planner.ts"],"sourcesContent":["// --- Context Planner ---\n//\n// A Haiku pre-call that decides what context to retrieve for the current\n// message, replacing the blind \"stack every layer\" approach of the layered\n// strategy. Drops --resume bloat by avoiding the full session history blob\n// on every turn; instead pulls only the specific facts / memory / cross-chat\n// the current message actually needs.\n//\n// Always-on core (handled by registry, not the planner): channel, scope,\n// landscape, identity, intent, and a small `recentTurns`-sized tail of the\n// session verbatim. The planner decides: how many tail turns to keep, and\n// whether to pull memory or cross-chat on top.\n//\n// Fail-open: any planner error returns null and the registry falls back to\n// the layered strategy. This keeps the feature safe to enable even when\n// the planner model is misbehaving.\n//\n// Cost model: one small Haiku call per message (~300 input / ~80 output\n// tokens, ~$0.0005). Win threshold is when it avoids shipping ≥5K tokens\n// of layered history/memory the agent didn't need — typical for short\n// \"ok / yes / do it\" messages where the layered path still dumps the\n// whole 12K-char session blob.\n\nimport type { SessionStore, SessionMessage } from \"./sessions\"\nimport { extractJson } from \"@/utils/extract-json\"\nimport type { MemoryStore } from \"./memory-store\"\n\n/** Planner output. When `null`, the caller falls back to the layered\n * strategy. Otherwise each field overrides the corresponding context\n * layer — an empty string means \"skip this layer entirely\". */\nexport interface ContextPlan {\n /** Rendered session history (same shape buildHistoryContext returns),\n * truncated to the last N turns the planner picked. */\n sessionHistory: string\n /** Rendered memory context, or \"\" when the planner decided memory\n * isn't relevant to this message. */\n memoryContext: string\n /** Rendered cross-chat summary, or \"\" when not needed. */\n crossChatContext: string\n /** Debug metadata — surfaced in logs + bench output so operators can\n * see what the planner chose. Not sent to the main agent. */\n debug: {\n recentTurns: number\n memoryIncluded: boolean\n memoryQuery?: string\n crossChatIncluded: boolean\n reasoning?: string\n planLatencyMs: number\n }\n}\n\nexport interface PlanContextInput {\n agentId: string\n channel: string\n chatId: string\n message: string\n sessions: SessionStore\n memoryStore: MemoryStore\n /** Caller-controlled timeout for the Haiku planner call. Defaults to\n * 8s — the planner is in the critical path before the main agent\n * runs, so we can't afford a long timeout. Fail-open if exceeded. */\n timeoutMs?: number\n}\n\nconst PLANNER_MODEL = \"claude-haiku-4-5-20251001\"\nconst PLANNER_PROMPT = `You are a context planner for an AI agent. Given the user's current message and recent conversation tail, decide what historical context the agent needs.\n\nOutput ONE JSON object on one line, no prose, no fences:\n { \"recentTurns\": number, \"memory\": { \"include\": boolean, \"query\": string }, \"crossChat\": boolean, \"reasoning\": string }\n\nRules:\n- \"recentTurns\" in [0,6]. Short acknowledgments (\"ok\", \"yes\", \"do it\", \"continue\") need 2-3 turns to know what they refer to. New topics need 0. Technical follow-ups need 3-5.\n- \"memory.include\" = true only when the message likely references a person, past event, decision, or project the agent should recall. \"memory.query\" = 3-8 words distilling what to search for (leave \"\" when include=false).\n- \"crossChat\" = true only when the message references another chat (\"DM\", \"the group\", \"earlier conversation\", a peer agent's name). Most messages = false.\n- \"reasoning\" = one short phrase explaining the plan.\n\nBe conservative — unneeded context costs tokens. When in doubt, exclude.`\n\nexport async function planContext(input: PlanContextInput): Promise<ContextPlan | null> {\n const start = Date.now()\n const timeoutMs = input.timeoutMs ?? 8_000\n\n // Snapshot the session tail for the planner prompt. Don't send the\n // whole session — the planner only needs enough to judge whether the\n // message is a follow-up, and we pay tokens for every line we ship.\n const recentTail = sessionTail(input.sessions, input.agentId, input.channel, input.chatId, 4)\n\n const userPrompt = [\n `Agent: ${input.agentId}`,\n `Channel: ${input.channel}`,\n recentTail ? `Recent conversation:\\n${recentTail}` : \"Recent conversation: (none — first message)\",\n ``,\n `Current message: ${input.message.slice(0, 1500)}`,\n ].join(\"\\n\")\n\n // Use the Anthropic SDK path — warm HTTP client, no subprocess cold-start.\n // resolveToken now recognizes CLAUDE_CODE_OAUTH_TOKEN so this works out\n // of the box for any operator who already has Claude CLI auth set up.\n // Planner timeout is tight (8s default) because plan latency is in the\n // critical path before the main agent runs.\n let planJson: any\n try {\n const { createProvider } = await import(\"@/agent/providers\")\n const provider = createProvider(\"claude\")\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n try {\n const result = await provider.generate(\n [\n { role: \"system\", content: PLANNER_PROMPT },\n { role: \"user\", content: userPrompt },\n ],\n { model: PLANNER_MODEL, maxTokens: 256 },\n )\n planJson = extractJson(result.content)\n } finally {\n clearTimeout(timer)\n }\n } catch (err: any) {\n // Temporary: surface planner errors for debugging. Will silence once\n // we're confident the SDK path is stable.\n if (process.env.AGENTX_PLANNER_DEBUG) {\n // eslint-disable-next-line no-console\n console.error(`[planner] error: ${err?.message ?? err}`)\n }\n return null\n }\n\n if (!planJson || typeof planJson !== \"object\") return null\n\n const recentTurns = clampInt(planJson.recentTurns, 0, 6, 3)\n const memoryInclude = Boolean(planJson.memory?.include)\n const memoryQuery: string = typeof planJson.memory?.query === \"string\" ? planJson.memory.query : \"\"\n const crossChatInclude = Boolean(planJson.crossChat)\n const reasoning = typeof planJson.reasoning === \"string\" ? planJson.reasoning : undefined\n\n // Resolve the plan into actual retrieval strings.\n const sessionHistory = renderSessionTail(\n input.sessions,\n input.agentId,\n input.channel,\n input.chatId,\n recentTurns,\n )\n\n let memoryContext = \"\"\n if (memoryInclude && memoryQuery) {\n try {\n const relevant = input.memoryStore.findRelevant(memoryQuery, input.agentId, 5)\n memoryContext = input.memoryStore.buildContext(relevant)\n } catch {\n // Memory retrieval is best-effort; planner proceeds with empty memory.\n }\n }\n\n let crossChatContext = \"\"\n if (crossChatInclude) {\n // Force-include via empty message arg — the planner already decided.\n crossChatContext = input.sessions.getCrossSessionSummary(\n input.agentId, input.channel, input.chatId, \"\",\n )\n }\n\n return {\n sessionHistory,\n memoryContext,\n crossChatContext,\n debug: {\n recentTurns,\n memoryIncluded: memoryInclude,\n memoryQuery: memoryQuery || undefined,\n crossChatIncluded: crossChatInclude,\n reasoning,\n planLatencyMs: Date.now() - start,\n },\n }\n}\n\n/** Render the last N message pairs as a compact prompt tail. Returns \"\" when\n * there's no session on disk yet (first message). Format mirrors\n * SessionStore.buildHistoryContext but with an N-turn cap — the planner's\n * whole point is to cap this instead of dumping the full 12K-char log. */\nfunction renderSessionTail(\n sessions: SessionStore,\n agentId: string,\n channel: string,\n chatId: string,\n turns: number,\n): string {\n if (turns <= 0) return \"\"\n const session = sessions.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\n // N \"turns\" = N*2 messages (user+agent pairs). Round to nearest message\n // count so a partial trailing user message still gets included.\n const msgCount = Math.min(session.messages.length, turns * 2)\n const tail = session.messages.slice(-msgCount)\n const lines: string[] = [`[Recent exchange — last ${tail.length} messages]`]\n for (const msg of tail) {\n const name = msg.name || (msg.role === \"user\" ? \"User\" : \"Agent\")\n lines.push(`${name}: ${msg.content}`)\n }\n lines.push(\"[End recent exchange — respond to the latest message above]\")\n lines.push(\"\")\n return lines.join(\"\\n\")\n}\n\n/** Very small tail for the planner's own prompt — enough for the planner to\n * decide whether this message is a follow-up, not enough to bloat the\n * planner call itself. Distinct from renderSessionTail (used for the main\n * agent) because we cap more aggressively here. */\nfunction sessionTail(\n sessions: SessionStore,\n agentId: string,\n channel: string,\n chatId: string,\n maxMessages: number,\n): string {\n const session = sessions.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\n const tail = session.messages.slice(-maxMessages)\n return tail\n .map((m: SessionMessage) => {\n const name = m.name || (m.role === \"user\" ? \"User\" : \"Agent\")\n const content = m.content.length > 200 ? m.content.slice(0, 200) + \"…\" : m.content\n return ` ${name}: ${content}`\n })\n .join(\"\\n\")\n}\n\n/** Extract the first JSON object from a text response. Haiku sometimes\n * wraps output in backticks or prose despite the instructions, so we\n * scan for the first `{...}` span. */\n\nfunction clampInt(v: unknown, min: number, max: number, fallback: number): number {\n const n = typeof v === \"number\" ? Math.floor(v) : Number.parseInt(String(v ?? \"\"), 10)\n if (!Number.isFinite(n)) return fallback\n return Math.min(max, Math.max(min, n))\n}\n"],"mappings":"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","names":["init_extract_json","PLANNER_MODEL","PLANNER_PROMPT","planContext","input","start","timeoutMs","recentTail","sessionTail","userPrompt","planJson","createProvider","provider","ac","timer","result","extractJson","err","recentTurns","clampInt","memoryInclude","memoryQuery","crossChatInclude","reasoning","sessionHistory","renderSessionTail","memoryContext","relevant","crossChatContext","sessions","agentId","channel","chatId","turns","session","msgCount","tail","lines","msg","name","maxMessages","m","content","v","min","max","fallback","n"]}
@@ -0,0 +1,2 @@
1
+ import{a,b,c,d,e}from"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{e as debug,d as getDebugLogs,c as getDebugState,b as isDebug,a as setDebug};
2
+ //# sourceMappingURL=debug-3MEUO2P2.js.map
@@ -1,4 +1,4 @@
1
- import"./chunk-4YCH6IZV.js";var $=/\bssh\s+(?:-i\s+(\S+)\s+)?([a-z_][a-z0-9_-]*)@([a-z0-9][a-z0-9.-]+|\d{1,3}(?:\.\d{1,3}){3})/gi,R=/\b(?:\d{1,3}\.){3}\d{1,3}\b/g,y=/\b([a-z0-9][a-z0-9._-]*)@([a-z0-9][a-z0-9.-]+\.[a-z]{2,})\b/gi;var k=/groups\/([a-z0-9][a-z0-9._-]+)[^\n|]*\(?\s*ID:?\s*(\d+)\s*\)?/gi,_=/(\/Users\/[^\s)`'"\]]+|\/home\/[^\s)`'"\]]+|\/var\/www\/[^\s)`'"\]]+|\/etc\/[^\s)`'"\]]+)/g,v=/(\+\d{1,3}[\s.-]?(?:\(\d+\)[\s.-]?)?\d[\s.\d-]{6,}\d)/g,z=/^(?:127\.|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/;function x(i,r){let s={byKind:{ssh:[],gitlab:[],path:[],contact:[]},scannedSkills:[],diagnostics:[]},c=new Set;for(let a of i){let e=a.frontmatter,n=a.instructions??"";if(r.filter?.length){let t=[e.name,e.description,...e.tags??[],e.category??"",a.path??"",n.slice(0,500)].join(" ").toLowerCase();if(!r.filter.some(u=>t.includes(u.toLowerCase())))continue}s.scannedSkills.push(e.name);let o=e.name;for(let t of w(n,r.namespace,o))m(s,t,"ssh",c)&&s.diagnostics.push({skill:e.name,kind:"ssh",id:t.id});for(let t of E(n,r.namespace,r.gitlabHost))m(s,t,"gitlab",c)&&s.diagnostics.push({skill:e.name,kind:"gitlab",id:t.id});for(let t of I(n,r.namespace))m(s,t,"path",c)&&s.diagnostics.push({skill:e.name,kind:"path",id:t.id});for(let t of j(n,r.namespace))m(s,t,"contact",c)&&s.diagnostics.push({skill:e.name,kind:"contact",id:t.id})}return s}function m(i,r,s,c){return c.has(r.id)?!1:(c.add(r.id),i.byKind[s].push(r),!0)}function w(i,r,s){let c=[];for(let e of i.matchAll($)){let[,n,o,t]=e;if(!o||!t)continue;let d=g(`${o}-${t.replace(/[^a-z0-9-]/gi,"-")}`);c.push({id:`${r}.ssh.${d}`,kind:"ssh",summary:`SSH ${o}@${t}`,fields:D({user:o,host:t,key:n}),tags:["discovered"],ownerAgent:s})}let a=new Set(c.flatMap(e=>Object.values(e.fields)));for(let e of i.matchAll(R)){let n=e[0];if(a.has(n)||z.test(n))continue;let o=g(`host-${n.replace(/\./g,"-")}`);c.push({id:`${r}.ssh.${o}`,kind:"ssh",summary:`Host ${n} (user not detected \u2014 fill in)`,fields:{host:n},tags:["discovered","needs-review"],ownerAgent:s}),a.add(n)}return c}var C=/\b([a-z0-9][a-z0-9._-]{1,40})\/([a-z0-9][a-z0-9._-]{1,40})\b/i,A=/\b(\d{1,8})\b/g;function E(i,r,s){let c=[];if(s&&!i.includes(s.replace(/^https?:\/\//,"")))return c;for(let e of i.matchAll(k)){let[,n,o]=e;!n||!o||c.push({id:`${r}.gitlab.group.${g(n)}`,kind:"gitlab",summary:`GitLab group ${n}`,fields:{groupId:Number(o),path:n},tags:["discovered"]})}let a=new RegExp(C.source,"gi");for(let e of i.split(/\r?\n/)){if(!/^\s*\|.+\|/.test(e)&&!/\bID[:\s]/i.test(e)&&!/gitlab/i.test(e))continue;let o,t,d=e.match(/https?:\/\/[^\s)]*gitlab[^\s)]*?\/([a-z0-9][a-z0-9._-]+)\/([a-z0-9][a-z0-9._-]+)/i);if(d&&d[1]&&d[2])o=d[1],t=d[2].replace(/[).,;:]+$/,"");else for(let f of e.matchAll(a)){let p=f[1],h=f[2];!p||!h||p==="wp-content"||h==="wp-content"||p.includes(".")||S(h)||(o=p,t=h)}if(!o||!t)continue;let l=[...e.matchAll(A)].map(f=>Number(f[1])).find(f=>f>=10&&f<=9999999);l&&c.push({id:`${r}.gitlab.project.${g(t)}`,kind:"gitlab",summary:`GitLab project ${o}/${t}`,fields:{projectId:l,path:`${o}/${t}`},tags:["discovered"]})}return c}function S(i){return/\.(md|ya?ml|json|ts|tsx|js|jsx|py|sh|conf|env)$/i.test(i)}function I(i,r){let s=[];for(let c of i.matchAll(_)){let a=c[1].replace(/[.,;:)]+$/,"");if(a.length<4)continue;let e=g(a.replace(/^\//,"").replace(/[/.]/g,"-"));s.push({id:`${r}.paths.${e}`,kind:"path",summary:a,fields:{path:a},tags:["discovered"]})}return s}function j(i,r){let s=[];for(let a of i.matchAll(y)){let[,e,n]=a;if(n.endsWith(".png")||n.endsWith(".jpg"))continue;let o=g(e);s.push({id:`${r}.contacts.${o}`,kind:"contact",summary:`${e}@${n}`,fields:{email:`${e}@${n}`},tags:["discovered"]})}let c=[...i.matchAll(v)].map(a=>a[1].trim());for(let a of c){let e=g(`phone-${a.replace(/\D+/g,"")}`);s.push({id:`${r}.contacts.${e}`,kind:"contact",summary:`Phone ${a}`,fields:{mobile:a},tags:["discovered","needs-review"]})}return s}function D(i){let r={};for(let[s,c]of Object.entries(i))c===void 0||c===""||(r[s]=c);return r}function g(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||"x"}function L(i,r){let s={},c={ssh:"ssh.yaml",gitlab:"gitlab.yaml",path:"paths.yaml",contact:"contacts.yaml"};for(let[a,e]of Object.entries(i.byKind)){if(e.length===0)continue;let n=`${r}.${a==="path"?"paths":a==="contact"?"contacts":a}`,o=["# Discovered automatically by 'agentx references discover'.",'# Review every card \u2014 flagged ones (tags include "needs-review") are',"# best-effort guesses. Set lastVerified once you've confirmed.",`namespace: ${n}`,"cards:"];for(let t of e){let d=t.id.startsWith(`${n}.`)?t.id.slice(n.length+1):t.id;o.push(` - id: ${d}`),o.push(` kind: ${t.kind}`),o.push(` summary: ${b(t.summary)}`),o.push(" fields:");for(let[u,l]of Object.entries(t.fields))o.push(` ${u}: ${typeof l=="number"||typeof l=="boolean"?l:b(String(l))}`);o.push(` tags: [${t.tags.join(", ")}]`),t.ownerAgent&&o.push(` ownerAgent: ${t.ownerAgent}`)}s[c[a]]=o.join(`
1
+ import"./chunk-FIOB3MEX.js";var $=/\bssh\s+(?:-i\s+(\S+)\s+)?([a-z_][a-z0-9_-]*)@([a-z0-9][a-z0-9.-]+|\d{1,3}(?:\.\d{1,3}){3})/gi,R=/\b(?:\d{1,3}\.){3}\d{1,3}\b/g,y=/\b([a-z0-9][a-z0-9._-]*)@([a-z0-9][a-z0-9.-]+\.[a-z]{2,})\b/gi;var k=/groups\/([a-z0-9][a-z0-9._-]+)[^\n|]*\(?\s*ID:?\s*(\d+)\s*\)?/gi,_=/(\/Users\/[^\s)`'"\]]+|\/home\/[^\s)`'"\]]+|\/var\/www\/[^\s)`'"\]]+|\/etc\/[^\s)`'"\]]+)/g,v=/(\+\d{1,3}[\s.-]?(?:\(\d+\)[\s.-]?)?\d[\s.\d-]{6,}\d)/g,z=/^(?:127\.|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/;function x(i,r){let s={byKind:{ssh:[],gitlab:[],path:[],contact:[]},scannedSkills:[],diagnostics:[]},c=new Set;for(let a of i){let e=a.frontmatter,n=a.instructions??"";if(r.filter?.length){let t=[e.name,e.description,...e.tags??[],e.category??"",a.path??"",n.slice(0,500)].join(" ").toLowerCase();if(!r.filter.some(u=>t.includes(u.toLowerCase())))continue}s.scannedSkills.push(e.name);let o=e.name;for(let t of w(n,r.namespace,o))m(s,t,"ssh",c)&&s.diagnostics.push({skill:e.name,kind:"ssh",id:t.id});for(let t of E(n,r.namespace,r.gitlabHost))m(s,t,"gitlab",c)&&s.diagnostics.push({skill:e.name,kind:"gitlab",id:t.id});for(let t of I(n,r.namespace))m(s,t,"path",c)&&s.diagnostics.push({skill:e.name,kind:"path",id:t.id});for(let t of j(n,r.namespace))m(s,t,"contact",c)&&s.diagnostics.push({skill:e.name,kind:"contact",id:t.id})}return s}function m(i,r,s,c){return c.has(r.id)?!1:(c.add(r.id),i.byKind[s].push(r),!0)}function w(i,r,s){let c=[];for(let e of i.matchAll($)){let[,n,o,t]=e;if(!o||!t)continue;let d=g(`${o}-${t.replace(/[^a-z0-9-]/gi,"-")}`);c.push({id:`${r}.ssh.${d}`,kind:"ssh",summary:`SSH ${o}@${t}`,fields:D({user:o,host:t,key:n}),tags:["discovered"],ownerAgent:s})}let a=new Set(c.flatMap(e=>Object.values(e.fields)));for(let e of i.matchAll(R)){let n=e[0];if(a.has(n)||z.test(n))continue;let o=g(`host-${n.replace(/\./g,"-")}`);c.push({id:`${r}.ssh.${o}`,kind:"ssh",summary:`Host ${n} (user not detected \u2014 fill in)`,fields:{host:n},tags:["discovered","needs-review"],ownerAgent:s}),a.add(n)}return c}var C=/\b([a-z0-9][a-z0-9._-]{1,40})\/([a-z0-9][a-z0-9._-]{1,40})\b/i,A=/\b(\d{1,8})\b/g;function E(i,r,s){let c=[];if(s&&!i.includes(s.replace(/^https?:\/\//,"")))return c;for(let e of i.matchAll(k)){let[,n,o]=e;!n||!o||c.push({id:`${r}.gitlab.group.${g(n)}`,kind:"gitlab",summary:`GitLab group ${n}`,fields:{groupId:Number(o),path:n},tags:["discovered"]})}let a=new RegExp(C.source,"gi");for(let e of i.split(/\r?\n/)){if(!/^\s*\|.+\|/.test(e)&&!/\bID[:\s]/i.test(e)&&!/gitlab/i.test(e))continue;let o,t,d=e.match(/https?:\/\/[^\s)]*gitlab[^\s)]*?\/([a-z0-9][a-z0-9._-]+)\/([a-z0-9][a-z0-9._-]+)/i);if(d&&d[1]&&d[2])o=d[1],t=d[2].replace(/[).,;:]+$/,"");else for(let f of e.matchAll(a)){let p=f[1],h=f[2];!p||!h||p==="wp-content"||h==="wp-content"||p.includes(".")||S(h)||(o=p,t=h)}if(!o||!t)continue;let l=[...e.matchAll(A)].map(f=>Number(f[1])).find(f=>f>=10&&f<=9999999);l&&c.push({id:`${r}.gitlab.project.${g(t)}`,kind:"gitlab",summary:`GitLab project ${o}/${t}`,fields:{projectId:l,path:`${o}/${t}`},tags:["discovered"]})}return c}function S(i){return/\.(md|ya?ml|json|ts|tsx|js|jsx|py|sh|conf|env)$/i.test(i)}function I(i,r){let s=[];for(let c of i.matchAll(_)){let a=c[1].replace(/[.,;:)]+$/,"");if(a.length<4)continue;let e=g(a.replace(/^\//,"").replace(/[/.]/g,"-"));s.push({id:`${r}.paths.${e}`,kind:"path",summary:a,fields:{path:a},tags:["discovered"]})}return s}function j(i,r){let s=[];for(let a of i.matchAll(y)){let[,e,n]=a;if(n.endsWith(".png")||n.endsWith(".jpg"))continue;let o=g(e);s.push({id:`${r}.contacts.${o}`,kind:"contact",summary:`${e}@${n}`,fields:{email:`${e}@${n}`},tags:["discovered"]})}let c=[...i.matchAll(v)].map(a=>a[1].trim());for(let a of c){let e=g(`phone-${a.replace(/\D+/g,"")}`);s.push({id:`${r}.contacts.${e}`,kind:"contact",summary:`Phone ${a}`,fields:{mobile:a},tags:["discovered","needs-review"]})}return s}function D(i){let r={};for(let[s,c]of Object.entries(i))c===void 0||c===""||(r[s]=c);return r}function g(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||"x"}function L(i,r){let s={},c={ssh:"ssh.yaml",gitlab:"gitlab.yaml",path:"paths.yaml",contact:"contacts.yaml"};for(let[a,e]of Object.entries(i.byKind)){if(e.length===0)continue;let n=`${r}.${a==="path"?"paths":a==="contact"?"contacts":a}`,o=["# Discovered automatically by 'agentx references discover'.",'# Review every card \u2014 flagged ones (tags include "needs-review") are',"# best-effort guesses. Set lastVerified once you've confirmed.",`namespace: ${n}`,"cards:"];for(let t of e){let d=t.id.startsWith(`${n}.`)?t.id.slice(n.length+1):t.id;o.push(` - id: ${d}`),o.push(` kind: ${t.kind}`),o.push(` summary: ${b(t.summary)}`),o.push(" fields:");for(let[u,l]of Object.entries(t.fields))o.push(` ${u}: ${typeof l=="number"||typeof l=="boolean"?l:b(String(l))}`);o.push(` tags: [${t.tags.join(", ")}]`),t.ownerAgent&&o.push(` ownerAgent: ${t.ownerAgent}`)}s[c[a]]=o.join(`
2
2
  `)+`
3
3
  `}return s}function b(i){return/^[A-Za-z0-9_./@+:-]+$/.test(i)&&!/^\d/.test(i)?i:`"${i.replace(/"/g,'\\"')}"`}export{x as discoverFromSkills,L as renderDiscovery};
4
- //# sourceMappingURL=discover-XJXQXXIO.js.map
4
+ //# sourceMappingURL=discover-D4VVRNAI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agents/references/discover.ts"],"sourcesContent":["import type { Skill } from \"@/agent/skills/types\"\nimport type { ReferenceCard, ReferenceKind } from \"./types\"\n\n// --- References discovery ---\n//\n// Pure extractor: given a namespace and a bag of source skills, walk their\n// markdown bodies and pull out structured facts (SSH hosts, GitLab project\n// IDs, filesystem paths, contacts) as ReferenceCard[]. Deterministic regex —\n// no LLM in the loop. Operator reviews the output and edits before flipping\n// `contextReferences: true` on the agents.\n\nconst SSH_LINE_RE = /\\bssh\\s+(?:-i\\s+(\\S+)\\s+)?([a-z_][a-z0-9_-]*)@([a-z0-9][a-z0-9.-]+|\\d{1,3}(?:\\.\\d{1,3}){3})/gi\nconst IP_RE = /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g\nconst EMAIL_RE = /\\b([a-z0-9][a-z0-9._-]*)@([a-z0-9][a-z0-9.-]+\\.[a-z]{2,})\\b/gi\nconst GITLAB_PROJECT_LINE_RE = /([a-z0-9][a-z0-9._-]+)\\/([a-z0-9][a-z0-9._-]+)[^\\n|]*\\(?\\s*ID:?\\s*(\\d+)\\s*\\)?/gi\nconst GITLAB_GROUP_RE = /groups\\/([a-z0-9][a-z0-9._-]+)[^\\n|]*\\(?\\s*ID:?\\s*(\\d+)\\s*\\)?/gi\nconst PATH_RE = /(\\/Users\\/[^\\s)`'\"\\]]+|\\/home\\/[^\\s)`'\"\\]]+|\\/var\\/www\\/[^\\s)`'\"\\]]+|\\/etc\\/[^\\s)`'\"\\]]+)/g\nconst PHONE_RE = /(\\+\\d{1,3}[\\s.-]?(?:\\(\\d+\\)[\\s.-]?)?\\d[\\s.\\d-]{6,}\\d)/g\nconst PRIVATE_IP_RE = /^(?:127\\.|10\\.|192\\.168\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.)/\n\nexport interface DiscoveryResult {\n /** Cards keyed by kind (ssh, gitlab, path, contact). */\n byKind: Record<string, ReferenceCard[]>\n /** Skills that were scanned. */\n scannedSkills: string[]\n /** Per-source diagnostics — what was found in which skill. */\n diagnostics: Array<{ skill: string; kind: string; id: string }>\n}\n\nexport interface DiscoveryOptions {\n /** Namespace prefix applied to every generated card id. */\n namespace: string\n /** Include only skills whose name OR tags contain any of these substrings.\n * Empty = scan every skill. */\n filter?: string[]\n /** Optional GitLab host to validate project URLs against (e.g.\n * https://gitlab.example.com). When set, project URLs from other hosts are\n * ignored — keeps stray GitHub mentions out of a self-hosted registry. */\n gitlabHost?: string\n}\n\nexport function discoverFromSkills(skills: Skill[], opts: DiscoveryOptions): DiscoveryResult {\n const result: DiscoveryResult = {\n byKind: { ssh: [], gitlab: [], path: [], contact: [] },\n scannedSkills: [],\n diagnostics: [],\n }\n const seen = new Set<string>() // id de-dup across skills\n\n for (const skill of skills) {\n const fm = skill.frontmatter\n const body = skill.instructions ?? \"\"\n if (opts.filter?.length) {\n const hay = [\n fm.name,\n fm.description,\n ...(fm.tags ?? []),\n (fm as any).category ?? \"\",\n skill.path ?? \"\",\n body.slice(0, 500), // first ~125 tokens of body, enough for a \"Globex Odoo Server\" header\n ].join(\" \").toLowerCase()\n const hit = opts.filter.some(f => hay.includes(f.toLowerCase()))\n if (!hit) continue\n }\n result.scannedSkills.push(fm.name)\n\n const ownerHint = fm.name\n\n for (const card of extractSshCards(body, opts.namespace, ownerHint)) {\n if (push(result, card, \"ssh\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"ssh\", id: card.id })\n }\n for (const card of extractGitlabCards(body, opts.namespace, opts.gitlabHost)) {\n if (push(result, card, \"gitlab\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"gitlab\", id: card.id })\n }\n for (const card of extractPathCards(body, opts.namespace)) {\n if (push(result, card, \"path\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"path\", id: card.id })\n }\n for (const card of extractContactCards(body, opts.namespace)) {\n if (push(result, card, \"contact\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"contact\", id: card.id })\n }\n }\n\n return result\n}\n\nfunction push(\n result: DiscoveryResult,\n card: ReferenceCard,\n kind: ReferenceKind,\n seen: Set<string>,\n): boolean {\n if (seen.has(card.id)) return false\n seen.add(card.id)\n result.byKind[kind].push(card)\n return true\n}\n\n// ---- Extractors ----\n\nfunction extractSshCards(body: string, ns: string, owner: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n for (const m of body.matchAll(SSH_LINE_RE)) {\n const [, key, user, host] = m\n if (!user || !host) continue\n const slug = slugify(`${user}-${host.replace(/[^a-z0-9-]/gi, \"-\")}`)\n cards.push({\n id: `${ns}.ssh.${slug}`,\n kind: \"ssh\",\n summary: `SSH ${user}@${host}`,\n fields: cleanFields({ user, host, key }),\n tags: [\"discovered\"],\n ownerAgent: owner,\n })\n }\n // Catch IPs not already paired with a user\n const claimed = new Set(cards.flatMap(c => Object.values(c.fields)))\n for (const m of body.matchAll(IP_RE)) {\n const ip = m[0]\n if (claimed.has(ip)) continue\n if (PRIVATE_IP_RE.test(ip)) continue\n const slug = slugify(`host-${ip.replace(/\\./g, \"-\")}`)\n cards.push({\n id: `${ns}.ssh.${slug}`,\n kind: \"ssh\",\n summary: `Host ${ip} (user not detected — fill in)`,\n fields: { host: ip },\n tags: [\"discovered\", \"needs-review\"],\n ownerAgent: owner,\n })\n claimed.add(ip)\n }\n return cards\n}\n\nconst PROJECT_LINE_RE = /\\b([a-z0-9][a-z0-9._-]{1,40})\\/([a-z0-9][a-z0-9._-]{1,40})\\b/i\nconst ID_NUM_RE = /\\b(\\d{1,8})\\b/g\n\nfunction extractGitlabCards(body: string, ns: string, gitlabHost?: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n if (gitlabHost && !body.includes(gitlabHost.replace(/^https?:\\/\\//, \"\"))) return cards\n // Group lines anywhere\n for (const m of body.matchAll(GITLAB_GROUP_RE)) {\n const [, slug, id] = m\n if (!slug || !id) continue\n cards.push({\n id: `${ns}.gitlab.group.${slugify(slug)}`,\n kind: \"gitlab\",\n summary: `GitLab group ${slug}`,\n fields: { groupId: Number(id), path: slug },\n tags: [\"discovered\"],\n })\n }\n // Project lines: walk line-by-line so table rows (\"| g/p | 269 |\") are\n // handled the same way as prose (\"g/p (ID: 269)\"). For each line that\n // mentions <group>/<project>, take the rightmost plausible numeric ID.\n const projectGlobalRe = new RegExp(PROJECT_LINE_RE.source, \"gi\")\n for (const line of body.split(/\\r?\\n/)) {\n // Skip lines that look like prose mentioning a path, not a GitLab project.\n // Real GitLab callouts almost always include \"ID:\" or a gitlab.* hostname\n // in the same line, or appear inside a markdown table row. Without one of\n // those, the noise rate is too high.\n const isTableRow = /^\\s*\\|.+\\|/.test(line)\n if (!isTableRow && !/\\bID[:\\s]/i.test(line) && !/gitlab/i.test(line)) continue\n // Two-pass: prefer a GitLab URL (https://gitlab.<host>/<group>/<repo>)\n // when present on the line, otherwise fall back to the rightmost\n // <group>/<repo> token that doesn't have a hostname-shaped group.\n let group: string | undefined\n let repo: string | undefined\n const urlMatch = line.match(/https?:\\/\\/[^\\s)]*gitlab[^\\s)]*?\\/([a-z0-9][a-z0-9._-]+)\\/([a-z0-9][a-z0-9._-]+)/i)\n if (urlMatch && urlMatch[1] && urlMatch[2]) {\n group = urlMatch[1]\n repo = urlMatch[2].replace(/[).,;:]+$/, \"\")\n } else {\n // Walk every <a>/<b> on the line and keep the last one that looks like\n // a real group/repo (no dots in group, not a wp-content path, not a file).\n for (const m of line.matchAll(projectGlobalRe)) {\n const g = m[1]\n const r = m[2]\n if (!g || !r) continue\n if (g === \"wp-content\" || r === \"wp-content\") continue\n if (g.includes(\".\")) continue\n if (looksLikeFile(r)) continue\n group = g\n repo = r\n }\n }\n if (!group || !repo) continue\n const ids = [...line.matchAll(ID_NUM_RE)].map(m => Number(m[1]))\n const id = ids.find(n => n >= 10 && n <= 9_999_999)\n if (!id) continue\n cards.push({\n id: `${ns}.gitlab.project.${slugify(repo)}`,\n kind: \"gitlab\",\n summary: `GitLab project ${group}/${repo}`,\n fields: { projectId: id, path: `${group}/${repo}` },\n tags: [\"discovered\"],\n })\n }\n return cards\n}\n\nfunction looksLikeFile(s: string): boolean {\n return /\\.(md|ya?ml|json|ts|tsx|js|jsx|py|sh|conf|env)$/i.test(s)\n}\n\nfunction extractPathCards(body: string, ns: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n for (const m of body.matchAll(PATH_RE)) {\n let p = m[1].replace(/[.,;:)]+$/, \"\") // strip trailing punctuation\n if (p.length < 4) continue\n const slug = slugify(p.replace(/^\\//, \"\").replace(/[/.]/g, \"-\"))\n cards.push({\n id: `${ns}.paths.${slug}`,\n kind: \"path\",\n summary: p,\n fields: { path: p },\n tags: [\"discovered\"],\n })\n }\n return cards\n}\n\nfunction extractContactCards(body: string, ns: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n for (const m of body.matchAll(EMAIL_RE)) {\n const [, local, domain] = m\n if (domain.endsWith(\".png\") || domain.endsWith(\".jpg\")) continue // junk filter\n const slug = slugify(local)\n cards.push({\n id: `${ns}.contacts.${slug}`,\n kind: \"contact\",\n summary: `${local}@${domain}`,\n fields: { email: `${local}@${domain}` },\n tags: [\"discovered\"],\n })\n }\n // Best-effort: if a phone appears on the same line as a known contact email\n // we already picked, attach it. Otherwise drop a standalone phone card.\n const phones = [...body.matchAll(PHONE_RE)].map(m => m[1].trim())\n for (const phone of phones) {\n const slug = slugify(`phone-${phone.replace(/\\D+/g, \"\")}`)\n cards.push({\n id: `${ns}.contacts.${slug}`,\n kind: \"contact\",\n summary: `Phone ${phone}`,\n fields: { mobile: phone },\n tags: [\"discovered\", \"needs-review\"],\n })\n }\n return cards\n}\n\n// ---- Helpers ----\n\nfunction cleanFields(fields: Record<string, string | number | boolean | undefined>): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined || v === \"\") continue\n out[k] = v\n }\n return out\n}\n\nfunction slugify(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60) || \"x\"\n}\n\n/**\n * Render a discovery result as YAML-ready file contents, one per kind.\n * Output is a complete `ReferenceFile`-shaped payload, ready to write to\n * `.agentx/references/<ns>/{ssh,gitlab,paths,contacts}.yaml`.\n */\nexport function renderDiscovery(result: DiscoveryResult, namespace: string): Record<string, string> {\n const out: Record<string, string> = {}\n const fileMap: Record<string, string> = {\n ssh: \"ssh.yaml\",\n gitlab: \"gitlab.yaml\",\n path: \"paths.yaml\",\n contact: \"contacts.yaml\",\n }\n for (const [kind, cards] of Object.entries(result.byKind)) {\n if (cards.length === 0) continue\n const ns = `${namespace}.${kind === \"path\" ? \"paths\" : kind === \"contact\" ? \"contacts\" : kind}`\n const lines = [\n `# Discovered automatically by 'agentx references discover'.`,\n `# Review every card — flagged ones (tags include \"needs-review\") are`,\n `# best-effort guesses. Set lastVerified once you've confirmed.`,\n `namespace: ${ns}`,\n `cards:`,\n ]\n for (const card of cards) {\n // Strip the namespace from the id so the file's `namespace:` doesn't\n // double-apply it on load.\n const localId = card.id.startsWith(`${ns}.`) ? card.id.slice(ns.length + 1) : card.id\n lines.push(` - id: ${localId}`)\n lines.push(` kind: ${card.kind}`)\n lines.push(` summary: ${quote(card.summary)}`)\n lines.push(` fields:`)\n for (const [k, v] of Object.entries(card.fields)) {\n lines.push(` ${k}: ${typeof v === \"number\" || typeof v === \"boolean\" ? v : quote(String(v))}`)\n }\n lines.push(` tags: [${card.tags.join(\", \")}]`)\n if (card.ownerAgent) lines.push(` ownerAgent: ${card.ownerAgent}`)\n }\n out[fileMap[kind]] = lines.join(\"\\n\") + \"\\n\"\n }\n return out\n}\n\nfunction quote(s: string): string {\n if (/^[A-Za-z0-9_./@+:-]+$/.test(s) && !/^\\d/.test(s)) return s\n return `\"${s.replace(/\"/g, '\\\\\"')}\"`\n}\n"],"mappings":"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","names":["SSH_LINE_RE","IP_RE","EMAIL_RE","GITLAB_GROUP_RE","PATH_RE","PHONE_RE","PRIVATE_IP_RE","discoverFromSkills","skills","opts","result","seen","skill","fm","body","hay","f","ownerHint","card","extractSshCards","push","extractGitlabCards","extractPathCards","extractContactCards","kind","ns","owner","cards","m","SSH_LINE_RE","key","user","host","slug","slugify","cleanFields","claimed","c","IP_RE","ip","PROJECT_LINE_RE","ID_NUM_RE","gitlabHost","id","projectGlobalRe","line","group","repo","urlMatch","g","r","looksLikeFile","n","s","p","EMAIL_RE","local","domain","phones","phone","fields","out","k","v","input","renderDiscovery","namespace","fileMap","lines","localId","quote"]}
@@ -0,0 +1,2 @@
1
+ import{a,b}from"./chunk-IB3F7VBE.js";import"./chunk-CJQQIRPD.js";import"./chunk-FIOB3MEX.js";export{b as doctor,a as runDoctorChecks};
2
+ //# sourceMappingURL=doctor-PBAH6XL3.js.map
@@ -0,0 +1,137 @@
1
+ import"./chunk-FIOB3MEX.js";function c(o){let s=o.store.list().map(t=>({id:t.id,title:t.title})),e=[];if(e.push(r),e.push(""),e.push(i),e.push(""),e.push("# Environment \u2014 only reference values that exist"),e.push(""),e.push("## Available agents (for `agent` nodes)"),o.availableAgents.length===0)e.push(" (none registered \u2014 the user must add an agent first for `agent` nodes to work)");else for(let t of o.availableAgents)e.push(` - \`${t.id}\`${t.description?` \u2014 ${t.description}`:""}`);if(e.push(""),e.push("## Assignees (for `userTask.assignTo`)"),e.push(" Any agent id above, or a plain string naming the person responsible."),e.push(""),e.push("## Available roles (for `userTask.assignTo`)"),e.push(""),e.push("## Available channels (for `action.*` and `trigger.channel`)"),e.push(` ${o.availableChannels.length?o.availableChannels.map(t=>`\`${t}\``).join(", "):"(none live on this node)"}`),e.push(""),e.push("## Existing workflows (for `subProcess.workflowId`)"),s.length===0)e.push(" (no other workflows defined)");else for(let t of s)e.push(` - \`${t.id}\` \u2014 ${t.title}`);e.push(""),o.currentWorkflow&&(e.push("# Current workflow on canvas"),e.push(""),e.push("The user is editing this workflow. Build on it \u2014 do NOT replace it unless they explicitly ask."),e.push(""),e.push("```json"),e.push(JSON.stringify(o.currentWorkflow,null,2)),e.push("```"),e.push("")),e.push("# Conversation"),e.push("");for(let t of o.messages){let a=t.role==="user"?"USER":"ASSISTANT";e.push(`${a}: ${t.content}`),e.push("")}return e.push(l),e.join(`
2
+ `)}function u(o){let s=/```(?:json)?\s*\n([\s\S]*?)```/.exec(o),e=[];s&&e.push(s[1]);let t=o.match(/\{[\s\S]*\}/);t&&e.push(t[0]);for(let a of e)try{let n=JSON.parse(a);if(n&&typeof n=="object"&&"id"in n&&"nodes"in n)return n}catch{}return null}var r=`You are AgentX's workflow author. The user describes a business process in
3
+ plain language; you return a valid V2 workflow JSON that implements it.
4
+
5
+ Read the node catalog below. Prefer the BPM primitives (userTask,
6
+ subProcess, signal.emit/wait, timer.boundary, gateway.parallel, rule)
7
+ when modelling processes with humans, deadlines, parallel streams, or
8
+ nested activities. Prefer the bot primitives (agent, branch, action.*)
9
+ for simple message-in \u2192 reply-out automations.`,i=`# V2 Node catalog
10
+
11
+ ## Triggers (exactly one per workflow)
12
+ - \`trigger.channel\` \u2014 inbound channel event.
13
+ config: { "source": "whatsapp-message" | "telegram-message" | "slack-message" | "discord-message" | "gitlab-issue" | "gitlab-pipeline", "filter": { "chat"?: "*", "project"?: "acme/web", "labels"?: ["x"] } }
14
+ - \`trigger.cron\` \u2014 scheduled. config: { "spec": "0 9 * * *", "timezone": "Africa/Tunis" }
15
+ - \`trigger.hook\` \u2014 subscribes to any on:* hook event. config: { "event": "on:gitlab-issue" }
16
+ - \`trigger.manual\` \u2014 kicked off by CLI or API. config: {}
17
+ - \`trigger.form\` \u2014 human fills a form to start. config: { "form": FormSchema, "startableBy": "role:public" }
18
+
19
+ ## Compute
20
+ - \`agent\` \u2014 run an agent with a templated prompt. Output: { reply, result (parsed RESULT: token), json?, durationMs }.
21
+ config: { "agentId": "<id>", "prompt": "Classify {{trigger.text}} \u2014 reply on one line: RESULT: a|b|c", "resultParser": "acme-result-token" | "json", "timeoutMinutes": 2 }
22
+ - \`transform\` \u2014 reshape upstream context.
23
+ config: { "path": "trigger.contact.phone" } OR { "template": { "key": "{{trigger.text}}" } }
24
+
25
+ ## Control flow
26
+ - \`branch\` \u2014 N-way switch (first match wins).
27
+ config: { "cases": [{ "when": { "kind": "equals"|"contains"|"matches"|"exists", "params": { "path": "classify.result", "value": "ok" } }, "to": "port1" }], "default": "fallbackPort" }
28
+ Wire with edges: { "from": "branchId", "fromPort": "port1", "to": "nextNode" }
29
+ - \`gateway.parallel\` \u2014 fan-out (one \u2192 many) or join (many \u2192 one).
30
+ config: { "mode": "fanOut" } \u2014 splits to every outgoing edge
31
+ config: { "mode": "join" } \u2014 waits until every incoming edge arrives, then fires once
32
+ - \`rule\` \u2014 DMN decision table, first matching row wins.
33
+ config: {
34
+ "inputs": ["{{trigger.tier}}", "{{trigger.amount}}"],
35
+ "rules": [
36
+ { "when": ["gold", ">100"], "to": "vip", "output": { "route": "vip" } },
37
+ { "when": ["*", ">50"], "to": "high", "output": { "route": "high" } }
38
+ ],
39
+ "default": { "to": "low", "output": { "route": "low" } }
40
+ }
41
+ Cell syntax: "*" wildcard, exact "value", ">N" "<N" ">=N" "<=N", "!=x", "/regex/".
42
+ - \`checkpoint\` \u2014 pause until a matching event.
43
+ config: { "name": "await-x", "waitFor": { "source": "manual" }, "resumeMatch": {} }
44
+
45
+ ## BPM
46
+ - \`userTask\` \u2014 assign a form to an actor or role; pauses until submission.
47
+ config: {
48
+ "assignTo": "actor:alice" | "role:reviewers",
49
+ "title": "Review expense {{trigger.values.amount}}",
50
+ "description": "\u2026",
51
+ "dueIn": "P2D" | "PT2H" | { "minutes": 60 },
52
+ "form": FormSchema
53
+ }
54
+ Output: { submittedBy, submittedAt, values: { fieldKey: value }, action: "primary" | "secondary" }
55
+ - \`subProcess\` \u2014 spawn a child workflow; parent pauses until child reaches end.
56
+ config: {
57
+ "workflowId": "<other workflow id>",
58
+ "inputMap": { "trigger": { "grantee": "{{trigger.values.name}}" } } (or "*" for full inherit),
59
+ "awaitCompletion": true
60
+ }
61
+ Output: { childRunId, status, output: <child's last bundle> }
62
+ - \`signal.emit\` \u2014 publish an event. config: { "name": "approved", "scope": "workflow"|"global", "payload": {} }
63
+ - \`signal.wait\` \u2014 pause until matching signal arrives. config: { "name": "approved", "scope": "workflow", "match": { "grantId": "{{trigger.id}}" } }
64
+ - \`timer.boundary\` \u2014 pause for a duration. config: { "after": "PT2H" | "P1D" | 60 (minutes) }
65
+
66
+ ## Actions \u2014 side-effect sinks
67
+ - \`action.send\` \u2014 post a message to any channel. config: { "channel": "telegram"|"whatsapp"|\u2026, "chatId": "{{trigger.chatId}}", "text": "Hi {{classify.reply}}", "accountId"?: "\u2026" }
68
+ - \`action.createIssue\` \u2014 open a GitLab issue. config: { "channel": "gitlab", "project": "acme/web", "title": "\u2026", "description": "\u2026", "labels": ["x"], "assignees": ["user"] }
69
+ - \`action.setLabel\` / \`action.readLabel\` / \`action.react\` / \`action.editMessage\` / \`action.logTime\` \u2014 self-explanatory; each maps 1:1 to a channel adapter method.
70
+ - \`action.callHTTP\` \u2014 outbound HTTP. config: { "method": "POST"|"GET"|\u2026, "url": "https://\u2026", "headers": {}, "body": {}, "timeoutMs": 30000 }
71
+
72
+ ## Terminal
73
+ - \`end\` \u2014 completes the run. config: { "status": "completed" | "failed" | "canceled" }
74
+
75
+ ## FormSchema (used by userTask + trigger.form)
76
+ {
77
+ "title": "Review application",
78
+ "description": "\u2026",
79
+ "fields": [
80
+ { "key": "score", "label": "Score", "type": "number", "required": true, "validate": { "min": 1, "max": 10 } },
81
+ { "key": "verdict", "label": "Verdict", "type": "select", "required": true, "options": ["approve", "reject"] },
82
+ { "key": "notes", "label": "Notes", "type": "long-text" }
83
+ ],
84
+ "submitLabel": "Approve",
85
+ "secondaryAction": { "key": "reject", "label": "Reject" }
86
+ }
87
+ Field types: text, long-text, number, boolean, date, select, multi-select, file.
88
+ A form with a \`secondaryAction\` and no required-unfilled fields renders as one-tap Approve/Reject buttons in chat.
89
+
90
+ ## Templates
91
+ Every prompt + config value can reference \`{{nodeId.path}}\` against the run context. Examples:
92
+ {{trigger.values.amount}} \u2014 field from the start form
93
+ {{classify.result}} \u2014 parsed RESULT: token from an agent node
94
+ {{review.values.score}} \u2014 submitted userTask field
95
+ {{env.GITLAB_TOKEN}} \u2014 env var (must be listed in workflow.envAllow)
96
+
97
+ ## Edges
98
+ [{ "from": "nodeId", "to": "nodeId" }, { "from": "branchId", "fromPort": "case-label", "to": "nodeId" }]
99
+ Every edge's from/to must reference an existing node. Branch + rule nodes MUST have outgoing edges whose fromPort matches a declared case/rule/default.
100
+
101
+ ## Workflow shell
102
+ {
103
+ "id": "lower-kebab-id",
104
+ "version": 2,
105
+ "title": "Human readable",
106
+ "description": "optional",
107
+ "priority": 0,
108
+ "fanOut": false,
109
+ "envAllow": [],
110
+ "maxChildDepth": 5,
111
+ "retention": { "maxRuns": 500, "maxDays": 90 },
112
+ "nodes": [...],
113
+ "edges": [...]
114
+ }
115
+
116
+ ## Lint rules (all enforced on save)
117
+ - exactly one trigger.* node
118
+ - every edge references an existing node
119
+ - every node is reachable from the trigger
120
+ - at least one end/checkpoint/userTask/subProcess/signal.wait/timer.boundary is reachable
121
+ - branch + rule outgoing fromPort values match declared cases (or default)
122
+ - cycles are only allowed when the cycle crosses an agent/checkpoint/userTask/subProcess/signal.wait/timer.boundary`,l=`# Your reply
123
+
124
+ Respond in ONE of two ways:
125
+
126
+ 1. **Clarification**: if the user's request is ambiguous, ask exactly ONE short follow-up question. No JSON.
127
+
128
+ 2. **Workflow**: if you have enough to produce a workflow, reply with:
129
+ - 1\u20133 short lines explaining what you built
130
+ - a \`\`\`json code block containing the complete, valid V2 workflow
131
+ - (no prose after the JSON block)
132
+
133
+ Only reference agent ids, actor ids, role ids, and channel names that
134
+ appear in the Environment section above. If the user asks for an agent
135
+ that doesn't exist, tell them which agent to register first. Never
136
+ invent workflowIds for subProcess that aren't in the existing list.`;export{c as buildWorkflowAuthorPrompt,u as extractWorkflowJson};
137
+ //# sourceMappingURL=editor-chat-OB7IQOB5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workflows/editor-chat.ts"],"sourcesContent":["import type { Workflow } from \"./types\"\nimport type { WorkflowStore } from \"./store\"\n\n// --- Workflow-builder chat context ---\n//\n// Produces the plain-text prompt the authoring agent reads. Packs\n// *everything* the agent needs to generate a valid V2 workflow JSON on\n// its first try: node catalog with config examples, available agent\n// ids, actor + role ids, channel adapter names, the user's existing\n// workflow list (for subProcess targets), and — when the author is\n// editing — the current workflow JSON as a starting point.\n//\n// The output is deliberately text, not JSON. Agents (LLMs) produce\n// better structured output when the schema is described as prose +\n// examples rather than as raw JSON Schema. The explicit \"reply with a\n// ```json block```\" instruction at the tail is what the API endpoint\n// parses back.\n\nexport interface ChatMessage {\n role: \"user\" | \"assistant\"\n content: string\n}\n\nexport interface BuildContextArgs {\n messages: ChatMessage[]\n store: WorkflowStore\n /** Full list of agent ids + optional descriptions. */\n availableAgents: Array<{ id: string; description?: string; tags?: string[] }>\n /** Adapter names the daemon has live (telegram, whatsapp, gitlab, ...). */\n availableChannels: string[]\n /** The workflow the user is currently editing (if any). */\n currentWorkflow?: Workflow\n}\n\n/** Build the single-string prompt the agent executes. */\nexport function buildWorkflowAuthorPrompt(args: BuildContextArgs): string {\n const workflows = args.store.list().map((w) => ({ id: w.id, title: w.title }))\n\n const parts: string[] = []\n\n parts.push(SYSTEM_HEADER)\n parts.push(\"\")\n parts.push(NODE_CATALOG)\n parts.push(\"\")\n parts.push(\"# Environment — only reference values that exist\")\n parts.push(\"\")\n parts.push(`## Available agents (for \\`agent\\` nodes)`)\n if (args.availableAgents.length === 0) {\n parts.push(\" (none registered — the user must add an agent first for `agent` nodes to work)\")\n } else {\n for (const a of args.availableAgents) {\n parts.push(` - \\`${a.id}\\`${a.description ? ` — ${a.description}` : \"\"}`)\n }\n }\n parts.push(\"\")\n parts.push(\"## Assignees (for `userTask.assignTo`)\")\n parts.push(\" Any agent id above, or a plain string naming the person responsible.\")\n parts.push(\"\")\n parts.push(\"## Available roles (for `userTask.assignTo`)\")\n parts.push(\"\")\n parts.push(\"## Available channels (for `action.*` and `trigger.channel`)\")\n parts.push(` ${args.availableChannels.length ? args.availableChannels.map((c) => `\\`${c}\\``).join(\", \") : \"(none live on this node)\"}`)\n parts.push(\"\")\n parts.push(\"## Existing workflows (for `subProcess.workflowId`)\")\n if (workflows.length === 0) parts.push(\" (no other workflows defined)\")\n else for (const w of workflows) parts.push(` - \\`${w.id}\\` — ${w.title}`)\n parts.push(\"\")\n\n if (args.currentWorkflow) {\n parts.push(\"# Current workflow on canvas\")\n parts.push(\"\")\n parts.push(\"The user is editing this workflow. Build on it — do NOT replace it unless they explicitly ask.\")\n parts.push(\"\")\n parts.push(\"```json\")\n parts.push(JSON.stringify(args.currentWorkflow, null, 2))\n parts.push(\"```\")\n parts.push(\"\")\n }\n\n parts.push(\"# Conversation\")\n parts.push(\"\")\n for (const m of args.messages) {\n const tag = m.role === \"user\" ? \"USER\" : \"ASSISTANT\"\n parts.push(`${tag}: ${m.content}`)\n parts.push(\"\")\n }\n\n parts.push(RESPONSE_RULES)\n return parts.join(\"\\n\")\n}\n\n/** Try to extract a workflow-shaped JSON block from an agent reply. Returns\n * null if no JSON block is present OR the JSON doesn't have the minimum\n * workflow fields (id, nodes). The caller still runs full zod validation\n * before writing. */\nexport function extractWorkflowJson(reply: string): Record<string, unknown> | null {\n // Prefer fenced ```json blocks; fall back to the largest {...} block.\n const fenced = /```(?:json)?\\s*\\n([\\s\\S]*?)```/.exec(reply)\n const candidates: string[] = []\n if (fenced) candidates.push(fenced[1])\n const braceMatch = reply.match(/\\{[\\s\\S]*\\}/)\n if (braceMatch) candidates.push(braceMatch[0])\n\n for (const raw of candidates) {\n try {\n const parsed = JSON.parse(raw)\n if (parsed && typeof parsed === \"object\" && \"id\" in parsed && \"nodes\" in parsed) {\n return parsed as Record<string, unknown>\n }\n } catch { /* try next */ }\n }\n return null\n}\n\nconst SYSTEM_HEADER = `You are AgentX's workflow author. The user describes a business process in\nplain language; you return a valid V2 workflow JSON that implements it.\n\nRead the node catalog below. Prefer the BPM primitives (userTask,\nsubProcess, signal.emit/wait, timer.boundary, gateway.parallel, rule)\nwhen modelling processes with humans, deadlines, parallel streams, or\nnested activities. Prefer the bot primitives (agent, branch, action.*)\nfor simple message-in → reply-out automations.`\n\nconst NODE_CATALOG = `# V2 Node catalog\n\n## Triggers (exactly one per workflow)\n- \\`trigger.channel\\` — inbound channel event.\n config: { \"source\": \"whatsapp-message\" | \"telegram-message\" | \"slack-message\" | \"discord-message\" | \"gitlab-issue\" | \"gitlab-pipeline\", \"filter\": { \"chat\"?: \"*\", \"project\"?: \"acme/web\", \"labels\"?: [\"x\"] } }\n- \\`trigger.cron\\` — scheduled. config: { \"spec\": \"0 9 * * *\", \"timezone\": \"Africa/Tunis\" }\n- \\`trigger.hook\\` — subscribes to any on:* hook event. config: { \"event\": \"on:gitlab-issue\" }\n- \\`trigger.manual\\` — kicked off by CLI or API. config: {}\n- \\`trigger.form\\` — human fills a form to start. config: { \"form\": FormSchema, \"startableBy\": \"role:public\" }\n\n## Compute\n- \\`agent\\` — run an agent with a templated prompt. Output: { reply, result (parsed RESULT: token), json?, durationMs }.\n config: { \"agentId\": \"<id>\", \"prompt\": \"Classify {{trigger.text}} — reply on one line: RESULT: a|b|c\", \"resultParser\": \"acme-result-token\" | \"json\", \"timeoutMinutes\": 2 }\n- \\`transform\\` — reshape upstream context.\n config: { \"path\": \"trigger.contact.phone\" } OR { \"template\": { \"key\": \"{{trigger.text}}\" } }\n\n## Control flow\n- \\`branch\\` — N-way switch (first match wins).\n config: { \"cases\": [{ \"when\": { \"kind\": \"equals\"|\"contains\"|\"matches\"|\"exists\", \"params\": { \"path\": \"classify.result\", \"value\": \"ok\" } }, \"to\": \"port1\" }], \"default\": \"fallbackPort\" }\n Wire with edges: { \"from\": \"branchId\", \"fromPort\": \"port1\", \"to\": \"nextNode\" }\n- \\`gateway.parallel\\` — fan-out (one → many) or join (many → one).\n config: { \"mode\": \"fanOut\" } — splits to every outgoing edge\n config: { \"mode\": \"join\" } — waits until every incoming edge arrives, then fires once\n- \\`rule\\` — DMN decision table, first matching row wins.\n config: {\n \"inputs\": [\"{{trigger.tier}}\", \"{{trigger.amount}}\"],\n \"rules\": [\n { \"when\": [\"gold\", \">100\"], \"to\": \"vip\", \"output\": { \"route\": \"vip\" } },\n { \"when\": [\"*\", \">50\"], \"to\": \"high\", \"output\": { \"route\": \"high\" } }\n ],\n \"default\": { \"to\": \"low\", \"output\": { \"route\": \"low\" } }\n }\n Cell syntax: \"*\" wildcard, exact \"value\", \">N\" \"<N\" \">=N\" \"<=N\", \"!=x\", \"/regex/\".\n- \\`checkpoint\\` — pause until a matching event.\n config: { \"name\": \"await-x\", \"waitFor\": { \"source\": \"manual\" }, \"resumeMatch\": {} }\n\n## BPM\n- \\`userTask\\` — assign a form to an actor or role; pauses until submission.\n config: {\n \"assignTo\": \"actor:alice\" | \"role:reviewers\",\n \"title\": \"Review expense {{trigger.values.amount}}\",\n \"description\": \"…\",\n \"dueIn\": \"P2D\" | \"PT2H\" | { \"minutes\": 60 },\n \"form\": FormSchema\n }\n Output: { submittedBy, submittedAt, values: { fieldKey: value }, action: \"primary\" | \"secondary\" }\n- \\`subProcess\\` — spawn a child workflow; parent pauses until child reaches end.\n config: {\n \"workflowId\": \"<other workflow id>\",\n \"inputMap\": { \"trigger\": { \"grantee\": \"{{trigger.values.name}}\" } } (or \"*\" for full inherit),\n \"awaitCompletion\": true\n }\n Output: { childRunId, status, output: <child's last bundle> }\n- \\`signal.emit\\` — publish an event. config: { \"name\": \"approved\", \"scope\": \"workflow\"|\"global\", \"payload\": {} }\n- \\`signal.wait\\` — pause until matching signal arrives. config: { \"name\": \"approved\", \"scope\": \"workflow\", \"match\": { \"grantId\": \"{{trigger.id}}\" } }\n- \\`timer.boundary\\` — pause for a duration. config: { \"after\": \"PT2H\" | \"P1D\" | 60 (minutes) }\n\n## Actions — side-effect sinks\n- \\`action.send\\` — post a message to any channel. config: { \"channel\": \"telegram\"|\"whatsapp\"|…, \"chatId\": \"{{trigger.chatId}}\", \"text\": \"Hi {{classify.reply}}\", \"accountId\"?: \"…\" }\n- \\`action.createIssue\\` — open a GitLab issue. config: { \"channel\": \"gitlab\", \"project\": \"acme/web\", \"title\": \"…\", \"description\": \"…\", \"labels\": [\"x\"], \"assignees\": [\"user\"] }\n- \\`action.setLabel\\` / \\`action.readLabel\\` / \\`action.react\\` / \\`action.editMessage\\` / \\`action.logTime\\` — self-explanatory; each maps 1:1 to a channel adapter method.\n- \\`action.callHTTP\\` — outbound HTTP. config: { \"method\": \"POST\"|\"GET\"|…, \"url\": \"https://…\", \"headers\": {}, \"body\": {}, \"timeoutMs\": 30000 }\n\n## Terminal\n- \\`end\\` — completes the run. config: { \"status\": \"completed\" | \"failed\" | \"canceled\" }\n\n## FormSchema (used by userTask + trigger.form)\n{\n \"title\": \"Review application\",\n \"description\": \"…\",\n \"fields\": [\n { \"key\": \"score\", \"label\": \"Score\", \"type\": \"number\", \"required\": true, \"validate\": { \"min\": 1, \"max\": 10 } },\n { \"key\": \"verdict\", \"label\": \"Verdict\", \"type\": \"select\", \"required\": true, \"options\": [\"approve\", \"reject\"] },\n { \"key\": \"notes\", \"label\": \"Notes\", \"type\": \"long-text\" }\n ],\n \"submitLabel\": \"Approve\",\n \"secondaryAction\": { \"key\": \"reject\", \"label\": \"Reject\" }\n}\nField types: text, long-text, number, boolean, date, select, multi-select, file.\nA form with a \\`secondaryAction\\` and no required-unfilled fields renders as one-tap Approve/Reject buttons in chat.\n\n## Templates\nEvery prompt + config value can reference \\`{{nodeId.path}}\\` against the run context. Examples:\n {{trigger.values.amount}} — field from the start form\n {{classify.result}} — parsed RESULT: token from an agent node\n {{review.values.score}} — submitted userTask field\n {{env.GITLAB_TOKEN}} — env var (must be listed in workflow.envAllow)\n\n## Edges\n[{ \"from\": \"nodeId\", \"to\": \"nodeId\" }, { \"from\": \"branchId\", \"fromPort\": \"case-label\", \"to\": \"nodeId\" }]\nEvery edge's from/to must reference an existing node. Branch + rule nodes MUST have outgoing edges whose fromPort matches a declared case/rule/default.\n\n## Workflow shell\n{\n \"id\": \"lower-kebab-id\",\n \"version\": 2,\n \"title\": \"Human readable\",\n \"description\": \"optional\",\n \"priority\": 0,\n \"fanOut\": false,\n \"envAllow\": [],\n \"maxChildDepth\": 5,\n \"retention\": { \"maxRuns\": 500, \"maxDays\": 90 },\n \"nodes\": [...],\n \"edges\": [...]\n}\n\n## Lint rules (all enforced on save)\n- exactly one trigger.* node\n- every edge references an existing node\n- every node is reachable from the trigger\n- at least one end/checkpoint/userTask/subProcess/signal.wait/timer.boundary is reachable\n- branch + rule outgoing fromPort values match declared cases (or default)\n- cycles are only allowed when the cycle crosses an agent/checkpoint/userTask/subProcess/signal.wait/timer.boundary`\n\nconst RESPONSE_RULES = `# Your reply\n\nRespond in ONE of two ways:\n\n1. **Clarification**: if the user's request is ambiguous, ask exactly ONE short follow-up question. No JSON.\n\n2. **Workflow**: if you have enough to produce a workflow, reply with:\n - 1–3 short lines explaining what you built\n - a \\`\\`\\`json code block containing the complete, valid V2 workflow\n - (no prose after the JSON block)\n\nOnly reference agent ids, actor ids, role ids, and channel names that\nappear in the Environment section above. If the user asks for an agent\nthat doesn't exist, tell them which agent to register first. Never\ninvent workflowIds for subProcess that aren't in the existing list.`"],"mappings":"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","names":["buildWorkflowAuthorPrompt","args","workflows","w","parts","SYSTEM_HEADER","NODE_CATALOG","a","c","m","tag","RESPONSE_RULES","extractWorkflowJson","reply","fenced","candidates","braceMatch","raw","parsed"]}
@@ -0,0 +1,2 @@
1
+ import{a as r,b as c,c as u,f as g}from"./chunk-F4HZTZHW.js";import"./chunk-FIOB3MEX.js";g();var w="entry-triage",f={person:"A named human, team, or agent",project:"A named piece of work with a lifecycle",place:"A server, environment, repository, or system",concept:"A durable idea, term, or explanation",event:"Something that happened at a time \u2014 an incident, deploy, meeting",decision:"A choice made, and why",pattern:"A recurring way of doing or seeing something",none:"Nothing that fits a wiki article \u2014 chatter, a transient status, a duplicate, or noise"},T={citeWorthy:r("Would a curated wiki cite this entry for anything at all \u2014 a fact, a date, a name, a decision \u2014 even a single sentence inside an article about something else?"),type:c(f,"What kind of wiki article, if any, does this entry belong in?"),worthKeeping:r("Would this entry still be useful to someone in six months?"),procedure:r("Does this entry describe how to do something, step by step?"),durability:u(["True only in the moment it was written","True for days \u2014 a status, a run, a transient state","True for months \u2014 a project, a person's role, a current setup","True indefinitely \u2014 a decision, an incident that happened, a rule"],"How long will what this entry says remain true?")};function A(e,t=2e3){let n=String(e.content??"").replace(/\s+/g," ").trim();return{date:e.date??null,source:e.source??null,context:e.sourceContext?l(e.sourceContext,200):null,content:l(n,t)}}function x(e,t={}){let n=e.type,o=e.worthKeeping.noul,p=e.procedure.noul,i=e.durability.score,y=t.minTypeConfidence??.5,d=t.minWorth??.5,m=t.minDurability??1.5,h=n.choice!=="none"&&n.confidence>=y,a=o>=d,s=i>=m;return{type:n.choice,typeConfidence:n.confidence,worthKeeping:o,procedure:p,durability:i,absorb:h&&a&&s,rememberOnly:!s&&a}}function l(e,t){return e.length>t?e.slice(0,t):e}export{w as ENTRY_TRIAGE_SEAT,f as ENTRY_TYPES,T as entryTriageQuestions,x as triage,A as triageState};
2
+ //# sourceMappingURL=entry-triage-LK34TSQD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/decisions/seats/entry-triage.ts"],"sourcesContent":["import { choice, noul, score } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, NoulAnswer, ScoreAnswer, StateValue } from \"../types\"\n\n// Triaging raw wiki entries before anything expensive reads them.\n//\n// The numbers that motivate this: 10,185 unabsorbed raw entries have so\n// far produced 257 articles. Absorb pays Sonnet to read every entry and\n// write articles for the few that deserve one, at --max 5 per agent —\n// 136 nightly runs to clear the backlog, four and a half months.\n//\n// Most of that spend is reading noise. Deciding whether an entry is worth\n// keeping is a classification, not a composition, so it does not need a\n// model that can write. The wiki's own absorb prompt already says as much:\n//\n// \"Choose the type BEFORE writing; if you can't pick one, the article\n// probably isn't needed.\"\n//\n// That is a Choice with a none option, stated in prose and handed to a\n// writing model. Asked directly it costs about $0.00003 an entry, which\n// puts the whole backlog around thirty cents.\n//\n// What this does NOT do is write the article. Jev cannot generate text.\n// The output is a gate and a label; Sonnet still composes whatever\n// survives. The saving is in what never reaches it.\n//\n// ---------------------------------------------------------------------\n// MEASURED, AND IT DOES NOT WORK. Do not wire this in active mode.\n//\n// `agentx wiki triage-backtest` grades the gate against absorb's own\n// verdicts — 815 entries absorb cited, 4,811 it passed over, free labels\n// already on disk. On a balanced sample of 200:\n//\n// default policy recall 25%, filtering 72%\n// every threshold 0 recall 45% (the type gate alone costs 55%)\n// type gate off too recall 95% buys 4% filtering — nothing\n//\n// So the thresholds were never the problem. A rewritten question aimed\n// straight at the label — \"would a wiki cite this at all, even one\n// sentence inside an article about something else?\" — came out\n// ANTI-correlated: mean 0.245 on entries absorb kept versus 0.323 on\n// entries it discarded. Worse than a coin toss.\n//\n// Two readings, and they are not exclusive. Absorb merges: whether an\n// entry gets cited depends on what else was in its batch of five and\n// whether a related article already existed, so the label carries a lot\n// of lottery. And whatever the model is judging when asked \"is this\n// worth keeping\", it is not the thing absorb responds to.\n//\n// The honest state: this seat is unvalidated, the harness that would\n// validate it exists, and the evidence so far says no. Left in the tree\n// because the backtest is the useful artifact and because a future\n// question may do better — but nothing should route through it until a\n// run of `wiki triage-backtest --sweep` says otherwise.\n// ---------------------------------------------------------------------\n\nexport const ENTRY_TRIAGE_SEAT = \"entry-triage\"\n\n/** The wiki's own enum, plus the escape hatch its prompt describes. */\nexport const ENTRY_TYPES = {\n person: \"A named human, team, or agent\",\n project: \"A named piece of work with a lifecycle\",\n place: \"A server, environment, repository, or system\",\n concept: \"A durable idea, term, or explanation\",\n event: \"Something that happened at a time — an incident, deploy, meeting\",\n decision: \"A choice made, and why\",\n pattern: \"A recurring way of doing or seeing something\",\n none: \"Nothing that fits a wiki article — chatter, a transient status, a duplicate, or noise\",\n} as const\n\nexport const entryTriageQuestions = {\n // The question that matches how absorb actually behaves.\n //\n // The three below ask whether an entry DESERVES AN ARTICLE. Measured\n // against absorb's own verdicts they filtered 4% of the corpus at 95%\n // recall — useless — because absorb mostly MERGES an entry into an\n // article that already exists. An entry can supply one sentence to a\n // person's page without being a person article itself, so \"what type\n // is this?\" and \"is this worth reading?\" are different questions and\n // only the second is the one the gate needs answered.\n citeWorthy: noul(\n \"Would a curated wiki cite this entry for anything at all — a fact, a date, a name, a decision — even a single sentence inside an article about something else?\",\n ),\n type: choice(ENTRY_TYPES, \"What kind of wiki article, if any, does this entry belong in?\"),\n worthKeeping: noul(\"Would this entry still be useful to someone in six months?\"),\n procedure: noul(\"Does this entry describe how to do something, step by step?\"),\n // Ordered low to high, so the expected score is directly comparable\n // across entries and thresholdable in code.\n durability: score(\n [\n \"True only in the moment it was written\",\n \"True for days — a status, a run, a transient state\",\n \"True for months — a project, a person's role, a current setup\",\n \"True indefinitely — a decision, an incident that happened, a rule\",\n ],\n \"How long will what this entry says remain true?\",\n ),\n}\n\nexport type EntryTriageAnswers = AnswersFor<typeof entryTriageQuestions>\n\nexport interface TriageEntry {\n id: string\n date?: string\n source?: string\n sourceContext?: string\n content: string\n}\n\n/** Entries are already short; 2k is generous and keeps a batch cheap. */\nexport function triageState(entry: TriageEntry, maxChars = 2000): StateValue {\n const text = String(entry.content ?? \"\").replace(/\\s+/g, \" \").trim()\n return {\n date: entry.date ?? null,\n source: entry.source ?? null,\n context: entry.sourceContext ? clip(entry.sourceContext, 200) : null,\n content: clip(text, maxChars),\n }\n}\n\nexport interface TriageVerdict {\n type: keyof typeof ENTRY_TYPES\n typeConfidence: number\n worthKeeping: number\n procedure: number\n durability: number\n /** Should this entry reach the writing model at all? */\n absorb: boolean\n /** Worth remembering but not worth an article — a candidate for the\n * memory store rather than the wiki. */\n rememberOnly: boolean\n}\n\nexport interface TriagePolicy {\n /** Minimum confidence in a non-none type before absorbing. */\n minTypeConfidence?: number\n /** Minimum P(useful in six months). */\n minWorth?: number\n /** Minimum expected durability, on the 0-3 rubric above. */\n minDurability?: number\n}\n\n/**\n * Three independent signals, because they fail differently.\n *\n * `type` can be confidently wrong about WHICH article an entry belongs in\n * while being right that it belongs in one. `worthKeeping` catches the\n * entry that fits a type but says nothing anyone needs. `durability`\n * catches the one that is genuinely useful today and meaningless next\n * week — the status ping, the \"deploy started\" line — which is most of\n * what a chat-sourced corpus contains.\n *\n * An entry that clears worth but not durability is not discarded: it is\n * flagged rememberOnly, because \"useful but short-lived\" describes a\n * memory, not an encyclopaedia article.\n */\nexport function triage(answers: EntryTriageAnswers, policy: TriagePolicy = {}): TriageVerdict {\n const type = answers.type as ChoiceAnswer\n const worth = (answers.worthKeeping as NoulAnswer).noul\n const procedure = (answers.procedure as NoulAnswer).noul\n const durability = (answers.durability as ScoreAnswer).score\n\n const minType = policy.minTypeConfidence ?? 0.5\n const minWorth = policy.minWorth ?? 0.5\n const minDurability = policy.minDurability ?? 1.5\n\n const typed = type.choice !== \"none\" && type.confidence >= minType\n const useful = worth >= minWorth\n const durable = durability >= minDurability\n\n return {\n type: type.choice as keyof typeof ENTRY_TYPES,\n typeConfidence: type.confidence,\n worthKeeping: worth,\n procedure,\n durability,\n absorb: typed && useful && durable,\n rememberOnly: !durable && useful,\n }\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? text.slice(0, max) : text\n}\n"],"mappings":"yFAAAA,IAuDO,IAAMC,EAAoB,eAGpBC,EAAc,CACzB,OAAQ,gCACR,QAAS,yCACT,MAAO,+CACP,QAAS,uCACT,MAAO,wEACP,SAAU,yBACV,QAAS,+CACT,KAAM,4FACR,EAEaC,EAAuB,CAUlC,WAAYC,EACV,0KACF,EACA,KAAMC,EAAOH,EAAa,+DAA+D,EACzF,aAAcE,EAAK,4DAA4D,EAC/E,UAAWA,EAAK,6DAA6D,EAG7E,WAAYE,EACV,CACE,yCACA,0DACA,qEACA,wEACF,EACA,iDACF,CACF,EAaO,SAASC,EAAYC,EAAoBC,EAAW,IAAkB,CAC3E,IAAMC,EAAO,OAAOF,EAAM,SAAW,EAAE,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,EACnE,MAAO,CACL,KAAMA,EAAM,MAAQ,KACpB,OAAQA,EAAM,QAAU,KACxB,QAASA,EAAM,cAAgBG,EAAKH,EAAM,cAAe,GAAG,EAAI,KAChE,QAASG,EAAKD,EAAMD,CAAQ,CAC9B,CACF,CAsCO,SAASG,EAAOC,EAA6BC,EAAuB,CAAC,EAAkB,CAC5F,IAAMC,EAAOF,EAAQ,KACfG,EAASH,EAAQ,aAA4B,KAC7CI,EAAaJ,EAAQ,UAAyB,KAC9CK,EAAcL,EAAQ,WAA2B,MAEjDM,EAAUL,EAAO,mBAAqB,GACtCM,EAAWN,EAAO,UAAY,GAC9BO,EAAgBP,EAAO,eAAiB,IAExCQ,EAAQP,EAAK,SAAW,QAAUA,EAAK,YAAcI,EACrDI,EAASP,GAASI,EAClBI,EAAUN,GAAcG,EAE9B,MAAO,CACL,KAAMN,EAAK,OACX,eAAgBA,EAAK,WACrB,aAAcC,EACd,UAAAC,EACA,WAAAC,EACA,OAAQI,GAASC,GAAUC,EAC3B,aAAc,CAACA,GAAWD,CAC5B,CACF,CAEA,SAASZ,EAAKD,EAAce,EAAqB,CAC/C,OAAOf,EAAK,OAASe,EAAMf,EAAK,MAAM,EAAGe,CAAG,EAAIf,CAClD","names":["init_questions","ENTRY_TRIAGE_SEAT","ENTRY_TYPES","entryTriageQuestions","noul","choice","score","triageState","entry","maxChars","text","clip","triage","answers","policy","type","worth","procedure","durability","minType","minWorth","minDurability","typed","useful","durable","max"]}