nRF38y5*hV)_8$Iq+X(h|v+n3%RsFTIbh_t4TxUB~2on}wcm
z+m-jc(Qr1NZ{Ok*BThE18nQX!MTCR>dZAI^-&*$Egw4G1Y*DlGlb{Blm}Pzb_BEB6
zY7{MX!qv5m%|&%(!s#&u{e$RAJ^XAFe3#KBmkCP24E)obdqNrq?eQGwoNBn*d1i$~
z+)mMGp(v%IKCjd_7hSa|m>aii@T=;Hdlnk_1PGPQEVHc~+D+fqc>l${5>Z1fu2vi^
z9?{o3(wDm`;QXzEU_15gw!w_hH;@if|ZWW8lRJIw3!O%r+0isjgCt{
z$4E}aV(XPF6!-Bhs_E|?>zl?=*56gS&h-2QN2|sFp^)0a8oRu1Is4o+*<2FvSNY5I
zX2tyA0omCTHoV{AJww@JiL#x*$h-UxMh_hHVydg4%lM(yi-KPA9Nmy}?TMdx&c6Em
zrh~>_`@PviCM)F+kTi%AHL7mbY}T7N;$R4;vg%Pg`*jj2mHOSwqt@=Ya$PeeQz$50
z`J}dxW7onYQ>pQ;t{399!fxu+T=4B;RJY8ska}Ss{bJ?7AQ5Mp`>_!loJ(kTn)s`D
zqddb-cFkMm9Lo`W&{8TiL3Qei^}61NcMClUdUidmclTq5^~zLB;>)iLyOpZ4-7@aj
zf|3541WwubL}-q-E3cwXtT%seId=F^TrFZ_X=icNd`bW=yfXu!1JH;Z{DolNa_Qh-
zTvVb3FdbkMz$JhpfC_*H0Nj)CZvxU#4p0D)3a|wL*1HfLfYvpV@Vlb{(+&fO1t`314f!1dLhXCvV7>kkN`aJ=b6cJT`Z2+$U;8cMB4Bzd^2z6sOfF=O)_O-wU
zFJUV_$pB~sXb<0i@mUk_auZ-M06Rc?0cC@{X9N7#%e=iZLY^J~pbXm60~L6V<^KPX
zccwg$H`D{H3#x{
z_wAJPu#Yqm0QPG9ar|Q+3HynW0Dqe9GxZv6{wVN}pY_^0VTE5HBa|0kC}
zY(=J+@E_2ve^mZ#Y;2T(fB*#ts8GX)52xVJPRh^EkAlNGDL5R3nm2DA1&4c5{rmT)
z#KgoXIHQYI_dffLauX2|q2SD^9|kEYDTaL_BYaN$A*eed7Dhw~`EKQuKpQE>PTLqFgUp3i86LuDyA
z)b;xjwi6S}0QImPUfX;2-Tb3&s;Q|_M~)n!>gwv+$@%*qq1=ugJ4PugDtoN}{j~+Y6}csONCl5tW{vPQk$f)SEYN7&sAt6D*fyfl;*06w5DJFOIxGL!zMVXIvfc7S{dRP8Y%M=lzFB{x
z4A8EjuCxu6m6eo*g+-?=e>f0_f&)U@lFhh}kB?`x|E#=z={FprMj@}v_7Uv})+e;3
zGiJ=_wEV*%D-;|O{u%#B`+@}v7=67j@yzFJ8Q$
z;6Oe~US9sQzGG+SpS7H@ZexEI^&H0s*hj{4W%lp!%Ix32di9E7=OQ8^C?g{yiid}%
zwal3~@rpJyBqW5ockkX0!<{>KI?^xwZvPJ}oy_0Z?xDTw)29z*XJ^;0{}T>qq?mqz
zFSQX&d|1cOcK{z!`bY4q{hv&F+q)tUOla?YCwumf%D<45Y$e-k3xaO0wUlOhua1X>f
zfIngX3-XA2`09b}!4cJgaps<2KFvU&%5CjVu7s8#=a%E>;=`Hs(=C$Sa
ze}Dfi2{6lnc}-~{q_w;XTkg4;<;pB)X1SwcYXeLHV4f2}SB9JK3)~Q`55PQtg8-<5
zxTglsWWX$bCE)%&?8jl7b_l=&09iqrz7^08BJ1k`uJ^Eep`q}xs0op76!}Q}mS?t^YY`OeSe8|J^%^l+`(zy$>zc6ke&ZUlZ>*#KD*dPvxR(6!DI$+8CA1B7d&Q5L_O
ze#{rwA+v@nSFSMjAGfWWc#r(x{@1o^U=JKPz^H@2lYU%JxNFxgR?-=3E^%KGYn^P%
zhq}5tW8EgMJxBe=HOuSPtz(qW@1!5sDWdMO23$uvbm-95`q-9sW*Oj`bOc;~iEGVK
zw|=+&{1f`oULYV2TrbbG-@jA-Xg8yyqgloOUHXIjpiiATMWNlp@;h_p3}a6>uIv4s
z^kaGB{;c-uFRs&WPj_(5uA7_NXZ3gAzJ0&F{vv&{va-~zTem(b1GFu;Zy06Cy0-?)
zYsioxjP{i^0rEe6`t*Or{v&tzWmIGe9cI`?H7%+gbF9_RI)D3K>5U?+eeQIofv9F4D
zlQk{=#{M&wrI(l2cLVNYW!m4i_&(f!wg1^(8k8yGL>tdKe!*v~@@_BfFL>5r`hNip
zpOsYy^7yaQ|6lf>;GA-02g>T7l=H7hi}T^n|HJgZ0@`o>hv~lqw8#91>5l{2X9N6K
z=Pz)68t1_Z0DeXN`jt3we!33;&fkRtbcVU=@8uWg?{FOzb50&t7W|jyVQ`Ke*9kBK
z&R<&qJOcP$8T?!E3V=qOYiFImhFiux@_^;g8FdMD2IY>l<64LI0+s{n64nRgvoqKw
zqygurqX2N70MhuafOQ(}657^10M`Hz2hNw_`Yvp@vETPI0ddv<;C$RO0Bl#WZJGpc>BNb{Su&KR2KQA;62E-5U(I-rrgm8A=m@Z
zh#dSS#7vTL`PLs{h%g*r1Hg5FG5}mV^|S2~;(P^wdElCj5db0pUkX^aO#pDs;#+{9
zP0zRRy@Nc@0Sp3YYiIC&6hHyMxA6Sx_qYyeEC2@pGZ@2<&OjTJccc;Jh(QS86hLQN
zD~x50`iT2JaNR7E_RexeeZ)G0YqUC>?3gmbdVLvydEeQt*rzG(#Pp%}Oz;
z)7i?Em7ZVGcCq6Br@uQ)ANoRnqd)YU^r0WUXV0FLt*tG?e;gJTM&Y?NOP4OC#*G`t
zIP1FY`87yCo`H?$pneMs4Gq6|{tf8f7wN-1aIOgFQ*v^080YP<&POokDex@n?c2A1
z;@3x>@EM*p_btG)cW@5l3-zE~`j9p}*ZR_>ON{wL<~#$gVZd`^@!U_=^(06uo}0#;
zH$nPv-O2X^JZnr|UcN*0$;iks<`J0k!Yj%U=Lk&U>^2h<6UKZ4>$(q&gX>c8+`?Z;
zAI_iQ8E80P#tcXs&Tp{J{o!vsC-&*nr_5*!9l-NRe^%)D@iD
zz&SoVFA?<+&p*St)?Y~>&oe-~ZpNaMdP#{!@Mea-Fppiz!k)~JualTOSF
z?J)Xr(bwFTHh9z;P#>|*L;zd@C<4H_C0y@=ZQ;)V#Dy|=3UCD=3IP4M=xhEYV8%#L
z3IGyK2cWTB;pZP6=)gUzJW}8u<;0X5&N0aWm;o^7ndqNM8D4%2Z17VNU;#i9KnVbr
z`9H1uhy!sY19$-_0bpLNffH_=0PX<%%DHUBoekg&fO%p-T1EmOjsKL_?@f>GJJNu8
zD*$8xd~e#H6%XZrW#JC+Z_K-6S)e|g1^8J!brcWkWZ}P<{|m_b-7zoC1qbbc{^9BbklY&;*{9KP#eZEel)^WvDf?S4nxvxEM-F9Gf?Yn$eS-nE-Q
z<~wcLG{(0Da6cXTy3vn-{%7><;+_iJo5}PW3>!9#@f|~4@7^Auj~e|q?dbYv`J>-%
z+qP}3--<$i3i_hZkAmMg!GJyt^abJ^0m=#YO`uQXOX2O?w+#RExAI4y1o|JCJ{$hrHsRF7z|ucQVm8
zjPnIQD}Mt61FF2doRL4u%*@Q}vogbd1*nhc8%Cc$p0ocm^JkXvm-5GbUO3-@do)m|
z(69Tm@<(4W`eK>#$8U_Gua;GBaNiJqdjjWUa1I5(YxJ}7NBzSxXX^6HmoFKCDVbhTdV`2nFD~#`^Fx>mO6j
zs8hIi8~3TSwLiEw8TWppkD9qB_FMJ`_ZEGx{bAC8eBqu*+}FdjzXJyjWPD?*tzQxC
zFWT5I*(lB9_Gy)f5J~jKsm?(pgy1vt^fdiH`qtVbUy-^7RMJw0I26U*2D28lW+8|u+v-0
z03QFoK!-e2AaC?jNC0R9F#Qx$y1-5Q0n5n7A
z!q4#0Ar8caIGJ?geMjm2R(!;Xd~65!8FaVJ4{@Tb9s;y|_p|Qv0r#(Lhp?T&`t!5s
zZ4VFH)6bq`e-isr*r&rQ-ZSH2{|48YV85g-F#A=^xN!XhKlX{SPlR*DfB*eA;~Qf*
zC%$>}X2$$DrpNU@_#VIE#|rqZPyB`_3vT?~iXUmj@6Kc27sp|^_Z`0*jpIEW6X948
z)62s+82g}Y1IA%~2bm?0R{YqX!|#k^Jfsi5FOK}1E2pB`JnEg3~;>!jx91WGAP`ij%zxQFI<;}^#5r2qP(#@(O-h&
z7(6oy$8=b>yI`L_j%iWHuuh;K1JB=J1=KZ6%PhAK*H-;ueU7}~_zQIi{Q#_E#SYf5
zUu_?%S?T`CztO(s{bZc1^2hdaJHW4OU$71PmF?rNZr_3Y)^>gy{C)s{eJ|82)ZOm^
zjEn6cwzVj4lnJAMLT7Z7;qDg%#EtzhF#xdM!CW88ZH
z%K;Dv2E1b5v=QKIX}|Uk(_)_)aUt%1f{U3C;y_$?0hrJJ=@sSmGy6Fx&!537D_>2l
z{GuJlGm_BfhBg`7Xy%$Q=J!Hzofs1^F7|0yaJ1sX^?_^FtYNhM=u<);9a8R*k#ix2HQo+pQXI&2HkFNf_)Vqzkr?Z@xh;yHS(Ymu>^g8srb
zYvvgA;de^W?~83Zw&BeDaBW_2a4=&%9J61++TZyaK5X;a;zM7hpPwH!a^y$`&hMoU
zWs2vTVf>>T%o7iUKTKbOgi~ONa4Eu}N*T8Z>z;eO!?$DQp<$+~XR#w&u
z*p|0f9!z*VpkESwg2*rWi}5}7f3a_a&zOLHyf*ssv-SCBw!5{!M>dn!pKwK6
zh<5a6@QgSRmo&i7u&0Ovabb`FVA?`#(=acrH(vvcgSHOsXRE!Zf4FN}6OG&Gbk2Zil~gM$OLWXY0N
zzKCwBt7E7G-y+q^NzUA8oxn~`&uWIt`Wy^JjxH(_p{26Ik)qnJQ(t0Et?LO-#_VhU|qPvns*0(W1Vvbc-_HtsGC@S
cu-;;C6mcLO-?Bld>lg>?9Hzzk`;Wr^0b6Bt$p8QV
literal 0
HcmV?d00001
diff --git a/App.Avalonia/Controls/AgentAppIcon.axaml b/App.Avalonia/Controls/AgentAppIcon.axaml
new file mode 100644
index 0000000..04fb55f
--- /dev/null
+++ b/App.Avalonia/Controls/AgentAppIcon.axaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/App.Avalonia/Controls/AgentAppIcon.axaml.cs b/App.Avalonia/Controls/AgentAppIcon.axaml.cs
new file mode 100644
index 0000000..a607faf
--- /dev/null
+++ b/App.Avalonia/Controls/AgentAppIcon.axaml.cs
@@ -0,0 +1,163 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Threading;
+using SkiaSharp;
+using Svg.Skia;
+
+namespace Coder.Desktop.App.Controls;
+
+public partial class AgentAppIcon : UserControl
+{
+ public static readonly StyledProperty IconUrlProperty =
+ AvaloniaProperty.Register(nameof(IconUrl));
+
+ private static readonly HttpClient IconHttpClient = new();
+
+ private CancellationTokenSource? _iconLoadCts;
+
+ static AgentAppIcon()
+ {
+ IconUrlProperty.Changed.AddClassHandler((x, e) =>
+ {
+ x.LoadIcon(e.NewValue is Uri uri ? uri : null);
+ });
+ }
+
+ public Uri? IconUrl
+ {
+ get => GetValue(IconUrlProperty);
+ set => SetValue(IconUrlProperty, value);
+ }
+
+ public AgentAppIcon()
+ {
+ InitializeComponent();
+
+ DetachedFromVisualTree += (_, _) =>
+ {
+ _iconLoadCts?.Cancel();
+ _iconLoadCts?.Dispose();
+ _iconLoadCts = null;
+ SetImageSource(null);
+ };
+
+ LoadIcon(IconUrl);
+ }
+
+ private void LoadIcon(Uri? iconUrl)
+ {
+ _iconLoadCts?.Cancel();
+ _iconLoadCts?.Dispose();
+ _iconLoadCts = null;
+
+ if (iconUrl is null ||
+ (iconUrl.Scheme is not "http" and not "https"))
+ {
+ ShowFallback();
+ return;
+ }
+
+ var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+ _iconLoadCts = cts;
+
+ _ = LoadIconCore(iconUrl, cts.Token);
+ }
+
+ private async Task LoadIconCore(Uri iconUrl, CancellationToken ct)
+ {
+ try
+ {
+ using var response = await IconHttpClient.GetAsync(iconUrl, HttpCompletionOption.ResponseHeadersRead, ct);
+ response.EnsureSuccessStatusCode();
+
+ var mediaType = response.Content.Headers.ContentType?.MediaType;
+ var iconBytes = await response.Content.ReadAsByteArrayAsync(ct);
+
+ if (ct.IsCancellationRequested)
+ return;
+
+ using var iconStream = new MemoryStream(iconBytes, writable: false);
+ var bitmap = ShouldDecodeAsSvg(iconUrl, mediaType, iconBytes)
+ ? DecodeSvgToBitmap(iconStream)
+ : new Bitmap(iconStream);
+
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ if (ct.IsCancellationRequested)
+ {
+ bitmap.Dispose();
+ return;
+ }
+
+ SetImageSource(bitmap);
+ IconImage.IsVisible = true;
+ FallbackIcon.IsVisible = false;
+ });
+ }
+ catch
+ {
+ if (!ct.IsCancellationRequested)
+ {
+ await Dispatcher.UIThread.InvokeAsync(ShowFallback);
+ }
+ }
+ }
+
+ private static bool ShouldDecodeAsSvg(Uri iconUrl, string? mediaType, byte[] iconBytes)
+ {
+ if (!string.IsNullOrWhiteSpace(mediaType) &&
+ mediaType.Contains("svg", StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ if (iconUrl.AbsolutePath.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ if (iconBytes.Length == 0)
+ return false;
+
+ var probeLen = Math.Min(iconBytes.Length, 256);
+ var prefix = Encoding.UTF8.GetString(iconBytes, 0, probeLen).TrimStart('\uFEFF', ' ', '\t', '\r', '\n');
+ return prefix.IndexOf("