From fcf954573618201e1a87ebf6d2987b0ef0e1d587 Mon Sep 17 00:00:00 2001 From: XYSK-lilong007 <267018309+XYSK-lilong007@users.noreply.github.com> Date: Thu, 12 Mar 2026 01:21:44 +0800 Subject: [PATCH 001/113] fix: use native textarea scrolling for raw config editor --- .../src/components/config/raw-json-panel.tsx | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/web/frontend/src/components/config/raw-json-panel.tsx b/web/frontend/src/components/config/raw-json-panel.tsx index f67bd89f5..5f195f744 100644 --- a/web/frontend/src/components/config/raw-json-panel.tsx +++ b/web/frontend/src/components/config/raw-json-panel.tsx @@ -22,7 +22,6 @@ import { CardHeader, CardTitle, } from "@/components/ui/card" -import { ScrollArea } from "@/components/ui/scroll-area" import { Textarea } from "@/components/ui/textarea" export function RawJsonPanel() { @@ -142,17 +141,15 @@ export function RawJsonPanel() { )}
+
+
+
+
+
+
+
+
+
+
+
+-&*JZkde2SK(!ln$kfl}N;BJ`JBlo0#2Jn!kD9>0%8bJ@>uE(9! z^xMw9V3_6=$Sna&T0B6F704v+ogu&(@{^kNlSVB4Sd% gR3jL=E3*u|T_ca0^c)$c0i*y^ PYn?^4SB@`Crr&v=(E?Sz7teTGbUQM4f_= zNx=KAJr#Fd> z(izdUm^+(dYv`@NBwx=n2uLA{T5Oo39PHxgo)`OO_b5t2HW_8)S4x{V41-J_^EC~3 zq$MWJtS~g{$3TyqjCBAkV~e8zatW OA zBV^bgZ{|sVg;nR9;Cx#gJ81s-_5hlRWb|W70N`?TgLUg8K2@O{28*;F*eat)rUx3r ztzuH SR+7 zfSlg#kbCY`YfP7Eg0!#%X}02VnP|B{8{HK0r&s4Fii=`G9MeP6*iSF44R2aOSA*VZ z#m7P!oi%?~C>#(dt%Y!ZHQSLgDOb3;PNgw+yYNpMq?Gs&=I&b|{x$){e|T7os<#9m z1849NVA{`N8ei9->#oSxoX@uuZsS95mJz$) B&>nER&CSm7jYo81IaIh X^~2lATOoWQtV%aKy m2Pn*kJ$-|KVUqzTdfRJ;TfqaJ@IXlE*H8RcS3`d` zht3-1!_Mj9$*FU-)_?wRyuWGdoIm#5)q?cVbXD{sb7)yRXkQ4s*1&4WWf`{>^oUE_ zWBpg#bFFQV)c)i)=3XSHNl!HT`nJDug0@5276N8qjJqkxdOp_wE-YoJdiyU=-O-|> zuNlIDy>Ji&3GTgb!L|PMHBocJcZ==NVDzhuDR?#NrRD90jf`Fv&U|?SP*`0q(mutg z0n(~{v9>noI`8LK-X{73`DHQfdCqmE#eRX=dUZ>s-|o($=IR0) zH&pQ-m0Rev2`m(-9{5lN(hY;9(^ii;7{=X>b4*z|mcjmuxJ%!qsV}UBe9jMO-fJFs zP9ym9UwY2m*P(^>j@_9VmAAE?l9_>9Q2Y(y{pd>g_yKFG4r{1WQv!eQK_n@<1NlF$ zb0}Ja(|Yj>x2h%j^`lBk%}lHP%=(bfcGtNN556+Typ+f~7ORqnJ?-c`1(7B?LLu-F zHCXD(kU*yF<8t1Zn$+`0HrO6rfrl84931hs%1r&=@B-oM$5V!uv$=1$KU{ELdP&8A z@Wl?{Om*6{me?h%2T&tosN;O@1C<;w3O}9bh~4I2rQIq26T+Z``}M@GjRg9UiBHUR zqrkL=o6Uuqbzq-Q*ZXxp6U7(ecLq#z_T!`3wYg{tC14ZS^OMx4t^a(mJ~7_#dsvR% z9K;U`&!DuEHeSqmw~=jB0=eSaE8XMGl%HA-aw7WogOHP;N-npo@Bt#yA(kVSKh^tA z&phIqS-qr?bqeBP!&?*RaiWlEr%PxC>6#E>BreKU%|t;5d5olM7Gj*VG>LBf?)RxZ zl?tnO;08-7EUO)aq8iM9i-rvWrlR` 2 XeyMrX*`cA`NNd1kcODxA9IgLK^NT zJVc&0$xYwgFViFLnB@>MM1kt>8YMRDgxJ~}N)4}LKBw~^el6$4w71!NIX)y41vUuN zP%h~YEDP}AtJw}%mz$2`Ii`v;va%f~ItFv=x|Mz$qmUPr1rq1%t_Y&)BAmD}Ish+g z#rvazXaim}G!a3%;L^1E_A>!tRX+trcz>l Kqb7$<9;t{C$X!BD29C%3e zTkj4Um-eaeP}(c2Gy)eX(z|T$sS?Q}Hh+6>w HPv~l7-&y$$zJi)|^H@zFvZ2@HdczY*mZBeb;)8XEdZxQc<(X GxBWS z4|$-)R4V&Dfs{J(v>S2NpnLs^dZvHxX1a`@kM~R`_~W`!?V=tnHGOnjhX216P1IiL zHzLh(JETO*2^M|xW&;4q*$3-#(@?8^`tA`P=2HGBK|1)|jUq*Qo$(%&V`4a4Aj{KB zhw&+1w4oKAzB3s$-g>~Qg Fq~h?8gqQXg~=k# z8dK=ipPSiA`xwBT#QS*YrQy5byX6^c%JRH`pL6eo=K#ZPvDy&XP44!uA`T1CS+H zU0S9+1$Ezeo<-8Xk>wLV_hUm!CAFxdwL44peZ-l hD?>dXZg1<@j-%u|=d`rPE6WeW*67GU^`k=qE58cyIX;29v!O3n)k@e65xC zWI+`2NrOpe!b7e1*jj!=0g$obc$} uWvQAc{|nWW0nDn>8`K6EO3FnG4v;Fn|w(Tt(_PeA|ek%$Hu21DNb^RRSO?Kc?J zV*V6)x-ec@h>Lt9PfG~zE@OdNH`IOs6Ac{|GceklZ;}!NY)ofqSk}8$yg)6yv1X0W z7Ro^oMa>u-JsaD|!MW&*91NT}MH3Mx{ob9z8sM__S?h!B--sWTn=u%(@^|BP7Lj|M z;sHO)D_Gm&fe$Ht4XF^~5tbhHRg>S@DKy`Xr?_;*3qevKZv-Uj-zw(@ (hq-5 z_WaGYmIppS+4AJvBN}=kn$(2;&wOo!`_k?fnHHQ3PUXme;VIRKxA!irARag4gh}#S zU%Q{#dpi@dCA8L`)rAaMZH;!v&r~Z}6WFZs)|9djQkE `Y-TYnzmEE!-u0WKGI+APrSEfof*gt51iM_8JG^o zhm2!a&SG!Hr62 CtBgK= z*IM-$oQza1Q@<6D;YXNXU|Hwyg(K-#&G TmW|TIB?y_5i^w}NO!k);3fdz%= z^^mBwpQGH6Ss60?swqPZMy}DsjipC_4o=)NNs^-X-rSR&jyX*ZO6-Rrk8otw5S&iu zF|>yvydqsaSf32i82dZrzXmm00&@)lZO_PC eAWMW7khM$3$IFNpvxAnuxGH|=l5yYa1U`i3l(&*OF0W6u{ zj)NxiF!9LAGb27Q RezNQnO$T!TKKsU(}N_H!HK!!#CBamgLZENp9PZkSQX$A3am&7-l_J{cQQ>t&yHb z-&&*$e}kbXTv$?Fc{}n)+wQt1-RL-_J9cVR4I9*=ruc&l(eF1w*FVD|bERrSWimq3 zw7g1O);@X(PCrleKb|)YQrxf9P+D!TDdE0G7LlY#La84ItWd(I?5bJ->yv;m6K|sN znrB6_RQ;Ntdg8^2#8yip*6mcyXGV%564O{SLU~g Y%{hxQ<|q+@cB&)5y=(ONl_vaRswh?YtFcZRFPU!59`S|XVag}3|h4lK&XLB z;H~KR#wr}i(4bu{s|*+%I>c{06xwX!26$d%ke0dsA7CBav~dHiWR-z+9_xJ|@Jhc^ zKPb&y3y9c~W|=rq5Uu_h@NoMb6U3)@ie!B!sTps(`)iaiBst>jb_O0m1ij=Mu }KRz(J9uFb2K z7;=ITkRG Rjc%apb8K(4!t=o04V?=|){nY69r>srJdD+@#nFNMSK((< z5mst-aqY#mwBT6HNE<1Nq9-{ #&aY$DS8#30T6e0%!x+>mYRt$!0=j8al417 z?l|%Mq4OESRr-FUHQ*$SH(Jhyxb~UiOMC~&`A0d<-*qJJ12&s*;i*Y@g2!{A8~8(6 zF?!WJhMw7x>bF>Rc4_zp*Y5vWDWFcDg&$9vNUIxfqB}SzYEFFadI*ggJe4r}d!`?@ z?Cr99^j VMpP=Z;N-Bd?x>KPYXdh(E|-@{d1qXKk *g!GLhCF*^lh78Q4Nhhem}`(d|U8;)uMRBkuP?M zk8I$RI`OwNM+7*~&iRzmMs7w=R9Cx6lksU&@6}tyC{gO+?r2AqOUH<29)j&`VLtK# zCq(t)q!}eu+(qUvGs59)qa{tdM$`)G4^@ tZvC5 zT9GY0z&zn$X3ABtp~fOd9aTK|q3QdQ;O=Euf|_7-@%NurCL>2X^`B$}m~MIuqAp+5 zbm?0*c{oB#(;-$03M>f|)&j=kgFv|Tqi$EOLqacjGURX1`*T#8f5|3w`4Qsf%Vtrv zmOT1xXFd%p)!%x%u#_<%gXwz~ Ea;n;ELKgVINZoP`JR(0IOW;#D zNjw6${?wkro}j4chs8~)c6n^({U5k;df c29=%DzzT)voz5_pX!PqgB}g+Y?K^D%UWP%Di!>PSrb$oTMgQg zbfJ^tUZyg;8o)lVyg*Nk?L>m;I&=C|@sYx5`PWm7gF-vbPlBj@KFQ(-sA21MIEUU3 zQ-~*BIH)rgFh2nb>MjT@9UMKICFYGuxRH#)qYHCpC&&yc3pK zdk5A4WUBy^mzLiEtZYE?+l~fJZq8-eaho|cj*mZ>w*6Mb3!*@%Z(y&7vL|b}r1xY1 z8AEn!>vFnSBoUpBS4BE;S9kdU>B(mvAQ{&O>XcxttHulCd!Qip^g%s6D4dfweC^Z9 zn Dri(+H>prj>=Wmba=#ixHEb=sSNB8ri}X*3FB!|3~U zmjR)KT92+;E+;Q!HP_mt>X>0T1f;3MU?X**!zFJ8N?+4|7dP&$PdXb<2A+RCV3#T7 z&}W$TqF@6921TIe8S?5-1(+FE@t 03bESloO?bD)JEWg0^?<1(=-$$q2VH|HFXzci{_bw z7}U|g!IEO{uk&Aj7Jxd~K{WHFEaPCI{btarN@h6`LlBuxesUrXTB2BA)c7jwsNV?! zGx~FR nT;%c5Bw4HC~U>Z>`7C1GipHOGnpUAj)6jHMjQSlUzTeT=(hrN#v%fK%r0v z%MnMBeIw`%PxLqrDvORrkovw9;9Mr#MLSRe!MCa$8|qkqzyA6oOQDzeq6aQ=yH5N& z%i2H5p|F*<4@-4yO3t3GX_@WloJgX2DLZEKIaKTp!qedLX~^aEY;DtGdWl?YRFe(I z9xrU{I_Z6?YSnZ&I!LwbI0)`LQ7`0tCo%fasf->s$21m@@h0!9GoW2%18a}+W?Vz9 zyX%W&Py`>u_np0gEr>D3GPQWGjg0gD--2dtSS>@&ci2XH|2dH?m24ZEL?_K3V#Ntb zu;7s??97>d?LhM>FfxPhx(+=FGJiB<8t3Wr1H~?R_c%o-GvEnSnivaL$$T<$f^9_E zf9pZAD2Gpi_W#?_(p9D-Yp${Vp}0*-n5Z_2ldr-IMu~#~q`M$wL)kyg_RBk~CV)Gw z8`MQYUgckC@7u+n`^p}~D`h!|KO1^n;H$O1{tCyOz9<$5mulQI`2TRz7ubO_jDsyb zG?7bwOqv<&e2>75Nzr!0Kh)o|U0m&nY?T*dc@NeS#HBaRLt-jvZQ6$YXLf<;y*{wi z{gvZ+1@gN2+!viF{}&tU(M^A#=_;kT)n{%!Vix}}df9_s04-@VAH{=ixd02ggA^iT zwF_#p<+XYHGH}e%#i3txkGq4Se*RG9hE2FrI^H?}p*5T{l(aoEryI{ex6DgVCUloS zR{fE=2`o_avi?#PR*ikL@7A#XzG#X1zr+_f`I3{Jrs$fQ0<=OZDx-;LEkwk?QOo0| zW=@GR8wRHIVv;AHw~AmICML=!@x{4HeV}J;l~_<43Eu!sK(fC$Q2V*zZA0YRt6seT z8r+dNW#9N-EjE{J*_=IsOtc-6fAHqdxD^e@gyDDg_)SWhky2uV^FjYECtNdt>FTEV zpx@BzUI}w->FoLdQF+Wd>7D^?i9Gt@>a?_{y?!+m(u7NvW_@F!6yF0l()UO8sWsAR zzNr_->5vi>L0z4n9b0Fdp4zpPz4TD{Ut+L_A*_pE&LUxf6UiZ#j;LAPxvDu#VX?+Z zsK)3}x+2I^I8WBs^0eW4ZmWT)5_av+}BF71=3PF)5# zaMa-&gU!ItRbP%B^1}DHG{sNeJmEXnl_^8WAD_BUi~FkOCx;fl;BEqT%mj^{Fy5vN z7@^xestiBI+xev!HNi>|0+jYQHwU9^TD0{RxC9_|5+r`m^?V+EGhLUExrC!<41$Jr z5Yr6fv{%Im%7LNy3C#%Y?K$qGU XswiptIYgSwmDh7Smyy3UUW-(z3O2TFzdJ?&XZDkRv2n? zZYWj?bx}0zsk&mJgUlAt15Rw+G)r|3rq0Deldyb4MwFh!Cd6QM2NeUXS-lp{x-=vb z`{mLqo0~@0PYg5C{5%rN-Vej??6f=Fma3uHpyw}UyodF{pSJdxl8GlY9d?LF<|km@ zWYR1!iS7SmL;{k!M+-Dm<#H2$S%zv6b6YNFu$xFt{kNhS3LF+3)9ww_2XHxfjzw`% z4SuH)Ko2iq4U-kT(m~=HK&TQ&ad~j+&Hz9Gm_XP%xGDW#4rHgBf_xI4U3wh$;=*Fq zSX_}#`k%6QmE(vyuMEpI-D+%Fo$yl#waVi>TM_Ad-tz!Ioq;)7t3ND#zn5PD1TlX3 zo`eGR^*PFmEmPi!r>l8J|L9`HTf*(SXI<4z A{56hsn zcgkh apc>gf9x{tEbS)iNo<{XtD*PdcxKBiy={RH6H54Lk)izDkPXx`xC zkwnhw2QhAAQh3hml9Tc%M)8J7SlAjlm!7ZJ9J^fxIh;=Y-iuxymBx5L{`|uIhHbDC zpN>0w^M3W?tAgG>l~;rVslj0^xN4Gxc%YeJ62`)XQFh+xdCG}^1nf45$ukQo-UTNO z{6pCSl8}YRtfDQ&UG=z@hiO)`$t%Ml-*nG=@-h%A?!aoT8Z+fZ4wG5^Bo@O$2DB|t z09UJ(jL-^5g@x||eV|I?F XNp!v_!m7P58*k%xH*}Bg z$Km|gzGo= v^uXcM73fBt`F{+`e%1YxX_3J66(TAd= zqqyg-^c))0izQ6K-zSmuY13X3!9=FNK$jrFXE^bzv+!4Hr$VM!%eFxy=qFzIjTk8L$(Nf{dVpKQaR-Gmv zc%o`QNkSk5XQ>g$xCeu9W3aH%6Sch&(jb; zv$At0Wp82f6C7xUQ>RIp+jIPlsk{gDxR=1M#g%LAPV-Mo--w_0cw;rEImlH=JC6(~ zh(DZFyGiLAJg!BdPZh0$%mBh-XZA%othoMK!ok+vU`rXUuBDWp^5qBvo(nCf%YWL$ zc$8@!c*u^&m3y<&BX{hr6Raj+Gcu#)>)`o%a{^@F5rU!&+iD5h;R4RSkADX$YK|uX ze7^wAnTJ3RFjqkyx!J+OQbJY{$rWGG(uBgeX$$_Ya=H4PJ`rQYB-Y!cv0&{qJBUAp z9&10Sal`g~Jct>3o~&imnq+s%y0+Z$v*f-&6WmRBrTa~voz~JSTn~MN%U54Bi!6Ua zlICHh%z#WdU0?QNPFHthu2?=fB}^hZE&7Y?%|q?z6b?M*zQ&y{HDS_G6{>}0VhpaY z=#)LX394!@-yOsfNr%1zUX@sC?$F;Bk|Zj@66MG`i6Ekso^{Srltfz}k#lfqoKu;B zNwsFaP4t%~tiFZ`f~wYHlP D*4fa36Mrb zv&^pC7Z#Xrq@?}HjHF61>Yw*reL<$#k?>HTJ$VVeNgX7Jjpm!msZF(`>ds+gw}4wB z gn?8Z2`)Mpwz_03YD2Z)W> z1IbopEq&?0Uyu->g?m^k?)*I&9pSuEu@qVhZGB&x<7K+QY5A2%yk8$jgvg{IK&Re} z6Nz$W^JtkVa`M_B802T&1zQ{~Fq3AF5azZLC^|eF2K-*AIj^OEnYP|VbLE|=zU_uE zgYYwKMAv@{@@t3&PDhdv@nMGX8jK2gssta24f)ttN=3u{CRWtg(5&~Num9N(mf+LA zF(-34xHZK~Pt$0zv(%)9&UAl^++2IN5uJyMJ cBIIuMV<>V=|2MZptG*mk z)TuCK>@dsZ9)WVv6oAWdZ=szdngcYuG~49Lw};Gyr%INTyrpTbWM9F28&aqb74WBs zrJ_OTS|3?eYiWmXwhAPuI;VyEk0_kzUZ-kF$3yUjs}r1trqjJ8ElCJX_m*iC10M^~ zW~u}gSXy~q>Mrg3UT^TSEx1s*Fz4>$SLzf~$~4-hkS HjJ0}K!;Uf(Oa0Trygzqx{A(uDcqB@#v8L8 (HCnj&6eFw|p zgGI2ig9qR{CX+(Y2o)Pvg=;nbw$_OrGZtcN_s4jQWxI|psFAHlLH@aMd~tEg^*H+F zJVgOL{;P{StJDjb5$@c^;=tY{#{Ni0vyW-SS(Rfhq *Q8Tsu9*G ==RKi^W+NcK(l;;^FClvb(e1byL%6or_VgaXYfS1GI4` z`6@i{bCgNPFO4&(f0f!Xax(I6ql_agPTNUT5d+%pyl6un@|#$#v2b6P9KFYT`gAbt zk)HQrayd9w0z#e+uXqa?r1qQ)?m{5TFq}-SNrtd})4O^{HFoa+d{M{GS}YxsG@n5B zJk&BELEihUurAJ$wp|;S0n=iuVElj0#scyExB%(TX3vnQe8xS^B@?*q isTEH&;Uin)r)nH6$_7%i&^tld zr*2Cu--tg+aTi5paZ9tBD>|4!b@gOGo)Y0bgmNz+zh`RvfSc+mWMX|4u7kZL `xh7DO)=&O%57^>MRkZ|sZ&!Jyz$zw1 zxL3 04X&w+>2p85o$O8h2=o%=m}*J(c}Ctt7HBJ40yUMjLjtt!J^oiX->Z{RJ0*< z#=!DanPwn9dx##L=NFjfcxL;uJ8}EpOEvCF;0UPB*5L08J$|Mpx+TQkz!cW7ulM4g ztz*2pT>wR`B8{yydnKKiqc;~TFbE_aWqBD=s~U1b>i+Xk^2x6GV0QNt|FUtcJL#hU z-Fli0&1oEt;inBBda~ekfS_HOPw^hRDlqxw=D(N;ow2#H|ADrEb20ncj!{V8#VT_G zlSppGv#m6^^pi~z69y&E@2BT0mgF8Ys^4TL#+)P)JGoFyfk~AZ7n+JEib4~=XkuhE z$BOkFquJ^1gu+~)^ipZ?e{+ 1z64a9od#+$e``)d;!;0ub&UP zl;0F@n2kB! aV4XGPdRgk(;bp0s$usO-GXMuEy}%^`(RB=8o laYZygqh3%V@Cns7_omQ*wqpb-R5nrwYrP#-f;E53jjxdngRi*eWZ6m zw0yKwa#P80VBOof0`(ni9Lclq!Hg5thz#>qBoz3_y;nK1A1G&7D> Ef)%ln&!t)8MT9ifa--1KLs9J zIvfsd*J9f6C_X<>YDNi}mvafiBU&c%c$>3{TG6lH5vl+~h#MO~#|h P+j@$=wp2Jd?eP^&!h(zI)>21NR*D)=RYU^>z1JS9U7 zpDYM#pxk=ZZ@7 @CwXTAdYgD DFfaxh0lg)0pVO5ohv3!q(uz|{0q>91<& AfTS2 zIDZXuo)dSOBUc$tQKA6ScjvIgtYjQZ(Zb=fA2T-yPW)J{3=8qpuXW9C{#t)2Z~TpB z67uA2M=8}cUGn(zp67wvR?i{yW>G?V$dUH~iiNiL6~H? Sqq*h9D%+`N7oOUF zevtv;KpIEQUm{Y2D~vgMSK_mGs*@cdC%17R+YT@i*9%hCXU&m|2=zNGqgpVT9%|IO zvP;C7zd4}~y;h1^a-xxFOQ0I|jn8HW4DPy!+piv_**Bv=-E*%bw2 zxFoB%(q5{Q6eMDZm5es25Bz<|vk)@$T)$Jk@6_k?s Yk)gpf3l)A84WkgZ@UutyICD@{k3iL51`H&ZDQnswu1 zM%wTX!;E6{i7hRiN>|f=UN^Y-8Q;Z)GRN43%6^@+r0lG{O}Ql;5n{D8OIPzxMcK9w zr$ozajqe|GAxxUk82_UHk;PkMCtl3~K6>M$8=Uui=k` 7- JXs9H% zumH{3VF7O>>1p%*D_V+aT%#(M<(nj@BdfQtMTJvQ&A%1D4(T#@v~b*39IGsXb~;hx zetA(i$b?u*n|PllaLEl2NCx!R-unHSW9*>!aLDMlA6**-x-%<3Y3#3rH!Dr`zw8i9 zq`r-44qz=Wr!Y=S3ZK3WJ &AF!M zh=d-8R5TmKD3#W`^m 00_YB8+P^8O& hTK`>)cE~?=DSDt>GY9&HaP+d&u$UkaO;o*MFh^%K8yTs?Nz*c1-srvT7*` zOR`>WLO#W7CPxi^Sa3;5CK!>Sr{49A nLV6G1 zN<;(~uyXPhP1>hUou{)GE)Z=Xc)1-GkvUWn05Z5DOr0YrW}nzp8^5j+Yu)BY7`R8> z?=4-3Ciljp&`U;us-a}qC?$A292BG41alHQs$u2~Cv~iF++)sj$y{b47U|@1EEU*5 zGC>w>N+l2Vh2K?N^1}^u%UWEY4F^YT?(y8sP5 0R;hf#%# ziV`xhvi14(7dXxX7ey?am3<|R`WUSr&Ca|sB}t~DUT=3Z6{}bHfGaYX>tv4C^*LCV zgpSz*)ny1%)x{(AX>`q|)#y<9{Kq?@q+WVK^fBGhVoohz%`5((ZA_h!Q3^ZBUoMHI z?MBhF7j|ib_-n40KTLV!ZS)3BWu=ITSZ*ox%dBafkW!^?TG3_ooW^=A =bKt9?}!Qm4o(qMqL}-7=`%5wXsqhrgPB9g%K?+s1GN|Mn~|@vZKbMg zh|d4Jxx?hvocA`BOhRQ|Sgtl)W;r{Ov*#f@d}BMxgqj)&BY`6jCYpQbKD4s;IbN#V zUlbe!!}B~`y?&3GZQ~gr)>&=jVYYoh6GnVlgN8-i97ZEBy{0 E}1o zHmLPwQp228v6-wIF34;W1&v|8Q~~gP7jrSTE1O>{UgL`ZGfodgc+MX*( nHRS z;Qe!Syq75IJnO>zt9WC0Wg>iJQ&5C&;0Nv1n H!%tEnu;dBnx~SEbFOZ;pxIz*M+|yPGuck|f3F(uB`b4Tw|VAnqf1IzamN zbk5UU2?s8bFj2=svwTPZ1q>*ULhDHymnm(np}~6& 4NLTFno<=PFzuCRqQ;gr%h07v*DdV^cvC zb1J=1r4th)zp>T+x)7rnxc#@GVPRM?Vx&lxgOmAJC3>RmbOVXQ$VBEMHo)-*Ge>Zk z06VLEJZ;edYSuG|ugyg0-`yW>+cFc^2r^0Qs+CO&&6MDgLUm`3JQ;sVrXM+=B|?0& z!aGtz?Y2I2ivc+E%5cYYuC;iuy-x^IQnBQWSdlr(tRvZ2uE>E$u5Pr1x!Zww;uLn4 z)T$JYz{4K YNr91OwC= z6T(u1xyxk~J?Fn-X#FkhS?Bt|5yiQWU>%UZg3Rz>D4t_}wcHet=Y9-1^JaCPTp;Wx zfEacqy0)`^AYPdowrlu7u073PxBU8h6C(0_0+8-#wRgAjFWW=|9_E!^K9GUxIn*y; zOS=#mDwp{Al3%Zr5(xA$9GRTS;4ksAn>KdRLwfF^sD8*-i?;#AET}m$9|Oa~otRXv z75wH8m3&PYFOKR#4Rg^@q$Vq|yJ>xNzSOl-9WN$p^*wsM!|egd(!N=4bxW%l4jR?r zAsR=vmDr_xU28lPC|jBwiBosHm`JLq;cX1R*ozBtR|HWr-y`bBa=qnf{vM~eTxcZ2 z$a)YTGysKIN4}oDnyK`gI)B7&fR04nXGY3x4q!i*%Ch628;YB}=tO9>Af0vpASUyy zi{A6jL(>Oy^F1kz=8)2V!z??($za45T!Fx^OX$S6)}+UdC0>GIHndQ7iRvdUwMXd< zs(w<5wcTM13RP8maad4To=3IJu_{QC4O@y2#txe+X M){3dc?G3@cT%#to#;;5w0;Jtlkgp~o)Qz?j zH^r`rJb0)!RbY5^&9Gp!I>WdESjbt|Mt9Dgk8(Opn*Mw{1@Y)#yFE_$tj(Nldxouo z*f*i-$k#mjWuYeyBnabXxG~yZ+?#)!Tz8(rLUGJ=hvQkGf1`U!S%EeNn22xRQ*@lk zjFWLb>Dy2 PA1_q*KK{p;n1cj@8Y~&M$;gE1}7n2 zUVA3DHh;c~G!p*C=gXxJUbd${N9#nw`2@kE7lZAoO{4STyHt$;0e#s4Etq65%&=k- z{{BWWv6!bgK4I9j3}FR*$#k?bagoO}i=UGLjV-i;+N4LCT|AGH4Gjvxo4~L^t=MJ> z`_X_><=zUGl4(bLW2KVCr>lh3QLFz5tLNO=yE_zH3CcH0_V?e(#@fA^?_o5%s^R_V z7jAqrEh5~4h%o*yNAhG;-R~&1w1ZB 8WJzG2`^G@*H0%LKU2+SS@?7kFrs`NOfwm_3vOnP3T2cKqJCUAVf zO!NMPkyW6Xs})pL_a;*MO4*2Pp)r6w-~@ytyYwXZ%6$n)m!Wo-`8#RIupyP_e$CAF zgE7m=Lqf&6J`I9TGgRtFF{M6xYpD>4?v=aJmY$vAB#4FAei9%K*NRfpbm?DDSUmdl zDbt1i+g7szpXL?Ea*$gn?|@ep#ECubjb0c0e(rlZY`6|@WtAIA68iaTK(4Q7o??;l z(+Z)1e&!DhevHaHI?5=3(HKbdfDw# 1x~Fm7IkaRlrijQOL_}_b0sh&&ewx zoYicaND a zb^bsqi#I}I{Zdb;txvB4Q)KX7b~TRamtJ#WT*h)@X`(VNgLl;?=$(!!QK6zY*0 e`_*YbmbJcDFQVIVYV z$^JTw1hC$`vzWPkzJpDMKhK0rbYcfk0gmMeiDQc?`N!70Kxma^c+^tt3Kkj`q?w!~ z33>xnl}^0fQdBDDS$sdkFs%KY2=Ee{y2M{y-J1W=F*Rsx(<8U=#eVFFuh>h_YCnrW z;(ncBqNd?=ju sX6tOsc*qXXpHX4eGuc82MLF< xLyuh)|}9tN?O-K~(} zJnffgUNN?em`Y3LMPv^0<{@e0;RrMQw=dAQUN9Zl Walts zgduG^2A7~Z%|MJn41}W=l={KYhy(@~u2qrTMKyzN!(?b}-C6>=oeUiMGKun8pJU!k zHK>&EE|1ih`r+nj`|oEUoDNFM!Qv$d(7(-YZ-eukYU}jsRjPYgt4HH {>PJ2Ravtc$FCBxXsJ|zpY8<%q+hi zN~MM9tWg7^4f1-iaK8hG?gkC(1MSWblA*IZ>?B{&S&9J I(0=DcJI0%Y4{MY zNn;MOV7u?ittX!+4eCg)EOOq`3dQ*K|`Co%>zsvc{NKdhr^VVQv25V(y0 zY|A(0#Kg=V3Qp%&cI&RpLe rBW$oA=XoqO2@^t*g{sIhM{371+!4mE3{}F8v4#|`xh$V3 z3ZAPg?!x0C?n }A3Xz;hK! zqq}8P@~4kZ!_n|;Z?A1ncoy1CH{{l SIDYo(3gc1oj!{m;%w{o z*QuuY3u14%MtOKfMM|XAU#&N$;+lQdUW{Du`tZM%6TmtzWT77b6@qi eL7{KM?I^XXN*uqgUp{(GSu?;SDx6^wnMH!?p6QjjZJd3dXPbSL%-uq8#LGl9 zq<25ttRAfaH*HTf3$-C@Ong1f@QQZa;QpH )DSin!VhgDL-sL#`dN2{tuPY1yZWA!3p}j~>g9+aL#Y;fNViP)hfpzI={p0e^Gr ztqsfTHcsYZSu bnb0cCpMY+r7Gcu+=nWy!R>g#g``(_;LB2(;+iT;LKzT08ntLd{&GKEF&nctdjsM z8ZNz;_>?f{EYQ){=gwv^ zt%8cAl!(x^qGn z)6l+YGM3l3iLS`GF_J5O4s+qB5*9F6PeC-cUz8rJ=7g!EB%?>ESjp(sKOqaxKZ<`)Lt97Kbe4&y9)AnD&GVRUA3>{++{X5d3(X@g zI>V9~3|HEXxxHxmuqYDS%)Se6-q0a&aKLbOPJg@)r^!-okywGOQ1Fy7r}+S2-0eX9 zb^7}VmUS3@>~@badX2+jPA2Plr!|M3#!9qv?cm)S1w_9}Cbk#pvF3qG@s V_)m;62gw`gfL{pJ&pp}HuS_6PSB)2JiQPt z1BjC=gRx5)fe=oBQoImx*UqoFRb$xR656S|=1^AqYb~OG5{9wknUN}MsI<4^g;z$J z#p5rSX}^m*UzY;3r+L`>pDHhS_w1#kxQZ{uSQLLd6ada|DuKOa@-=1*cJQ`jcnf*E zbd2l4M4|^EzQj3Wy=*50vlK2ko`$r>9cJDzneE8sq;!FCFbC;%9Wjhanl (hh `G)U1Rm3v^AUp_Ycl-+$JD=OFBqj@_7MRZ9y&%TIQ*%E#J4*>N>og{)W zadOQBI8^9NhYybnS7r^ukg afPP{vv)h<&v z<6lC6WW|tXzXue@KhVjHkYABz!8(p?r^!`^_;V|5@)v1169%Etd<7(nq&t*(O#&SI zC;+UQ4O0noCE`olMsm~(azC)v kragE)rytWugG>^Lcfs^ zcpX! !P0Q!mnG|9#h z4T&o(C3>-!M{xu1uiXO 8pu@abE$-8NrZ8q(Lwz#`B+#~7 ztqsLnMFrIL=?~(m3vN?hGRss2lb!0QG`H{FL&)JSEIDK*Mf;{D%oIcfLwd{YzF0Rp z*853k=VY`}GewGa1=sKtCk3p^O)OviSwuq(qt_Twfyb>NL2!6Q=R8UI_4oC}_%khV zHWMQgqUtBOY_a?2P~~-BPgA1HFXx%5pE-oWAFT yg2 kMRaZV#C93>u zn-`bItg{2h{A!=XOzLgtz%+=AHRNPjFb(8`t297Kzi6Y$2VZ}%z;TLQh~K&MRNCfk zGbDE@6h3BJa3(71FtU%6nfPacKhV2pBkpg$(dlZ!(_OKy^%oP(>-`5r^zi&WDvCfB zg|*;EX2ww?Fum?k+Q=}!erJ`w554SAyKg@9RR2FL%yJ*NDrZ-kO-S0bDo4c;@lRWG z?ULVb2hDzYQz69~%%ck-zkB@#7*mN9L`x?rVp0&rKrGzhQ?BkT*=d%BJH(MM>S+b_ zJ{_Upx1Bk66Zg-x%n-|bxhu;A=B%_nL+NDbH`RBU7GGy45U~V2IeOG%&S0?GbcNKX zjA!6#{Z{G4b|d XG{AGH;bNqrrqGl +Qw9Mb+U+m zS_`DqnVmE5H*ol6pFmdb@8Uaw&3%yqOsvLl U5r31GZ--lo2^A=L2~p~s|*-_3Y% z96xEPazz>2^N(2GHUs}*VT7MwuP;F8I&z(`7P7IN${k&^+WJ-GW}O^3B1#al$2Zb2 zEam`05975Pd^n&-JJupG>N{f^v2aY)J@jtJe8q7_5_== _y&6 zOB?%;cJXpPg)B<_a=d~^1d3|%xw^|-kY<-m^4o745!z^c&io4S!`k&B76FrOx5df1 z%Cn@@UBS>jj{`3(PTzBKSrdr7+@=EDvi6do9%iLPecA4Fc8&yu-kf1UQMqQqJ@ts4 zoZ|Q&mErOEd-}31%LwMx;Ez@KQ7bNk+-fBgX2(GXqv@rAifE-rxY7aFjY~6MhLrxS zq=pR)E`f`0YV!tnep`(A5L*f9Lm{I?Jn#QeKUMgXW<7v1T9gCsBR4$4^qr_?YN^O- z70zYmCsL2hhIH1vL_uyv<2v1`b4E8yZED+0 k22jns~s}= z;HqE7Fh |$>+T3V3JA6?mJ|3J%MD3w)hCEjjY9*xxrqc*bDfs!2%Uoq z8~;Q;m!VDzqo`##A62i2dt|EW4FOXBz^jpwRJ8q6t<^SfGCHvpw-;frpG6%ZIvCNg zwE#r}sud|56Bblh7$>O;`up1h6EA--ndbQ$D_o#|K5X~F9BoDYHb|ff--5TQpJPkt z{K4ajOEc_0uA#7(l1hMOhMBY(qqm(6iG=DP}zXg%dYp=M?3%wiVeiZVHvEbso8C z9dhk=mG80x+1CQ992<3DX(kYsY0ag(D*%{#X8tYBy-(Qk0di+dgFb%jcONk3h-AfI zd)Mcpz}zM>H%w|bQqSJQ_mE4E@qBNqaEVrhbV(S5r5%;}E2y2bLrQhGj`?2xu}HQI zKmtu5RDrtq40|hDvS$TcMTK&oTlMPp0ZPzd$2JgBwcAJ0uN8=)3KFakM`HCVs|;+z z38&FRmhOHlc_u<@FX&wkqtjZTR|E$1{ptICNUzml3BDhoj`#?ptWy%#Nl#g@rH 5#S;(VbJV(8O7ggogmu~YzAJ8+~kyNS3 zy$il<#3#~M58k`)W;A~Tc$hu!#^QAx=3gZ%DNLWqinp;4`|}wITo;pnW19PjF@ZW{ zL1rLpPga0IVa6!~5tj6#-7$E#QQpf>lZS70aL@j!fetF$4gAJ=@&^KZ3)zd}VR%OH zq_AvyYf;n0(LwO>iN}HwszaFWB+R)CU;UaGl9hSjI2X~nr @5b@U#Nl|Av!u(OjZz{gt%MmYv zTS|}`K57krm~{n0*YH9t*<;9Ik@laOnS4afv%5QR$m&{t`~#NwUEt4fwq|JXka0}K zj)~e>op!u!NFmq_DVJJT8Qf7o(BIFaA*9V+&iUr+ufpWBzimTD=d^)6{FOb2lld4C zl6cfUM?YhHwziBMRB%*r{dQBJ3l=}#no863nV=5vzPY0|Pi@W?*PLkNm#-3FwS5I( zlWyVSTZ;2Sd@{ng)T!L!^so_$J ~4q5*wP|f>M+*HaRNqjD? }B{}(WX3O z7YZfGt3`x#LN!IqWh9Hq{D+QK8oN#}(m|0yNR3x5A-m3iy!NF}DC|pc$cUbs!WpEO zm`XETg%QUV@>%oi9(wkKhMm36`h$tZ9=ii1!(|02m04amZmp9;g?j>8##cC7$J66> zD{HCr! cI94>ud@F(2qP;TuOh2#|Ey{N$J-_m$vDC(o!cduD z+jIU)6`O44p8x%OIvJjA0>o#U)XZY70hoCx{hYzl2X7tW&u83e$vLv`>^eh;y+4Xq zr5zo{-|z)zfyzzJkFON;v;0r>KBEUBgIag9r&c^O?3A;-Hoad79}b1t(*zVLZ?-=k zKO@FocS4cs$j^T4EY>0u9TppIT6}^GB{0XJS&+D*t>QhmS&h^uXYYsHxQ4)8F8i>P znKYuDo31IDt(XhC_D7Jslnm3B$bg8T#Xn$drWsr?f5xpyo!5t`e|FL5xfkL-U(+~W zi;*<&nV!B}L|fTP2Eiz66 } +Inl^8?QFSZ z)l`vVJFpOHV=jLHjkGRNPuU~nlpLPk?HmQkQkEy&HwB|C;UZgUVK@QXel;f&`JsGR z47@JhN%^xJL@~Etma6oKFGA3O%vf=2BTc^FXVf%>!Ki#|M^}(iM|Lt%dJ)3CmVu%; zNI{R|9qfNVPWjc_AU-X5iEwemtN^{kIP4jV{B~#jI(EFNg@wro5@~mrMNM^EL|f02 z&f{u89|nj{c2TxCHuE!;X=#7CY3h22#xO9Z!53mMOoajO@i=8cxSN)DGK!%OClKir zrc7c^clL}+=BdDQ&sbw3B5s3`@1cVl2I9cFHY}gAot<~Zun@StPX4CNRet6Rt3Hs$ zJ@5cb@egCd $lBv}`vSQfOoDl+=VM(bhQgZZvO}=_Pb;4=_sh!uYz9PXI23rTb z0&hGyX&DlSS?l1Q2XQ+I9~b?iUwZa{5+b_4Tg^8|*0{ER0&TRG)v)+xwe~76Nl5@} z46dP!rO}ux3vWdRmAmZ>0rTO|EE%S$oCe8aUyQ9llVBQmM3uNIhOn)6!Xh?_`TJU` zoQ3oZK=jl-M~Ga2f$rn&q r+|-U`$yv;g8^Xak{ppz>uq7bd_md@ zooJCF2~@RsHtj# Ijk)B*>i`m#HKT9Pb|idIlO2!Las zV5#j~U%cTWlYpbPSF8Bzb!59`XQZjvieNy<%bsf5H%ki)i2f#)*6=tT |QNCIXh z0wb>cB+I97pFHHQsS~Z4@jn^_WPnikTzJm?VDb-$6W)dD+xAoze+>uTM8cm93Qy92 z^G*E;HUJxIaoia%?J z*o9{W4X nI=ng65@05S**v-;O7LLRt#;1}H>i>W)&yweBh&A7Yz%zNbb z3dyxT0flEPSu5;pnQZK_dJvEs^_kOm Cgsjmx0b{!wzvm-1p+I~6TvJ3&+AA4?N zFdDZ<043cCfAl`hIS0j$V@ic}X3*r!SGP9$Po+BeNDII_@vZl~`G;+M9)hk!g1Ud) zeQb;fh$f2rTSG(uoO8Q8MC$o}c9J6P^Sj8I%#SA94^o?=Y(0us)D32#a{Yd`dw4E1 zbar%ybWC7fGoHWOlkQp!0u9gO0NGX!IY0X}CHLb9?W&a+Vz*$YtiR=fQ#fVJE`E&$ z3=ky)MTN*zQFsFPxnKIeV~8Nt+)e-M$B*Jp%n?^TvSHcosq#}w+yjs_mtR>D0F6^5 zot01@ k%777;#yWlx0=j8;Lblg=mg+d7;=k?>H!M$n zO6JPC^O+JJQu5nRhULTmCO%lmUo7ZFK>Rcigsf{oVRVq>s7iS+3e?+m)e!0NE;DiS zKz{n$ke^R>ZnF7wyCwT?@6sb2d|_JWF|jIZBd4``(fTf}7R_;nbc|cDdZI)D!?Ud` z3FcbxzB@OsFJZvd=}#D*CSKcXf}2?G9KHns#9sLoQZxgo`mTIMub$&*c`XXM=W RN+ 3>53sPg(#OQg?X}ZE{R$@_l1wB*6l-sdn#A0QFKzc3K{DA zqsB>uPXOs_JVyRnZ#3Q}0TLDM>Ru8x)@+4qh(SB=ev--?BN!{DAGu%|noBLJQBPZ3 zZVDL7!>!J!kZICWPsSqXxDlAFo>ZDM-o&T-31Uw;NQsxr2Lsr=Iz})!11NfFn6zgH zxutYC1R!DdS8KZQWDbf}Wn@kj=GDk;DvJ>CqnqjG5*3`5d&&;ekoUeIfq`0ih&~w& zW|_l#j}R`2>4&$&h4DKiqiH7rRDv57%QtHdpl&RQ+$XBU(U? !LmV# 5N$s_A z>i^Lc$Mh#`Y?**$*j6Lg6jrQ)@)_IQB!rEL9_Ij>1r$27Aak}R9n_n!qC_JN2Ea $wI5L%~-3lauY#*dO&;$ 8xmP zU3%?Gjc@?#M?xkXIR|4==0p#*gzzl1KW-5oP7e0lGqUvWl@>%7pq8J-efBK(q6m~Q z#X}4cJJB$67esn%#~MZ8@nsXIdJ2#hn>VQG?d3(0*O~%*Dnbn_M*JnGB<}wsnO3e( zh9hcKn3Rk9O_;UqnP*~OylWsG)VJ|Kh@3%<(kD$D+PZItqJC+N$eS2?+yIQtF6ib2 zj}fx6P$zATvk4LOX#*MBEU;eRjyM0L0g?hw25;>15r@C6aDcch=Atv5so~V(m(#d= zDbP{PdiLg^TC6)%VR?0rfs3F)n1;#U)P}K<0`i;BlIz{I>uF7P5c=Xs_#q5IZRz9o zIv9z^Sp4wI{Pep1lc9Ho2px*a!~;ycWI!t!!sLhMr^mRIUP0O7Hyj%-;Vw#aC?6yk zJ%C~nTL+n%Y;Z^H<(1l=wsK?0rsX(oxmX2zki&XF=k;SyI=xW )w!3 zwpolz&yUyFDC%*7TQq!Tj=`^V_VA6LP}8GDYj}}YPvDAkc|AJ0ci-c)qb+%{V1dkA z78id={58Q3*Ac{y^Df^gVBDoHahQv_b06)*vVfK2LV|1kW|dZOgU!deM86D7KNimt zB9Oe%`qvBV 9+TlAnvQ+g@yF97H6VN$DUh*9g0h*8VJ9pY18vt2|?Vw z&|8oV9 G0&k)!nyx_ARSoh(y11Pk5J++}TgL}A&~7*O5xG(< zbN? XA``}P@tKj>B;WP#?+}vGG*k{79?uo) z4EPkXFQnycvd8fT3DlomTl577q_acW)XNsf8mN>$`UU~|kQDOk!p(q(Oz8)rv<_tD zKV7=^Xn+`dVS0XIuShv2i<+CtK}T%?)&pG@A%$m&H;qvyK%rupk{z-(^74&ufbyYR zZa2w8a0y=Ia$)!UaWf{CLfaf3EV5Q2?RO*?Nb2S`w5Qp}=<1bl4B-`5rB{_*Ztw(c z0<@MCB~;^H=>EoJLIyOEPxekvg%?lg-Lnaqcip)i 9z!%|^4KT(oFJ2Qoto zxLI&^q4lVbMZBC>XI!gH)!~}_=8#4|1+bAKkd&5^p8aNd%P$U}#~H*<*b}ALj&XLC z71^QnEeLdhd&biFtQbRrD3Hcc6DQj)v_^=@eC}KDQH5gmXtIlQS82_PLAqA9Z06rf z1nxlH^l+U+?2>{$^F|T3bS1u?o@dinshoqdEju$;e$nY2_@$M`=}AExS(02*;J=L@ zzn ktxXc3E&_G`-0Fz83XrvV(g9Ze`} -T+qeO=+M$w;`7<>ob?3ZMsBD z$fCn=y*J=FR!p%ODo~ gi8TsBK+9 zkM<1jupAsb`7F^APLn>Y$(-C?ml&&JpmzBQFyjNoK}fW`1@ln%nsR7V_yJ_W$ye96 zkzpu{M>4I7h;g|?Wuh&JggQ}*-?ZB=9AX{$yqqhgOH8z@7EhjLrQZ9>#ShdVZa6jd zHkep+3NB{P0ocT8;Lt$oiWKp?6W5=jD;nogd u0wNLCT~_Iy-fzH)(?U)A4;eA#*bb4+e^n*U1?QM z--qGe7uRJ=#KLhh+(~PSaEc3NpRvDk^AKv{us@zay71+3H86>n|J3m??B7vO0jZT% zM%$9}98hW*Ixx#YbNp23WJxf*nSid;#SwRLHV3~K)XU0R=tm85htw>|r!o<^feiCD zO=F!ljF{+YrpR)XEIOnA$t*GYd1)1Bq9~S^=Z!g`hxG^-ABB1{sqNwHEY7n?1JyBM zl0+tVHV9*ABA{F?H7LgGq@`l 7SZ0IbJDnjO-b0~OY7iw9>;d{i*1XuCUD 5JfeLQ;kf_CQ%wYaNp1G^ !O#g2eV?Uyzhme0Q3L9%|Z;f%uAf+2a%GK*Yo>MFw=(mhWd#+q$n7 zS;Do`KI(Z^{d9DXb^IkVAS!d+V%uDgqH ~YnQIc=UG)Gz!Re_Kwq{^ z3a0M)Q*naCPEU(y1w4jZ^nG@m>`Z{FV{nV9W)Vpfr=WBTCLovYl71;ZE#_gL2_$eg z&N*VZ-W(1z7#K&f$^y`ZC~A*}DPQfQ0(;LV;iF#in6P0hO<;exDg<0SQ#$z3f$lWC z9Vw-$jwt}02gWy*ulbZ?c~C6KVxmH$*xwDi#l$jdjO6kN9epdEH9U8NHBH!UY~|_; zGUApsW!)U(ER0{Lsa;j>feK)sX6VFx?o@ppbml)YDxIXE7gGR20+7(i<4`4JSnBW~ z7}sQ4OlSw7!@ks=ftclJf=AlN;A8|S)ochgWiG}af49-u6Iu48NpVz5h6aV2Ch1_s z)pLku5ui`~^NT?vs;oE$524`juGsRcxzb7L^#~A$BrbC@#W+mj=AMd9E^2(MWQj;M zdNQn)e4QZV?-edA`+hbNx_^1W{ TB=aNY4~VhGu@sAJ)Uuz+y`ItvhU z`(GWbP%tG$#x~_fC6GhcO1*hg>O$Upr>a?>1O#pD(vN=z_0Itr2G76(^P3cTC1qjy z51NnzB!i=Q8K1cjSlDIIQ-5{rhJqh} {Y11mNzxS$Y{(ywxEi;)*pg;7!lD_EI4 z+w^A5Uaw3G3XA@;A>Jz15By&!p6L^jGDrGwhW*%W9s1OcSoi4$J}Vw^ozZt5 Fwj{Sz!zB2Qychk_@U_!|Ih%LNC(Uyl;CRiCK9xZFYQv*P>uaQokT}z z?p{zY8HH;eLlm9u>xprS-+#LEOl!>S!F=V6c67u`#jC;F2)m-2x{k7kF?%lA6Z}Tc z_=hH;spMVY7k;dOXW?W0zC##lM~_=c87vG -Y2=Al$W8?R#7a0l@EAeRqzV-C}I~b8qxbnK0sZ3kOP8M1Xb5i{k@=x4$eD! zD6c1r;`Jh|fu{}$$IIqrb9LHtIkH@e@N}X-4wR==>vF6K6jMoUcs^B}_SwKt7M*gt zNV!iQQW&v&R4JvV8xr -P?#DYm$LtT^w#9avarpO{4P?~t&73AX*u`KZ9BC=;h z^fN6$N8XH>m;+w5+C2sZ6je8+4x!DBwt_yCQmK~fshc5j{wB6Mm@#*w)sZ)!)Rb5u zWUB4+=z$7HB`%_=VQVIR6qK~O6)u+mAfQY~f{q+2MN^zunt(%nkA;@!Zsl%Fr^wsA zs$+b}goJNs&}I4mC-i-g%hk_ctGVxi|I930WBoG+Ca`BhQ|=gz^_2v0LfJ1lkYhkK zu4j&EemA+4TQ?;esHH`_ Y0MHt(S%)baac6b ra+>3{ZxlrISo|4`=f`jLmAP;7R z4nLn Eb+UM*+Jv+`WR --NNO2*y*7~{WcL57<*N?dA|)nf-%o2$(95OeY3XG#8hfWyAdk84gj zESW{IPdwr277TJQVbMbwp9GNdFx>{kdJ3;Xf!omKZw=bvXqippl^%iR;{hA_QaQy7 zI}p -iv^yl+{PMph>LHDX&&=ixt=%@1$n0QmQx@Q)4G=@UTY>9ATPUBH5Hz z=@=we8&oxoF1Ty&HidsSI)wPaUU$M`ek8HIJ(EFC*cU~oH(TY;NkxwTglE#L>r#uk z;=Ma}j1@*+>7Lr|$f56tV0F*7XMu?6&D;|RkCT@DVW&hkz~h23tUyD3$7}fG_ZJ+} zPXK*AM_m@xyA{NQ{zy$hWNn$8OIkSL3GhJp0H-p@f-W*Xl)z%mL()k^>a10Sq-xzk zwORd!?EK@nvN6sk#1p|)8JxDt1?1&SYG95;xaIcT;K$4Md!`mBR32@6R-5Hxzn;>q zkI*| #vKSJzZQfe=>@XHH&kmj0Nja5b+kxCGE?b1~zEPFu9 zhS1x?Sx#*zY%(?vPD?BgF7A*0h*pr1LvV+MElbE!{|mDSC!U%Y%)Fmi#Q;!Wx9t8< zp!(BGrB26DtQ~o@_=I%+M!BDKZPQo&lHTYWhaAlX$Q+}CRRKw&WimP|XN0|X3)L0t zxn@FX$1pwZyopeAl2G$q-bbuxU-SV~-oTH 4k?(AvFIGTQi@CeybwHRjz3p>4S-Xn>0Wm4LOS+ek0029MGyni6qF19X zHv+9*jvhrjO9zTGlHu)*Q8Tu}og$S2HunGkq9EQK-p`mMO`F~$2dVh-?Z5J^^MWy7AzDKuP{9j-#a*D^G{hpGl Z56`XuJ&^~nf))o7Jd@U zLC=lw%9#= -H}4YLv?SWybYL82ke18X2+8KUzt7q1SkfyaxidHs8J(=fS>8~J z!!fz7CNw6**zD#CL|+#ZoYTj72~(n=ae^rYak+sIMNMbIRzB!tS6M0r2}J3oZD{bE z7(atT`JAC;la{7WV?6QR=psN~liF(W1L_EVL)?@%8Z{@fTerG4I$9&RU3K<%I7X z^vjvgMsm@&5_|*?$xf+PyHGoI&C6@@6+1D5y&kl@AZ>wK9I~jcsT$FKKKV$+k*SdZ Ke82?8pa1}lUAL70 literal 0 HcmV?d00001 diff --git a/cmd/picoclaw-launcher-tui/internal/ui/model.go b/cmd/picoclaw-launcher-tui/internal/ui/model.go index 47ca5a355..698502058 100644 --- a/cmd/picoclaw-launcher-tui/internal/ui/model.go +++ b/cmd/picoclaw-launcher-tui/internal/ui/model.go @@ -49,7 +49,7 @@ func (s *appState) modelMenu() tview.Primitive { Action: func() { newName := s.nextAvailableModelName("new-model") s.addModel( - picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.2"}, + picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.4"}, ) s.push( fmt.Sprintf("model-%d", len(s.config.ModelList)-1), @@ -291,7 +291,7 @@ func refreshModelMenuFromState(menu *Menu, s *appState) { Action: func() { newName := s.nextAvailableModelName("new-model") s.addModel( - picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.2"}, + picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.4"}, ) s.push(fmt.Sprintf("model-%d", len(s.config.ModelList)-1), s.modelForm(len(s.config.ModelList)-1)) }, diff --git a/cmd/picoclaw/internal/auth/helpers.go b/cmd/picoclaw/internal/auth/helpers.go index a0a229167..4bf132685 100644 --- a/cmd/picoclaw/internal/auth/helpers.go +++ b/cmd/picoclaw/internal/auth/helpers.go @@ -72,14 +72,14 @@ func authLoginOpenAI(useDeviceCode bool) error { // If no openai in ModelList, add it if !foundOpenAI { appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{ - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", AuthMethod: "oauth", }) } // Update default model to use OpenAI - appCfg.Agents.Defaults.ModelName = "gpt-5.2" + appCfg.Agents.Defaults.ModelName = "gpt-5.4" if err = config.SaveConfig(internal.GetConfigPath(), appCfg); err != nil { return fmt.Errorf("could not update config: %w", err) @@ -90,7 +90,7 @@ func authLoginOpenAI(useDeviceCode bool) error { if cred.AccountID != "" { fmt.Printf("Account: %s\n", cred.AccountID) } - fmt.Println("Default model set to: gpt-5.2") + fmt.Println("Default model set to: gpt-5.4") return nil } @@ -318,13 +318,13 @@ func authLoginPasteToken(provider string) error { } if !found { appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{ - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", AuthMethod: "token", }) } // Update default model - appCfg.Agents.Defaults.ModelName = "gpt-5.2" + appCfg.Agents.Defaults.ModelName = "gpt-5.4" } if err := config.SaveConfig(internal.GetConfigPath(), appCfg); err != nil { return fmt.Errorf("could not update config: %w", err) diff --git a/config/config.example.json b/config/config.example.json index 1eea37683..b259df6f6 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -3,7 +3,7 @@ "defaults": { "workspace": "~/.picoclaw/workspace", "restrict_to_workspace": true, - "model_name": "gpt4", + "model_name": "gpt-5.4", "max_tokens": 8192, "temperature": 0.7, "max_tool_iterations": 20, @@ -13,8 +13,8 @@ }, "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "api_base": "https://api.openai.com/v1" }, @@ -41,14 +41,14 @@ "api_key": "your-longcat-api-key" }, { - "model_name": "loadbalanced-gpt4", - "model": "openai/gpt-5.2", + "model_name": "loadbalanced-gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-key1", "api_base": "https://api1.example.com/v1" }, { - "model_name": "loadbalanced-gpt4", - "model": "openai/gpt-5.2", + "model_name": "loadbalanced-gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-key2", "api_base": "https://api2.example.com/v1" } diff --git a/docs/design/provider-refactoring.md b/docs/design/provider-refactoring.md index a214d9857..38f379c50 100644 --- a/docs/design/provider-refactoring.md +++ b/docs/design/provider-refactoring.md @@ -66,7 +66,7 @@ Problem: Agent needs to know both `provider` and `model`, adding complexity. Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design: 1. **Model-centric**: Users care about models, not providers -2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.2`, `anthropic/claude-sonnet-4.6` +2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.4`, `anthropic/claude-sonnet-4.6` 3. **Configuration-driven**: Adding new Providers only requires config changes, no code changes ### 2.2 New Configuration Structure @@ -81,8 +81,8 @@ Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design: "api_key": "sk-xxx" }, { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-xxx" }, { @@ -128,7 +128,7 @@ type Config struct { type ModelConfig struct { // Required ModelName string `json:"model_name"` // user-facing name (alias) - Model string `json:"model"` // protocol/model, e.g., openai/gpt-5.2 + Model string `json:"model"` // protocol/model, e.g., openai/gpt-5.4 // Common config APIBase string `json:"api_base,omitempty"` @@ -180,7 +180,7 @@ Identify protocol via prefix in `model` field: "model": "deepseek-chat" }, "coder": { - "model": "gpt-5.2", + "model": "gpt-5.4", "system_prompt": "You are a coding assistant..." }, "translator": { @@ -200,7 +200,7 @@ Each Agent only needs to specify `model` (corresponds to `model_name` in `model_ model_list: - model_name: gpt-4o litellm_params: - model: openai/gpt-5.2 + model: openai/gpt-5.4 api_key: xxx - model_name: my-custom litellm_params: diff --git a/docs/migration/model-list-migration.md b/docs/migration/model-list-migration.md index 0d4af719c..eed228d4d 100644 --- a/docs/migration/model-list-migration.md +++ b/docs/migration/model-list-migration.md @@ -40,7 +40,7 @@ The new `model_list` configuration offers several advantages: "agents": { "defaults": { "provider": "openai", - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -53,7 +53,7 @@ The new `model_list` configuration offers several advantages: "model_list": [ { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "api_base": "https://api.openai.com/v1" }, @@ -82,7 +82,7 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier` | Prefix | Description | Example | |--------|-------------|---------| -| `openai/` | OpenAI API (default) | `openai/gpt-5.2` | +| `openai/` | OpenAI API (default) | `openai/gpt-5.4` | | `anthropic/` | Anthropic API | `anthropic/claude-opus-4` | | `antigravity/` | Google via Antigravity OAuth | `antigravity/gemini-2.0-flash` | | `gemini/` | Google Gemini API | `gemini/gemini-2.0-flash-exp` | @@ -109,7 +109,7 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier` | Field | Required | Description | |-------|----------|-------------| | `model_name` | Yes | User-facing alias for the model | -| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.2`) | +| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) | | `api_base` | No | API endpoint URL | | `api_key` | No* | API authentication key | | `proxy` | No | HTTP proxy URL | @@ -130,19 +130,19 @@ Configure multiple endpoints for the same model to distribute load: "model_list": [ { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-key1", "api_base": "https://api1.example.com/v1" }, { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-key2", "api_base": "https://api2.example.com/v1" }, { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-key3", "api_base": "https://api3.example.com/v1" } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ad89d6d2e..1c93028c7 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -444,7 +444,7 @@ func TestLoadConfig_WebToolsProxy(t *testing.T) { configPath := filepath.Join(tmpDir, "config.json") configJSON := `{ "agents": {"defaults":{"workspace":"./workspace","model":"gpt4","max_tokens":8192,"max_tool_iterations":20}}, - "model_list": [{"model_name":"gpt4","model":"openai/gpt-5.2","api_key":"x"}], + "model_list": [{"model_name":"gpt4","model":"openai/gpt-5.4","api_key":"x"}], "tools": {"web":{"proxy":"http://127.0.0.1:7890"}} }` if err := os.WriteFile(configPath, []byte(configJSON), 0o600); err != nil { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 492b22e3a..2a3e66043 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -194,8 +194,8 @@ func DefaultConfig() *Config { // OpenAI - https://platform.openai.com/api-keys { - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", APIBase: "https://api.openai.com/v1", APIKey: "", }, @@ -256,8 +256,8 @@ func DefaultConfig() *Config { APIKey: "", }, { - ModelName: "openrouter-gpt-5.2", - Model: "openrouter/openai/gpt-5.2", + ModelName: "openrouter-gpt-5.4", + Model: "openrouter/openai/gpt-5.4", APIBase: "https://openrouter.ai/api/v1", APIKey: "", }, @@ -287,6 +287,12 @@ func DefaultConfig() *Config { }, // Volcengine (火山引擎) - https://console.volcengine.com/ark + { + ModelName: "ark-code-latest", + Model: "volcengine/ark-code-latest", + APIBase: "https://ark.cn-beijing.volces.com/api/v3", + APIKey: "", + }, { ModelName: "doubao-pro", Model: "volcengine/doubao-pro-32k", @@ -311,8 +317,8 @@ func DefaultConfig() *Config { // GitHub Copilot - https://github.com/settings/tokens { - ModelName: "copilot-gpt-5.2", - Model: "github-copilot/gpt-5.2", + ModelName: "copilot-gpt-5.4", + Model: "github-copilot/gpt-5.4", APIBase: "http://localhost:4321", AuthMethod: "oauth", }, diff --git a/pkg/config/migration.go b/pkg/config/migration.go index 8e693506b..af6391651 100644 --- a/pkg/config/migration.go +++ b/pkg/config/migration.go @@ -61,7 +61,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { } return ModelConfig{ ModelName: "openai", - Model: "openai/gpt-5.2", + Model: "openai/gpt-5.4", APIKey: p.OpenAI.APIKey, APIBase: p.OpenAI.APIBase, Proxy: p.OpenAI.Proxy, @@ -335,7 +335,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { } return ModelConfig{ ModelName: "github-copilot", - Model: "github-copilot/gpt-5.2", + Model: "github-copilot/gpt-5.4", APIBase: p.GitHubCopilot.APIBase, ConnectMode: p.GitHubCopilot.ConnectMode, }, true diff --git a/pkg/config/migration_test.go b/pkg/config/migration_test.go index 807d93e49..0665ededa 100644 --- a/pkg/config/migration_test.go +++ b/pkg/config/migration_test.go @@ -31,8 +31,8 @@ func TestConvertProvidersToModelList_OpenAI(t *testing.T) { if result[0].ModelName != "openai" { t.Errorf("ModelName = %q, want %q", result[0].ModelName, "openai") } - if result[0].Model != "openai/gpt-5.2" { - t.Errorf("Model = %q, want %q", result[0].Model, "openai/gpt-5.2") + if result[0].Model != "openai/gpt-5.4" { + t.Errorf("Model = %q, want %q", result[0].Model, "openai/gpt-5.4") } if result[0].APIKey != "sk-test-key" { t.Errorf("APIKey = %q, want %q", result[0].APIKey, "sk-test-key") @@ -384,8 +384,8 @@ func TestConvertProvidersToModelList_MultipleProviders_PreservesUserModel(t *tes for _, mc := range result { switch mc.ModelName { case "openai": - if mc.Model != "openai/gpt-5.2" { - t.Errorf("OpenAI Model = %q, want %q (default)", mc.Model, "openai/gpt-5.2") + if mc.Model != "openai/gpt-5.4" { + t.Errorf("OpenAI Model = %q, want %q (default)", mc.Model, "openai/gpt-5.4") } case "deepseek": if mc.Model != "deepseek/deepseek-reasoner" { @@ -558,9 +558,9 @@ func TestConvertProvidersToModelList_NoProviderField_NoModel(t *testing.T) { // Tests for buildModelWithProtocol helper function func TestBuildModelWithProtocol_NoPrefix(t *testing.T) { - result := buildModelWithProtocol("openai", "gpt-5.2") - if result != "openai/gpt-5.2" { - t.Errorf("buildModelWithProtocol(openai, gpt-5.2) = %q, want %q", result, "openai/gpt-5.2") + result := buildModelWithProtocol("openai", "gpt-5.4") + if result != "openai/gpt-5.4" { + t.Errorf("buildModelWithProtocol(openai, gpt-5.4) = %q, want %q", result, "openai/gpt-5.4") } } diff --git a/pkg/providers/codex_cli_provider_test.go b/pkg/providers/codex_cli_provider_test.go index 414e0844d..0f66e25f4 100644 --- a/pkg/providers/codex_cli_provider_test.go +++ b/pkg/providers/codex_cli_provider_test.go @@ -490,7 +490,7 @@ echo '{"type":"turn.completed"}'` } messages := []Message{{Role: "user", Content: "test"}} - _, err := p.Chat(context.Background(), messages, nil, "gpt-5.2-codex", nil) + _, err := p.Chat(context.Background(), messages, nil, "gpt-5.3-codex", nil) if err != nil { t.Fatalf("Chat() error: %v", err) } @@ -502,7 +502,7 @@ echo '{"type":"turn.completed"}'` } args := string(argsData) - if !strings.Contains(args, "-m gpt-5.2-codex") { + if !strings.Contains(args, "-m gpt-5.3-codex") { t.Errorf("args should contain model flag, got: %s", args) } if !strings.Contains(args, "-C /tmp/test-workspace") { diff --git a/pkg/providers/codex_provider.go b/pkg/providers/codex_provider.go index 47618300a..cf5c2d876 100644 --- a/pkg/providers/codex_provider.go +++ b/pkg/providers/codex_provider.go @@ -16,7 +16,7 @@ import ( ) const ( - codexDefaultModel = "gpt-5.2" + codexDefaultModel = "gpt-5.3-codex" codexDefaultInstructions = "You are Codex, a coding assistant." ) diff --git a/pkg/providers/codex_provider_test.go b/pkg/providers/codex_provider_test.go index 4157e53e9..dd5ad2637 100644 --- a/pkg/providers/codex_provider_test.go +++ b/pkg/providers/codex_provider_test.go @@ -568,7 +568,7 @@ func TestCodexProvider_ChatRoundTrip_ModelFallbackFromUnsupported(t *testing.T) provider.client = createOpenAITestClient(server.URL, "test-token", "acc-123") messages := []Message{{Role: "user", Content: "Hello"}} - resp, err := provider.Chat(t.Context(), messages, nil, "gpt-5.2", nil) + resp, err := provider.Chat(t.Context(), messages, nil, "gpt-5.3-codex", nil) if err != nil { t.Fatalf("Chat() error: %v", err) } @@ -599,7 +599,7 @@ func TestResolveCodexModel(t *testing.T) { wantFallback: true, }, {name: "non-openai prefixed", input: "glm-4.7", wantModel: codexDefaultModel, wantFallback: true}, - {name: "openai prefix", input: "openai/gpt-5.2", wantModel: "gpt-5.2", wantFallback: false}, + {name: "openai prefix", input: "openai/gpt-5.3-codex", wantModel: "gpt-5.3-codex", wantFallback: false}, {name: "direct gpt", input: "gpt-4o", wantModel: "gpt-4o", wantFallback: false}, } diff --git a/web/backend/api/gateway_test.go b/web/backend/api/gateway_test.go index 84d784a5a..d4265776a 100644 --- a/web/backend/api/gateway_test.go +++ b/web/backend/api/gateway_test.go @@ -247,7 +247,7 @@ func TestGatewayStartReady_OAuthModelRequiresStoredCredential(t *testing.T) { } cfg.ModelList = []config.ModelConfig{{ ModelName: "openai-oauth", - Model: "openai/gpt-5.2", + Model: "openai/gpt-5.4", AuthMethod: "oauth", }} cfg.Agents.Defaults.ModelName = "openai-oauth" diff --git a/web/backend/api/models_test.go b/web/backend/api/models_test.go index 7061eb3f7..2377b5b66 100644 --- a/web/backend/api/models_test.go +++ b/web/backend/api/models_test.go @@ -62,7 +62,7 @@ func TestHandleListModels_ConfiguredStatusUsesRuntimeProbesForLocalModels(t *tes cfg.ModelList = []config.ModelConfig{ { ModelName: "openai-oauth", - Model: "openai/gpt-5.2", + Model: "openai/gpt-5.4", AuthMethod: "oauth", }, { @@ -81,8 +81,8 @@ func TestHandleListModels_ConfiguredStatusUsesRuntimeProbesForLocalModels(t *tes APIKey: "remote-key", }, { - ModelName: "copilot-gpt-5.2", - Model: "github-copilot/gpt-5.2", + ModelName: "copilot-gpt-5.4", + Model: "github-copilot/gpt-5.4", APIBase: "http://127.0.0.1:4321", AuthMethod: "oauth", }, @@ -128,7 +128,7 @@ func TestHandleListModels_ConfiguredStatusUsesRuntimeProbesForLocalModels(t *tes if !got["vllm-remote"] { t.Fatalf("remote vllm model configured = false, want true with api_key") } - if !got["copilot-gpt-5.2"] { + if !got["copilot-gpt-5.4"] { t.Fatalf("copilot model configured = false, want true when local bridge probe succeeds") } if len(openAIProbes) != 1 || openAIProbes[0] != "http://127.0.0.1:8000/v1|custom-model" { diff --git a/web/backend/api/oauth.go b/web/backend/api/oauth.go index 04cd595f2..919b47fbc 100644 --- a/web/backend/api/oauth.go +++ b/web/backend/api/oauth.go @@ -791,8 +791,8 @@ func defaultModelConfigForProvider(provider, authMethod string) config.ModelConf switch provider { case oauthProviderOpenAI: return config.ModelConfig{ - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", AuthMethod: authMethod, } case oauthProviderAnthropic: diff --git a/web/backend/api/oauth_test.go b/web/backend/api/oauth_test.go index 2103e1efc..7d63abbd4 100644 --- a/web/backend/api/oauth_test.go +++ b/web/backend/api/oauth_test.go @@ -168,8 +168,8 @@ func TestOAuthLogoutClearsCredentialAndConfig(t *testing.T) { } cfg.Providers.OpenAI.AuthMethod = "oauth" cfg.ModelList = append(cfg.ModelList, config.ModelConfig{ - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", AuthMethod: "oauth", }) if err = config.SaveConfig(configPath, cfg); err != nil { diff --git a/workspace/skills/summarize/SKILL.md b/workspace/skills/summarize/SKILL.md index 766ab5d0b..ca7008e7a 100644 --- a/workspace/skills/summarize/SKILL.md +++ b/workspace/skills/summarize/SKILL.md @@ -59,7 +59,7 @@ Default model is `google/gemini-3-flash-preview` if none is set. Optional config file: `~/.summarize/config.json` ```json -{ "model": "openai/gpt-5.2" } +{ "model": "openai/gpt-5.4" } ``` Optional services: From 6460a0a7c7bda8946ad0ea42cad0469d816b9ec0 Mon Sep 17 00:00:00 2001 From: Guoguo <16666742+imguoguo@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:16:42 +0800 Subject: [PATCH 005/113] fix(nightly): reuse single nightly tag, no per-day tags (#1415) - Disable goreleaser GitHub release for nightly (Docker still pushed) - Use GORELEASER_CURRENT_TAG with local-only tag for version/validation - Force-update single `nightly` git tag instead of creating per-day tags - Docker tags use only `nightly`/`nightly-launcher`, no per-day versions - Set --latest=false on nightly release to avoid occupying latest - Simplify workflow from 3 jobs to 1 job, remove all cleanup steps Co-authored-by: Claude Opus 4.6 --- .github/workflows/nightly.yml | 162 ++++++++++------------------------ .goreleaser.yaml | 9 +- 2 files changed, 53 insertions(+), 118 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 321e35ccd..0103fcff1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -9,64 +9,37 @@ permissions: contents: read jobs: - create-tag: - name: Create Git Tag + nightly: + name: Nightly Build runs-on: ubuntu-latest permissions: contents: write - outputs: - version: ${{ steps.version.outputs.version }} - tag: ${{ steps.version.outputs.tag }} - changelog: ${{ steps.version.outputs.changelog }} + packages: write steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Generate and push tag + - name: Compute version id: version run: | DATE=$(date -u +%Y%m%d) SHA=$(git rev-parse --short=8 HEAD) BASE_VERSION=$(git describe --tags --match "v*" --exclude "*nightly*" --abbrev=0 2>/dev/null || true) if [ -z "$BASE_VERSION" ] || [ "$BASE_VERSION" = "v0.0.0" ]; then - TAG="v0.0.0-nightly.${DATE}.${SHA}" + VERSION="v0.0.0-nightly.${DATE}.${SHA}" else - TAG="${BASE_VERSION}-nightly.${DATE}.${SHA}" + VERSION="${BASE_VERSION}-nightly.${DATE}.${SHA}" fi - VERSION=$TAG - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then - echo "Tag $TAG already exists, reusing existing tag" - else - git tag -a "$TAG" -m "Nightly build $VERSION" - fi - git push origin "$TAG" - - COMPARE_URL="https://github.com/${{ github.repository }}/commits/${TAG}" - if [ -n "$BASE_VERSION" ] && [ "$BASE_VERSION" != "v0.0.0" ]; then - COMPARE_URL="https://github.com/${{ github.repository }}/compare/${BASE_VERSION}...${TAG}" - fi - echo "changelog=**Full Changelog**: $COMPARE_URL" >> "$GITHUB_OUTPUT" - - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - release: - name: GoReleaser Release - needs: create-tag - runs-on: ubuntu-latest - permissions: - contents: write - packages: write - steps: - - name: Checkout tag - uses: actions/checkout@v6 - with: - fetch-depth: 0 - ref: ${{ needs.create-tag.outputs.tag }} + COMPARE_URL="https://github.com/${{ github.repository }}/commits/main" + if [ -n "$BASE_VERSION" ] && [ "$BASE_VERSION" != "v0.0.0" ]; then + COMPARE_URL="https://github.com/${{ github.repository }}/compare/${BASE_VERSION}...main" + fi + + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "changelog=**Full Changelog**: $COMPARE_URL" >> "$GITHUB_OUTPUT" - name: Setup Go from go.mod id: setup-go @@ -95,6 +68,16 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create local tag for GoReleaser + run: git tag "${{ steps.version.outputs.version }}" + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: @@ -106,6 +89,7 @@ jobs: GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }} GOVERSION: ${{ steps.setup-go.outputs.go-version }} + GORELEASER_CURRENT_TAG: ${{ steps.version.outputs.version }} NIGHTLY_BUILD: "true" MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} @@ -113,92 +97,42 @@ jobs: MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} - update-rolling: - name: Update Rolling Nightly - needs: [create-tag, release] - runs-on: ubuntu-latest - permissions: - contents: write - packages: write - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Update nightly release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ needs.create-tag.outputs.tag }} - TITLE: ${{ needs.create-tag.outputs.version }} + VERSION: ${{ steps.version.outputs.version }} run: | - CHANGELOG='${{ needs.create-tag.outputs.changelog }}' + CHANGELOG='${{ steps.version.outputs.changelog }}' NOTES=$(cat < /dev/null 2>&1; then - echo "Downloading assets from GitHub release for $TAG..." - gh release download "$TAG" --dir build - else - echo "GitHub release for $TAG not found; falling back to local dist/ artifacts..." - if [ -d "dist" ]; then - cp -R dist/* build/ - else - echo "Error: no GitHub release for $TAG and no local dist/ directory found." >&2 - exit 1 - fi - fi - - # Delete existing nightly release and tag to avoid conflicts - echo "Deleting existing nightly release and tag..." - gh release delete nightly --cleanup-tag -y || true - git push origin :refs/tags/nightly || true - + + # Delete existing nightly release and tag + gh release delete nightly --cleanup-tag -y 2>/dev/null || true + + # Force-update nightly tag to current HEAD + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -fa nightly -m "Nightly build ${VERSION}" + git push origin nightly + + # Collect release artifacts from goreleaser dist/ + ASSETS=() + for f in dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/checksums.txt; do + [ -f "$f" ] && ASSETS+=("$f") + done + + # Create nightly release (prerelease, NOT latest) gh release create nightly \ --title "Nightly Build" \ --notes "$NOTES" \ --target "${{ github.sha }}" \ --prerelease \ - build/* + --latest=false \ + "${ASSETS[@]}" - echo "Cleaning up old nightly releases (keeping only the most recent)..." - gh release list --limit 100 --json tagName -q '.[].tagName | select(contains("-nightly."))' | tail -n +2 | while read -r old_tag; do - if [ -n "$old_tag" ] && [ "$old_tag" != "$TAG" ]; then - echo "Deleting old nightly release: $old_tag" - gh release delete "$old_tag" --cleanup-tag -y || true - fi - done - - echo "Cleaning up old 'vX.X.X-nightly...' Docker images on GHCR..." - OWNER="${{ github.repository_owner }}" - PACKAGE_NAME="${{ github.event.repository.name }}" - - # Check if owner is an organization or user - ORG_TEST=$(gh api -H "Accept: application/vnd.github+json" /orgs/$OWNER 2>/dev/null || true) - if echo "$ORG_TEST" | grep -q '"login"'; then - ACCOUNT_TYPE="orgs" - else - ACCOUNT_TYPE="users" - fi - - PACKAGE_URL="/${ACCOUNT_TYPE}/${OWNER}/packages/container/${PACKAGE_NAME}/versions" - OLD_NIGHTLY_VERSIONS=$(gh api --paginate -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "$PACKAGE_URL" \ - --jq ". | map(select(any(.metadata.container.tags[]; contains(\"-nightly.\") and (. != \"nightly\") and (. != \"$TAG\")))) | .[].id" 2>/dev/null || true) - - for version_id in $OLD_NIGHTLY_VERSIONS; do - if [ -n "$version_id" ]; then - echo "Deleting Docker image version ID: $version_id" - gh api -X DELETE -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/${ACCOUNT_TYPE}/${OWNER}/packages/container/${PACKAGE_NAME}/versions/$version_id" || true - fi - done diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e410eb51c..622cf054b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -116,9 +116,9 @@ dockers_v2: - picoclaw images: - "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw" - - '{{ if not (isEnvSet "NIGHTLY_BUILD") }}docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}{{ end }}' + - 'docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}' tags: - - "{{ .Tag }}" + - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly{{ else }}{{ .Tag }}{{ end }}' - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly{{ else }}latest{{ end }}' platforms: - linux/amd64 @@ -133,9 +133,9 @@ dockers_v2: - picoclaw-launcher-tui images: - "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw" - - '{{ if not (isEnvSet "NIGHTLY_BUILD") }}docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}{{ end }}' + - 'docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}' tags: - - "{{ .Tag }}-launcher" + - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly-launcher{{ else }}{{ .Tag }}-launcher{{ end }}' - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly-launcher{{ else }}launcher{{ end }}' platforms: - linux/amd64 @@ -215,6 +215,7 @@ changelog: # lzma: true release: + disable: '{{ isEnvSet "NIGHTLY_BUILD" }}' footer: >- --- From 7872bb3f0a25f19ec5b22826cb0c7792870178b9 Mon Sep 17 00:00:00 2001 From: wenjie Date: Thu, 12 Mar 2026 18:15:16 +0800 Subject: [PATCH 006/113] Merge pull request #1421 from sipeed/refactor/config-ui refactor(web): redesign config pages and extract raw JSON editor --- .../src/components/config/config-page.tsx | 23 +- .../src/components/config/config-sections.tsx | 456 +++++++++--------- .../src/components/config/raw-config-page.tsx | 207 ++++++++ .../src/components/config/raw-json-panel.tsx | 202 -------- web/frontend/src/components/shared-form.tsx | 72 ++- web/frontend/src/i18n/locales/en.json | 52 +- web/frontend/src/i18n/locales/zh.json | 54 +-- web/frontend/src/routes/config.raw.tsx | 31 +- 8 files changed, 529 insertions(+), 568 deletions(-) create mode 100644 web/frontend/src/components/config/raw-config-page.tsx delete mode 100644 web/frontend/src/components/config/raw-json-panel.tsx diff --git a/web/frontend/src/components/config/config-page.tsx b/web/frontend/src/components/config/config-page.tsx index d7e1aa1b5..cbce7d27e 100644 --- a/web/frontend/src/components/config/config-page.tsx +++ b/web/frontend/src/components/config/config-page.tsx @@ -13,7 +13,6 @@ import { setLauncherConfig as updateLauncherConfig, } from "@/api/system" import { - AdvancedSection, AgentDefaultsSection, DevicesSection, LauncherSection, @@ -30,7 +29,6 @@ import { } from "@/components/config/form-model" import { PageHeader } from "@/components/page-header" import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" export function ConfigPage() { const { t } = useTranslation() @@ -56,11 +54,7 @@ export function ConfigPage() { }, }) - const { - data: launcherConfig, - isLoading: isLauncherLoading, - error: launcherError, - } = useQuery({ + const { data: launcherConfig, isLoading: isLauncherLoading } = useQuery({ queryKey: ["system", "launcher-config"], queryFn: getLauncherConfig, }) @@ -111,10 +105,6 @@ export function ConfigPage() { ? t("pages.config.autostart_unsupported") : t("pages.config.autostart_hint") - const launcherHint = launcherError - ? t("pages.config.launcher_load_error") - : t("pages.config.launcher_restart_hint") - const updateField = ( key: K, value: CoreConfigForm[K], @@ -287,21 +277,14 @@ export function ConfigPage() { - - - - - - - - - -