From 4a01429e4aa239c6033a178e79230db30729e7d7 Mon Sep 17 00:00:00 2001 From: Raj Sharma Date: Thu, 9 Jan 2025 16:27:26 +0530 Subject: [PATCH] linear manager --- bun.lockb | Bin 463672 -> 464424 bytes config/index.ts | 17 +- interfaces/events.ts | 5 +- package.json | 1 + tools/ask.ts | 3 +- tools/event-prompt-augmentations.ts | 278 +++++++++++ tools/events.ts | 234 +++------ tools/index.ts | 5 + tools/linear-manager.ts | 728 ++++++++++++++++++++++++++++ tools/software-engineer.ts | 128 +---- 10 files changed, 1111 insertions(+), 288 deletions(-) create mode 100644 tools/event-prompt-augmentations.ts create mode 100644 tools/linear-manager.ts diff --git a/bun.lockb b/bun.lockb index a93f9906780fe074cda47a905f8a2177d4c35f5b..9f65166bb0d3e82f9aca7d47ddad06185f63cceb 100755 GIT binary patch delta 72282 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 D!(_Nj delta 71504 zcmeFacYGDq8vZ+zWJ3l;DJs2-5Fubd*?|a~-kTIb5hR2F8`2BSl7LFF>!^brD?~v- z5l~d@iio{8EOhG$v_UvKpy5?K&Y<_u{ zv>xxgQ#{}wrF}j)~fQ%4X%Iby0r~zJG!P{F{xF>^zkVn zah&XeMcHyLUT75OBstEJj#F7+aTs_Qe0;Lw91b1^%6~fgBftru^oejWcpdoXWKbXc z02IGG;y6cv6=3~=ICkRE0L+gU#Pj2&WwT1ta~GDCWY?mYN^ovzVZp3;UheO;OaY2Y z)5}V7bDbkojeoJ<({1JDS~kVcc3V}fBqxu!KZDA&HzgOJQIegLTk1HQ;L`sFs_-Mp zNdEb`C9`w8InGyjs7oWC%fiup1A zDo%O9%y>@RahmzpCbb$?O^(v$7L>)y;<=?W3-im%;(4X>F|T6H0adwgEq*|F88oM~ zv@oB1UxO=Lk?$sVX!4RR)583`ZgWd%o5sfXNxxrmtBUiRI8H0VE(R4R4^-N7vP%mW z6gbYKO&tdtRDMY;)g-hUd0f=Y7_6`$Cs$QnP?B9#w8(LmqF1;j&5fNt1C{Pupwf*O z%*ic@mzBl~X640aR|Km=8GEg2}IO%zuKE-zRdFDl9{8Aj_N+Bmy~2La{eH^jFJv2-8PmV11fx7&~yB0DdQ@-wKruCgKTx zzeuZ!PMwS?o7?37-L%=Iiwbfa$4O-Sa7$yU?pRPYUN9$~GY9_-=vBPLz-`&ZRCgw* z@mW@KVh@5jYY3;N8`d=}X8+2WxiiXVYRfE8^;ux`+fOjOEW0Fq7P_MH z{33;4>9?uXXjr-py@^mV{NGqi#aWbHzBrFi3%i?ai*gIXLGvU2%H=k~%UYM%crkxT ztq$&MepRhj70*!MhWHJQnz$vo**RsTbv0aRADe0P7hC^s;4$cbLVqN9oDEl+n>RC; zIj1PQ1ZVD|w!KW=d8DTrtm$dexd@~KS2iOab?}GOu3zz9mhnrBh|frOsIO_(Tu}B~g+EwPd0Ib{z*hEJ>t7e+REUk0R|KOX9ap0t<@ltUr~11ob-89au>aX1ysV) z-2C~uB?rcS@?cY=pKOFDZ8!`gqdF%KF$G>@JL-3Ejgv%6uktshc5vVE-$`v%@!N3I z3l^4{?y(mxtL+BW2Vb#RG&?g+J$tlY_i`m%EV8sR$Jlf zWp{x}&l_Xn+dkjRxegCm=NUW{aHWmVm%@ASd&Zi|vb&w+Rk{8)o0HBk#jJ)m zK%Z#2(`+%Hnd~^s*OiH4cSGM0zHXW+=53&i)5_}Wf=cIio1VtV;=K6rGfLw5CE2qa zr}K1U{@j9`!kO`c+1;{@Rabzj|CfH7!x{}+N^}*ow6uh#5KvuE-`&3rcFDqR(?!6DuSKy3p{Zi_Fj( zXyZLU-(>d#dR4D8xwHkhEjGNMum}y4Jf98GSAa*!F7t7y-iM!U63)q=nO-`Ftu1I{)p!(DE7Po^cU{#1YPUX!w z)WNQ{n2kkeFhDM{e2K*c7V|CUSezN3#ZYsc-AhbI9)eyKVl$i*pIPF(>;HO0t5Ys> zjkdfz9yZ@%xTdCS&bRh0%`2n_JB=?et+NzVsTP1LuI=8HqnDXnmizG|yH?a*VJf#A z#F~}b(5fs0SDH}UK@HKPE;OmkE-VUjzXUGheg>+n?^~QzSdz{nJ?FZMO!&>jmw(rb zO{?7ws#mlC8>saz$Dxc@6HuKipKACn3e_mFYupK!{!yzh zz~0K>Dc{()ykJ&t85=n!GIq{|&aGFO9ecQEImvOZf-B>EmSy$(tZPg+Ob4ZJC9J@4 zd|*t|5QQ?RZSjw*O%1;TRq}sY{Ltb|3MBo@*Bc9s0#)EO*O`iRgx7;_hbzOxjbh@4 zveONY6Y=oqbvxP>FlD zn)c0vtMIe4%V#rxJKv(0H9xhO*u=aIS8sgL@7JJF#S5gZD!6x;&|lwf%5W20MJ~uL zP-k?e5Kbi>4ysY_ztg008>mv`7Z%JeWRYLC#w_dEYfZS#U=#fR!`8YfTjGL6tZca@ zL{u-FbeFN=I)7h-Mqc8UY^7hnVYH&#dQ*r6cbg87T{1gt>%{sJN3TNnw1qsu7W!Q5 zuSzDi<)t-A z$<5E5nT_r5GX>oUD&wp1XRPhtYn_+id3o9Sgg;}GX}di1nkWnD7&1ob1ID^9fDJTM zvO@>2TlEhe3opt|6Z zpekBLy0U0^12dAoc;7>&W;3!&a?=Y-oe;UT$=hZUEajjgJAa{*ZTa7z3icx?OMC^Y zA9eKWAKj?p`bUiK=U~`ykD64*QB7t4Azb*H)$ah6%01f+-u#%E%Ps_uM!x`5@n>2b z2g>gKK=s-aEVc&gY3{3M9F>m6A4ynt$)h_vTt#`=WlX5~-_t1kbqM z*ll)6VR3rt%z2q`6{Ha;|Jv67J9?n>S@b;V7sLzZr59A>6;iu0T?FPdWPqIk;Y zrk6~^d;`jkWpi>1bXv5>PixenViTdHy~$SklQm|tnD)AfJqT=qe|xYsSo(%3><+Lg zd=l6kyzEWKX$dwWoVv}|uQ^T{_};6g`RlxA>NFM9Quj2-u&Sv1R*YBzkb_ zhA)B&f8!UXrrFmT90|&DH-2e4OwFdKXpDz#i%Q2PoVnLnqzR~upR;Xv7a6FD;x?n9 zHsg+#|4z7c{MUji$cbPRu-QJ-$5O$D@RsP+v@}gcSh7TC^YBnf6BbCcaqaI-LQjD* zWnzv`)M)7s#v+L}PxPTg#cI}A6;9ko?-*6vnB4JCY1*h_)z7Bo_kildE3Px;Ow8~( z?6UH+XU<6e)fh5+?tDkrR=DbQJ9reRbJ_Irl6c|(XBO!wTV|NjvcdxAbygg5+Q#>UO)RiK?VLSaE}dKtIyvkJNtWasBP>(OgwxfGOr z=9TBEXF8lB%*e}~>3rw+Yu2dZ-;ZDtXFel{r4Bzcdy%$eP86VE;MK+l0P&wZfc|6I!r?LOCTBIYiT(H!Tr+D88z*pzUy%L?b^7Wk(& zZ;*By)0^rV_uSC*_krRoY|^@qm>+eV!TuG^8&ym>%=jk01Xu%?C3+ri3fu`)yA+k? zg&B{+Up#RtIM9aA&eQGXA}0f`jTm;H; z@7VxH)OW)rV>7$~`a5m7uhFYI%Nv;LEd-TO+~R4V){J2mds+RzTd;`qIB&kQMT3ge zqfMQEIm$$x;+o!&`1W8?c79m-qV$s7vXVu78KF;3N*kM^M2|5=?+mK5U)97Exu85h zy|l2T>^r!sRspJUODwP3ER5f)@-*Y9Y)1lNH%u=s&n?e&Iy5(YuEoTyN+ZI_28a8_ zEgS7$-;!GRoHne6PtjdI*hS>W7v`2+-oXts%tx;# zv>8;+Vmm}xoWwS!1tn7T^JkufHra6ke`PBUBy6+A6_#t5<+I95bGtC?6X)=WbNbx8 zMWyj<$En@fbkUzWxuM;13Jd4Ob6MMMz5l(qiDD+~F*Ca?H+@z?MQP%W@Yyb=OWqI4 zfYnq|{o*#b>h)b$<6n;q)Gu66UHE$nt+B-_9EN)V)T+54+f2e=WS9m>9AqWF2Qc50 zjBqMDv^EJOzH|AI2r|@qs;&ZD2g=~{3rpvbHXV6ZdO>zk{KXT^pib<&*ZccgH)?WI zkAKZ>K3sGB!+x7KjfQ=YX?jiKTZ|XsszhG)BDRE%^CVn(CcZS=YWZ4FMwkk!a(s=@ zrd7o|C!4%p1QlyiZ&UKWvJ5ZJ>5?DMDJlE~z5MfMmY3vFgRkN0n|b9rnsXeEPDX$e+i@sDk>ZIGX?l9YKm74D&Qti)yn8^+A?u7-3YFU zH>Z$05<1trHrz7RKjP`?UiA?)Z7%6z?E_T;cmF%L_zug3aS8vDl1Hx*WEeW z&uG`EXQF;niKeb|=SUMhF(>vMWemBPVydqegGzqb{Y%Rew@#yeiff*`qg}{_S1&O+}(bBXv}M#;y68to$MD6iMkX0YJ4v9(}u;8eo67? z4(sC$i17797+~MZOuu?q%=-b&a5S!;F*fS<@#Di|?uCBU@R;{REtA+`{>EWZPhYGSDDJPh{g$v^Ne4G?k{xRLfyV$ zQTGHtZB)#i<;VG4<5%(dsb9@!Yd>vt%$?xJ`MlDv;?wo3N5{Oun7S9Sll%-~SNicW zG51lwYD~;a#kvFWiKu*3J#DP29v>U?p2Vk5=(BNT)N|>cDrJhl@q^4%h|H1XZyXbi zoCAyb>xXB#FZorc#JnUL#8gPuq)Ovr-b!>bgBu#Zfm38&?`_zLu!xH99Zr9dDU9C)%S^mwmQhjSZM7`oZ=6N5O^A6->C@@NZ5KA% z5I;UK=3eDj@%f}*JuwzZrSEp}^T%d+J#d){tBiSm)u}OetzXUOKm4>wG4FV0u->E; z+Q6OdS51n!*ZS4?{D6;QI$>RpXX=$1Q~iw5QLhrFTBi8>CQv@hlGPo&zhJT|O))0w z^M&a6bvuIE=zjk$yTv}rN#!lov5S_|9kg!MP27#Vfz`qk57ks<6( zPw@8+&hoCuH8k|4e?&et`e|8S!{#Op>O~quV5&0~C5_Ad`1F|f4LVtc;Y82s#2uvU zYQh)7WEF-deRYi=&yIP!(W%AE&~D*ZXUE*>e%g!}-!jK%#JuU;shyfgA+i}Z*iRpo zcnM}qqN3MtoABE=F6y4* zr{%`H%h9RAj-N3!>OSvR<;L73znagUe%h>38XT9X z_r57nZ!Ao0$Rxs)avw|~lf$w46HFCP348y54*QEeI_fU))8@v!t>{d6wbcZ_3f)y5 zjVXzh9rgCW6oSz;E$VelGgGt~YbCJ3@(VH!cQaFA>Jx6*Cw{PuvBvx|?D&M4HMOtw ztMX#rPIR5alr^$`hG|qVAI*wJPV9sk{k>DN+_`>Le$2hyug;Hod(f(37|lbYk(NXr z?C;GvITeS78kMF!u7Rn*WK6Q}!iJe-v2i=*DYZR*7^?^t4$&ZE9L0ypSjqlA2J9z( zT2ajF*d;OeHTBJhjSNGm=R5&B0mcxZSA7S|f;I5fD5rNdW(rpT?;e=)p=HVSBN&BJ z8OCO&l7ehjD;#M^|#PmBzfo$U*j_ z%MXouG0O-+wM$_#NjMjITVNVD;qvJo=2w@6b4Pj1ThiUw*M!>!Q!X%C@JpCVN@r!c z>)@x&XCRQFQX&@y&jWrHx_#)v(H|5Kb9=H5?=Pq~mpwftuRJU))b%h0tQ{u*8BErv zf@Id7Aryr%rm*H2FpVkvXwfTis-odi#iI+=3uE3X1UA;sAoeXVN=sY7BCo^x`g@ty z(#cNU$HbiNr!9_ox1yVXjQdv&}L^b0;V!~zUGDve%e_vx7v@N74tfijG6v6 zmPEZdFx8UM&W(DjU^W+We--B>KmDvM_k6$VY$i^Msj#*Ej5{(@NkdgJE9-ceBGLiJ zMZFtgGFBbGm=W`xUwux@o512|O-5lahN;p_CQoFhLKNB4r0cn~ib;xo9+?d5uQGVo zS$(*0CQXa^$B*sf-4;vq!D2r%)e_Tl+f#j6fZ;wk>Mh54DlF`F-Ul!>K4~#Fn)Ne` zX^1Im7OX#h#*X*FWG&M*zlN!7sX@k3j5>nJk6u9A&a|vnIEAc+DR;UY)%_Z#{HP&a zy7K_jrLj3JUIde0t#I_+1JjhqmF5BBd` z*2fz>gndz%NCp#5nV+^i=50V{GToOS^=eUzv4ltp_MO6}uX=gRUFWB*h`HbUaXydt zt5(FkNlb4V9cHb&1g6axt-^Hu4NS=~gt0`%@Wd3M4wh>fErtcJg{gjK{2ewTu@jfy zM3}6^xEURdEQj^?_fF07cHqKfHs`ubjbf%Vp8^X@Wvl^H8&JlgsQ0w>qaPJTBfrDO z`sqbk?g+o?;+Xg5C{t#ZA3FZAe%d85_bfktNz8kCw8Pur@E;dz5nR#HVAHOV?^y66nuFLv(olh}cfL$~lq7rtX zf=|I@A}3r48;^6Gt}sR)Q$W&0$C;5x(whl89cJdyM=fKHXP?=a5j4S1XE(e8*K|UK zcSzn3Fp{iH4l;()*6}N2k$WbE3*3Y(?+0A!Z&>Wv%+$%IjN!mbT0GgGJG_tgAzM7zuJ#^XP#!}HnSk#50hQY*s4YOR2i0Atkegl)}n|LGE*UHZsYZ&Wv1i*2vdg& z2X-Vn#c{g(=_|6lxww=ZdOF&zFlA*j{sKl%!BWa<^cjvrC+C1m>}nWEnx^o+fzcc6 zWRNx0j1Nt^}! zYuPcQQ?9fYQ`eg?dY~5a4VkI5kE&&Egi2s6QzTW)s8|D2=P;T63bP}88GE_x{p-U8 zQST~PUqUjrn8#j+nNEeNTw3b-m^YTbBm0}QmceAOBf_fI$uSFoF<2H%^CdIM1<}X? z*l<66d6xG$E|aRpc4TH^3#V~E5ym1|+0ZXunwbibJxo@Az%($8jR)l#7H)jK^I)2v zYWw@>;ZMO-L=&>^EHjlD!}QzF)WdatKG@G6p5<-BrJlrM$7tFMQ?D?6=cL&hzPjHk z#T72-!M+AeoUmSmGA)}lXMd|R(T?=1Zef3lPUd9SS(orI86;e!BTeHle{X)4cPcI| zv}O!0gQ+A;9oI*bw#NOt#`N)iK&dW81+iJ5xu$rgu*+a71~V3I{TNJ{m_q#l8wg`1 zVjpLmdc`t>jBxcL-3`}Jv5zOrr>^B{KV7O(hd+ZOcf|=T>1~0c^b)|Nn!_*N>721|2a##CIhnZ^R6ay4l&&sm~3b$$kRpkt(*Wo&G0bs|iy zX!d(cVPiw4oL_}0FVpRtoNanW$Rbl5}wg+}9jDr>GHSnC8He3jkpUL?} zm>HS6S#5A`Vro$9j)wI}q!~FM#-5mE_!(RS(3|DStEk!P!z>GJ;jM(l2zi*lkMRrX zlSt01RcW#cx1*8%VqxOv;xdh(BHU*~nZkYz>m4R1zfMa`3a0bTw#+obZDL`1-{Mkd z4)4Oe)=SMGpfgN}Ce2+MZs%`Dsd^HG{;~&la#+lLvocf9GabYje=_VqXS^8Jjeuqw z`6x^cKw~o{oS$f2`UeM4)!R6LLf4-FEETh&-e<7xF#6MqsP?4~>yWtm;h6W_1;$KF z!`z9~TE++qmxeQOsq&%Ty}_@3B<6jJP6cA~#eCdoxnZf{UDagESjMMv>jP6^&1RtC z3KNoz84LIjSl=*}3{trsHVj6?ltkS}j zxOa(M2cw&?zXd!DY7M zLB=4~MVQLO5F(!oVd|}z_EfeYHYCl(VsO;uCLYTm+vh7_Dg(zF#QPXF9A+kpPFEy0 z85wLOAnIA+0_)!GS3eW;K1L@anY)OzE6pkp?&Q5fn0ibk*w~HY!E7s0@qIQV@i1=G zHz8s4;6*TnWIoNwOof=qRbFkcN(^b15_hs6-w}&Ej&4>sRytg5x&;dq)5a9oKvOhg zR>3s&9v<#(#$9735!0F1!W5Fd!j!1{m7lgV=JxjEJ7eC(fhh_L?5mlnmbk&bfz)T! z{^@|shQnkVvo0@#sjt@wGkendVba^eXbMQrIW_8j=Erx%+!Ot(T`}*x8}=K4R(Tp` z!!fbBH=1)I#%g)g9qd=V7<13{t6z+H+wrlzba*uB*Bkx0FZFR_e$`7H*4$*wksNI7 zLwPOZZUBq#hN*4B!5^7m?zevY&6wBfPE!f0OUkFgG;r*63Ntn%*Q7u0^yj|S$IDt{ zwq01OJnH=on-ONN>`K;}3|ML?^h+>}b8~3Xw#sy;y5RzSCTwKb-|xnC0--44yP2sF zwF?GjXf*Vz-ibwK-K7~%Tkfs6WMZZRrmtUMipcs}67{;@Z3dN@DtwsfyI9D5*-v{f z#@7h(_hMewI%7Kq&GM*Q=vU*z7l7}_yw~u_2vb*oNm+09z*v>MvS4Om(cyAAOvBj? zich3Z!g`u`bf4c~rrqdgZV$iugP2?Dr+pZ6AM@iM#=PI|G5$<&vqFD#Iro}@5#Akn zYhhWW#|~&()E(_t?T&eCH<*sctW_HIzJ(15L#X|u8;#K^X=#)*hpLZa-m~bG2H`o+ zPr8q3!{7U2RwRn6n@%kvaa^ovyRzJEe)Y$(q+jpzkN>1kX1IP^OruiyxlwNkOy*n<*wYU_@jGND4>a$^TZhJredCZ&kV8VLE3o=t6qk}8ble_z^N2Gc`KnI)H*s|9{ zCJ*yv*tsx`Gv;tweH%=DfOcVO`w`Y7WE=CMZYMup&0MuDFR9Bkhtc_a zCuBv2;o?M>lb@BiRLO9$_MV0*UD}GN;&1E6oIo$kdc^cX{N_i!I82iYLxnFQ*Z9?6 z#=IZVX-u1iZ{VZ*YtNb2QrM8N8TR5*Ey-~gV`aN(6h`?91}#jb!Q|YmZuP77#=I?$ zB^G#1d&!U6*g^4d`a4XM8D+xnBFnHoi}Eg*8KGLd>ONsgLq61O#D1oDD`Cb4Vo(1I zbD!MrM=HHx(Xf0RLp-!|>|;`?He9k^=Zx`aP;Lh=g{k=Lkgv*2h0KL8a^GR~ z-C=qu1DcldL&`*_cz(@euc%RcZJzv`zL-}Y7i6!SJcZ|X{R??qXB<3Go|i*}m* zY<<7@lBo9uYgIAKr#Hi-{X_$|jN4e~wr>RDIk2Fy=Dz|nRpGl%ug%9bj~L2f#!tn4 z8fMdETr~Tnrr$i6ZPNUxcN=W_{&FUN>NsOUR?Ko+0Gpifi#!9%4R}_Tg=df1Dr1ea zxJ7}TzCYMQuv1L1!#)dtBP#nSHQ0fseFD}$j6`|elpvn!#*!xg!@uj#K9T$X;W$(M z^?znX>U~am{PlllMb5;PC6{*(E;A$;^O2um*8@<7_Qcrq3h2qZxSv|>*l_CJ?!NDtQw}$Gc1_0I(=pAi#g8A zOogc5;6;EIO@*@TOVp*#AUu)r8$Audzd zcW|8*`cC*Z5$*w8{+k&ckblq=Js$(&BDP^zk_OFth zGnK(c1ncY3(A#m!)@C_s{k`e8hLs#(volj6nm^3AYWRbhD_IeDM!mr>nJ)BmZwS&F zQhZ34FV$(Y)(e3%^`RCEhWA9Od~HjC_a^?9%kExq(2X;8oM!fQcw+e z-~FDbnC{I_{v$Ct7xPsQq(_*+zK3~43C66-_d!SfS!4g=4TcRO6fIEAJ_4o|z-nv* zpM{w^X;{|!%M{AomGprrbz|Q$m~3cvfA_!!8pdw!4_LR5scK#RPLxV)Cd}lg2M<=m zRBe;P+c3oomu$CA5O3keA|oBwp3$$sr7XR0!FdAKHw>(!nxr7!5*zghssL{h57PDy zlikNAXEW?17_IOUPbR>Wr8()yN^--3Qvixn0aK@8QTZ-26=DZ6i5!+}0-A;$13Mwi zKx<#c0Y4h{NtmtS=xEa4$-&$Y0#Jpoe~#yF?pf5B8SdeQ_Q*9+op zsrRag>x>9*zdps)2XCg05zOuU5WqAa8@R|U*og2uwT-ycXJ{U7;pzm{?Jz_hzY6Gr zlFIV*h$Zwt8sZb!m@s@X)yqhA?U~WWJ2O)uC*ozkWq;f5MBfQz0@fTb^xE?^jqjF2d|)^P2*- zIn|-HHdsa?3ou$o>N}VpDJUgVf)y~+D>ak70Fx~+5cjRWSwC|x(5J3x5A$v3VwmaM z7%H*}*27=F;p9{tL&Ds3mz{Bh8x9q23aC&^e%&y^Bu?{|z+_umjPCI`O!2~&@ltu% z+ZK@payU$zLQ|-VU|Atk{hoozWSIJssMngGJ1CVSf_;tMXk<2YP_XwHX5w`?L-U0` z-fvJDiLEXtVEpJoO~NLTf%!5_eFRpPnbHsvcCa&WDWmXCByzi0sQ*eKOmM2$jUPqG ze$1NO<4hL|AJe=Pm#jq<=t$4O)O_q_-{W~Xeo&#Floaf%$Fv4BcGZ@=0;XKU?&v-n zRG+{AemY3YpdA}DHmNYXkV=1;W*&;oXgMF&9mYw*v}j}ttXDVyFiUtc&mmnt`)+<( z!54UZKANqEyI{)VYT(KM7c+BDG^x{p}|8yEVi-FM2*&mWo@_uH>$UHLJQM6 z=FIDCn5`y7yANgx$_Y>83s}tGo15jOwM-b10qEug@hE277*qjCf3*z0;8AF|dytk% z(JtWk9A>K1l<-JUCFL(D?exTo(b^1m)atyyLAtK)+jl>lW+Hm)9X^_?rTR$Ge0k3suW1ets zau)~HQa+4Qb}YiZYktQc_LxT7q3V0sM7(uw5 z<)OGIf@&$93{!Vz5|*%eFutH*q;9}vW@t6h&o&g}oZj0rNE<@bdEH$5@D7!7gP;nf z`(aQmzhk@aZ;Mg11x!QLHBXzu4lGSS!A2XuGgxoahOyq3pNL7qX|Lr;X4zn@ZsXe; z%gi~#ov^uz8TO)j{1C?W6z2IpFf-hWG4b6nH870<+Y2*Wn~W!-kyiY~W3slE-U3|O zsbDk~rLs)Z#`IB^+RtH1&wT&Zs^|W3%j7gg%wJCr3~(K2t`A`9C1#sazn7`C*`$qu z9Y}Av^)oej8m4kE9+;YwvfRY!J7u2?o4G&DXJP6{gyatOOn#@z_b9cYao>XrWKd+bBOXSY*ZL;BYwU6nzB$v=G;qR z{rtUOW--0Q$J0gH_hV)YA2gqiOXf1)mfr|d70jOKeT6c9%vgi^t9Nsb;`%}L1bXQ% z6sO?LpgEIW%S2QY2bdUi0*d=8OdZ!WVe^5en&EkqHyLKSvAWnbFd3NQvR!x?X7}|p z%+Z5PHR-l&R_4NnnKJQ>$NjJoFuLY1neL!q?qt@CQ9;#Y%Cd5>Yab+)Ss#Ka2V<-s zVCq2Oci)lDL+E%x{$w{R^88SyAwT_*lT(KoQyarCgPG>z-Xi&_VXlAo$lghR3=6(E zogMMi;jS}{)Qr`(!4#H3M+fvqB)a3q_cK!=W`!+2EhHLN>;S*NqQ8d4vxcB;wHf_lF7z?9rMG&9H zdiv1l;I3(IpQIl~2VYEcd!^B*)UWhAFio`KB}_svz2hQ`MSh3a`%Je{g2?Uqfu+Ms7(sF@ z&HEfhf0IuHzG=JHKJ&@AmX@nYf$0VTMkx)5${2vj5k=1gQ5KIv;H;FL$?j7gFJ2hh3Zl-ST0n?yDUF|&5Y)N zq6QDv&%i)xfL9U4f6e%YsPepFxlsOZS}v5q-a%B3j}Y-sE&c;k_|FktLgn+N2$xXl z@0Edc;Rr9GD#`&*c>SNCGWrFHAb*Cj3`+kOqJp^)Wyk?QcpX6Umo9}5VZ>rB5iX$$ z#0_6~36&7HYhn0?RxgyEJEssES$$2E1-K;%{kdrfL-B8`&8?HFqHpf8*i#> zN7OVMp(ZNf9GhUS^{T9C( zms-70&dV$p%Kr+B6~6TlYVcoYxlqpQEf=auR$KioR{y`CqTFiZ2~#y;+=W9WT4z0K zqEzeo5Z+_`g>v3&xlr*pf->Xb)p(pWpo_=!sBf?AwxWF z*UK(Y)g!}t|2L#XoSuYJ&@4VwoRdK%-`Dbf7W;#`gbFvn;y|khEA+#6XN>h2YdwUD zaEiroRxdmp{WPmT)8cgNFO(r?S$;57y0fkS?4)4$T(?d*#Nz1WHy2blE(B$i5|BTq zOdl5IoNsweRQv_#g$qFiS#0ra5w4o3vM!Yov;tgA07bk6l!tF4UTq@^<$sOkHBm;n z6}?*FPEZxN8&n1E0r_(_=)JNtX@lTc@dfM{l51@0+TfzB0}Q zb=5>AG}}g)V=->S9SoK5T*z@ zgiEM!_gK6aRDm~!dQHb+tqaOn4O9_};*Bg9D&NMI z3&op&3fI!=YohAW8og$*&Nh5HsQgYyp(R!Do;IRT3HGvFC}$tbg-Wom_{GgLM0k)6A&uE7|RcaD#$6;Uns8sr6e4omY)eKo#~)1p~B6ue1>lE z6d=bcgyJ(T=30GCR0f4sUlUb<5~~*~ol;P$1y(PVexc>T+r{p&!B@qc9;jSrTeVOI zy}f05@}X=mwCM@OF9M~yOg%v}T3{8|+Wdr4|;XE zhitfQpzQOg<=a7BLOGvI^n@p^hfpPa%JTn)3i6x{zr%(XD#I77eitaOSM+f(R8L5F zDB|mu3)M7lT6`N+!QQd{LQMj@Ew70R_YwNK;9l$hpRnR!9?JL|8}WZZW&ADig!^oK zp(^$RsM+jy%l`t&hmlAF<91Rkrh*E1q#E9$3REAigd14BPzfJxxlk5qX1P%DT7Zhz z(&}qsxTk1i1GcjPgep)6PzC8?_5Tf3kPI76s7~11@|vh{eXU*?-n;h0p_1ygP0DzP zJiwtAhk?3;N?;_Y@T0B&80#-oys@APa0aNYSH9I3fQnZH^5>NE5uP3{v>uB=72sUU zD=jVsW!e>BM{tAH-)C_XsEi)~Rlu#5KWP28S^cA+Ec&#?7b152zm7r)ykP^r4f5xF zVEIR&{67X&gHJ7f2J+{8Vfj~}<^jF#O5uL8{4Y@P{|5PUQmBo1q?YM_P90T`ODK=S zK+S|lfvQm>Pzg2xWuX?J5^4+b=XB&lHR}q>zdNYgraqtwG}!8ggZw#TEuT<ws;$;g560W;>7Qf63&odME>!)Nfl7CU^}pEq*T5Ng zbJJJDLlwCSuBN>iRC#Z;{z7c!thN0A1l{10MSS<6WY*beHBsfe$LfXR8$i`_qt*W} zsC+gNPf@qo_+SI*zbLSO@5O9QM=o|#!v*Rw8~9+T!aR<@>i#S!{+taj6yIU}UjkK$ zS3ni!b?g5Is7omSw?H-0`?a+|7)R*wp^dQHMi8oXKeb$_4EI?443yVbd??(vA{Ld= zcb5NWv|Gjh93Xt7U)aahJ5u>j0X&O!KxK3|s2c0Pg_D0hi}gWWLY1X4sBq0d4afEs zHbDFT0hM6~o6zw#o=^#=gDOB5tFMUz(U(~NQtMwKhY~Eeii4pNUTDKDw&4zj%J?k& zg=gDvLbdf0Q2Cr^{m(c4$2ivUuNdq-oBNyQ=h)Nli)^HeLB+d@4^5-1MJ&p>*7Ad4 zF8+0y^cAlOsQJIGKK>1*>SXnx=^!-}VFYp2va`i>Q0;r7jVM%Ydsu!jlotFLPD8;gyjcAS!5Fas?cOm6*&`BI@uLC_;cp!!=mQL za?6E^I3LsyTnsAW*;ap!#dASj2SbIg#9sxyz=mIDv0}N6C{zX)SzZ&Bz$I2+6P4hV zHr_SX|G%Loxf=+l3f*Y1!nw&RZU$wlyFndyJP7KliE6`#t^WT6mC++Myioj6Pz8A^ z)DJNZ)$kb`;8}~$g#nn3K}C4c@`Irpp!A7Wwig>ft3zhI{%Y{wh4}nVXVXGI4KME@Rc8fT} zXC(GNC(&FQt^tT53_=uPFmmWYi9-)c9C}c~JW+AzK?xq5V6xKOaOgpaLk~)5Q9AUX z#GwZz^uUDXgqjaXKtq1$K?w|zp=d_Y3R5mzrd^q@q|2PV{W4m~Jg z9-L6`IP{>zp$8=nJt)CI&;t^>gc=x!9+Vh^B{W^?0SR3~@k0+vgy#5{2P|ZwLk~(E zdQjrfgA#`xlsNRD#GwZz4m~KLuXMO-K0tBkK?$?9JoKQ%p$8=nJt(2sq2>b<`ug|K zgA#ghLN{559+Wurpafmv|KA5CW@`-Ui3!u^YCa$#z4-qp4@xZWd562D=N(C}F9;rK zkvt-p+#cv3mtsyU6zyB17!?$^ zM$x!6iZ`Sf6SQfA;yEcUXoKRE;1wwrwn1@HTNLAiC2di3Y>VPEDJBLP?NGce#ntUl zObR}hVnsU?gN{XUT5!d&C^C;l@uL(|f`08${8NfM+M}2n?2}?udlVCoLvdzs%W=tt zZtX4gJ0xG`x;uhfIws#AX^0W)Zpp|@e#zxE&?#P@df_vHl|zH#fyrH^3dR;C*W0qN zQ}R(s6zr1p6wgyf?l`Rzzy%PZZJYnv2v zN^-h)qq%x)al0k&blu!7ySgVYcT+d|M@~D52Ni=WMkKFE zy_(nB@T^l9Vo&gLG`ThJtJ)V$&T=ma+QgD4ZTVc!Rr zTD+8HQn-_6T&P&3rmoW5{Q0>h=z}HWl6`-};ErMJRh+8v98cv{LpfYi@u#@KbCZ&v zBKd_|>h(#k=bBun95B%qOf*FKj}4126Z}ibNc*VJXG8zw)W~nS{LoOA_Cve0C@7qn zn?8F{(b#z=W0fsYg`rA%yz81WCTbY`F_;`a3a%KET%mNmqU@Z)GLj8dzk9tGhffAo zhO}82qM)!y@Aq)_1jXa2tRm%vYO-lOZyx`Ii$zQu8wwyCEd0SGX z*_Aw57GQwgVcA=crR+Cftv03XM<&(%;$~yhpTN+}ox*Sr529>gc?0*;pvm~;N$#F4 zWJ-&7+|qku^7v$T%a$u9C)bWdemRdm7d$>R*{}Qi^LjknsjR=#aoVXX-JN7w^?&`u zMw`?{QnoCdm%JjyP2J)aVL>nPpL5?4Of62nC{n;17{bcU4hucGB>9BMqE`He+N#`| zuwk}jmL*S)xDN+cEJ`k{oA__eZ|Kc`(5?a&CWSFCpNd(+iYVsK?v{1uCXalx&RU&5 za=kt$m^m?}Z!6wK@wd68)ldC&c&nz?b#L(a#FT~=8&FFHgpZ)fEAb;hC8x*gbm@nH ziXK7q44|$}R;MtHLk&NXK&Sf~2}3lz9!94;^nIj#JBahBi90TQv@-Ej*mfI`dBJHJ zM&;3K6EK|asZw}7VRb4(-7h2tWR@WkUWfDe{%|}_U5Afp8k7fY!)(tNX+z(FWbAR`;pZwMCbB z{(6tqwL{mHo}ficOT@RR(XUC7(@4! z)g5VdebIewb@i;SAG+_YuD;dvM|Ydm=_kN)4nWpeT|=u2A0OF_ho+9Bta1?Ut%$Cp z(W!QWk%y${S^7~D(IX_{zbbz^a#Z^N}k7d{+)3d&wq z+0F(Whi)CctsQu*)s4r!3f-|_d#js(dj&cT-Q%ooB5wVtOIHW0I~BKn@TIGx)lE|W zTWOVPRymmfziDuC9dC7~;r`p|I-yf%Jsr^xg*0^2(eW3){Omrf%dm0JK!2r;+s*2x zqVq!?_V12T2~I;2zpy&V20Rnpxi)eStDBDQRMrg51yQTZ#yuIGu1u?&f%`P8OZ*%> z2i@sbr=NuDB^b_3WFkb@$u@E>ZvBQ*S8uDEg}a{B>G$L6aI+Ep%2bm}%k`ThVD^ z8e(;Yxb><5O-w_rt_b&~B+MjK5&qI$H7Q0eS8iOxZNL)Thg+R~*sgG;NaBSYBhksb z3<<|6koduSIqu0euD-if-1*2E=rnr)!GUosB#3{+Tn-@fW_wZfN*hb;xuZP?N_JBFDgNt5YB8 zgsvZ`pWCY@l}LX%xN^`bza_{3tIM_FmZBSIb+fGQJakR0u3|Py{+#oXcMx6kP$6NIp8nS%!Ro=qj+f<+wi#HRk913bz8;ZFNO9y_N8Xt+YDx7(TUY^%Ex-St*?j@9|-^rIb3r{`MTRk&|qGSlp*Ul_^?S0n4uY4)qMx@&O% z&XCo$BoQ!xq%e(Yrdw(QuEHI$y7SPfNv=h9+6n0btGfQb(dJ(E$H++*=+Dq zbX4BC713{MXMk7PaJS)}s_{1y$JHoRyW5e0)Fc-SY`{Bk&&924mDSyeyR+3@YjtbT zHO8&U>N=}ii(Btv({(*M^`j~z@mjQ-^sWT?--WcK1e#oKwvq3~U0X4@R-;n^*C7E8 z%^A0%<1c&<;|*4)mp4#;=N_bT=`Nbqd% zKC8PAcY#X4bw4^e??-xD-2*n$mTkNM5zNomQub4CzG*@Wce}|60un%djknSfG5w-uPh}ur${&yRB4{p5+ zPUHR$tNRRhk=6Zab^k!uBdiJcfL8Z8x~SFtZFT=d*BzaPq-H32S0febnHp-YRepi{ zXJk8)gicNJC88LQ0}&hUE8KgLCy`oKC$5Zi)wa5?aVyQI5WNOP8GnPQFEN66Ym!xd zi$V`)t9RG2x_!9S)76s?v%2qa%UaSMZgt<|)=LJ|yX#ur54b;}_Ugz-Sly4f=h#lD zcd_Utc+O8KM-WgQFY!VaSwJ@s>Uer7i%P8tMfVEQHMF{4ai4@v*HKpY8*a6``jcMZ zBJbZ3wYzjp(D4^eMjN&N`xv4(xG0l95p^!ePf4$694F%0!#Cx4IM?Q0;Y`)kV-LV>ON5CIbzpX}!xq z&Ct>6YU9z%S|-ivG%vW#@;R(Ci$Sw=c- zVxUf4K4N5|%(`0L5x8Xq<#mG9IdPQ)dFq_t#fp@+!7p=DUhbz~vFaS~Pml40{Eg_P z1v>NNj4wRW(@%E4M7~1yBHti7*4u~N92}jO(zSt3>U1)vexiOc*9%U~OF2F=0^O+K zg1nRt{q-aDA;?f<7%~tUgy?;t_aXNqI!Nn*M3J6IZ)6Hmh`gN~e4Url&HYEvCO@S^ z8=YExhv=m0N8~3&Cr~<((h1b>K|DXDd(y7t;HLbPwvmTWEeambPib`g5}ZpB9UGmG z=%{EOl8+Q1g-8*y*$t8kQjX(4sYokGSm|r}h|XP}#gIC4-hn)i z=*BTK1kpQ8G;3&PtmLfVb(Gf`nQiJl zSVJp|CdL~Gr_H}^Z`vX4kq$^lBn_#9=%idFEfHHE&G(AN-~5q*`Rw=u3n%8+s$ z@;1bd`e}n+bE;E@E08OZI*3jf>LN!VdP}I@K&lglACNx~t${8w3AauZrXsW9OTqIH zy|Yp;)twHWj+}uEMTQ|qAV=zDIIj}mHRN^VP2?TqJ>)~A8u48oQ1|vg} z5lA~kzub8W>4tPgPCz;%>4;uGybjT8i}gjNfO{2kEuvd?AGrv*7}2XVRv;^p3lZIh>y^QJr;Fb6vIwcF#Yylw z#!n9<6PXe`UdHI3fpaD@3K@#%_Pq_*6se2o-CpI0-UakBq8HP2LOLUjk$<539Qh~m z1)^8_Jdfy3{sm+gqSaZ8tX>B70P+x`*RFMoIKiaylr|N5Nz>IxfUH8UMQ%WDM08uP z1#mg2-Ma4HwL{mATsv^xm+J1l3b_l>J*n>8n$N{4rYOaikg20>5TRYuqUs#1V+@$US5_O1+SI4({tf?Mw7J^-@H?8rI$SaY!5F zSVV8j)(y07n0q0s;kr@Qjqwy@G%^MmhYUxCAZ-xc({?~o6_A0w|Jx&e9)xf5A~+=Z-0%8?voCX$QHMkWNq7Nj(*&{HSZBYJO#URLrvvJ-g$ z*@ZlXJdNm8LwW;SKV$$BNA&8c4-oCgwAa!$O4}xFll1Pe#mHI6*$D5(3U2`K0`Ep{ zLvGhAsq|>7?k08j_!ROCqWfgs;Opl7UStEJ6A7I_Y(jK1sGGW(;F-v9G8}@mKy+)g z5Gh5Q>h4mvPq!eqBDW!hND-2c%t7La-XilM1-^kaZ$efhtB`9oMPG-k$5VHr^GWzE zQ1|A#7k>?T9@&ZLpx{wtJE9W@-BBz6yCJ$6&>MKKN6sRR^AYXIOA-Ay1QU^-h;FIw zMM{xkq@sY2xyUUVi$ux@KMyIO0R7PqKn5dykUaE{ z(3kWDb~5rEHP`pd=OFqnmhWD}uUbz8`Fgd&IT=R`(f6wQepKH!E<`Rs@{l<47@6LR z==6vdb1IROkSLOc^hWw1oyeq$On(C}0GA{By6ZwjFS60&o|jR`2k^fHS?ZD66+~PF zd4`O#!CA;`q>_M3kuz}XjZHleeKU10qHm*aM)VETg@|6fr5j4UyzNQkQKS)Z^+l1s z7}A08DfmxB^c~J5x#=TduwNvFx zate}-6wAL)b0>=GSY>XcHalA{ov zM7BqcLpmb05FH4rV7jaQ1N;d21knM3j>?`zb|B9qRmg?v&4Upg%IURqJ23MNh|I1t zImruHLhseriG)rd^hQ6OugNmXY&GrzGTaS+8qu-XG;}%&n~r27Gmx3cX~=Z^^&QVh z((PG}L&sif4;^dGMRaVn6j7}kW0@w1Zi^p4HY2+Ay)($JU`uxtUJ0vSi#lOh7q`l) zvm~7z^+zru$>qp&WGbRo>s3RNI_ao#JMZbvoIYsne{*DbXs@?p5u)CoV0 zA^GdsvO&SVH7O0f)1gXY$dZiN{G zYJ(+%X~k@ZdlYW1Ia{&Ac-*HT+DR@a-RqHYxOF=zR9u}RFc6M&PD3#WnS#7aWIb7? zN6u8pd%>`d!7X>Cj6X(OTLxX&IIXoO=34z&Q0MNHkAjqSDaW`k1kKi^G#T?L`g)F2 z`F{TNsY^N?H9Erw`}ErK2EQrfs_!I$e&y7OPPwpyRk#O)gtZ;vzy#;s3j%0s{T>+u;E z+#;VT!8XCn;0-AY0`H!bQO(rzk0z74w|8~F-#zvmGRf$Y*(C#02gUcKENQmNmg1Z) zgKkP2@xoOEpxoWMoZzep>fW1jeKQ>_sF>%K7C!jp&!a90Q|Q)%08a)_-kZ|B*-Hdy zK!6!jzgg=3bLW*NfU@{FsK0>{eHC=tz=_h2?w08rQkuJNEI4OlN|R>8u*6}+yQ1sq zmmK#%@1BZB$$E6@J|Wmh0C#$@Q{l83R{@69f3WpU_a4zq;WA`g=bYeg1vrd0Q-G@D zZXUj4&jV|c+~ci@c`EuX65=`L`+$FCJA><`{2=JMHqwOuI{l^n?3k9_n5by>`XpL1 z{K2m~zny+}SgRALwR3sU;{KE-Nx#<$GVkYDtyVA(YC+o7_op;=j|?8XKV>#;(q-I-W|OCG?KKi3JKcp#-)v)Y^L#|b;DE3$~rfG{Oanp`j(VYNq^N3&fb#p z&t}aUnt%^|F!$V+n~JXvvkEQxT2Qf-vV1@Q<+1RhvPT+z^_zN{1`*Y+4t5d1ofEhZ zV$J!%#=|4sgV7H%1ilUC?Lt-4XFsc{JSO=5&d4!AqlZ$mO#EYl_(Lfz+@fIlL$p)< zz^zR$SQ~iTsPmV>B%oPd6JxyF=FTr)^w*RYVSzI!lJkGH_vLXtX7AtK`P^Bu6e)F= zWH*uO(+6258H^BnJ)O53+XB{9G8^pUP~<;1Se!J06as z<=hOX%i95YmX7X2bA#-6m^v9ti%PKt7vA}xuE!4>nPpYHe8Keqis%niYiackNRPt; z|Vf*4hvV*9-MeM~d4imGsC? zQxU@hfXvz%OIJrW>F06=J5vrEtna4Q-)FiV6gyzHE)6WOrw~^FDffc0LW%2 z=BP^HdrgjH-en45Ln>;~i_qL%rgnLPOG@%TvN>yNo(LY>QQ~fs6P!R*igO0{Txmxl zNGw}Qs&<2Eqc0uRPP6VqE!l#7DGdOcHQDY4+yKDYfzk=*%N?u`5#Jneev-`+AGO?V zaxu!N^~9gJ`R%ga8=_cda={iF&uFq-mY7b^=d8`3i-6M>QH8yN;Y=%)-2){S^Ego3 zeI}j0!2{P<$br~3R;hpJf$uDr0fHof>vb7&-4Fj_Lo4?HOJQofA5Cex$~`|CHw5di zM|1dLrim~IcxyJi;Tl<`%4mz~{|rvIPm(mW~AO?RL! zj=D=REZl50!jQV>Zhb8*fra`Il=i+03xklucO$(L(?*KXkZ_>IY1I_!^?;0dQj>sI><-3IrW! zm+#W9W!BmZJYmjnpo9Y$qlU^UDDOcGO`yPoaKSIUX|20nM9V=F4}zpI)V37-OTI(! zDP_s?5XO2_okONh)=zzD%^~>ecd%PF!D2-_I0lWJI>E&1=+B&&amg3}N5{^*_GGsm zpH-hRU094@s2x>2jP)XJ9tK{qdb4(t4x?x6uY@ffMoqbMEGX4n{)Dv#m+Iw%jkZDW z7@#!%%k!3~U3&A2a_kAMmF0KoG`7OD2Kb5yhM?36e1w)%yJAHjsv zCT+9Nd_yDY0htW zc!kpV-3ZctgWvE-0rUT$09HO9N_%Q@q8=$Ghul?BZEyh%QZJ2aC_JzU%v~|%u4Ca7 zKY^qw;0VN1j}m$8DY}Tpcn&yT(O)kkzu(<*>SAl{H#}kQn3D<|GdXuI9UwKXL+GB} za|_JVtu=c*;cUwft%%Df@0I*9!Y))yAw(I1H$1sdp0vB4)pamgYc=r10Z(S2WzB0; zarXk}LwoR16WovpkEdudrqgngy|3eQHf<7u!Np71fvISKLuD8|s8DEXwR(g$|yehRO)k^d>cCsVgmP^qlWpjlxDia*VS2X!K9)wcJpCF-q6R9(`H0utX7Uqi1 z5Meh8xd6ES)CG+(T)`PZv6VY6k2nZ8k+yqVjiVR1L0u= zB{JGyDdi@bMfBz`G#jbFWiUpq03JFVo#mlB_$ zsYCVvwS+d4o~q7^qq)uu3wem?*a>2~?&?j|*oJNX2oCInH_8u$e4D1QLIR`c;VZ=3>eoyolt?uT^?VJkNS&g*zk_HwC)NBN zIf-OEP2m4$Vqm%u7Lq-bQCmfx+$eo5L2bDw^vj7RUB}+MJgvSCn#DY`un!u10}Jt` z*!jAnqb~~A;nJ91aG z>>}_{dAzEW#zW&a+uk(nYqnMIG7>UeGY2DTZ+*0{cH8K}(~F zJZ2G1z5_-Kp{sY$` zvLmUDCg@KnIo(6nGlPcS1K#;G^ByuwY;uzC!F+{W3en4ZrtR8>&71F=Dr?$6diVg= z%V&d}0QAzk+`LtD(qF*s?S;Y#j-kGlZp`}gmc^?E7x^4-RLqi$H_Eq@UToU79St|R z=1#9vftXAzmZJ2g(+^=XLJyoN@1x8D`o|pokc;(fDl?Nm;2M3F21WmX>qe;Z|Jxge z4YFGIHHQuqHQ3;U<4@NHV>U!z0BmRzR^PTl(%`WIP8g=r8mW3&sfZGVR1uo~7%MA5 zagQN|0d$y~k#yrRgsgN(WGBjbId%^a~~<@#6fq%O^d(Wgq5omDce>Np&vDWimYHLz#Uj$)}`DW z>tBP@DfVEHLnhB<{0g{2fb*_1>Wcws!#WE%QBM%!NlyyM;~BIIP?xP1(^GXv))k?$ss@IfY6sj3)i1Cs^OQ zX!M@GZ?FRc_qo~w+6B18VH5=%mrQDY85+8A^{W>GPN=MDN5Qw@$tDlmyASoP?_Gx)!}gQSY~j;}KA?zUWbH0kJ1@m=>$+a$n{=Em?PYKT@T3iaLSEg+g;6ng2oiJ6I-Yx(5iUmTE{< zYuyjqU$zs)5d2byNF9~xU!^pDmm%bg34`C0bb%6c=);YuQPO30Tr~3Q#xo}jgH+&J zxtgC!ZA?f<#a_vg#+!6EZ91*QBcsSq(t4-c4GT24SyKmNP(bkV@yC6_s{pW!004)h z>-V;~>?-!=xBy_BR*go>DBG^;E1Ow!`fyPOg$Mp8sxh66oj9%Gw7X&K;Fyp#aSt$* zYn&K*n7~|Ti(M&cncEMZ>X_QeI{*Oex&UxN4P83se#g?`WxvE5Rojjt`=z>rjo5hp zK~6Sc&Khc9gZkzpjN#pY?UoZGED@6`V+_I(2hP_~G|Wc#g*4aO=)tleY}TU4l2bQZ z3XhS)?4FFYb1EHSQAEI=Jn)8h{Lgb9?ALDNs+Q^vN-C9T4ob>KZ2;H9(#0c za!LS*BJMmo&Nb&vWN(Ygk(NuZY)Jqx2ehW`^PlmY9ky9vL+0f$R@!6E*^`fy73eq8TD zEYLb@6=@tW8k2sJ0pzsnu1iff?ByhjtDj&%R--6y9^J)QgfX0sFUUV*Yv1wlH8U2Y zCR9_rpMGKO}=UYP@^!sVXr%+-%4{(@C%fA(iN;Lt>&2qQ<5z- zNEK-d9I;x(D;IkMgVn*pY(r=^?1@;Yh@lHs{lwG97Mkj_sBo61g=^F6hTz>krmCzj!~B zjeY86KV&t4IbvJj74m*E;w``qQpQzRpq8I>L+yTH0aC}?5PD1R- z34$PBKM1#idQ+>(is5@*^047^WgI44jqWoMpOMTL@~${`tg31D_W(dvflLGd9tFUL zhExrW5k6C@f?vNA_CbDfaPZZuo9{sKjs9(GJFaKmpY6oMdr1(GAfRB#8B0rZlGE4%ylP?k(Ur2id**(b+=n zeYj}G#{sAm*iirgES}MFn->=SbzEfSam57G6`SCkqY! zlPv>;m{BL2OKt1{zLfd{4vC6326e05_PQ(7{;Iv+$vCP=Nw^!8I6i8{H9@2|CbGIf z;RP}DHboZHgDsB%#|)i(sDn3tKL(XJ_)I>ov&xCHaXY+u-fqpfcQ-udWxR2sH;kby zX&z{*kWV2r!PK(^R``t?rTKijdtt;ENph4g96(nZ3OHjV0GPc6@3v`ox%5aRFFc`7 zB|Zjx!$qy7bd=$C0Im?=?%%7n_^G+>LPP|Wq#Ps70iM^B8arfAA*Y8P&Eba;^ll@X z4RqWAY+R~%@uH=5)s#L?15vp4MO=bSOR-3v;jFwcV(wFoaQf_+!;H@=cttoAo)6K% ztmC3+r$VBlDq_ssM@C%rGTd*C7l+}Rnj zFQRXRv*D^Be!VNT2$~>zRRrR$GG7{b-muxlZkGSrU1*eulC|-M#og&d#ZA|LvhSPm zrWp;!36zn0fh0L-rPo*e)(tKO03{LO@jP?y1OQ`DrQ>9Jhhdg;c*+Ul(rsG%IX0_e z6<*k-R$IQ=*`)Ix>$fK-;f*T8WF$?-?-cWkvx;oe(dGW?sE2_d~qt{Xy+TxpgpYZITf3Kp%f-7Bg-+;+21OStetN-tF zM|U#Sa$uPX2NO#Xu3*JM05}6+`h)L!?7e1+a?ld+goStwtsB}0p%XT3xV2#xh9cXl zf=XLa+JT`)_%fLtEGT(bH(~PP$@SgAT3tN_!hBo(Wxa3uRT>f zg;uP>8>Vi{96z0CecQy`b+fZf!LQ%%h z)uP~#J@qdNJyku?1j9)9bU2GsWT7fxl`n+@040$~cisF^jh(K!BY_qE%Nf7GoR z{^T(i2p`8vXK>tVFflf!cfM{^{OMjrseX zl>ei8eWT21wQop^&>f0Z^78ezda^-=8Be$6PWq_&G7gCwIT2KY9h36E%1tTn*@WJ7 z!wSR6r+a#4+nXA62lx@nS5ohUCKU&e%rM~@9IL>T=4_I>;acKJ@*@%krPZtjP)|-oSn4De7723fVge>_$CaoM6{WL*>pQhm0B7fE5JvbM6u!vASYMNFArq+h{VOgMi}Try6uNRs4g+R zzZK|7Ar_#KLbvcWXx=q!4JugEpf-8C8E8ZNW z`xdDMJ<)6-k4k8AQ#CY3QQ&g~zRepdE&cZG_??iJ*~?rRKEsb90ATqz9T5PQD#*^3 zVQ4W7b@*;(a8J5rVp1wqVb64c5<@V>MVizmUBI{QtC~sx#Ez6#i!v^2Ao_=%I z(Ks=Tw{^Hmk=6$AooUBcXvR=vQ#31Rd}TDrl*SJqP$Ku7$i5+(QWRGc@eA&!4i%_^ z2Vs;LhRSfwmXJ*t%@2YUT9D6|cvFXB{UtzcG`;CX6}&r0734z!sRk&>i8Q&681Ugc ziC?WKor+9zW6@eQjI*LZUVB6G2}cu7jrpPEMEc5HXbwO0r&wNRQ##JQA1O5y%?x_O z57jz1lTS1BlBoeV52#ypG`2L9d&Ox!_q-^s6`Fccq!yZB3gjLR?!Q4Z zmb!h7W))53hlgoC_fqL<9okkJ<)6ue;Og z01Q#g2ZLEYnPsPXV!_B(ysd=Xnf4DwTZ%xlqY0*<;00$KiXm^ux1i^7w+XsBVYU0q zwvDz95n3$D=~roZAZE0q@Va7RF@Ft83DnK5UKr(qQO!CyK7Lx>4<;poDQpV-Dq0Kc zQuHd;$mJQ+ugKiqc3|Y=+9_K&2&&DDU_-ECLpoDtQnfEKD2Ss7?q%jjaXR}2c=i6F z&`Cw|`vHP-Nr~ieCO+`~1y}M}_>LxQ{T1`xt2vK0dY_gX@+x0w+OucHVbX$ffD4hD z(<#V6lbLTyHw3V=Cb;MQcEyb}W%D6ZM6J}P65=avS9*qFiVhs931{t zR1i8YlwL_g#V08~W6m9!nRb{`!}`*jHa9W?l3In5#^2C<`t{!mm?$-=0%qpm@LwMl zM93_xbLudMCboo`I#HXhh!Z_p2m|5&!(=bF6cOZn3!&`!TEcIrQnB-WxyhfnzW$_X zS7-Cr9;(`s37=5~a2y2|AH7}o2+i>SP}zaK(??NT3Ff~rPG@#(+3CNspZ}Q9{ofhu z2ilRDESd7WPhB$mOc5mBXD~JBYFlVc=J9HP^a)1x*=%MrS1u&839I}rQ^NaSoaX(1 z=46`#JL6-Rkk0UD26gES52lhom8#iM&L2dcm)HgFulo;hf6+81;}N`I+7B4Kk8A;8 zq+XE0JD>S+xz7XN@Qof-#3@bz43P7Q%tR|~T7RHd)Rbl!0Mn^=m-jPid+8#BXOXxn zd~>J(JK+q!Tqqab@OEE5!bO7qUG-VWuL^O=QPM~4x?uq`D6$)d$|GyeSJdh6cs_%6 zHm*OHRmD03YemN=*2yPHM0wBZ!}29>LQuIh;|ljs6fhfqe1L}{Irh+;;m*YY4{wkY zE_4r^U10iB2d&TxnZ-x+=-55HGrkesIBZgmNo&_Ty#M|{p2fT&-{4lhw+9>g&nK6FEcd%8yx8HaoPY>0M1ZgFUTO3TJ!``l!Les zpdQ9h0{7O^`JR|*6Xouydr-YzdU@QR?Ar_1KSEK%O^$v@~s??sT z0#)pT!sZy0CaMix&h%Y&bZcxlp8@)E9p7p>4ep~4M}g#BAK+8PjsP zn$VNEmD)Fs+uNxR7Vm=-KycUwf4Bp1p0tu1f7&+?0_lNYhGDfTQ9QMy=G|eX02h_P zffW8D;6_sMvTIj7Wl~#>NXf~7wHX;dO#YF z(#aXADq?%FSF`u3{Cfnu3FRM^VC%PwSQSUk)^BD6}#cEgyV16Nq!;+H`n05Pl7W z9zb}s#^_rEO7|Ec&LYkHjRT6N<1z6Vdc_T@Kyhg1Q1A#eE2-x?G&^ZH_m0uK2|#rp zsMu`{>trh4G_rXTP(fs@6ALK?aE2WmHz((wZq*7dc8(1JfC@g=l)8a$EAf$|6Gco0 zQV-g}jW>miL^FiCjMVwt_qdUI1nysXjN+1(4OJfn?pCLjzXGN-MT|m#s!O&%VX|iU zl`=lkDR?aT)Mt6TuZV>S6KY2R0KnN$=uePIUz)rPy`{ADCpgEUR3RQcq5`AQTS#q2 zqqmXX!~-&ge72*PO8Z8mH-KJn(~BxZVYZ>D2lCp-NBwo@h0TxKAQuiHkjqpG2LL>6 zWE8;jQ7nUp8j?7VqUS@`%}eD2UIi|a250#lQzs*f%WQnsgK}sY8u~NP{y;N-23q0u za993ky5Bf;^%%mH7pBA#8!e%bs8Kbow20SgaaSZvPD{kZ(3*(erLJR0NU+e!>4bs&x&GC&d#vClm0 zU9fzcK&QeE+GhxE{OzvMRjwphgp62KlJdB-(a&(!~KU%%wr+f5?jyyTca69;vX^P0$G^;BAG{C<71>@p(k$ z?VVkgx#a&I0PF?eP(SnntLZ)iUjQ)IXFIj&x}!_eD~TB_`;$D_Kj4r9jnM+Gg{j+r zE}_izn+s10q~5rSmF`ZQC~hOb%g|wNq;oMMW=mQlugredywJHZf>sOj^gBvp?A6G2 z3Ys7qzDc)Gt0_3#2*r<#aF7k^y7%$svCC^a@WnC?C)+QSBuMAwBk;Lym+NeqPEeP<#(D-XckjEH{xap-*|h7o{{D45Zn*pt5-ZHRiA-t zC<>prUh*EFL}bbSHp+j(oqp4-=1feH8Mn%$B@I}|<(Z&7gA3T56rs#f>V?!X#OJOo~}(77}bC=h@I1nMznc^Q76cA&s20k)JLmw>cOuSmj_N z(lmK(CtYktH^_@sd0`KSq<&H3aGO@$3zfWftm5PCouy30MT)=kIbA!H`b+|PjN~H$6r>g+K@m(Px%DCSP zwCZa+XUpS?(hCo2V~QQnJCd)0^F0|DsBgGD96lKP`L+(ts^h|*`t}P6FaO*PIWB-Y zU7-dGz!P~xpN1@e8vXr}wDiY*R(ELq0z_c>8z0VOEX2q%WLbzsL}TN{rnxT1k!5aq zTTTHTDwSJJBNyr$jn#fKudLOu+@XMKEA|Kgv8P^6@r!^cnYt~)D*MrV?tS>snI0@c z9y6hmWaM1Ry%^3Q8-!T-2I{#ODE81eG{yz|rXxoJuS2`6>nuH!Q*lV}KLG%imjGZb zoqX~u*CIPt1pq+Y_yzm$177xc6I~+2$v1DuFfml@X$;D@1XEO?GD|>n0JY{`8|urw zWwe}|Ep(Y1@sR-&hEZYC?OW}b7YXKKF%ZqDRDRsgMD3T&4tX(`4p zrB~d`@@I$wsQofbXzJ9TLL%V?vTY(Cft2wom(^2X&J*Y(P>Kgyqm|@9idB@P? z#C z>m`-lT$mVnDItYo7@$a?CTa_P>P~mPb22wBi=-zYLT(OntL{Yi0qo~fOA}wDnJstX zk0+gKEr=DBkH0&+v4G{sN;OOB_`#Ev*hR@-bfWGnbr(za!pxFnT)yPraN4mFB+vUo zu4&%H>tmg4k_w46iL84ar2#;TC)-tk+YdP2Vst2Ke|g=}hz|}M3Q_<7%wN}$%Xzyp z{AsQ_9@smkul-zrChkf-I{cvL4`+ACn`q<5H zbiREw7J==`YJEidU}W2oYe2a8d4yoPwnlH4yVsX8ulU&X1g^YmV? zc>f@>)`nq3F+3?jnBaY8?q;!Ct5B7`uAMnygq?QK=HUq&*{5=LamD1{_D)Ev&uEQt!a4SRs6^ zm#>f9V>qM8_m8$0Mk^p<{pdFI?YlblqE9(!_3?x?TW&mXe_=3hC{)oRt6fI==Lup< zQCLS(WA(nM5xfbauBHkjb3p*G_EdD=H|Fa8SU$Z$ zzRDYHzoItR(9CW^`~)EH2xs&w9g1iTj==bo&BlW8ZmZKC4oY+3V_%>61t z%wdPa|CGN7XGYo)33MF&Q+k#T8(e#o6DZ)S0b*Ti>b4WXy)wn`1R=HROM~e@yw0?8 zzfR@)v?|Vvg9&HKm4J}&0#K~`&HjjK9=>jw{D~h?R2@V$a8vaY0GV#Ix^BYFS4LzM z&k++c&kq+eIDOCbLc+-{H+M2ES_^ox{v9_`Yxlg6hnyQ%mdy>3XkYVv;RjOkN;Y20V>Mhp5} zNQv;D+jU9#-qRYO&!;#wLP~wtYRsi|Ez#$?)ziCoR@AqQC}N-*!n&}q=;*!0dY>vD zL0k9e?$)nE=)xYP8+qu(9(^dzHU{q1-F<5LNIlGRr|-xYx4U0&fIjVf9{P_jf?Y>i+CTOZ_(#{!~K=H z3ij-F path.join(dataDir, filename); interface PlatformIdentity { - platform: "discord" | "whatsapp" | "email" | "events"; + platform: + | "discord" + | "whatsapp" + | "email" + | "events" + | "linear_key" + | "linear_email"; id: string; // Platform-specific user ID } @@ -22,7 +28,14 @@ export interface UserConfig { // Define Zod schemas for validation const PlatformIdentitySchema = z.object({ - platform: z.enum(["discord", "whatsapp", "email", "events"]), + platform: z.enum([ + "discord", + "whatsapp", + "email", + "events", + "linear_key", + "linear_email", + ]), id: z.string(), }); diff --git a/interfaces/events.ts b/interfaces/events.ts index 89db4cb..e3f281f 100644 --- a/interfaces/events.ts +++ b/interfaces/events.ts @@ -5,7 +5,8 @@ import { get_transcription } from "../tools/ask"; // Define the type for the event callback type EventCallback = ( - payload: Record + payload: Record, + awaiting?: boolean ) => void | Record | Promise | Promise>; /** @@ -74,7 +75,7 @@ class EventManager { // Execute all callbacks and collect their responses const promises = Array.from(callbacks).map(async (cb) => { try { - const result = cb(payload); + const result = cb(payload, true); if (result instanceof Promise) { return await result; } diff --git a/package.json b/package.json index a28e762..26dadf1 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@langchain/community": "^0.3.11", "@langchain/core": "^0.3.16", "@langchain/openai": "^0.3.11", + "@linear/sdk": "^37.0.0", "@nextcloud/files": "^3.8.0", "@solyarisoftware/voskjs": "^1.2.8", "@types/node-cron": "^3.0.11", diff --git a/tools/ask.ts b/tools/ask.ts index 75ef4fe..28f19af 100644 --- a/tools/ask.ts +++ b/tools/ask.ts @@ -2,11 +2,10 @@ import OpenAI from "openai"; import { saveApiUsage } from "../usage"; import axios from "axios"; import fs from "fs"; -import path from "path"; + import { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs"; import { ChatCompletion, - ChatCompletionAssistantMessageParam, ChatCompletionMessageParam, } from "openai/resources/index.mjs"; import { send_sys_log } from "../interfaces/log"; diff --git a/tools/event-prompt-augmentations.ts b/tools/event-prompt-augmentations.ts new file mode 100644 index 0000000..6631200 --- /dev/null +++ b/tools/event-prompt-augmentations.ts @@ -0,0 +1,278 @@ +// event-prompt-augmentations.ts +import { RunnableToolFunctionWithParse } from "openai/lib/RunnableFunction.mjs"; +import { get_transcription } from "./ask"; // or wherever your get_transcription function lives +import { Message } from "../interfaces/message"; +import { buildSystemPrompts } from "../assistant/system-prompts"; +import { getTools } from "."; + +/** + * The shape of data returned from any specialized event augmentation. + */ +export interface PromptAugmentationResult { + additionalSystemPrompt?: string; + updatedSystemPrompt?: string; + message?: string; + updatedTools?: RunnableToolFunctionWithParse[]; + attachedImageBase64?: string; + model: string; +} + +/** + * 1) Voice Event Augmentation + * - Possibly do transcription if an audio File is present. + * - Possibly convert an image File to base64 if present. + * - Add any extra system prompt text needed for "voice mode." + */ +async function voiceEventAugmentation( + payload: Record, + baseTools: RunnableToolFunctionWithParse[] | undefined, + contextMessage: Message +): Promise { + let attachedImageBase64: string | undefined; + + // Transcribe if there's an audio file + if (payload?.transcription && payload.transcription instanceof File) { + console.log("Transcribing audio for voice event listener."); + const file = payload.transcription; + payload.transcription = await get_transcription(file as globalThis.File); + } + + // Check for an attached image + const otherContextData = payload?.other_reference_data; + if ( + otherContextData instanceof File && + otherContextData.type.includes("image") + ) { + console.log("Got image in voice event payload; converting to base64..."); + const buffer = await otherContextData.arrayBuffer(); + attachedImageBase64 = `data:${otherContextData.type};base64,${Buffer.from( + buffer + ).toString("base64")}`; + } + + let message = ` +You are in voice trigger mode. + +The voice event that triggered this is: +Payload: ${JSON.stringify(payload, null, 2)} + +Your response must be in plain text without extra formatting or Markdown. +`; + + const systemPrompts = await buildSystemPrompts(contextMessage); + + const prompt = systemPrompts.map((p) => p.content).join("\n\n"); + + const tools = getTools( + contextMessage.author.username, + contextMessage + ) as RunnableToolFunctionWithParse[]; + + return { + updatedSystemPrompt: prompt, + message, + updatedTools: tools, + attachedImageBase64, + model: "gpt-4o", + }; +} + +/** + * 2) New Todo Note Event Augmentation + */ +async function newTodoAugmentation( + payload: Record, + baseTools: RunnableToolFunctionWithParse[] | undefined, + contextMessage: Message +): Promise { + let message = ` +You are in new todo note trigger mode. + +The user added a new todo note which triggered this event. +Payload: ${JSON.stringify(payload, null, 2)} + +Make sure to handle the user's newly added todo item. +IMPORTANT: Mark the todo as done if appropriate, etc. +`; + + let systemPrompts = await buildSystemPrompts(contextMessage); + + const prompt = systemPrompts.map((p) => p.content).join("\n\n"); + + const tools = getTools( + contextMessage.author.username, + contextMessage + ) as RunnableToolFunctionWithParse[]; + + return { + additionalSystemPrompt: prompt, + message, + updatedTools: tools, + model: "gpt-4o-mini", + }; +} + +/** + * 3) Message from a Manager Event Augmentation + */ +async function messageFromManagerAugmentation( + payload: Record, + baseTools: RunnableToolFunctionWithParse[] | undefined, + contextMessage: Message +): Promise { + const message = ` +You just got a request from a manager. + +Payload: ${JSON.stringify(payload, null, 2)} + +Handle it accordingly. +`; + const tools = getTools( + contextMessage.author.username, + contextMessage + ) as RunnableToolFunctionWithParse[]; + return { + message, + updatedTools: tools, + model: "gpt-4o-mini", + }; +} + +/** + * 4) Default/Fallback Augmentation + */ +async function defaultAugmentation( + payload: Record, + baseTools: RunnableToolFunctionWithParse[] | undefined +): Promise { + return { + updatedTools: baseTools, + attachedImageBase64: undefined, + model: "gpt-4o-mini", + }; +} + +/** + * A map/dictionary that returns specialized logic keyed by `eventId`. + * If no exact eventId match is found, we will fallback to `defaultAugmentation`. + */ +export const eventPromptAugmentations: Record< + string, + ( + payload: Record, + baseTools: RunnableToolFunctionWithParse[] | undefined, + contextMessage: Message + ) => Promise +> = { + on_voice_message: voiceEventAugmentation, + new_todo_for_anya: newTodoAugmentation, + message_from_a_manager: messageFromManagerAugmentation, + // Add more eventId-specific augmentations as needed... +}; + +/** + * Builds the final prompt and attaches any relevant tooling or attachments + * for a given event and instruction. Consolidates the "branching logic" into + * modular augmentations, removing scattered if/else from the main file. + */ +export async function buildPromptAndToolsForEvent( + eventId: string, + description: string, + payload: Record, + instruction: string, + notify: boolean, + baseTools: RunnableToolFunctionWithParse[] | undefined, + contextMessage: Message +): Promise<{ + finalPrompt: string; + message?: string; + finalTools: RunnableToolFunctionWithParse[] | undefined; + attachedImage?: string; + model?: string; +}> { + console.log(`Building prompt for event: ${eventId}`); + console.log(`Instruction: ${instruction}`); + console.log(`Payload: ${JSON.stringify(payload, null, 2)}`); + + // 1) A base system prompt shared by all "instruction" type listeners + const baseSystemPrompt = `You are an Event Handler. +You are called when an event triggers. Your task is to execute the user's instruction based on the triggered event and reply with the text to display as a notification to the user. + +**Guidelines:** + +- **Notification to User:** + - Any message you reply with will automatically be sent to the user as a notification. + - Do **not** indicate in the text that it is a notification. + +- **Using Tools:** + - You have access to the necessary tools to execute the instruction; use them as needed. + - You also have access to the \`event_manager\` tool if you need to manage events or listeners (use it only if necessary). + +- **Sending Messages:** + - **To the Current User:** + - Do **not** ask \`communication_manager\` tool. + - Simply reply with the message you want to send. + - **To Other Users:** + - Use the \`communication_manager\` tool. + - The message you reply with will still be sent to the current user as a notification. + +**Example:** + +- **Instruction:** "When you get an email from John, tell John on WhatsApp that you got the email." +- **Steps:** + 1. Use the \`communication_manager\` tool to send a message to John on WhatsApp. + 2. Reply to the current user with "I have sent a message to John on WhatsApp that you got the email." + +**Currently Triggered Event:** +- **Event ID:** ${eventId} +- **Description:** ${description} +- **Payload:** ${JSON.stringify(payload, null, 2)} +- **Will Auto Notify Creator of Listener:** ${ + notify + ? "Yes, no need to send it yourself" + : "No, you need to notify the user manually" + } +- **Instruction:** ${instruction} + +**Action Required:** +- Follow the instruction provided in the payload. +- Return the notification text based on the instruction. + +**Important Note:** +- If the event and payload do **not** match the instruction, reply with **"IGNORE"**. +`; + + // 2) Decide which augmentation function to call + let augmentationFn = eventPromptAugmentations[eventId]; + if (!augmentationFn) { + // Example: if your eventId is "message_from_xyz", handle it as a manager augmentation + if (eventId.startsWith("message_from")) { + augmentationFn = messageFromManagerAugmentation; + } else { + augmentationFn = defaultAugmentation; + } + } + + // 3) Run the specialized augmentation + const { + additionalSystemPrompt, + updatedTools, + attachedImageBase64, + updatedSystemPrompt, + model, + message, + } = await augmentationFn(payload, baseTools, contextMessage); + + // 4) Combine prompts + const finalPrompt = [baseSystemPrompt, additionalSystemPrompt] + .filter(Boolean) + .join("\n\n"); + + return { + finalPrompt: updatedSystemPrompt || finalPrompt, + finalTools: updatedTools, + attachedImage: attachedImageBase64, + model, + message, + }; +} diff --git a/tools/events.ts b/tools/events.ts index efdef62..07e8577 100644 --- a/tools/events.ts +++ b/tools/events.ts @@ -14,6 +14,7 @@ import { get_actions } from "./actions"; import { pathInDataDir, userConfigs } from "../config"; import { memory_manager_guide, memory_manager_init } from "./memory-manager"; import { buildSystemPrompts } from "../assistant/system-prompts"; +import { buildPromptAndToolsForEvent } from "./event-prompt-augmentations"; // Paths to the JSON files const LISTENERS_FILE_PATH = pathInDataDir("listeners.json"); @@ -349,13 +350,25 @@ function replacePlaceholders( }); } +// Example registry mapping eventId -> zod schema +const eventSchemaRegistry: Record> = { + // Example: + // ping: z.object({ message: z.string().optional() }), +}; + +// Generic function to get a schema for an event +export function getSchemaForEvent(eventId: string) { + return eventSchemaRegistry[eventId] || z.object({}); +} + // Function to register a listener with the eventManager function registerListener(listener: EventListener) { const { eventId, description, userId, options, tool_names, notify } = listener; const callback: EventCallback = async ( - payload: Record + payload: Record, + awaiting?: boolean ) => { const event = eventsMap.get(eventId); if (event) { @@ -388,6 +401,13 @@ function registerListener(listener: EventListener) { return; } + const schema = getSchemaForEvent(listener.eventId); + const result = schema.safeParse(payload); + if (!result.success) { + console.error("Invalid payload for event:", listener.eventId); + return; + } + if (listener.template) { // Handle static event listener with template const formattedMessage = renderTemplate(listener.template, payload); @@ -400,219 +420,73 @@ function registerListener(listener: EventListener) { return formattedMessage; // Expiry is handled via periodic cleanup } else if (listener.instruction) { - // Handle dynamic event listener with instruction and tools - const u_tool_names = Array.from( + // Combine the user-defined tool set with "event_manager" + const requiredToolNames = Array.from( new Set([...(tool_names ?? []), "event_manager"]) ); - let tools = getTools( + let baseTools = getTools( contextMessage.author.username, contextMessage ).filter( (tool) => - tool.function.name && u_tool_names?.includes(tool.function.name) + tool.function.name && requiredToolNames.includes(tool.function.name) ) as RunnableToolFunctionWithParse[] | undefined; - tools = tools?.length ? tools : undefined; - - const is_voice = listener.eventId === "on_voice_message"; - const is_new_todo_note = listener.eventId === "new_todo_for_anya"; - const is_message_from_a_manager = - listener.eventId.startsWith("message_from"); - - let attached_image: string | undefined = undefined; - - if (is_voice || is_new_todo_note || is_message_from_a_manager) { - tools = getTools( - contextMessage.author.username, + console.time("buildPromptAndToolsForEvent"); + // Now call the helper from the new file + const { finalPrompt, finalTools, attachedImage, model, message } = + await buildPromptAndToolsForEvent( + eventId, + description, + payload, + listener.instruction, + notify, + baseTools, contextMessage - ) as RunnableToolFunctionWithParse[]; - } - if (is_voice) { - const audio = ((payload as any) ?? {}).transcription; - if (audio && audio instanceof File) { - if (audio.type.includes("audio")) { - console.log("Transcribing audio for voice event listener."); - (payload as any).transcription = await get_transcription( - audio as File - ); - } - } + ); - console.log("Payload for voice event listener: ", payload); - const otherContextData = (payload as any)?.other_reference_data; + console.timeEnd("buildPromptAndToolsForEvent"); - if (otherContextData instanceof File) { - if (otherContextData.type.includes("image")) { - console.log("Got image"); - // Read the file as a buffer - const buffer = await otherContextData.arrayBuffer(); + console.log("model", model); - // Convert the buffer to a base64 string - const base64Url = `data:${ - otherContextData.type - };base64,${Buffer.from(buffer).toString("base64")}`; - - // Do something with imageObject, like sending it in a response or logging - attached_image = base64Url; - } else { - console.log("The provided file is not an image."); - } - } else { - console.log( - "No valid file provided in other_context_data.", - otherContextData?.name, - otherContextData?.type - ); - } - } - - console.log("Running ASK for event listener: ", listener.description); - - const system_prompts = - is_voice || is_new_todo_note || is_message_from_a_manager - ? await buildSystemPrompts(contextMessage) - : undefined; - - const prompt_heading = system_prompts - ? "" - : `You are an Event Handler.`; - - let prompt = `${prompt_heading} - You are called when an event triggers. Your task is to execute the user's instruction based on the triggered event and reply with the text to display as a notification to the user. - - **Guidelines:** - - - **Notification to User:** - - Any message you reply with will automatically be sent to the user as a notification. - - Do **not** indicate in the text that it is a notification. - - - **Using Tools:** - - You have access to the necessary tools to execute the instruction; use them as needed. - - You also have access to the \`event_manager\` tool if you need to manage events or listeners (use it only if necessary). - - - **Sending Messages:** - - **To the Current User:** - - Do **not** ask \`communication_manager\` tool. (if available) - - Simply reply with the message you want to send. - - **To Other Users:** - - Use the \`communication_manager\` tool. (if available) - - The message you reply with will still be sent to the current user as a notification. - - **Example:** - - - **Instruction:** "When you get an email from John, tell John on WhatsApp that you got the email." - - **Steps:** - 1. Use the \`communication_manager\` tool to send a message to John on WhatsApp. - - Use the WhatsApp ID from the payload to send the message instead of searching for the user. - 2. Reply to the current user with "I have sent a message to John on WhatsApp that you got the email." - - **Currently Triggered Event:** - - - **Event ID:** ${eventId} - - **Description:** ${description} - - **Payload:** ${JSON.stringify(payload)} - - **Will Auto Notify Creator of Listener:** ${notify ? "Yes" : "No"} - - **Instruction:** ${listener.instruction} - - **Action Required:** - - Follow the instruction provided in the payload. - - Return the notification text based on the instruction. - - **Important Note:** - - If the above event and payload does **not** match the instruction, reply with the string **"IGNORE"** to skip executing the instruction for this payload. - - `; - - const voice_prompt = `You are in voice trigger mode. - - The voice event that triggered this is: - - Event ID: ${eventId} - - Listener Description: ${description} - - Payload: ${JSON.stringify(payload)} - - Do the instruction provided in the payload of the event listener. - - Your response must be in plain text without markdown or any other formatting. - `; - - const new_todo_note_prompt = `You are in new todo note trigger mode. - - The user added a new todo note for you in your todos file which triggered this event. - - Do not remove the to anya tag from the note if its present, unless explicitly asked to do so as part of the instruction. - - Make sure to think about your process and how you want to step by step go about executing the todos. - - You can mark a todo as failed by adding "[FAILED]" at the start of end of the todo line. - - - Event ID: ${eventId} - - Payload: ${JSON.stringify(payload)} - - IMPORTANT: - PLEASE ask notes manager to mark the note as done if you have completed the task, plz send the manager the todo note and the actual path of the note. - Whatever you reply with will be sent to the user as a notification automatically. Do not use communication_manager to notify the same user. - `; - - const message_from_manager_prompt = `You just got a request from a manager. - - The manager has sent you a message which triggered this event. - - - Event ID: ${eventId} - - Payload: ${JSON.stringify(payload)} -`; - - if (system_prompts) { - prompt = `${system_prompts.map((p) => p.content).join("\n\n")}`; - } - - let promptToUse = prompt; - let seed = `${listener.id}-${eventId}`; - - if (is_voice) { - promptToUse = voice_prompt; - seed = `voice-anya-${listener.id}-${eventId}`; - } else if (is_new_todo_note) { - promptToUse = new_todo_note_prompt; - seed = `todos-from-user-${listener.id}-${eventId}`; - } else if (is_message_from_a_manager) { - promptToUse = message_from_manager_prompt; - seed = `message-from-manager-${listener.id}-${eventId}`; - } + console.log("message", message); + console.time("ask"); + // Send the final prompt to the model const response = await ask({ - model: attached_image ? "gpt-4o" : "gpt-4o-mini", - prompt: promptToUse, - image_url: attached_image ?? undefined, - seed, - tools, + model: model, + message, + prompt: finalPrompt, + image_url: attachedImage, // If there's an attached image base64 + seed: `${eventId}-${listener.id}`, + tools: finalTools, }); + console.timeEnd("ask"); - const content = response.choices[0].message.content ?? undefined; + const content = response.choices[0].message.content ?? ""; - const ignore = content?.includes("IGNORE"); - - if (ignore) { + // Check if the response is "IGNORE" + if (content.includes("IGNORE")) { console.log("Ignoring event: ", content, payload); return; } - // Send a message to the user indicating the event was triggered + // Optionally notify the user if (notify) { await contextMessage.send({ content, - flags: is_voice && !is_new_todo_note ? [4096] : undefined, + flags: !awaiting ? undefined : [4096], }); } else { console.log("Silenced Notification: ", content); } - // Handle auto-stop options + // Auto-stop if requested if (options.autoStopAfterSingleEvent) { await removeListener(listener.id, eventId); } return content; - // Expiry is handled via periodic cleanup } else { console.error( `❌ Listener "${listener.id}" has neither 'instruction' nor 'template' defined.` diff --git a/tools/index.ts b/tools/index.ts index 223b9d9..799a9c7 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -64,6 +64,7 @@ import { dockerToolManager, DockerToolManagerSchema, } from "./software-engineer"; +import { linear_manager_tool } from "./linear-manager"; // get time function const GetTimeParams = z.object({}); @@ -437,6 +438,10 @@ Try to fix any errors that are returned at least once before sending to the user `, }), }, + { + name: "ProjectManager", + tool: linear_manager_tool(context_message), + }, { name: "actionsManagerTool", tool: zodFunction({ diff --git a/tools/linear-manager.ts b/tools/linear-manager.ts new file mode 100644 index 0000000..4285f3d --- /dev/null +++ b/tools/linear-manager.ts @@ -0,0 +1,728 @@ +import { z } from "zod"; +import { zodFunction } from "."; +import { LinearClient } from "@linear/sdk"; +import { Message } from "../interfaces/message"; +import { userConfigs } from "../config"; +import { ask } from "./ask"; +import { RunnableToolFunction } from "openai/lib/RunnableFunction.mjs"; +import { memory_manager_guide, memory_manager_init } from "./memory-manager"; + +// Parameter Schemas +export const IssueParams = z.object({ + teamId: z.string(), + title: z.string(), + description: z.string().optional(), + assigneeId: z.string().optional(), + priority: z.number().optional(), + labelIds: z.array(z.string()).optional(), +}); + +export const UpdateIssueParams = z.object({ + issueId: z.string().describe("The ID of the issue to update"), + title: z.string().optional().describe("The issue title"), + description: z + .string() + .optional() + .describe("The issue description in markdown format"), + stateId: z.string().optional().describe("The team state/status of the issue"), + assigneeId: z + .string() + .optional() + .describe("The identifier of the user to assign the issue to"), + priority: z + .number() + .min(0) + .max(4) + .optional() + .describe( + "The priority of the issue. 0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low" + ), + addedLabelIds: z + .array(z.string()) + .optional() + .describe("The identifiers of the issue labels to be added to this issue"), + removedLabelIds: z + .array(z.string()) + .optional() + .describe( + "The identifiers of the issue labels to be removed from this issue" + ), + labelIds: z + .array(z.string()) + .optional() + .describe( + "The complete set of label IDs to set on the issue (replaces existing labels)" + ), + autoClosedByParentClosing: z + .boolean() + .optional() + .describe( + "Whether the issue was automatically closed because its parent issue was closed" + ), + boardOrder: z + .number() + .optional() + .describe("The position of the issue in its column on the board view"), + dueDate: z + .string() + .optional() + .describe("The date at which the issue is due (TimelessDate format)"), + parentId: z + .string() + .optional() + .describe("The identifier of the parent issue"), + projectId: z + .string() + .optional() + .describe("The project associated with the issue"), + sortOrder: z + .number() + .optional() + .describe("The position of the issue related to other issues"), + subIssueSortOrder: z + .number() + .optional() + .describe("The position of the issue in parent's sub-issue list"), + teamId: z + .string() + .optional() + .describe("The identifier of the team associated with the issue"), +}); + +export const GetIssueParams = z.object({ + issueId: z.string(), +}); + +export const SearchIssuesParams = z.object({ + query: z.string().describe("Search query string"), + teamId: z.string().optional(), + limit: z.number().max(5).describe("Number of results to return (default: 1)"), +}); + +export const ListTeamsParams = z.object({ + limit: z.number().max(20).describe("Number of teams to return (default 3)"), +}); + +export const AdvancedSearchIssuesParams = z.object({ + query: z.string().optional(), + teamId: z.string().optional(), + assigneeId: z.string().optional(), + status: z + .enum(["backlog", "todo", "in_progress", "done", "canceled"]) + .optional(), + priority: z.number().min(0).max(4).optional(), + orderBy: z + .enum(["createdAt", "updatedAt"]) + .optional() + .describe("Order by, defaults to updatedAt"), + limit: z + .number() + .max(10) + .describe("Number of results to return (default: 5)"), +}); + +export const SearchUsersParams = z.object({ + query: z.string().describe("Search query for user names"), + limit: z + .number() + .max(10) + .describe("Number of results to return (default: 5)"), +}); + +// Add new Project Parameter Schemas +export const ProjectParams = z.object({ + name: z.string().describe("The name of the project"), + teamIds: z + .array(z.string()) + .describe("The identifiers of the teams this project is associated with"), + description: z + .string() + .optional() + .describe("The description for the project"), + content: z.string().optional().describe("The project content as markdown"), + color: z.string().optional().describe("The color of the project"), + icon: z.string().optional().describe("The icon of the project"), + leadId: z.string().optional().describe("The identifier of the project lead"), + memberIds: z + .array(z.string()) + .optional() + .describe("The identifiers of the members of this project"), + priority: z + .number() + .min(0) + .max(4) + .optional() + .describe( + "The priority of the project. 0 = No priority, 1 = Urgent, 2 = High, 3 = Normal, 4 = Low" + ), + sortOrder: z + .number() + .optional() + .describe("The sort order for the project within shared views"), + prioritySortOrder: z + .number() + .optional() + .describe( + "[ALPHA] The sort order for the project within shared views, when ordered by priority" + ), + startDate: z + .string() + .optional() + .describe("The planned start date of the project (TimelessDate format)"), + targetDate: z + .string() + .optional() + .describe("The planned target date of the project (TimelessDate format)"), + statusId: z.string().optional().describe("The ID of the project status"), + state: z + .string() + .optional() + .describe("[DEPRECATED] The state of the project"), + id: z.string().optional().describe("The identifier in UUID v4 format"), + convertedFromIssueId: z + .string() + .optional() + .describe("The ID of the issue from which that project is created"), + lastAppliedTemplateId: z + .string() + .optional() + .describe("The ID of the last template applied to the project"), +}); + +export const UpdateProjectParams = z.object({ + projectId: z.string().describe("The ID of the project to update"), + name: z.string().optional(), + description: z.string().optional(), + state: z + .enum(["planned", "started", "paused", "completed", "canceled"]) + .optional(), + startDate: z.string().optional(), + targetDate: z.string().optional(), + sortOrder: z.number().optional(), + icon: z.string().optional(), +}); + +export const GetProjectParams = z.object({ + projectId: z.string(), +}); + +export const SearchProjectsParams = z.object({ + query: z.string().describe("Search query string"), + teamId: z.string().optional(), + limit: z.number().max(5).describe("Number of results to return (default: 1)"), +}); + +// Add new ListProjectsParams schema after other params +export const ListProjectsParams = z.object({ + teamId: z.string().optional().describe("Filter projects by team ID"), + limit: z + .number() + .max(20) + .describe("Number of projects to return (default: 10)"), + state: z + .enum(["planned", "started", "paused", "completed", "canceled"]) + .optional() + .describe("Filter projects by state"), +}); + +interface SimpleIssue { + id: string; + title: string; + status: string; + priority: number; + assignee?: string; + dueDate?: string; + labels?: string[]; +} + +interface SimpleTeam { + id: string; + name: string; + key: string; +} + +interface SimpleUser { + id: string; + name: string; + email: string; + displayName?: string; + avatarUrl?: string; +} + +interface SimpleProject { + id: string; + name: string; + state: string; + startDate?: string; + targetDate?: string; + description?: string; + teamIds: string[]; + priority?: number; + leadId?: string; + memberIds?: string[]; + color?: string; + icon?: string; + statusId?: string; +} + +function formatIssue(issue: any): SimpleIssue { + return { + id: issue.id, + title: issue.title, + status: issue.state?.name || "Unknown", + priority: issue.priority, + assignee: issue.assignee?.name, + dueDate: issue.dueDate, + labels: issue.labels?.nodes?.map((l: any) => l.name) || [], + }; +} + +// Add after existing formatIssue function +function formatProject(project: any): SimpleProject { + return { + id: project.id, + name: project.name, + state: project.state, + startDate: project.startDate, + targetDate: project.targetDate, + description: project.description, + teamIds: project.teams?.nodes?.map((t: any) => t.id) || [], + priority: project.priority, + leadId: project.lead?.id, + memberIds: project.members?.nodes?.map((m: any) => m.id) || [], + color: project.color, + icon: project.icon, + statusId: project.status?.id, + }; +} + +// API Functions +async function createIssue( + client: LinearClient, + params: z.infer +) { + try { + return await client.createIssue(params); + } catch (error) { + return `Error: ${error}`; + } +} + +async function updateIssue( + client: LinearClient, + params: z.infer +) { + try { + const { issueId, ...updateData } = params; + return await client.updateIssue(issueId, updateData); + } catch (error) { + return `Error: ${error}`; + } +} + +async function getIssue( + client: LinearClient, + { issueId }: z.infer +) { + try { + return await client.issue(issueId); + } catch (error) { + return `Error: ${error}`; + } +} + +async function searchIssues( + client: LinearClient, + { query, teamId, limit }: z.infer +) { + try { + const searchParams: any = { first: limit }; + if (teamId) { + searchParams.filter = { team: { id: { eq: teamId } } }; + } + + const issues = await client.issues({ + ...searchParams, + filter: { + or: [ + { title: { containsIgnoreCase: query } }, + { description: { containsIgnoreCase: query } }, + ], + }, + }); + return issues.nodes.map(formatIssue); + } catch (error) { + return `Error: ${error}`; + } +} + +async function listTeams( + client: LinearClient, + { limit }: z.infer +) { + try { + const teams = await client.teams({ first: limit }); + return teams.nodes.map( + (team): SimpleTeam => ({ + id: team.id, + name: team.name, + key: team.key, + }) + ); + } catch (error) { + return `Error: ${error}`; + } +} + +async function advancedSearchIssues( + client: LinearClient, + params: z.infer +) { + try { + const filter: any = {}; + if (params.teamId) filter.team = { id: { eq: params.teamId } }; + if (params.assigneeId) filter.assignee = { id: { eq: params.assigneeId } }; + if (params.status) filter.state = { type: { eq: params.status } }; + if (params.priority) filter.priority = { eq: params.priority }; + if (params.query) { + filter.or = [ + { title: { containsIgnoreCase: params.query } }, + { description: { containsIgnoreCase: params.query } }, + ]; + } + + const issues = await client.issues({ + first: params.limit, + filter, + orderBy: params.orderBy || ("updatedAt" as any), + }); + + return issues.nodes.map(formatIssue); + } catch (error) { + return `Error: ${error}`; + } +} + +async function searchUsers( + client: LinearClient, + { query, limit }: z.infer +) { + try { + const users = await client.users({ + filter: { + or: [ + { name: { containsIgnoreCase: query } }, + { displayName: { containsIgnoreCase: query } }, + { email: { containsIgnoreCase: query } }, + ], + }, + first: limit, + }); + + return users.nodes.map( + (user): SimpleUser => ({ + id: user.id, + name: user.name, + email: user.email, + displayName: user.displayName, + avatarUrl: user.avatarUrl, + }) + ); + } catch (error) { + return `Error: ${error}`; + } +} + +// Add new Project API Functions +async function createProject( + client: LinearClient, + params: z.infer +) { + try { + return await client.createProject(params); + } catch (error) { + return `Error: ${error}`; + } +} + +async function updateProject( + client: LinearClient, + params: z.infer +) { + try { + const { projectId, ...updateData } = params; + return await client.updateProject(projectId, updateData); + } catch (error) { + return `Error: ${error}`; + } +} + +async function getProject( + client: LinearClient, + { projectId }: z.infer +) { + try { + return await client.project(projectId); + } catch (error) { + return `Error: ${error}`; + } +} + +// Modify searchProjects function to handle empty queries +async function searchProjects( + client: LinearClient, + { query, teamId, limit }: z.infer +) { + try { + const searchParams: any = { first: limit }; + const filter: any = {}; + + if (teamId) { + filter.team = { id: { eq: teamId } }; + } + + if (query) { + filter.or = [{ name: { containsIgnoreCase: query } }]; + } + + if (Object.keys(filter).length > 0) { + searchParams.filter = filter; + } + + const projects = await client.projects(searchParams); + return projects.nodes.map(formatProject); + } catch (error) { + return `Error: ${error}`; + } +} + +// Add new listProjects function +async function listProjects( + client: LinearClient, + { teamId, limit, state }: z.infer +) { + try { + const filter: any = {}; + + if (teamId) { + filter.team = { id: { eq: teamId } }; + } + + if (state) { + filter.state = { eq: state }; + } + + const projects = await client.projects({ + first: limit, + filter: Object.keys(filter).length > 0 ? filter : undefined, + orderBy: "updatedAt" as any, + }); + + return projects.nodes.map(formatProject); + } catch (error) { + return `Error: ${error}`; + } +} + +// Main manager function +export const LinearManagerParams = z.object({ + request: z + .string() + .describe("User's request regarding Linear project management"), +}); +export type LinearManagerParams = z.infer; + +export async function linearManager( + { request }: LinearManagerParams, + context_message: Message +) { + console.log( + "Context message", + context_message.author, + context_message.getUserRoles() + ); + const userConfig = context_message.author.config; + + // console.log("User config", userConfig); + + const linearApiKey = userConfig?.identities.find( + (i) => i.platform === "linear_key" + )?.id; + + // console.log("Linear API Key", linearApiKey); + + const linearEmail = userConfig?.identities.find( + (i) => i.platform === "linear_email" + )?.id; + + if (!linearApiKey) { + return { + response: "Please configure your Linear API key to use this tool.", + }; + } + + const client = new LinearClient({ apiKey: linearApiKey }); + + const linear_tools: RunnableToolFunction[] = [ + zodFunction({ + function: (params) => createIssue(client, params), + name: "linearCreateIssue", + schema: IssueParams, + description: "Create a new issue in Linear", + }), + zodFunction({ + function: (params) => updateIssue(client, params), + name: "linearUpdateIssue", + schema: UpdateIssueParams, + description: "Update an existing issue in Linear", + }), + zodFunction({ + function: (params) => getIssue(client, params), + name: "linearGetIssue", + schema: GetIssueParams, + description: "Get details of a specific issue", + }), + zodFunction({ + function: (params) => searchUsers(client, params), + name: "linearSearchUsers", + schema: SearchUsersParams, + description: + "Search for users across the workspace by name, display name, or email. Use display name for better results.", + }), + zodFunction({ + function: (params) => searchIssues(client, params), + name: "linearSearchIssues", + schema: SearchIssuesParams, + description: + "Search for issues in Linear using a query string. Optionally filter by team and limit results.", + }), + zodFunction({ + function: (params) => listTeams(client, params), + name: "linearListTeams", + schema: ListTeamsParams, + description: "List all teams in the workspace with optional limit", + }), + zodFunction({ + function: (params) => advancedSearchIssues(client, params), + name: "linearAdvancedSearchIssues", + schema: AdvancedSearchIssuesParams, + description: + "Search for issues with advanced filters including status, assignee, and priority", + }), + zodFunction({ + function: (params) => createProject(client, params), + name: "linearCreateProject", + schema: ProjectParams, + description: "Create a new project in Linear", + }), + zodFunction({ + function: (params) => updateProject(client, params), + name: "linearUpdateProject", + schema: UpdateProjectParams, + description: "Update an existing project in Linear", + }), + zodFunction({ + function: (params) => getProject(client, params), + name: "linearGetProject", + schema: GetProjectParams, + description: "Get details of a specific project", + }), + zodFunction({ + function: (params) => searchProjects(client, params), + name: "linearSearchProjects", + schema: SearchProjectsParams, + description: + "Search for projects in Linear using a query string. Optionally filter by team and limit results.", + }), + zodFunction({ + function: (params) => listProjects(client, params), + name: "linearListProjects", + schema: ListProjectsParams, + description: + "List projects in Linear, optionally filtered by team and state. Returns most recently updated projects first.", + }), + ]; + + // fetch all labels available in each team + const teams = await client.teams({ first: 10 }); + const teamLabels = await client.issueLabels(); + + // list all the possible states of issues + const states = await client.workflowStates(); + const state_values = states.nodes.map((state) => ({ + id: state.id, + name: state.name, + })); + + // Only include teams and labels in the context if they exist + const teamsContext = + teams.nodes.length > 0 + ? `Teams:\n${teams.nodes.map((team) => ` - ${team.name}`).join("\n")}` + : ""; + + const labelsContext = + teamLabels.nodes.length > 0 + ? `All Labels:\n${teamLabels.nodes + .map((label) => ` - ${label.name} (${label.color})`) + .join("\n")}` + : ""; + + const issueStateContext = + state_values.length > 0 + ? `All Issue States:\n${state_values + .map((state) => ` - ${state.name}`) + .join("\n")}` + : ""; + + const workspaceContext = [teamsContext, labelsContext, issueStateContext] + .filter(Boolean) + .join("\n\n"); + + const response = await ask({ + model: "gpt-4o-mini", + prompt: `You are a Linear project manager. + +Your job is to understand the user's request and manage issues, teams, and projects using the available tools. + +---- +${memory_manager_guide("linear_manager", context_message.author.id)} +---- + +${ + workspaceContext + ? `Here is some more context on current linear workspace:\n${workspaceContext}` + : "" +} + +The user you are currently assisting has the following details: +- Name: ${userConfig?.name} +- Linear Email: ${linearEmail} + +When responding make sure to link the issues when returning the value. +linear issue links look like: \`https://linear.app/xcelerator/issue/XCE-205\` +Where \`XCE-205\` is the issue ID and \`xcelerator\` is the team name. + +`, + message: request, + seed: `linear-${context_message.channelId}`, + tools: linear_tools.concat( + memory_manager_init(context_message, "linear_manager") + ) as any, + }); + + return { response }; +} + +export const linear_manager_tool = (context_message: Message) => + zodFunction({ + function: (args) => linearManager(args, context_message), + name: "linear_manager", + schema: LinearManagerParams, + description: `Linear Issue Manager. + +This tool allows you to create, update, close, or assign issues in Linear. + +Provide detailed information to perform the requested action. + +Use this when user explicitly asks for Linear/project management.`, + }); diff --git a/tools/software-engineer.ts b/tools/software-engineer.ts index c125b1a..c65540b 100644 --- a/tools/software-engineer.ts +++ b/tools/software-engineer.ts @@ -49,9 +49,7 @@ export async function run_command({ wait = true, stderr = true, stdout = true, -}: RunCommandParams): Promise<{ - results: Map; -}> { +}: RunCommandParams): Promise { // Step 1: Check if the container exists and is running try { const isRunning = @@ -72,31 +70,19 @@ export async function run_command({ }` ); return { - results: new Map([ - [ - "container_creation", - { - success: false, - error: createError.stderr || createError.message, - }, - ], - ]), + results: { + error: createError.stderr || createError.message, + message: "Error creating container", + }, }; } } if (!wait) { // Return early if not waiting for command to finish + const results = { results: "Command started" }; return { - results: new Map([ - [ - "background_execution", - { - success: true, - output: "Command execution started in the background.", - }, - ], - ]), + results, }; } @@ -134,8 +120,9 @@ export async function run_command({ } // All commands executed - console.log("All commands executed."); - return { results }; + const resultsOb = Object.fromEntries(results); + console.log("All commands executed.", resultsOb); + return { results: resultsOb }; } // Tool definition for running commands in the Docker container @@ -156,102 +143,39 @@ export async function dockerToolManager( context_message: Message ): Promise<{ response: string }> { console.log("Docker Tool Manager invoked with message:", message); + const toolsPrompt = `# You are Cody. You are a software engineer, and someone who loves technology. You specialize in linux and devops, and a python expert. -Using the above skills you can help the user with absolutely anything they ask for. -Some examples include: -- Browsing the internet. -- Creating scripts for the user. -- Any automation Task. +You also love to read markdown files to know more about why some files are the way they are. -You exist inside a docker container named '${containerName}'. +With the above expertise, you can do almost anything. -The current time is: ${new Date().toLocaleString()}. +Your home directory which has all your data is the /anya directory. -## Responsibilities: -1. You have access to a docker container of image python version 3.10 (based on Debian) that you can run commands on. -2. You can install software, update configurations, or run scripts in the environment. -3. You can presonalise the environment to your liking. -4. Help the user when they ask you for something to be done. +This is your home your desktop and your playground to maintain and manage your tools and yourself. -### Container details: -- The container is always running. -- The container has a volume mounted at /anya which persists data across container restarts. -- /anya is the only directory accessible to the user. +Each directory inside /anya and /anya itself has its purpose defined in the readme.md file in its root. -## The /anya/readme.md file +Rules when interacting with /anya: +1. Make sure to follow the instructions in the readme.md file in the root of the directory that you are trying to interact with. +2. Make sure you remember that your commands are run in a docker container using the docker exec command, which means your session is not persistent between commands. So generate your commands accordingly. +3. Any doubts you can try to figure out based on the markdown doc files you have access to, and if still not clear, you can ask user for more information. -1. You can use the file at /anya/readme.md to keep track of all the changes you make to the environment. +Use the above to help the user with their request. -2. These changes can include installing new software, updating configurations, or running scripts. - -3. This file can also contain any account credentials or API keys that you saved with some description so that you know what they are for. - -4. It is important that you keep /anya/readme.md updated as to not repeat yourself, the /anya/readme.md acts as your memory. - -The current data from /anya/readme.md is: +Current files in /anya are: \`\`\` -${await $`cat /anya/readme.md`} +${await $`ls -l /anya`} \`\`\` -You can also use /anya/memories/ directory to store even more specific information incase the /anya/readme.md file gets too big. - -Current /anya/memories/ directory contents (tree /anya/memories/ command output): +Current file structure in /anya is: \`\`\` -${await $`tree /anya/memories/`} -\`\`\` - -You can also save scripts in /anya/scripts/ directory and run them when needed. - -Current /anya/scripts/ directory contents (ls /anya/scripts/ command output): -\`\`\` -${await $`ls /anya/scripts/`} -\`\`\` -This directory can contain both python or any language script based on your preference. - -When you create a script in /anya/scripts/ directory you also should create a similarly named file prefixed with instruction_ that explains how to run the script. - -This will help you run older scripts. - -Each script you make should accept parameters as cli args and output the result to stdout, for at least the basic scripts to do one off tasks like youtube video downloads or getting transcripts etc. - -If a script does not run or output as expected, consider this as an error and try to update and fix the script. - -You can also keep all your python dependencies in a virtual env inside /anya/scripts/venv/ directory. - -You can also use the /anya/media/ dir to store media files, You can arrange them in sub folders you create as needed. - -Current /anya/media/ directory contents (ls /anya/media/ command output): -\`\`\` -${await $`ls /anya/media/`} -\`\`\` - - -Example flow: -User: plz let me download this youtube video https://youtube.com/video -What you need to do: -1. Look at the /anya/scripts/ data if there is a script to download youtube videos. -2. If there is no script, create a new script to download youtube videos while taking the param as the youtube url and the output file path and save it in /anya/scripts/ directory and also create a instruction_download_youtube_video.md file. -3. look at the instruction_download_youtube_video.md file to see how to run that script. -4. Run the script with relavent params. -5. Update the /anya/readme.md file with the changes you had to make to the environment like installing dependencies or creating new scripts. -6. Reply with the file path of the youtube video, and anything else you want. - -Example flow 2: -User: give me a transcript of this youtube video https://youtube.com/video -What you need to do: -1. Look at the /anya/scripts/ data if there is a script to get transcripts from youtube videos. -2. If there is no script, create a new script to get transcripts from youtube videos while taking the param as the youtube url and the output file path and save it in /anya/scripts/ directory and also create a instruction_get_youtube_transcript.md file. -3. look at the instruction_get_youtube_transcript.md file to see how to run that script. -4. Run the script with relavent params. -5. Return the transcript to the user. -6. Update the /anya/readme.md file with the changes you had to make to the environment like installing dependencies or creating new scripts, if the script was already present you can skip this step. - -You can also leave notes for yourself in the same file for future reference of changes you make to your environment. +${await $`tree /anya -L 2`} +\`\`\ `; // Load tools for memory manager and Docker command execution