From 5b49d51cebb5bb5d1aea73e9ef44d4c3c78f2fa2 Mon Sep 17 00:00:00 2001 From: davidkaya <3832960+davidkaya@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:00:34 +0200 Subject: [PATCH 01/10] Add Usage Insights canvas extension Contribute the Usage Insights canvas with current and historical token and AI-credit metrics, per-agent rollups, in-canvas session drill-down, installable plugin metadata, and a synthetic marketplace preview. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/plugin/marketplace.json | 6 + docs/README.plugins.md | 1 + extensions/usage-insights/README.md | 84 ++ extensions/usage-insights/assets/preview.png | Bin 0 -> 47888 bytes .../usage-insights/copilot-extension.json | 4 + extensions/usage-insights/extension.mjs | 206 ++++ extensions/usage-insights/renderer.mjs | 904 ++++++++++++++++++ extensions/usage-insights/stats.mjs | 470 +++++++++ plugins/usage-insights/README.md | 31 + plugins/usage-insights/plugin.json | 30 + 10 files changed, 1736 insertions(+) create mode 100644 extensions/usage-insights/README.md create mode 100644 extensions/usage-insights/assets/preview.png create mode 100644 extensions/usage-insights/copilot-extension.json create mode 100644 extensions/usage-insights/extension.mjs create mode 100644 extensions/usage-insights/renderer.mjs create mode 100644 extensions/usage-insights/stats.mjs create mode 100644 plugins/usage-insights/README.md create mode 100644 plugins/usage-insights/plugin.json diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index cd19cc914..fd0f0e800 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -1832,6 +1832,12 @@ "sha": "f8abab778cfb5b57a3c745d540631b7aab15d5b3" } }, + { + "name": "usage-insights", + "source": "plugins/usage-insights", + "description": "Inspect current and historical GitHub Copilot token and AI-credit usage with per-agent rollups and in-canvas session drill-down.", + "version": "1.0.0" + }, { "name": "vercel-plugin", "description": "Build and deploy web apps and agents. Comprehensive Vercel ecosystem plugin — relational knowledge graph, skills for every major product, specialized agents, and Vercel conventions. Turns any AI agent into a Vercel expert.", diff --git a/docs/README.plugins.md b/docs/README.plugins.md index 274783eff..296ab2406 100644 --- a/docs/README.plugins.md +++ b/docs/README.plugins.md @@ -122,6 +122,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-plugins) for guidelines on how t | [typescript-mcp-development](../plugins/typescript-mcp-development/README.md) | Complete toolkit for building Model Context Protocol (MCP) servers in TypeScript/Node.js using the official SDK. Includes instructions for best practices, a prompt for generating servers, and an expert chat mode for guidance. | 2 items | typescript, mcp, model-context-protocol, nodejs, server-development | | [typespec-m365-copilot](../plugins/typespec-m365-copilot/README.md) | Comprehensive collection of prompts, instructions, and resources for building declarative agents and API plugins using TypeSpec for Microsoft 365 Copilot extensibility. | 3 items | typespec, m365-copilot, declarative-agents, api-plugins, agent-development, microsoft-365 | | [uizze](../plugins/uizze/README.md) | Build product-specific interfaces with a focused UIZZE workflow, optional real-screen evidence, and a practical finish check. | 1 items | ui, design, frontend, ios, web, design-review, quality-gate | +| [usage-insights](../plugins/usage-insights/README.md) | Inspect current and historical GitHub Copilot token and AI-credit usage with per-agent rollups and in-canvas session drill-down. | 1 items | ai-credits, canvas, copilot-extension, session-analytics, token-usage, usage-insights | | [visual-pr](../plugins/visual-pr/README.md) | Capture, annotate, and embed screenshots and animated GIF demos in pull request descriptions. Includes Playwright-based UI capture, PIL image annotations, PR embedding workflows for GitHub and Azure DevOps, and screen recording with variable timing. | 4 items | screenshots, pull-request, before-after, annotations, playwright, gif, screen-recording, visual | | [where-was-i](../plugins/where-was-i/README.md) | Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly. | 1 items | branch-state, developer-context, git-history, interrupt-recovery, pull-request-context, resume-work | | [windows-app-storage-inspector-cleanup](../plugins/windows-app-storage-inspector-cleanup/README.md) | Inspect Windows application storage, understand local disk usage, and safely move approved cleanup items to the Recycle Bin. | 1 items | app-storage, canvas, cleanup, storage, windows | diff --git a/extensions/usage-insights/README.md b/extensions/usage-insights/README.md new file mode 100644 index 000000000..d1741e0ac --- /dev/null +++ b/extensions/usage-insights/README.md @@ -0,0 +1,84 @@ +# Usage Insights + +A GitHub Copilot canvas extension for inspecting local session usage: + +- current and historical AI-credit totals +- input, output, reasoning, cache-read, and cache-write tokens +- root-agent and per-sub-agent rollups +- model-call counts and durations +- recent and all-session aggregates +- highest-cost local sessions with in-canvas drill-down + +The interface follows GitHub Copilot's native visual language and refreshes while +the canvas is open. + +## Privacy + +All metrics are read locally from Copilot's session data. The extension: + +- binds its renderer to `127.0.0.1` +- opens local session databases read-only +- does not send usage data to an external service +- keeps historical session drill-down inside the canvas + +The published preview uses entirely synthetic sample data. + +## Installation + +```bash +copilot plugin install usage-insights@awesome-copilot +``` + +Reload extensions, then ask Copilot to open the **Usage Insights** canvas. + +## Canvas actions + +### `refresh` + +Returns current metrics for an optional history range or selected session. + +```json +{ + "range": "7d", + "sessionId": "optional-session-id" +} +``` + +Supported ranges are `24h`, `7d`, `30d`, and `all`. + +### `inspect_session` + +Returns overall and per-agent metrics for one local session. + +```json +{ + "sessionId": "required-session-id", + "range": "7d" +} +``` + +## Structure + +- `extension.mjs` - canvas declaration, loopback server, actions, and refresh events +- `stats.mjs` - read-only SQLite aggregation and sub-agent metadata resolution +- `renderer.mjs` - responsive, theme-aware HTML renderer + +The extension intentionally has no `package.json`: `@github/copilot-sdk` is +resolved by the Copilot extension runtime, and SQLite uses Node.js's built-in +`node:sqlite` module. + +## Requirements + +- GitHub Copilot with extension canvas support +- a runtime version that provides `node:sqlite` +- local Copilot session data containing `assistant_usage_events` + +## Source + +Contributed from +[`davidkaya/github-copilot-usage-insights`](https://github.com/davidkaya/github-copilot-usage-insights) +at commit `206d53faab1d8a3331de96e203d0206ad21845aa`. + +## License + +MIT diff --git a/extensions/usage-insights/assets/preview.png b/extensions/usage-insights/assets/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..7ecb6c4565b8de1be0b614d0a21408276de114f1 GIT binary patch literal 47888 zcmd3OcT`hR6K50^6zNJ85S1dm3DQwOl-@f8q)QhNDIp+8FDktwNH3xHrU(e3O7At4 z0HKBw2Tia;Q) z(2(Rc_N%YSvJY?IzC=DE#jV!w6&b-b;8QxAAP{#sQ~kzE+~*+Mbtc@W%{~9CM+k;V z6X5<$EBF6oc>Wtw0^w$_(w5Ry*^9Lsf^2K~=NqkO$XE3=39g4(6<8v^Bz@nEXvE$5 zTfnMN5*~}3OkiKsXRdA+GA|yuoh$aM6RSAZ&gHn*3IVnHew(C;9Q`r1c6$_^z39I4 ze5=;ZaTP;zq?@~c9LBK2Fx9>c>*qSDn%`|EVZJB@983KZBn^|D`|Q7!$0$xcN2jE(zvsnI@& zyBirWocSZ#@Qde&_3J!i5`(`BgPrxjHALHpuJ~`8mhzrVD<(6&i!Cf{Fy6W(BAMC$4RH`q@Ug$HiMaD6_FGp)da>x^}*xT(IS= z6Ix2Mml`KN-6Gs?TwHl5a(uLS>GRiX$hgY_E(Q}np5b8S&iCdyHv_-h{d3msDI-$$ zeDuO=;pww-`@wFWR1V))w&9A{ZYNd!9k37s{4#ml_!i7_5$#bjUU)L6@D(z{}5W$`HfL`l#q<}xUZbDi&8uZI)W+xlVLxpIu;Vy(KB#J(jj%( zE_WusvU{LD4Sud>c1mU>j)t$@N)XwWDj3n7E1711B`A|URZz`QSzjNw?R_b9@lZeo z4EAa=bKaa=bFft_Y#mh_{pxf|*(#LneAtK&Xg7j`_2#erGu3r-2^QJMUI`xD&Cg0#)_H8m5q*hV*z4hF&>t$wI6E!1JR-()s7jLkb!$4bfE4ZC z(ZARt*?B@;tf|VqQ*fw@uc+GN9j0ZFt;W-?@rQ7O}2+}{`?GFbdo94X+>d{Q{rOYQHdNWd8@MG7U!j(Zr!XgXK zZyn|COGlCk2c4Hzu+YI2I+fOfb5g_4kag$R=Sx8|^6fH=zCHu_8LGRkJP@di(wBtp zkNd9+&eeu>6NpbrsPwK;gDzrSIIesbB9r%_-^CCV9mkV;p!h zw=kfUVcC#jd)(??6!aL_6F(P3@f%Cp3{{Ms`2|UaF$Gz(m3MbCR~*l!;*>`$Qo(B# z`5Klhe7X0P2=)}aWP*|DE(|$Q&Y&n^stjDWXPezSY}M|jXZpE6u#E^J&qqG z@J}-4-V~4e->b@}U!JYSdEX&ckJgO6dAaW!Ob0S!t>K1J#oO zhQw~8Q|I02fh?aLj~=-_G58}(+JO_Dc(t%#DeApV!NB6a3m?djQ>CMJsK6oD4JR*j z@YAnZ4J+V^D8eq)C$uf)J8Om(FaEmicTJnnEgMoYML`{2(K#YdJV=0@qt;b1`mofT zAfV%m9CEr`4;gxLo|SQa(UQtTuvd*B+lQTIBBaWqa$}&BgqHRNFIM8yg>J*vM;=r4 z(|fy*$M{C>J8dK=MvZ%f=@Ff$f!C1N$K^~Cbo>LXs0hE$%vwcih6LS?wdiRx zQFgDvz5U;+Di2Q95S72I-P&iJ>0zEqnfQot3Q){;Z1Lu3U1+doSbGTZyLl8iR?>Um%I zz;^e)D|Dy{Z?+zqjs5-J8!VtR6{WWNuteQDlgz&$fF-2>x;+4k)F|~?4SlX(R`KMN zal+7@4}KxB(y=r5sPKlqB;J9;-`@O;fx^oPCdv4q%gu%Hjgw4bgdzQ?m9VdXdT6f6 z-n8nNL+vw6$4-910O$PP?y|fF_;fq!ImCLnTVkHU;pgoxClSYSHo)(@95yC&kW=A| z6%2&@+=~@m9!Q@l)Ye{u1*&!DkvGTa_lzLz62O^HU1%H3vE3!SO9Y*XL7xC;q;=aO zW;aZ)WdqI>bdyQphtHP4K}!|P;0=Gbj1~CQ2|oEY9s8*SLAXc8e;vR*_sn&3X41f~ zo6w;x%1lQMeb)oNmp9qi^-xiDI&ri-Uq{;%=-ji9&N<>|jhc80GukBq+c{e0@p^4!$*f%*1G!jes3u z$D7Tz3zRt6H>{#0eccj+Gs?zq#UYpMvWZO~AL3qg{^;j;wfX2#k8aHV9}_pDRW8>^ zbR^GQi&Dh*=|2N&RR|g5c>8VY^G{~>BzU42ciMI{WpwAyouQ9%o-x0)oQj2n1iRIa ziGYsHbUKAQ(v)NMecramG?FW0Hcky24c6B@G*E91G@_+??l=id4{ zjtV_7*~>4-w|vF2&VQ7Ta{MWK{^Psp#{Bl=VLO;hAR(b>Sut)Xbd_ZprYoWQmBxY- z_6|An`8UVU?1C3#Bey=~e)Lt{NM?L8)z9*1c`3^}w7*A&IDLzDmdw08>Z8nh1>?oh z8bbNS{=FlqF-@qObbTID=esaPO4dYep4=x+@^W2=?%Ns z2o>qE;PrK7fF+sRHJ`45t4NalM$U#+PtF~4O4bJXGkJXv8=a~MUECK^YdSU^6qP@o zk5Zns{OmG_hfzQ0UYSqeh~9n{*IM_!y$%1Y{=DMyRKZ5}yfTA=CL4D9)MqHAsI^sG za1Y@Tl@2HZU3p#?9lb8pv!;1CmDF2@sEluRMxvh~h(u34@sk{Gc9<0!7{+bYAtN)8 zeWIG$gUMvADoaPcY1JQI*ZxE5MMuS2l(_>Un8(bNXC2}mhl$O>d)4<2AJ6Z5j&uhI;L4W#oSVTlf#NDB!WOkAZ#V~|_QC62`g<_HNTb;uSSh0TdJC`pu z{5m}!lIY!o5Oz9H%gBbK+H~h4$=In$zpeq3M_LSQM7y>!NbTmbwrJj=zisy0e{>P*CR6^ zg>_mw-MOFch&;R#3a<3h|H4=6wxGcbzO2ED!(|Mjo1WWbOr-3O?$CdQ&JJkIXJcxL zinhGniT8hsaa^7&oc8&TmoT=Fm#zvX-7oRTUv2*?G5Ry3K3hWYW7kr;ajy8L9{3`g ziSI0$he3Bv+Ga+eyH?ceGN}4;pW_ss865(y_R{tV>EB;waI6}zz6>hPia}fPH?u)j*-kB!2$gw65&$#JTX2et@BoG z+IRer+JdPb(LMXsr%>l`)`{`b!mHU;(k!Nb>mm#mjg^A+^w^) z(92craH->KrCZkUiPcZvs|2 zBXHM1fxBJOYkvRb0(R+I-q&_jJ>=7QLvwSQT-NRV2eVgM)lWH}S$h{&5c~1BRAs82 z_RKwCNe%dyYpV=HvTZ*qZqjqbuxiIY5O~F{q*0q~p8eY!5jU1cG(5bbi|RO4)qk{P zZ2pH0H{Yj0ika_;P4lQGLl-9;1vO1jn_aa454xvF5s+io&#s86yvB^(SyWr${ ze&MjjSO3!aJX^0bW?kX5c^UBG;J~({gMASeiwN^^=_1Z3)u)PK`~7Po-nEfSnJe$T z2c-zp4dVX}+}LSxDcHK~;c@X$X)-C_FL&U(xF0-%dF#-weOYZs9>%UpVEvD4Xu}Hd zl8)%5`hLi4F5GG+N+RTDD6Vee?CU4$?&f4uQU_}@zJ|9R_~$>a=M01V{3KWRvFC`pTA8IZx3K1toSsr{OAj)?&x?y=5b*d9p+OSv`RzpE**b zE@Y+mcVnF`*}rFBOX6Eg6_A~%UlHpw%Br=Ge(>9V{;b?urULp6w_|P3O*N8S+}`R= zbwwu!^>JW1-Vb|>vv;e_$nz&eQOXD49cD}P7XJ9@mu>i$gb=qjiu?Yg9_?=RKh3=V zBAIo=-vr>E^LzDNI-fH3D7<>}(WqHq@Jr6+TgsNFJ$@SQqzgmGSz}zGlJyXIZy_(DYYKAedGzw5IH=&Km9gg=CcIjg0A~y`_KL#RwKNzW-=8 z7Zx_-&}bK&`bcyF+$xyuyiqowSwEn5nY_BN^-H`N8~pqngV(NH(iL?ch&zH2Fqb4P zmiOCP!yC4S`yA)vl#Ew!xiWOUEaD~Q}q9z@lX3Z2GUaU;9t=hdj4*Hsv z4Dgq7t``p^-4r-O!ga)0Is)lbr$2r9%PMi)oIhgGo(bm|vbpd+jc?`;LQEVRIB`Ot z?k5U{=CVD%D&y0+6h{u8OEy(lq@dqhUf^|ctIee;iShRqA4tU&eRwW5eWaHbF!0;g z)-$f!-!i@^l~7M~E2MB%?0~~S>3;o2yT;Nkot?eVX)s%clHQDu*`0_tu_;64>ThFz z&1Pzfv|x5smw%U5EzvUjIMSrw3Wl(3*5th+*}8pumn1q3qBQHhfc#68(jLTU_mruS z-rm;vryLwVQHL`qod{J|IqK-bXSGv7MMuqCd10JPc{S@c?k8C{ z?3Q1^mp_;>`H!{732aFkP%z1;IGlQtRcqb89~yohmtG;n{8-1##Q*%8B{O#G%Zmh( z+oUy$Axvg}KbSA=ZqS}Fb-uOZ>lt&cYPZy&_Z8V}qePXEvu*pN@sF)ClOL%;U*?UB*4zyG-UO2mzkn z*AHe)^xyZcGxB=825WXODTY=Y+E+_H)GJ9ah|+F1nYGlE?P)V6x&U>Aj&l@CIRYYs zmTkX@%jjtzrN54RCfij?;Nz4LK{S+2B>RAepQzNPUwNRk`tA+-nsHJj4=SR!nXAuz z38gQUgqh^BR#cEvI{I7o8*~|tZ`XxDD!eJP;H0NZHhfh@l=4Vk!gplJNr*g*^vPw&X=~2W9w-u^Jik@;jW%2nf601eo=(Qm3C{cTIjv+=d3Yoran!=j!5uak%|*UO%z@~NKUMAS72m?@Y|n|fwXLn=bQkf)Z9gpJ z9pYAORq;I%e8@v$Xb+xnZp8OE?WG3zqob$OyhDLqZ#z0F3~)l2`pgF3F;i4*)2!fr z6082Xe)?4}y+(2x-M1KhwZyC@wI07QPIa#u%WYAYhtvDW-=#APTfI%6+>socD}p-w zRu3lPt&_H7(|i!|pJlW4^b%`7IIGGB>(ZBS>*bmB>lq8|KZ#>LW3RE+>o?~VvuuF; zEPHo6pfWjUr7V~9R4rnWG4WHAak&mT_JXOql41!~N zCf0_4G=_#hII@@-HNd8(irCE)U)S?wLRjY=4)hvk33Vqk*^%IU>GFW3`qtylaH`+U zzciMk@`t^N`>T%EkjKYDz^|YgO)~WZRQnh8yRJnq;xw^H+(p!5%G_4)txlyV{`~iM z!1TbSwm@fC@V{`%ay{tki{FR;zbI?eJ`|AUid2_2kCnD*`uGAT45&&r{uT{Cu1N6z ziZ%bg%-Jhc`a7HNi9JKR%e&sHx%|S&ijzfJY^DRd7YQ~#S)=A*VuzAt9M0bIc13dR zphAXqTPZOeB})waDWBK1S|S5}M0|4p8n3J=hpL&aZa<k1pHAW)oz_o75Y z{EdgYCzBM>V`)(&xRc<=SEuT~njT79@q#-1jNd}= zr=Gv2uX|%MC@2&xWb;k(C_H#XJp;&(9KVkJT3S6zVJFrX)!LRcc>IWp zg=phz9aqP^o&)WOMzVzS%iv&~3fd-Sv>)D8oQ=1q{+RToLByUj__KXzH04rQ`H##( zlaRqW>u(*2fyEc&OBJqEo9~c_@z1WUUMeA}+3(M0+oiB2`=q9QeCSZ=c8W{St+dvW zqS-kvZ$!Q2SUb_c`AXtcNegPGJmJx;w2up(zkpl@853uWIoa-6ggM6Up}Wt==W7`B z4p4NL^;Y&C9EzTb=+0pe?MB5OJvzObodrvrDnb1oy8dM~O`b1aMJ3k1Q9v*;L%|(S$75H!yS}UJ=s<0&*35@|EI_==TD3QSr(*KrJ0?!x zxwzp7C4I?Qmt-7mNfWQV2@QAQ0DaANEaQggrKTLSc5U5jjovRZIO!IMXJ!QgwHrNF zo#xWj6Z85VD=d{NQ;N^5j6z72wV7Mvd5CFKbUxqekT@mUn}W{n=MNy941^RDixh=m ze=u0-N(1tH=&oAJbAz;`Ro4N>u;K7R$eU=0JOsPt-|8b^wU=xyr)D(6Il1>|H85|q zJpsG&yXE&FS;AA#Jws*@`BriY3rErPuiwRGPF9MTO!!;UPs`EdOh1|0quyXQrp4k< zM;UtzFeL#2$pChI&l*ybax%uAv?mdhOpWP^lYK!LHbi##gq>a7v#DIOv(n3GGuhp4e?Ay!`y(Ce= zS!PE#hn}0LghTj#oVTZ(*_;l-Upk3F^PJ=iRE*f7#@Z4xUN$VVnKG3xCvQ#=v@xQBW=z}Ewz!nw!s6D)C|cwp?ps9x`ix<<`>LFx8f0bv-^`Tapqmu>`hA`W1^SK z`3jnI-obNW9NPR0C8c@~MjRQTN%`afdJya+cGz349F%xC-`re${zE;G+oYHSe6k+m zC?(p$%q{ZBx63K2gdJwjzbO;zo1rk3?A`QE`3DoGdp6)aX!$NUYj=O6N%F$unA1nnaL4i3X(NS$rGJAx0Je#UdN7qS@{ue^3%@EfHsVvFKVv zCEv;Vs%Jc{WHd(`xtNkcf|;zed-QUdLPm^EXe>u4)u@31Yo9V`wF=~$p+3wDJ>CUslA_m-Gkf7 zLxRGHkEkvd$KKf3D_NfE%gW+*!=vXvXy#v-SA3MUI*aDa#{J~i9r}M%8XphKFaIwW z@c&7b{l9o2wsFu8$eMeZy)5BcUmG1fw%gKy+`2ahnI5lMFBZ-o>_*wSbht%UeSZZL+Mw3fU40 z1=fBVP0A|_rTHbr+M=h(UrPHDuvw^Hg=@05H>Rl62F7cI^N(ELu`_C|CuSi-+ze~_2YUQrxKiklbYqsu* zcyo`nm`^oH-Rft@4A({+^26d$II@X9XVP$l7*N}R&fSzit0tq?F(0f~I4C!Nl0OUz;X2ziuZ?>HtQrG?{OQPjwz2BfkX@IHXeo z9e8E{3BzZw*jMJ}KlgD2^V`(2o6meMEXOSCFT=8Jk|ZY=js)YvPpAEeL)t zgKzuyo9991`$!KL^zG1LdFGABSrZlC-6&c@ckBxihWJ%i$Xu(M1 z(@RVmxgYP9@t+8|^l3Dg*J(Y)me|L4v@zXm5ByA)g|II%bAA5@MPAL!>2CRBeR8Wf|*xc$O~fyw41$yc$JCY1yZca}7f$AE4lbt>xAQCpZ6thACQDv!Ty& zyXH&p-19F(?Wd>Trv>gDjNLhF_;h>BIF5Px$oih%hui9v(C~_1vVXVk8isE$L{i}> zqlJ6ij-nZ=oGD~+D|8pSD3g}>&h-AVsGt}JcUDIqH-keo@gZu{wty40SwHEH#rv+! z9sZV)&!p)g$aJRHcfr4==7>&QCgkb6hUkIA4N2XPL8O|xA7w2f%MCyjiQ!#>t-%)x zjHmfKtnrPWDwTpqGd8wGQj;dSAC~tsj4@}g#B|{PypP_c-(rcwNzQw}9_fQ7Va9ZO znNF-3&$`EVqM~w-8Ympxoo1Ufy3OZm|8?2$Ce?YJZf29CzrJbtew*8IOxE1-EWa65 zax}&7TIqiNSj3t0U)v7Lwd0LS3xbgWZ#PV;9+j@HV)mrgxXD>O=j~)zGmrDye{SO@ znJ2|IJ)yaoCsFsO#lX-yyu1V@JB{DvJtcXsj__aa4@J~kgzEYlM4u78UZp=2AAfOi zk)k^Q_cAryHk6=GxS8sIKkCZy`79MT)mC;y=OyB4&43sUR;ro&WwkId=Qml4U&kD zBf~o{6wSe^$C1(fY$9cg7GSDy>S(d6o+Ll-y!nn%VZj%SNh?&RQ1M)Rz&O2og=yy< zi@b}nzh||s1+_K5ym}_@g^`3$JHuYM*T~NIIEFa^E(eIQ@D$zUlTCm6NGNjW8R3lU zKdF~HI%cPjDd4ekEA6^<-lqn%-hB?+XlN|DjgB#m5+ZT^nB=M#$}gt=qtZdRp2%bV zZq_YA$Mj*&NQt!Z=}na$uZ2j(HCZUE?S}N#^a~FhMVrC0S>O@&`T7hG_i=sNMkm4A z@idzvjz-T@dMZnH8l{K-=J)966H{Z1%TGYJeR)nLZ1;QpU1Kpf?cP{l5!6N9(C7xs zgMZ>jw?i>KGddntj)yaURZS&(NOOhsv|1%56fnlk4uU4`v7}n`6%7j0dG*9U0j+vYV zqmH6)urU3bR^9Wa+-5ezsJtZdKB=wk#?DUb?$i_`ef^=40PQ~rOGQaeuJg`a1A zJ5+xo3dzsOX@woHB0S=}OlL#q`YHXN{<9tFAs(Y#{4N8ee|igv9_^Wzx1*70kvOBC z?%4P!3vV^)RacsWtII)B%0rnc(|>(wHs6YZ zohGO(BnElj<}{AqRWx)u?xTy$n?m-8(sE$DMQ`ScUWt*(#EqJ3S%d@YIiZ?$lZ@}2 zD?K_23Oj13po?@mK6bxtbL(oC6EQ{-kn>-c3XE|p2cbN5o*xZqy+xJji4fb>mh=hz zAuT4?wwbQftQnu;7zDP+0M;B5A!y*FrY^VkLPkSr&U$D{Yueo_XHuRzxs;l zdqjrsoHkH48Z3k*kWDMioSY6h>CJ!lXnpUcI0z}mM?ThRz&XVR11b9=o*0qA86S1i zAHLr$ugt~tePurd#n*MJuj{KKeSbeLXS-IAPw045Y$mHOP*xeUGG8q%=37x-^o|TT zl`Lt6;^?L#@z&k3U-sEeY(4q`DvN*FFCC6S;E= z<&S8i9N>hr3ifx5D%TtyB~HJ?9TK{WiFsE|kVn*&FHkZuG z@@cO)P8)Lop3=`{r68jy8dKprS7=))6pI3BZ-mTs&HVsHOb9Z#! zE|Pl>FPx!W3{Yn9XKtP zWPbxh$YH0bobtTv`jU&lV*I(L^QiEKw+%H^-R*TL`~GDW7KR3)4RL^CUG1=ctO@92 zEZYI5`*f_o2A>e9JLofO|Jo22zkfY3Yi4G)xv9zM1ZtC*c}y7}kq{L)BvE3`!f@tqq%oAwA#9@|l_Ub8GZeGNSmT$Cf`ah% zP~$W6JCw{J?W~UDnxRkt$_-a;A%0I~0SGF82mq14n&PqM%5mnai;H{^(aZFXSOf}H zI^5lwm^4PM8PgaBjWq8;?j&5wMbA64I=dhH?7EtKhr7EnxExd{nX$PpAWGqRy^Pw2 z$?sMag_H1_^KQ==I&wE}zig~JJWgU7f!=Mlp!HF>=P3$rQe2!`CG`Rw_&mld#9KbHCp zs}s4>zw)K~NsS|;v!XJx)(3J)c+>AL2WKz7f>@~kTU-V4I#Tn86+|*+C+`1x{Kzh1 z^g6po#w5eAwTBRReP^RexIvllue_w_W8}~VQ--UHJ4H`+0ya%|pmsE_8oGOyPN#gG zLJvI9SGvhapd1#G-1Pw0@6@(k?{yjsn-#$RlzZO@t35+*WUabNjmty2dDv|+0pZ3Y zlT~MD7)K-DT4fq4OfU`!B<}w6ZQFepMnTL(3Z;$=m6B)2FEX&+@MHxnq?tZp&K5Y- z%_v#eX#|bu#7m~L*s7gpZmUH&XRTqAiHZJvrNloW^Ux_<67V>ytWZtgjaE$wu+3#r}&ae6v zw6}XiiJrLn>n`~0WU3;qBrWu*2TN;?ci#H*p7*!#I{#v|Vx6*XndME9$y~gPRNQi@X0xLzRg6J2wtpxniK~ z(U;vY8ngKOi`$q6CpQzA_A6f#Z?Grvu7NUipzkL=8|Zq#m3DYv)O)Gu;zND^iaGo9 z<14lK2+tn=q{p0L8m1(-nsWOu-Z#JU-EnMv0*auyS`o#|R_DEB8b@QS>S|l&sc7pL zGl*B8gO%k`0?7z+{>*q>YJF#$93;towfQI#T~(m5Fk;{%=H-K{qR}nuVjC)yGy8D z0baNc+hsmrxIMbjZe(PP??K&kqe&FM_l)xziIV8mqy$vawn!utNxs{2eCjC5cxFNZ zC*LiJ#NEMnbw}R@)l1>WGjP!}`1Sh54XULj{vSWA!PkA0d5yg;?kNaOI1$=@=R|Ce1lN0E+=Mb(+pcNokMWD!23eko7W1J;JdydEbn-nXh;V=HXc5e=^@tj;JNfPK~9k<5ZuN5=sL_a z>pF51^e};`lRpH8Q`289{nMR`B53807v08dRLLih))4I;J+(^$K%Wcua7yqC?HHEo zfnPF_ToSIL!Oh-m?xJin6hI5Ne<&v8{o z^paq<*E#J{rXh(;6l+AKD{KjF4Y=UiY4QTn(Z>mX_>*_pvM)G{*^eQqq z@fy7QsKj~bq&-O z`#t``0;yS%kpK-`>kWRzfi1w39Q~Vtm~5@Nr2qx76g*`li20P_TDw(v?-9`2a8NK( zktP?{-bgJ3i!~dtfZUHyj;UXHH~Cc4qV0>xDE{AlMIg&hykj)uCh*H*SN>8a{7j0v3hlGY8j@`VZ<#U=&}^px=NaRA_o-d&o>H4T-7iFwFTJ{bPaL0r8X@$ zV2#b{hv++?#D=PayJlAiZhm*)RPP<~Q@ETM-Pi=)BHN*&z@4~|=a@Q8z}m_FOl9yd z=0RgISSCw?e94i9kw#^x6U<=GPHWuTX1zw~#8&7wXfP3=*+!W%y^>aX_A3<%gHVby z@igv|9)>xuja=cbTJiRZ9O|{?HI!D4B_0@oKmf?$5RGVBf8T8TzY^>c z^#%&RcEfe%{1+z}wGS8Wb^oogqu~MnJV%#+5#>Iv8b5Y7+GyPS3Q5SrJeyVZB_yZ* z-U_a(tGjV)Z_%`e-=jv@_)^+tbc7@@o+%S45ct`XD_||)O^+T&*zivAjK|yYi^NZI zx0iNrFDk9}r$8cJ{~USdy4p)8EQZ*r2jxEE>Gn?Ub~83jt*`U+T$frDIbe!TT5O;p z@kF*G6m>~v%N(0mYfv?vsG6YkF_Ok9q_+WGqmz-##ty|vT~kx@^wboNZ2RCrKs4O{ zWvJ&HU@6AZfq714oZ3XF;rV$OH`-)=cFI&}^l5;U zGEuYDuliwhikR39}z~TXCPi^g(-awO~_R6#3fy&c;H^EtAq~KP-=JEmU6D+@$uo=*^BS`X?r=F4d1X0hIBX@0|XMf8g#>B z9)At8{pG9*1c&!;R*$xREQS@CN5HY@!O>)l0aLzJD0KpI+T2#^yCu ztm@h4H8BexZ^(vz92g^ss!oN}H)0p6J{@DsdI7-OU6>sh%sAw70A$sjzU0Bh*R zHnMJSsJ=fwJgtw)SRz+~^zyQD2f4XjZ*r>m7vN-#aB-!f5D5dk92OfFR(h@Iq`OBw|4X_hcoDNZ8;VB97 zZSB`co_O_zr3;;DieBdb3~xO~yUp5T!Z|_7DRKOnlUi&1Humdfp5Up?xo==VPH&vm zw57T1C9XHY*$W9hMI#(e5dY%4qt8%fFKvv!fagEhYeG1?+XqHQ>1T zEm~YZOh?<|W_oHV?S2~k;&$2QYMb@S^EuwwXjX9@<0F0}^;ns%~nbF5v zszsa9dgP`P3pXb09Cd^J*t4Y(on=X;O?k=`fGwy1I`E?enCQ_p5M%r^X`Uny+7+L* zExljIRk2#h&#L;?bNg-lbi5nDn}WD|S=eqnALTW3MPFxs)>a>I{9(RnY}9Dhv!tQ2 z-hiboJ&NNx@IEYVUvpW6(@btj>0>Kdq#^4I&9lPEef-yY_%n;InFt)s*-!#KojBCa zD_6H+^-*fK%;O6`Sw272`rwU;QQwYlpvDo>2|FMLf&`-hT_+Zg+&Hm?fAa$rhxt6= zfj&M?nU+LqjC~SLj`Po?I%}8$bVZG00uu#inylDw^R8>gcbkf%e;yg*piu5Dcdn1w z{ESMj?$7}uAf!9uN_L5LGfj9FM+eE4ihjPXTaB1AufB0Ru~FJg{yT@s<*j)@u=W$f zanni<6|QU9`OS^pILUwedF^xxTFQNz>VA9d1po_W}2{uzZk8Lv~twHrsp{pCAmoKrBI_9kbrKoLJ8Sl52&x zo#UIKPP$!@icp<|0GLoMb)&(i@vXEzIY4-CoJs+hm_BSQ0kO~?G+tX?LP=~^4onPZ zuF#*nX`gT9@|ZOA>f5GgOy9PzXIswb^_Os9B$lMR4cP6OBAx$WnWy)w;^%RVmHF@0 z>@;5eZ20qY7HGPh{xD8lm2w4aMvOzJ_s3r48~&%nn0sA#_HGht#;v6^y=A|#VR3nN zV-2~1Kp}Pt57fb!I?s(!qWPbb16k1DBF3}gQ*`q}9Glln1i{{|dDsd-!xT8Af~!ED zbM(CX{b|vgcrA~y#zCId+PYtoWjJ$2j}_NBq%C9GS@Vkdg4wc|h!d_Fv&ajyWw_e? z4K)tF2~rYwoV(_zaC2aM)Vv`j!gU{R-q4r_3ttlyPRsbazOi|ql)1IB=VVPpd8U4j z!4-TQc~KfQhoS@O3*y+-y9{LP#u5jWn^2L5Kssc>i^f|FHMV7q)uV1)piCnl=X98a z1xmO7y_vOSo%7Co<7dVFB)-rkS(*G+pg>lwrDy2dVku*jI0GCLZi_zUf}xGr-Bm04 zj1BYIBQ^eX&dCD0;ke}V1_vU4RJzB9?=6xv+apHfbMuhO+M;Y&UbgCN*{Vc+ZIN-! zA;)ZNq={<^d@w7M`ysmpf^h#9Cr=}eSoXvj`I}GH1fMk+jL7tARJkypZ=BSoR)22p z>7*=w_{1P~tae?ulUBB*n6$I+*hvI=D7gNpexHpfm;00ug--}s&`s@skox6A_fl2bq>U1f%c5rlRB^760E|PMq6dCidfcK?^q2J@PINi+I@bV ze-!QR!Pe;I7$&x|y2`&*--_Do40^1O@dO|3LfU+`v70;Ciq<@1J1M(U9%sb-($pDI zaPBT$)YIZt-Qm``8!xNH+4|QCZ?=_vY5VC-o1%=AB3a7|kNEUUyk5T&3V$9!g+4e) zS!8_Z_f#SG1rN;j8Zm=#$^(qCgwaf1eS<0mYS>u3$#^D@@YuFOZdHP6Iw(i(Bin2& z@%w~n!K8qWw9TE(ot?&~UZQ8-2#?CU@3U$v0S|%otpsDarx8JA%~~zT9~vU`s%F2< z1B4Nsr)LYsHn*4?Qxt4V*K#p; z(+z%V)}A$#+;5sg5$pfftYBohzhgVx)SOo>2)^HT@BRZ?jJkOMP<(C?W8@czBPED= z`ztcfgXrCEt<)oG`Yj%Ver*=cUVD)^J7vEkd_u*WerIwzD;D;yx_K%tqXqT-?MInu z3CXAthv^d~#_8i|f#9%_@o9%QZUHSLHsiClo|8opy$@z4zFW0^xb+DS8`PbZ)+_*?_7_BU)sgN8WrDMx%zr~)%HZ9Mm2%G`46*B3=I zx%fAIkS;s;lC!Sa^`j@32J=#0x=CGqnnA)IcIEwD%GS`Ce(T=>A8H)*yAF0w>ty)O zM#55=YxEUAVB;OoPsQLe9ts)U=piw~2@1&UQvX*V?`!*=hrVO>>sLtpYCr%jJJ3^P zKfu;&$T7l<@4>-TCMYKSgtanLZJXD|<)yT_g;qFb8ZX^Fq_-8aHeE^7x;hJ>8yS0KzAT z_ZvKGLOz0uhX6#K2Gp0+E+{anrRms7r@M{`_iHcGOQR+3X(EA2#pXuU?;2mrNj$@< z%?2~rs{U%{1L~s5Q5L_@Kx+a%#LeI!p|Np7)39&Q_infxP6K$ABAw|5Z{XRXembTe zfbF}3!EE!`mq2N@URBe|kXGbPet1!1QQkg`Y%}7iaeI12%>49OgIKeBX&QZQf0W(3 zaEJ7`bh<0h2OxSNkPmVX&;ZGt8UKanx{SMk)b|5I^x%N|fpZgqNkYN*t{EYRu}?oE zg(XGL7Wber_nQpCf1dH~zUqqXPZ0)~2oMYTbtYakkkx!d0=ePi5lzP2;Q@3klXIt$ zY6iZs(d?@aC_q}r){)r{1&!Et?$hS&Y@j&l2m;wzpBS1+YAP;U&qye^A;n4mn8vhMnSeuZHH2VI`68F7tO*p8*&J6amzbS?*Z2+btl z41mub9!5Hv8JNTj(FIM%!{c%_F<<}+;5MDRfI`a%{y8V2rM`v+3@l3=*x6^b1)HMz za#GbxdG_-*S-$~o`<(%0#hEaGgWG;1m9vzmxtswo_FsYJU>c@jcP&~!Y-&Ow#@mm@ zVsI1uDAD7Ap6EX0jfj%FXqAo3W42%2a7?%3VRQs|tf) z7mzImNeIJ|hOMDf{QH`#(z6lu#A3&8$(&}YeZC1m$`?BgP-g(R1o57d5%5`dXJzCK zJYe2=x-2dO?BXmjI+0-=Fr^GpIGowNcP$ustca}e6}}oF2aFixekz=&)* zxt6~5GaiEXVlJEFPQKBTFxLtA@ zcKD$NU?qY7ndYUEUb?_9%LziGrtWXIxK@s2z)v5PXEm;GwtO#MH+_0s4c4gytYQqO zNRX-9jh8dJr3^snJ{TMl3@%e)Tw0`9`c5)*db3eA1CiC)a&fUJe~3;LOPQm)cODxl z=~{%F$2piFuuk6X+JU&`?m^n}#&KpIII(dj03c0+4;uB{JdCz&TRCU`2Xk*77027H zi4rA(yAy)D1-B3q2=3ar2A7855Q2NK#vwp(8g~y)W5FGQySv|N_Co5`^J7k4`s=VsaQ z#60ZkUFYwjrmCu|j?olC)o4>KxSvW#KI>AcF+ub4DjKtP zojJVy&G;{T&bc-Dvo@mta&@-K;_v4ky*`y|{J>vt!n-{`K0OT109Ft?+0Ga%$;~p0 zp1;(W#7Xm&gY)Kvcb)^6Pd&$mnalGl8XFE|AwS_g_;(lmg)Ke6<2ZRFM7ifnw<73X z>~CFM7dxkS*EqMEfYTW5G$=RDdj?-@Hi|NY0y;*5oGAUV=t8y+~* z4SC@s2k?1%>{1N)wBoYFYm-KVJCoqD6$3;e;5P_o zcwTZK0H(wsULTXCH2x-$*e`?J&hTgRY|l^6PJ)*tWT#h{GRI8Zn~Sm=Ov^K`Uw8mN zYo_?{CMjVi>ltuOtwG^6!fn08mG-{YX`so4iIsGz8tdj&G6zHV7z+VG5zEWw`{91_ z(%nXp^E0OPjcWb8TgxlNPO;OC*jYDY(Av>4^2u@8b0OJzok&!|G%l3wV^Z zHlsWWmVhaX@CD7w=B?eyVff{g@R{i&NbDojJIhwWX}-kUMV>DnxExRW0mhfTlb<#i zRF!b)GSaODtOJFoJvXyEz@~?Qp!BpQ@bldt+#S*8aWSM4)x&cG3iv93Z@{Lm>29iCf!QZF^A$>s#-5X-r-P&HLic0xuM$BH8o@)d zLS+cxP^9+Tm736q!zi>UPlC;=E9F*F0-2=2N}O^PSg{ed6d>D}{_3S0IR?F$Js= zh?hGoejD=a@yeHEucQpPS``(!7zKzASAX#UFBkA)o#No`M|s0OdC8a}Aa$(5!=vDL zBQr0jM0A6P+N_}4dNi$_UL*^H)*CvMm^{lzVh*1-Gj%KAl1@}l*_V^G4DMbRcx z;&GYCHs4zRn2u$6=nJT2HQluc@a;8d(;R9U6Ux{kM4im^%B-qjM?C|ufqa$Xs z((=N>!tTPtQcqJMtMbWfTwISL8x)Big(3*A2ka7D63|we7i4SC5JHz5+qFXFXC1OM`HTE3TKL+DoCnccr;Wblw z`GawduKj$g-dXW}t465y&pK!Kfqi6KppP02G;C2&)u4(KpU|il;Lu3^EXv@{Aifh< z?h?rRTWaGs8+zRoTZGhNLgwj9V|Zuj!r~9HTUsv=aaN45m|_yMfE-GH#Jzk*I=Y_z zUVu+pm5=%>H!4B}vYkHxq%BmbpT79&5NW;PY1H4%!L@z*PZekvA_Lb^P62u_*jO&m`2$WQ<2H(maRwLNHE+ z4y9Y~@<`W(zQhWA9J!Av{g6ai4)$t*Su)h{zDwjrVjxW&qw@h1hG$xe$Lau6>K7HE z=+^)lZFJUpM%&O8L?N{}?!^2gYQ{4qUDz1KRtcd#2!$rJov}nVQ#72rrq^q86g41v zyHC$dF<6`-fN+R?q2!{40^Xb%Y9LoI~7 zf7xgaE}8Ll6aSEj)M|ha9JEO$eTa!`lrlD`q{B950_-Me1JyN#Z!9j53)!9?NaC1`syC`gK?(4j z@%|?2B+;?~G&&(NNhiq(jW3dG;`rw6gXx#QZ(0--aW(nX^gJ02+)sb37hP*ILvk02 zcS~toFy=9FM3qwc7&?}>HxgdBuE#dH-QsTfHo;qGLyE^Kgza!ES94Rr@MINW6Yurp3q&q?k-6{Mu&Y;L?~j`lj^t8R9yKz+&Jh$Ghn~iK=0@(*|LA;LQLD z=_h3;E$ZdCFIHr8ezFwl4sM`(TVYlfVj}u+<~1P;fzNZ&4im`+6wjk+N|E0bGE7$L z6etq_v6|J`puw&YTy^l6M;d04>cj_6dyA3Lj86{mg|gWlExBIr*7eG-?>U=K0Uo0z zPaZ!eJed{x<`>p(63$R`_Qdm;*iDHrEv?JA%%nK1B^}!wCGLnZs+b7u>1{?BbzHQ( z93J?MbVns--H=SqfH@4S3f$$N7#)Fb^G={n#aaJPmf2aqC4>?GZb)L}$x-R(nski9 z7gXrKd;#6MhIi#|l1pP5t*MyU=(Iq5tA%Z)_ovn&m-sK(A05I{W7D2V#t#(tdFW>v zEn~ftokNF46NEG`VM14B7Sm(hR8`xh*gpRt*ZgZ=9TfwNQVX@r4&+wj16H3RAeD!j zA!-Z>g(}k|f1F7I&7Ux4!RJ$jexVJ?&a)5s_v;v=6han$c!Hd#NuD)DEQshcSaI? z(w*EoydJ#oVFBZbdN$2-|NKX?G3sb6>!?og&lTGn%agscF=AHIk7qX_4BXF>D);j8 ztOuE2L2BL${9$V=XKgEI`*@pM<&K%0a zl22jS2aocKU4RAj^4Vr{9Q0~@O^gcpw92fE%#L5izu9&cPQt~9)U%+8cH~rQlGIt{ zN`*Ah$$8cjg_!FMYtA5l%>>rUgD`vim*d*BDOuzT- z-+lym(;*)}_6={B0?vOx=g|Qk0{Ji3dHnC~EB`-LCSh73gt)j18w(q`!!^KP9^42- z`1|!zsiC1k%NVTcsOs5-N4L@*v?2FYt z!keWZuaik%2=Mc}xw~o227JFiugZ`uDQjff-g3usc6PQUHwk~*Q1|5Y^z`V692q{e zzhQR&pv-_&RmFn0eEJK}U}mew8^drwh|oN#si~PId$zH)#g*9CBA%oI13RAWpPbz9 z{@-kufWL)>@|Kk0$7uyxux2{=j}VqbJD8Z6trsZzU-}%T@CyjsUrRTsdw-?CED8KL zExSMJ@D2RTnI{s$GHXfS;RpK_so{qZ&U~`{!EfWhB%=0?q_NL-;TQ>loX1=ne=W&( zFdhEMW`LXiI}a^dZ7IgRsWC&|AEf(~2maOnXBE(rpY`WcyIhq_2NeyMe|4n}!By+S zr>BKR^XJyKT$Oi+$A_zXo(CIseDKxko9)@f@g7xwg~b>|ux^j>poC$%n4Pw_bn>g! zUZE;M`^Plj_fCljoI=f2J4*eyoOc6XI)$385kR%(hdIlu;t}=B#Q2(@OegYq{!3k|`k2i!383pENAtdHeXS`KNP4 z#8|kK4*>yMV{=Xk;wZX|!8~b14_*rlUw}RanI5#H3VlgrT@q&1lj?Z2FyCm;-^1on zw)&T>q(kFIQPkmieNDb|je~_rUt0UtR~Yx%m9?j6Bg)-rYX5aZ>tKo}AN9Tp?$y+x z^WwWt;y1X;B+&@LP_HUD~NeTt;#A`9Ji7rd!E8232(Eu!Nx zq3WEoy>@4N{%E>u=(6?hcIaSGwsbofHMq*tY6mUpnwgNalD}T%EOF;qcDp;v#^bce z=WD40>IGCxMl9K1%q#bhbZ$adliFGD3^q=$W^o&fF9>zc_oju)a2r=;O~zhO!a~=9 z4nEuDJMwV8l!X+lg7?cj%UXZqr_49;?nkc_-kv&3A_7^7Ntd@KqnJ~BCdUeJTj<-% zts{D&CD0k+6}wO4Ys`;>P1a36eQJxE&hg`)RN>~dN(iN5IlC~tP-Ao^Ttf_*qHR3 zXKJF$aBPf=upo8h1%Lf@IZzcgAA9&S&dtk4`IG>1L;)`wEpy}te>T*|7-=fk$E7VsDyKQqY?!pZ7(RQ7( zMcwq?H?ih|@?Z-FMQ;o8+FJmkm?5 z^VZsElAIvf{9f*1CMTZB(E5Kg%9RQH5Avudv1Tth2*}TUkwBtS+gm_eUvPw z9|cp;G#nQTV*>)5d1p@p{?`9$(&o-+RB*RqnN`8NUN=ju)}hk!wON~Y^X%ViN_JSa zhsc@WLtp9$7MHDlKfe-6zNjqCl{%?ub?GwChFB9jK;+Sj+gvz1^M~%vxEkNwZk^3n zCXM|rV9&d|kTbLtxXFf&MdheYM%1fCCtaNWywVfJ2O6Ur8Gn_T%y{Ey*ysUMX3*GoprUn@(~bdfbTf~`SAFZ zm-;&oH5!1o;SOhnW`V(#UoTdZd^hT9YwvfAzbWgSe84#y_$`fsW$|yx;HQP~l}&CJ zm}(|fZhsUL;{%7nFV|chS(4w7p~W3I{8%s54n%x^4;HJbX+D78>l?)l)^ky>-hOkR z4%&SF!>eb;2&aE3N!(}~h(b>e4=-&kZEddh=G?bLV{CkSd~9xPc4v8mKbdrTnNgcv z%hAf>zEORV2NHe^+1bJi$FI^p0jwjC`Ly&|A_)n}?(VLYl~p0#{f1SYEU0X4Z7nMs z-s^Q(herx*;EXOx56vQzuXqA~lz%Hj-P0=n%LT1xR_=h2aNh&pI%vH9{9DW_kO7(P zpEv)%S5y0cWNX>oyS{9w`IUf<2fF{TuK9d@XJ=<)XJbRiqrB$~XE{6AkWwC97#%YrMa=6H?oDVykrUBSrL3MS9 zKjX7y#)sULr5F#1oj2>-C=4&sW&|Fd5@hSB3iAf~R!d7u-)w^|k$QtS2tpc!*-s81 z!MgS&BobyeVDKKBY~v__RUgBXw5wmeA0ec$*?$uHu<@Ub?pZ~B@eO2+-1`!#@`6z) zx~}twJpAW>a&ie?aN#s5D(X1C6N4W?AOfqrvBzuvYy|(nVtatJ@O1w_sHgykzlk&M zQd0+Eib${U0U6rinUp6nj*e{{F+PCiM}Jzu76jpX5}+W&D=^-A(3_{^1)m8H5P*G?!#?_WoqvjahkqpMjc(g&UX-7h1#oZ5-o=}F;u_t9^Sr-N;quxZ@=5NW(_SpefQCd=L&*2IRv zLLGHxzs%NL>IC=nPs%vFy9@j~$dm`V*=uqa6L6l@k*U8d>M@*Y~g|z8=9lvT`cG+)agvnfejj9ooLO#p; zP<@*%G*NB=xJ;HK*i}EuNomUYOcCK$O%!ePMl4Au^RoQ4`Ij%i!*8XBe39!$>^4G- z4d$Kdx^YFA?$Bb$uWp5fZflwksHav~j5j8oM5HeqM=YJZrSOTl{+N~k!%mr$a+$5^jrq#qhm>iDmvULsdCCL&m;22#)g&Qt8m<}u_CTsGww1I z&xI(E3MExm@Esi<)TC=1uw@4F-`*GouqR*7WjC5Jd6>A?Ppa+UqEGH?td9?LHRuE? z-41ao-!AgWdY6q!l<^yExhG*P^|zMR+dxDnez zHWPfen^`B!Hvii!I%vZe^^Ll*eYQ{V2&UuNt=w{XbS@yJu0jHY) z>&&>8y-w5lw0lu>{NhkkBKOfXpR^vT&@yE$^Np#_LfUKnjH$VOQ9owPVVpzmS^-W|WA%m2+gcQ*ON^TJr_^D(vI+mOYPg4( zz};nP9oiYx$ z3Q+@f7&+_Dp^-@$Y-kCIVJ3$Aa6T>qRmn1kpZbZ(tDcqlCX>x^ib5Artm_FqF?FNK z+n66&Je*#y%Nu&^29LVEea${XMHBqCMCbK-su=CI{guPN$P1s=d?0 zi#K#Sx2Hml3@N>nje6@Fr)~~uPRhnDS&KFFWzmDq&yN!4!|pC`pR>xWQVJwrT?mXK@G>uYgbdDTlb4J>mAyX(8zqI0d9)BZ}X-68f(}!mtxdrGPj>o#Ll1LN8*YF z(3`w!ENjX$yK|Jtv*LLqR&x*(SK)TmsOo4L^crVr0rNGvTipPu>&nemazpIxd7@)c z03vBn<#xisMQ9)@Cj^J65Hh(7e%&fCBNBVpdWze01zjN{X));_s!&WkSSC%JPP}!K z&w4!n4e{erCcXZu38#hI^>BpX;+&m|@BX-|h9`~jGhad4w&2RjAMIo=@!u1oNv&J& zZX|{dq~k#;QX#=1I;rlu2aDON#-HjH>d9r84HTSB8%pXqc2kG-M;2l|YV}M|2PZ)c zgverz7Y*Lty!M>Q=vC;8)Ye&L$}ja-=3UF|PW--zXw0nnkbSgI;W>J)1+i zUr?qLFp)2_q9*xtROqQZx~I#Q7E_3$QJ>4-=5Z!M$%W3ac4GAG)WYelZ(B_oZ_TfI zlDH~+o)iX@Or%y;3I~$(sO-^bGK)O9PGyDY_?4J)z67Z=lbjuu z352LlQeZX->Z_NYoZzNtSrqZ_LX|ukJ|N|5ppslx+~k+fEby0+MGR=%MZNDT3y#Ug zNhFw4(&q5Uu9x5uU&V0_R)u-8+8G|5&l3W}jV1F}k$M3ae)#porOAE|rLt;04zp*! z-uyV-)~EH-JzT*f-|Fg6--|n5yS;*H#)~c7fFA>vE*r7Fxr@~$;yxXgu6kOUGqu;$ zxLaaLC8f%%=t4JUCwrb8E*3Q01{lKV2Q3$Z+$I)BVKOe)Y53Y!sBXKGsR^Tsp1bu% zs;(D}L%gZ}To&ioQe^%3LKt4bx4fs1m=|_2lgjlzEq^iDW>qB>s;^qsOmzC|RkKK9 zH^uJb`br{IvtE$R?xqJ9qRdsk`TgyOm{f(W?#MklTGL@v&x&46ZL4=Lo4$C5^uSfI z6W><$2t@Q~Gxu_BBK0h8|IS;lVBfIqF=2FF7}+w1C4OIA{BYe{d70?c_=}&1m&v|w zw~8DlO8{#4jub3H@CtG}OM=Li87zL+SJJ*Y!+e0Q-Qsrn=d3{XNWgRb7}GEAoI4(U zp1<02IRZjfyyR@{%{(@h+sl2_Tcj<4m3~Ywdqi=+q$XPzu&w))nEjQV7wHUTW(W?y zQeF5tbJ7`--IVRQ5wAS$Q7MtEWPCoiht=n@5Lk5mmrEzYwJ3>pk{kuH3JsiS-0a?q z<)75J$Qs1rxmK?@jP1#fh3cGSRE06#`SM)6bU?_SRJsTzUMfcNMZ-ne6Id==7%aM3 z`ao7&G~QHUZ5^-PTeiLBWPa9&c;{@O?=qCRN9>VFq1zgZIqXOgw0mlfD{=Rx?FovE z$;7#}CuU(U%`kZ9npqOoCa_#ebBeyy{<;PKgUa0&YKm49_lRur?x;nrz8kw^nL}uo ziO1#F%Tjg78*8&X{*+kA!8|gV4!x2I4#wt;FeNk}*aq?#U#l=(jW6P+1fVK^hvZ)V z30C&o=0MMi;uFebnzeFTr52LV!O2xMP&3(z$s2F1Ii2^Dd1Vk^(e7jj5Nrdh<(Q5B zu5OHf^*G{dsszgGTptCIGN59d_(#Ti^b9`D>~E;j>f9U?L(xcPUkgo$?i!1N=_eeH z;%akWw;~V1DmKHfy^W?c;Y;rK7f49a^C#>#$=F0x(E&NmS>}xT7_}}ZR=6V3d$aLg zHrM7}$Bw!55HwA7NN8ui8g?{#7oegkv9TU;ZM&C~2O}%Q5%uMJHPYLbqZ2mh( z1@3U(CkiH`-QVk6o2@l>;R>z@yS^_8UVu?jL51up${catNWr*by@wddqn)11Nt0f; z75m@FC=6}OwE@d&?#Qw6UANQR{XAVbJ~^$atpOawwKX-}d-|{9>-RpxA%oPP_g^g7 zih@A({B|XW>17p{edBgbN7gGXwAXL-7vK8U-1a5Y;y2+sTJi6}+Yo0{>*``}mC_&} zkit9LN66KNl|JlDyY*4(!>mm0PWQq6*8lCwDB-N!bT}Kp9#hlPAnEaTti|lq1$K8A z)mU{YPA*=nnQG^%1%5z|W_XMY?jL8NJUOOo&tU?a=*sR9h>Ba&i8%|Aiy-`LrS z*!wKSd5B2nNz@{KpIV7vl~GkyCBm51{eJyzn@EJbhszDn;+dJ5wq>%tuT$k49+&us zw?@UAiAhJnFj5~(6VYU(EjTd}p?aQ|ukYrAR4S*I%BV(>PNLv!C*X&d_K;4Iq5&&e z%Q{-i7S(&QzC1ZSnf+qT#b;Wa^Xwl`8pqcMpg#b)0^WsLEjw2QnNBuw2S>-Mii(pH z9+L~y}q7@O?NgM$D?ZQ5HA zW)rv;_1~PGWf@KzyOoJ?yZLl*K&R_dFLX1t$t~a=pS}WviVPLB- z=TN~?Fhvt|`f5!niRu>IF!Vn0Cv7$j7BApD4=U?dI}N#EuK{! z(}yTKQ5o|wR*?I>C^fSMpc|~B5sd(en)#uuu(F;G7wHZi)%Pe69#IK&-GXbgUs;A2 zJXH?u`7tP<)5mA}>XkG$<|ngsVWoy^yO;paxA(|6j$fk8vZ53g6ijvZttR6%nwDwC zX#@jzbZ1d-<)gfMY0sEK#nqQk4`I#nR(9X^K3rT>l3E&wAcP|8or%f|MAoMppQ1`j zw$!xrhr6@R&tJ&jXsqM<9xC8h)s&nk?+9lW1umaR9H>qzQ-T&_?5wtadLid_R;IuQVuvuGL!QoXxBsOM zlOz~B+Yaje$tayq*}kVm!@jub3yIVYElJkL7 z43&4W6>Ut&RVY%#c=fzrK5qRTEFR6K+g6SB{R59M`#RX0O9<;p^xXoI`r&qwb!<%7 zEz(6)q6v9nY9h%xZhzwc)GUyUrH%gJ$s96w&qJ~-mkQUoUgDmOD;w2$*RA;DoTk;GD+VyN3INw2)3kF9sap< z?fjNi#(Il+_-C3~_Eh7M;h#%`C*xYAV9^~N{gF;C|K=K%titlWWm9<^&jv*QDq?Nrshb zXm@+8=`1vHQctpcNoA{$ z(p7ISCpxKB}cwzVnl1K*2r?uYKuo2+rPk6ag6X}w6j1&**rK* z#lI&nzJbN5?gL>a-NwP$4h^4&p&^poo}fS1q?fS zX3`An8SwY>BvnJ=%0(f_zJmJxA-RM0~37+ zG)EPFabVK%$SF@6N?5>%b8IvsUIMnqx0e#c%NJ$B@z9dV<>CxpWYb-UR!l~U1mg^{ zw&e$&HXJJAFJG(%5uf%9&F7dD)LF1;&2i}$n|CRrV-pw)$vu~}o}V(jnJ_9OgeLOlu^-Ijy}3Fc5RSG@JI%&dRv+s`iCM^aGC*m?AX5(2 zev6qE(BtNCAA7KAqO_QW=+1`7%%N@RfK_s@@HwrS@$jAz;Bd`1WqR09WivIH#XbOu!KuuyJf$V9j!ykhsFlPhz<>vYf(t$#Q?ox=F#Qm!ElNi&vYaLH*%vW`+;117# z0}<-tiL3YvZUZ$_aBw^*p(E_}V*R5qevbEGTm3I9i|*>beJR=34KI(O3RL4@cBrpf zQQ8H>vTWCu@l!B9Rq>47r}Q=6by~;o#@+jmW0< z3hjk8dJwSMj%H9W0dZua*+>tykg{TK+jjyxKE)KRmM`IkhO5j=E|u~7&{rHAm5Q$% z&I%J3w}q@m!?M}IdA4wF_8`Y5BnicNLn`HESHKtupxg_Fi)&31*6BA%6W+uX4uB%?mDzLOhav2Ff-D*Gq+=pA**j`gy;KpyG~kb?H%DZIwil zb?zZbb9FS8A#4>iuqg!!hH-Z~j!PeJF&zJSAfp}#JGjau7%4rkOvo6^(*o#yQSSaF z69la&q`Z_kZJ$sDq@Fjm)IVOKC%3h61@u1w`rr(iJFs9g;Qv#{A=C|GP?ZfE9@FCd z^vlp1r7W3eV&2149%Nt6>n8u*=3_?>RJ@hYsDu79_EpEbE6ID-;NL%AsE3Q}41gr& zYb`b8IuN`q)u>5|WJs@HT9_7MI`(Q~)F=C`LeMQCYnR*6inPLoe{}GDE?BKPL{kjC z&(&SV@ciSR1MI$j9vY3AYPEkBgkwbZAP;fT-6v~GFU)%ef+@`lD688z zBu`ES&{@ZZbO1YNvEh)l5F0(Y{%!hY(h1M|OeZ)?FF*^LLI)^~8uqDodNbyK-IJOx z!g)eZ2zskvk=X(a)J>TpPdq@u`;e^Ef%Rot59{^oqcJ)Vpg+Q3AYt@fLVe3d0pBRC>9d1m zyq9s8jkLPLbWupFrg?m|k)gQdU!X<;<<$-hxgSmf&VI)u*p?=YIX%(;HPI_0bFzl4 z@~lvX4qTqa^2uFY%YbMl4*j3oC;f}UWuicht86@8ZVm5e1S$xCcV!ZkjDLVa2ku!; zQh5Cu{x-Tu%gG@R2WGgISRVq7EQ5eKd>C1$s0w89>naJ~U6A*F z{@K)iEj!<@HUH!wJKd+ffCLbfJcegdKiiq-j;;( z0z8+KTU6$mOfQ3dHqf!?Xco2-0=4cRymG%^c&wP=^`gwmGX#K&N50ORs-o!)th_wa zQ*p5kJiWikrZL)}6c)s`KlP4Ir-@Gvkdri>`Ml@4&h3nS|Jd7^tk2k7R4n}pn76eL z3EXa+QcJ>9(qcr)=X{kd2_VH<@dGk=e_O^E(Sh$?bggFUS^r`VX8jTejAQCtd=Sy1fPGXUg>Ip3TEbFFQ0m4!n z%ym#zu;*4c948aaOlB#wp|0-GV&t<`b#{G>Gn!{#pNOOrvHKGdac1Dk46{Ux!3jPA zU^d}Ie#l4Zupi8dn`R2zdjvq=H4{f+m$e>}zI<&xUs1V`7w`%>iqIP*^7vZ1FBF0T z2n_isIM=P1ro$_FUD27hUqF!JDNs|CF-uJuWCjF*x)au)!0M9QLpJj6?`h9y zg-0|Nl=#%b6-x^6Ma?L_D>wCru ze+la~H1J2@ndD3HxiLe6q2W9kr_8JX?xB&TFoq({%3CRXuz-uT09p;q0N?neBp)g} z^NG4B0B=yl)S^aBg}$Q~P^#wEA2PHVToC>9FLFC;FUJzfiEWl;IFMl~T`7u{Cq z<)#6^LnaPW0d*9i;BRZFm+o<9s408>hUhA~Owg_fFU*i-c#BGs^mQv7soDR~{z%hG zg~-cc8CkNUN0x7S0;DB8VMZ?1F|?C*FKy5AaU*sO@l)&ZQC_F z9I+SfZDF+u#FaSeUJqythJO~q-+bT--wSMXly~Oh8>dTY&21$?{TjhuetpyI`XIxX zg~wqsu~?W)*RAYFkO`0q6(LHjQs|KTAEy!d-ektsu#f`t9mcGl=jb>XG}~3YIER+2 zJ&sCo)CH|_$@)YoWW5^d_0?1>p<3!}8_JyV7>RbI9&R5SrxFi~BNwCF>E_VVbPCMa zDmYhAJcnd=wMNU?kAo#-PvjdSaHm1@<+DGr2>0G$?gj-CU0S5|mH@zvVeRFs2=M`z z0SOeP&_nGm_+HW6rkmTI+s+HOQ>A6=mLw81s(WTDr%6Tug{{JnU>xO)5j>y<)$ya> zttlEr>AR)f_RczE`=9aIj%~&88QOQ$GKy-Md~IZ?h+<7%lr^4fIQ+T{(2_Tefy1nc zbd5@e(Zhr!)GDbV>e00d z$`W7WEzx1(pm_e>ho_8;3>6Qe$fdHE3*gY~=^X_qzkPsbq6NM=6x|@98*ZgV&gn%T zHoaFS_}S{BRfRO_nyGGu)pmNYRzCxW&2_mL-s~^Ria#vWi|RRJ_MFt532X*baS7&f z@bQ{+Q7ENp-P7lI!^$gaYex?+k9eV_h^Jt+j`%*=%R-Ehz|+CV zqeByd*zPX$byUQafxNHuVZ@I?Q&#wEro^uJ+HqCsErM@`ss1T5n2X2CHjK8ZGfb4E z6_*`?#}TvA6NTDz+iasaUGij!Uz)t=gcTSfJvA`-e`B*GAaQ@tS#<6J5gEw5@&Oj0 zad<%$S{`X}vGm?r^0!6{w~QN;rERhDKN2)kluPNQLwF6#@5Pf#;i}L)hK9zUzj->f zZ9g#uNsJy@PiV68|IMXrJN{4LOr{HvtWf|e-bjiyI?5-daH3rYl~!WC>~Tny2o+q= zb=%mlgTwr9t^?vCqDH#;JZ8|T7!n3TJQdUO&?9~zNmC6CSKych|N7O@tLUPTw%6K{ORKf&ldQb#6fZ1wg7sahJdo# zCaf!=EDRAucl3K(HP{QtGDWAY9GZus(DpUDldxN8)1HP`sOndOzGIdFSgRkN+yL_1>>Li z7|j3R80L{iA&&r_%ijtNz0E+#C2`yMBMh>C2(d=Q> z4;Xh%6Q^BJA1L$29mQLd!&aB&eh*1lN`U+t@GY-T6+tST5`qxNl2n1R(2UnrNBnT0 zn!4kkLan;#gt7n7e#IXI@&6m`_d(hCo9O?5#~NK{6N$L$!xR#Da*zn5ywCzS#Hg+9 zjWLncYE)tdJ|O^XU6(5t>$&aijSckvze%qa3MRoS*Qv}`h`n!tY?UkwS=rg|{sxy< zz-Yzv0_4l;>imm}iv0ckZJw_^ZCRJ?1l%%!dZRTU4&x4BHq5}-SQ&0_0GUbMy#XXr zuqd~D07bOGw!Tqm?k7<&_)~uq1!&0H=B9usmXy4fmN9VJ$D!$Q`lR0gd--r68@QEX zM9tA^XxP{LuO>H=?$kDt-aD-v`gh*W7l9Ir>>c1F=B!s>6O++Y*}7_=5mes+0k@Qt z-O9>JfEc`hBm?r_AM!srl!V_Bf$HT$WsU!7So>eYzy)5N-+h5<{|fxzwZdME{vQv4 zxiDYaPw*Z68>esPDnDh=R~+TAQlWc~7Epa>EcT&RUS;xqxmb&>%Kk_#V7RZuhPl+- zhvx2ewWbo00FU)kBI0TvMF9TQh}ibHg5LtKvw+zzf6^ML=r`>x(W*Klk0u3=4GN2i zN{15$V|wT1`|6E~%9pEhk6SXqBhI>Si?gaBieZjD1#vYW{e+e_yeezTPO?g`3nKFt z)*{r+C3B7VsB84p5D8mXV{SXlScS&knB})MIB*Ny6yI=muzt^@0_wGleDuz7xmck? zzxx;le_cO0#;{a1mz=61pJ(*1t*VwI&4Vj!)gY3ZSf+X0hR}Lqcqv$ShVBENrPbhI zL{u%Jiq_}_VEocVf7h$elmNklZB=xH67o#_Qz{sZABdo`VLztfQLs}%Zwn2|Bi*@_ zwXwDeENsh<&J#;peXEOgL_QHqMKw#`cvEU%4fVrZl={n z!)gq#UH(JttYVbKlDMD~nV`w#0jk|+XSM_0N;6$rpw_W}eHc7+=7IfzG7dlQt6x7d zYI5CuK#RxBz*tjJyn5<9L1Exu;Vc`xU7AQsrNh4w4Ij1z6h;YZoMNF`;@&MTDFEim zZW{fhd|9phR@O8&Hdtxe`r&z^G<-r7oDb<;!Eu>s@S3e(9nx1m0hLK#bavuHn9$bf z4IN5=wOJ9Uerm~-0U#|;Ntc9cn82uoegI0~?ri`m1!-FJIr0RPORq44Q0L)LY1Wd7PHLqFa+=;5t~9*|x1O4n z0P`Z7k}oqGyQEzRP8Ab2W*Gpc=oIrUZ{ z>rcfg&ajd9i`Or)PznWko!9>FaVIOF$U}GZMD*4E5_!wST(p0TgccCzKd}w~8MkP$ zMnFYW66HvqNzG7)bs()zj zjKena7G8f|*4T8C@B>nbamdoJR*T6)Asy`$FeMhME#YQegxr^3VyOViaxiy^bB4^3 z*_OFmw~RSNaG3aqmLN$=Yte0!b0Sgzh2l!Wa4UFi``1Y;-dU8t&Ki@b-_d~av6{03z3u9G+RNkQ43Z-kdGGvllZxymdwkml-Wz0pgf920UDz( zf-~7wdb~e{lRB3gw<)Dc^s+18=zoD)Z*6Rz(8}&&=(t@g4vK9>nQun<^7eeGq2>8< z9318m9>#pX?v=h)OOUS~Ddj1qyZJe~=?{4XjYfPdz1JXEj;H2Q>9SKh8d!HkNTi(u z7>|WraF?!$z)k=OARgTgdd>b?zy0r(yx+H1K#YEvvBPsk zQKv_~A}2OPghgL-5}NH^5kRI2dp<8D!OFDyHfe+>KdOvCSD<-eG8tcFbI~M=V?TWt z$fH%3Q#Opu@E?_L=KV<)7oX>1qrs42Z%7op|F;2^Jp=tS-k)badshxrHfxTaXO4Sq zx2>s1H#Z(!Wz~fv$#ki2IjR=J22OKP^W!gi;$;cz{FL#G*r_h}=3K9FnI-JY z`q2I{buvgrYa6ADJR$*))J$lufaLP5@^pG=H{Uh>CQpS_!;1wQ4RVmp>@(>rPDpN1vM|WO zR#_%h)1RU_qcZRwF<4jWXyev$awB(9m4udBy1P7}q z8;VL8)f$YQ%b#X1*KYST9(H##YFdQQ{(hEF?%;l#Ca-R%VUCY?``(J4ChT;?JbY^0 zo%Q$->f0Fj+gcn-93wVt)-bv?N;*1+8M!c#A02>a0Zn=rmhfrZyh-{)EK$|GuzTe> zRC~bYc9v2B-Y}LpRxg&xTKnU}pEG4&#P|E{IE$*!ZbthR!^aimC(@|~Hlb4ZZ~(dg0b}*kSU~*^f^e%q|rUBRg{`O0HY>*S@^Z<;CIOKOd3jv0Px9 z-03H=kNLFgMy?ZHAaOSms||1ax8Fz!Y@740KeJ&V z&>DuNV*H<>uE3uK@^reJ@;_;HQ00jmgvZBdxnkn$(R&yYKl89YvkyYHj#gNcmv#D( z%XxF+A%8AP9K#Mi9vgV8Mr_MI`VFcZ;B~T~W z`578jUD?jf9@=C0QC$f8rhpG-nLx$M9}fyoakC_C(GQU;i?jDxA#mduf#jb$bu6?G znmBR)sM1LNv&d2X6JCk>2T6%PAgShGDQMnqtUMPz=HpBOWxb%{_kdoqj?Tq?!b1YwbfbXqH_S?#-FSl?__5VYJ((yA722)2brEpMo6A;K zpp(9#cXSp7p@+QmNn2R(G`sN+7s9JK*wT58Fta-4PpVCf4>v@5o2j|1wVMjB{YU-B z3$^~Q=Ds_ssczes^NI2)MMb0vh=@u@q$pLTNl|)@6sZDIq=h1hC`G#TDo7`Efe>n< zRO!7Xgx*^K>5y=D;yGvBao)S*-S3V2&UpM4GS=FAXRS5YoWI|1uDP#qGFG)Up{S|u zv6Dt$>du%BNu>Wj4%u(!%1rC`F}S}x;(xKb_tT?vhZb$pEDJG-Ub!HMoYs6X{u{}?G@w`7lkR9|B?TOWu0BZ=N?X5 zQl^ZZJ<=!#KW3A*_~X@vc!fRuV|`PD71P_M zymE1Rk|&&Ku=Hxv*SP;R==*Fc4SyAvz_;f4$6ul^R0s~wj~?bR{jA~Yyp@FzwV^NM z{9s{4)Q3l!Gn`tGvX%p5mW<^(6r( zd)jw-q3_Vz-SlF#bAqxM)zMcqw@aAUuq zrr{~4*TkW?>2?!*(T*x~p zIo<8?Rk@*R${_Bt*bIw$3&+Ms9oUH8P1|;RJ~2C9aLz|~w)LEnN&C?zxAMC^C&Fyb zP{3;B#-$K`X{ff^ts&@8W+Cq2i^NsBx}9gR6jOZvaK=)oItA)Wlr?-Q_?@YxdaXs& z@%qT~TOHvp4$3Nq6Q+V%p6V##ad^)nuDsIN&^@DvYR|(EmR_snu}y?6o!(|H7s5{b zs!?gP$cvY29;LMg(8m*n=Ac2J-=~w+@-2id7rSD7oHL;v@c2f!kjY?d89_*?zgkgVWPKhwT4NH0jb32 zBOSVqN<0!AD0~RBHFsG-VyYI^OIt8pzOBYQcz-?z)C?6`TAS*x-LySE;w#(kZ3JRR|Z$$8#a^@9TzAy0T0`5eYVb0DhN^b1Sy z0H$Mln~p{*%Ue-vPc(jBT=7L*Ek);=Lind`hRXIFyR5cxsjJ~P>P$`E&OBC5ueJz+ zTNWe8lX-Fze~oKt-s|Vinu!ZKP95l)mDup`>9oIX-SJu>10u+U@|Di(hn~o5FZlV= zei1Hcm9o9}6L&?L5NG+K$g^wPQ!lNw{W4fPE#vFcU4`IHodp}7`@`_Xd~|*)wpy~r z3isYMw3e*Ax$OMs9kaZ!MTyuy^q0H7jdIR79ZEvnaY^KGvRu&eJt~HAkM&V+{0~<# zE_p7ggM=Bw-0+m4_Sm;?Beu?G%=Bm<_Y*kWo00=4ujL}WzMo-#Iz%E_Z)oF=Q`M+w zd)zVFaY}S;s;n~@+pCVRQpKz%Dgvmxi)AHGeud&zH#&lYm-aW65n&W8Qj!NNi2eev z`?C8DqaXXb3eWOq2B*$~)x5K-{dJ;7d&%RVat^(YQBG1ep)8l#yd-ai7De#yP}p|p z$`QnC=S_ZZDV|z$rmvnLwWbP7#=gAkgjJcHMlT#EJWgTjJ+2iDoGpe$$*43N4M?<@ zqcnW9!KWhyFWbK0knfV**gth~Z#~%A!`zM9M{-nvGrf<` zCPyHkWbL!^9V>Sx%WAWHx0qOm)iSvIqAMsqPAHL0WW^rRwurTvf z3JI&-L1R{ytE#D7KkN`4+@g3vZ=C$FGshZ{~6N2_HQz3=0fSw`WJc8OA zISqa?J8k8-6kz`j#I)F1ug!9j0kHch8+&f)bVzxxWB#yxcU zM*zxO*H!s645!b^4U_qKX$I4TD_q-4^_pSTZayxMlfFTd_GzC9@++=&x|*w@=;-r^ z12mnPvN+lc40pTOFKbM6msTp0r-=;B8VrcoEJKI`< z6E3(E&sWnY%}t#PAldWY!y$%BPJKwtQuf6+CW1?K$3vca-$1LOv8$Pc?LGh4PolP7 zuT%p{ZD4C7jTS{eQ~_A<4A1{-jb+~aWZryk$0~k{M!bE6cWz;jnhI(*WfTpAzFqHm zaWQQ~`{i<@UvySEyl0LI9BPW<(%$TNzw;pF!@lSvbAJj$4ypaANuI;DQys2#xZ~<2 zC!UIDiZ9kr9M=V&s*rxI)}69WZ;3AQ zWkW$~m8?&F$(`!H?Kic1RjTUOQMBOV$GNip$Y*Gq8V5dfXZJdIb)F0;xw|SR0plQUj@IU%jpwA8zwgbh1 zH;WbMRqBYx1;f_DmE{yqjTaAIIH&a98u>2=P8G;cpu zj28Yme4x&Lgj}ja3kV}i0kDiDuyWkTZB5x@@;%}f)+cOEOq%SES-I#Q?$=Ife&gCX7k6oz5~{o8E4J5O zy=7b*5sRNm4@@BM5U1HcovyiSwcHHtI9)u`MxxLY>FEXjV`nwyYw7l^oBIf&828<{ z9DtY8zf0wK^4;^>dV)$M9^4`mQvC-S{PzU@{}mSfZ{B^+pD}yY@+>!MXIOHdl#rSj zZUJ5)GCjD*hdvQ`9x2AM>#{`3sK2{{!$guwWMQ$9&(v$E)v&$uEgs~U6IR8KY}03?33|}-8=qH0?U-Y;0-paEYxO%0 zc{Z?S|Aw-)^rwm*4Xg4ttyHm%6!hfivsIFNEs$0%j{d`;-`ir>WD>w13m0;pcnJXn zw?i2sW3{j52Z3X6(C9j5Q;#r&iJKeS|2eE%EN63hp}i!q0xwW!AgmH(-b=)Y-`@;q zCPA0RQvf$7ERn4!DN2qm$HMFi0fK{zmd0smBAN?}h^2_Z>e}synCHG8I#+O|)#_mj zLW;}Q%gg5HqJ-h2ev_)rN_1NE(8Fj=`tA?}2Ino2!*MgCXD55X{^cGCi$-RocCYdK znMk()5z%2!>6YH;h^|k3CB2TDns2+#!jJ2YxDZMixxueeWse_@tYB)?rN?k~7gaup z3ZhNojkDS58(@|#m85D$?|bjLpG!PsGqwE1xKIe9S!AHUmku7VTa)U63221`A>t_J z6cqV+#UC~sL^@N?5HhY35#amYJ2uMh4*NODr_0eOOK?>KO0>MHtR|QHwP}p&i94`q zFPNY37g8t*jwFcteqMleh>Bd3X?$GG%l(8n=hF|KVK|5Muha!E8>?W;p7wyB!yVKB zXP{IL(46LpK{x)K6E>kLz1t=EGLggBDgT!eo-^ZNTduK@s}-o^Iq4er#rD`T&KWZ( zYL|0zA#e2RrBqb`3n#NgXUbM_x7%9A(VKR5Kgo_gq0uY!PA^UPh%Mfa`(5H7e)w!bn0{c6mozg$Z1`{8EH?O%a37ANob;R)k6iy6KGpS>FRn(N2Y1Gw~ za4qIM+2-HrC7nML1n|+o_h#1gd@pa4IWsJFf4NKKjwb89)pp_$|PRxTlSJEVXh=sm|9wb2S6FZX#rbS=w`$q{`30BrJgkhz#ozNfE)q*{y z)y|Vr7I!KDt4M4+JZ$>`?maWFHYGFjUbVmL`0{Ky%amyc9el{8O67BTW+CbT1U*HN zOvqD>@qrbo;Mvv~>D{_O;i3PpcjDL|er3SU+h;cKTE3H3(k(gRQ<}io<-0s#W*jK4NUv3P3HhigXTKn)mX^#nozaF1bi`z*~R_>29-7 zoWn=Vh99SLXXqYI_wKN>?rq|QwhVL0#Q-rNUlJX?UgFXMKDo3{^V+7NMGl~5Rxfg` zI2fZ%Vy2c+O`fnC7KaXJ?eu~%`l&hIA{;>M>2gdAJA3#%wecGG)zY7vxm58xKTQTR zByvxQGUda!@L*NGECvik&L@X#CSHi^tLCy7VN5*!=SF;=?@H|# z8r7}l2)uTh&e8|P#i*+1T|ae%EiGME#^t=CMwYV&U4y{SMH}g~CEzu=L6!a9EE9u! zku})tC$Wvz^W((#wgIP7Vj?kZbBu7_0GDtlZq@M=`P>Z zxQFXkMR9cW^StTjX4A&>?^uD1oRA}%_K{Qc6}n-qhO;3!8KL2@KF=u4&JJpt`|(fw z%A9Ve?&j(YGp7|A(hK}pFuZ1I>6t!3t@J@*y{3OW4YLvbc^;Wg=x@&l*gKcrNMNfW zBLj2W{PMPyWh+^v9vVfc-tG*@)K)xU#(dc3Ex#jn(|uh4TU&{rp7et$-e@o|o!osA zZ*v^3j0**Bh-_gm&v|~-dVz~tLTUFRQ1EYtb^^tJKRTz>NBuOk}1?aD+;DN zhs!`kUl0%Xx5kztUMXFpw!4H?orj}J=g|9h zr*<@V;C;J}Wr)722CTDXlROLMajWW50;2uuiY|78`trgGTveL7?Or{=w>U>&ho>ul>vn+71W_FsZBiwV!EOR*X+yt2e`Cq2 zsdFYQZ}VY+gIG*5DB}Xk@A#H)2yFim^Jfj8=LCM15FTIVV}}xu$(x7gS{s7O?n%P( zff&W@b(xOSpYHLcdIWU;&g}Gl#*Ym<)tiil+4}WwYt-CmZYXQb&WfDFVZOD#El0g6 zL#dsWxJ7u?v>5M$;q^kX7}?muckGnpjn|*TrfQ66zVK2az+xT&F4st6u61sfu<1~@ zMV0N;2kP_3@_oowT2sdWM6DiP7@g%L7ULbsc|yxuKZ)j|K7XB6Soij+A{fO_Fg;|E+tY+Pn$em?+WS30p&UPv5R^t8ZjZ=pM z`lRRG(qO#);~x@5N8;ndLMT4qKQ;B+Vvv<2rFy;(YRvyr45u30j=Isn_rMiDs;?x7 ztTXG+^rB?wAxb>NKgGKJekFDqnhR$cR;L|7!WP@A@r?UyjfMIpr-LxWd19NbL^Ew- zMNqQ;*CNONkN_dgp;+*SWx_G?-9=zAPJSbmm;FvI9J)5({mb6y))Z@}j9IWR0Bwwhv2zrjU6c@T;Js1HjwCk+qRC7H3n{rDR5O>|;@A9kn z%Pc^YR{DTVKiwrMn!|@>!UbjR`G`0~U5U|nyS10x-=05W>kaJBJ#GAO+L*j~SgczU z!Ck7>jeQ-8PcvNaW|l56(RFrnYZ#pi3o8v&bQdx0)zWX-w7egv>F2EfQQY`W9Qi?A zngA|#m!Xg}^)eMhD$^amM3MU%Z!EOx$<^0%pZpy;6xe?Ol`DSq3&uUN<<8ij>qfr? zB;U`z&m&x7Q<1_*l&&BL%@E=OA2D>ka^d__#NexoY1Qr@mI>Dg_fKk77VhDkaeeI_ z&rXO)!a;W?Gt+ohxZ`-wD0A{T-nGmWFE3Ej*-12{=~usS{DPle9EOSUaP-A*ywHtg zH7w+da(<{Bp#(c&h1z8Ps2O3a?PF!K`?}Wnx&y7Ij4f-VU3Q3Yvy;I(8OEjYC<@)X zl6%MCDJKDwd_W!f?1MWxUk~NFx5N22rZqlQWpD2#nTCEOmUs9#d$7lUV~GYA?Ndvv zZB54309gIh(e=qybgf-)^axB352);+IGEFJC#T4X2NU&;;4Vol05Obiya3*tPV6H7 zD9s3QV`$_GeSMo#!EG|KOHYV-0Wo?Y1`}$=1-`c}VD?X&Ous%c(*1d&_%z-hWAkm1 z=rj%q_TY}P-#{k`02b2atLoVRuE;OFKs6{H{HPi{txa*s7~)j0*#F8RI{QiKmwcyf zAJxGE`4Dpu*ukawyiv#kGUtwgr9AOTrXR>-Z!7W{rqvcb3&m<93flB1-Mxm&Z);lK zL_1WvKR%{avWgScg(cc{ueLr&hzR8fOBSx3T3A#0IlS+E5i{&uC1NrEQ<48xLFmPe zwcQx6Aj$`PgOQeCMH5cV@pya6%KXrCxEUAX-8_dX@n+?mrO7|H-@t9_8nYMm@?DiE zRzoXc*~fq${&Yk1XqA@&?*lqYvW@j3X)f_{HX>?d{<-NLB#wh!;shleI6)FeyKhy6)vIY(HMr7wVcznF`&{wvA zg53FhQjM#bKGmte@k`ewLc@Y6V0`YwXz{)4zg7jZIuo8ZV3++PR7}l*joc%p8QEjP zR1C{DD!mt2>&pecKf1fwWmq?pG~EKW^7tt^0gCU7-aVzVP9o0A_w{)`Za7kxkS`t` zOyo7He*>0j8Y7+#D~}}KQqu89GAX)>!l%_vZOkKoBXw(f50Z;LX2&!=4GYzw%l;3K#HVZonFhW2< z2TikIi12ki9Y|u)ZzET%783)^10c)hVp7CQO;P(ZLF~-sE%}VqfkJezb!GkmShPv? zk;tSx{uS~4#k_-mPxs$c5vr37FE(OW)v7Ca*>G}ZQaA{;zVhXSw70TF+)&@Ba%Nm5 z@@EU1)Lxx0AF+IBR2|FC22q_MjvdbL<= zczw36eV5>y;0^D?mrps~1Awdr>aBi>oZ=~qCSMQb zMAD>V!Js)npWn>|1aCQnsETf(i>~gjj+;TTw4juZliYBI>x{icXIy{XGAr8?E@EZw z?>1WH?yxW#>IroHyCg0*ThHUpQ@N~i5zThb9{7G=z~;~S0lyN;EN=S;T<%SmRKJQ# zpwPiZLObE(GZ9*+ic1EE zE(-abd6z+`U-Dgl-fxxgJ@$UOhg7)?cQGI#9!{xQNJ1$u$@GL=S4V`mrIE?;%Ejmr zVRn(&^@f}`W1295heajD+A@Nj0q>=l12=?u(7D2eai8^uC1No~y4mMzhR8$5UVg-v z1i#=^5yIr@(mden7u)O2T+*J;j=qcFeWi}!9*Qz!Vp&OHgidWeOKK`GhNSNlT@-c+ zG`1m{J)<)A4;}1ojc*ra)=}=pH}%!5>V&hjZ9K)P(H81FrGX!X2JrYtVnx!SrrV%5vs~wceW? zt9HDHKx8Qas16Q0wl)3eToEUgZWWK0k;UTQKh{U_Y>9SJY-w%<+JXZBuj>b&5z5g7bp z6|-cO#N}zE8a&ugh0GdAFqkPW!RFqeNZx7UrN#Snrl=H1yBpdaI8HeLLxo9&v!q=# zd8gk6D{%k$NUODOow##Ik@rMvt$`NyTKUdW9$`ZA*favRzJ2X^LhX)JCe>Tn5~|dR z?OZpndYq385;_iv+^Wha&Y78tHKl(GOG58!5bDI@w4dubBLBL{DmV2&#OY!T2Nb(#)(NA48Y7>tY$HD#RgQ zqBuB>(a)dwyU6ZXsogx?>iA0fhb@HUPYJ|Rj6jvtk~{LihYQ4D@*Ek>WR50V6+evF OP+8%He92R@5B~wvw1biW literal 0 HcmV?d00001 diff --git a/extensions/usage-insights/copilot-extension.json b/extensions/usage-insights/copilot-extension.json new file mode 100644 index 000000000..be5833b6b --- /dev/null +++ b/extensions/usage-insights/copilot-extension.json @@ -0,0 +1,4 @@ +{ + "name": "usage-insights", + "version": 1 +} diff --git a/extensions/usage-insights/extension.mjs b/extensions/usage-insights/extension.mjs new file mode 100644 index 000000000..3aecf5e5a --- /dev/null +++ b/extensions/usage-insights/extension.mjs @@ -0,0 +1,206 @@ +import { createServer } from "node:http"; +import { homedir } from "node:os"; +import { join } from "node:path"; +import { createCanvas, CanvasError, joinSession } from "@github/copilot-sdk/extension"; +import { AgentMetadataStore, UsageInsightsStore } from "./stats.mjs"; +import { renderDashboardHtml } from "./renderer.mjs"; + +const RANGE_VALUES = ["24h", "7d", "30d", "all"]; +const servers = new Map(); +const copilotHome = process.env.COPILOT_HOME || join(homedir(), ".copilot"); +const statsStore = new UsageInsightsStore(copilotHome); +const agentMetadata = new AgentMetadataStore(copilotHome); +let session; + +function normalizeRange(value) { + return RANGE_VALUES.includes(value) ? value : "7d"; +} + +function json(res, status, value) { + res.writeHead(status, { + "Cache-Control": "no-store", + "Content-Type": "application/json; charset=utf-8", + }); + res.end(JSON.stringify(value)); +} + +function notifyCanvases() { + for (const entry of servers.values()) { + for (const client of entry.clients) { + client.write("event: refresh\ndata: {}\n\n"); + } + } +} + +async function buildDashboardData({ range = "7d", sessionId } = {}) { + if (!session) { + throw new CanvasError("session_not_ready", "The session metrics provider is still starting."); + } + + const selectedSessionId = sessionId || session.sessionId; + const currentRuntime = + selectedSessionId === session.sessionId + ? await session.rpc.usage.getMetrics() + : undefined; + const metadata = await agentMetadata.get(selectedSessionId); + + return statsStore.buildDashboard({ + currentSessionId: session.sessionId, + currentRuntime, + range: normalizeRange(range), + selectedSessionId, + agentMetadata: metadata, + }); +} + +async function startServer(instanceId, defaults) { + const clients = new Set(); + const server = createServer(async (req, res) => { + const url = new URL(req.url || "/", "http://127.0.0.1"); + + if (req.method === "GET" && url.pathname === "/") { + try { + const initialData = await buildDashboardData(defaults); + res.writeHead(200, { + "Cache-Control": "no-store", + "Content-Type": "text/html; charset=utf-8", + }); + res.end(renderDashboardHtml({ instanceId, defaults, initialData })); + } catch (error) { + res.writeHead(500, { + "Cache-Control": "no-store", + "Content-Type": "text/plain; charset=utf-8", + }); + res.end(error instanceof Error ? error.message : "Unable to load session metrics."); + } + return; + } + + if (req.method === "GET" && url.pathname === "/api/stats") { + try { + const data = await buildDashboardData({ + range: url.searchParams.get("range") || defaults.range, + sessionId: url.searchParams.get("sessionId") || defaults.sessionId, + }); + json(res, 200, data); + } catch (error) { + json(res, 500, { + error: error instanceof Error ? error.message : "Unable to load session metrics.", + }); + } + return; + } + + if (req.method === "GET" && url.pathname === "/events") { + res.writeHead(200, { + "Cache-Control": "no-cache", + Connection: "keep-alive", + "Content-Type": "text/event-stream", + }); + res.write("event: ready\ndata: {}\n\n"); + clients.add(res); + req.on("close", () => clients.delete(res)); + return; + } + + json(res, 404, { error: "Not found" }); + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + const port = typeof address === "object" && address ? address.port : 0; + return { clients, server, url: `http://127.0.0.1:${port}/` }; +} + +const usageInsightsCanvas = createCanvas({ + id: "usage-insights", + displayName: "Usage Insights", + description: "Inspect live token and AI-credit usage for the current session, its agents, and recent history.", + inputSchema: { + type: "object", + additionalProperties: false, + properties: { + range: { type: "string", enum: RANGE_VALUES }, + sessionId: { type: "string", minLength: 1 }, + }, + }, + actions: [ + { + name: "refresh", + description: "Return fresh metrics for the current or selected session and time range.", + inputSchema: { + type: "object", + additionalProperties: false, + properties: { + range: { type: "string", enum: RANGE_VALUES }, + sessionId: { type: "string", minLength: 1 }, + }, + }, + handler: async (ctx) => buildDashboardData(ctx.input), + }, + { + name: "inspect_session", + description: "Return the overall and per-agent metrics for one local session.", + inputSchema: { + type: "object", + additionalProperties: false, + required: ["sessionId"], + properties: { + sessionId: { type: "string", minLength: 1 }, + range: { type: "string", enum: RANGE_VALUES }, + }, + }, + handler: async (ctx) => buildDashboardData(ctx.input), + }, + ], + open: async (ctx) => { + let entry = servers.get(ctx.instanceId); + if (!entry) { + const defaults = { + range: normalizeRange(ctx.input?.range), + sessionId: ctx.input?.sessionId || "", + }; + entry = await startServer(ctx.instanceId, defaults); + servers.set(ctx.instanceId, entry); + } + return { + title: "Usage Insights", + status: "Live", + url: entry.url, + }; + }, + onClose: async (ctx) => { + const entry = servers.get(ctx.instanceId); + if (!entry) { + return; + } + servers.delete(ctx.instanceId); + for (const client of entry.clients) { + client.end(); + } + await new Promise((resolve) => entry.server.close(resolve)); + }, +}); + +session = await joinSession({ canvases: [usageInsightsCanvas] }); + +const initialEvents = await session.getEvents(); +agentMetadata.seed(session.sessionId, initialEvents); + +session.on("assistant.usage", notifyCanvases); +session.on("session.usage_checkpoint", notifyCanvases); +session.on("subagent.started", (event) => { + agentMetadata.update(session.sessionId, event); + notifyCanvases(); +}); +session.on("subagent.completed", (event) => { + agentMetadata.update(session.sessionId, event); + notifyCanvases(); +}); +session.on("subagent.failed", (event) => { + agentMetadata.update(session.sessionId, event); + notifyCanvases(); +}); diff --git a/extensions/usage-insights/renderer.mjs b/extensions/usage-insights/renderer.mjs new file mode 100644 index 000000000..8bcadd948 --- /dev/null +++ b/extensions/usage-insights/renderer.mjs @@ -0,0 +1,904 @@ +function safeJson(value) { + return JSON.stringify(value).replaceAll("<", "\\u003c"); +} + +export function renderDashboardHtml({ instanceId, defaults, initialData }) { + const initial = safeJson({ ...defaults, instanceId }); + const bootstrap = safeJson(initialData || null); + return ` + + + + + Usage Insights + + + + +
+
+
+

Session usage: AI credits · calls

+ Loading session... +
+
+ + Live +
+
+ +
+
+ +
+
+
+
+ + + Latest model call + + +
+
+
+ +
+
+

Usage by agent

+
+ + +
+
+
+
+ +
+
+

Token breakdown

+

— total tokens

+
+
+
+ +
+
+
+

Session history

+

Recent locally recorded usage

+
+
+
+
+
AI credits
+
Sessions
+
Root agents
+
Sub-agents
+
+
+

Highest-cost sessions

+

Select one to inspect its agent breakdown

+
+
+
+ + + +
+ Read-only · local Copilot usage data + Not updated yet +
+
+ + + +`; +} diff --git a/extensions/usage-insights/stats.mjs b/extensions/usage-insights/stats.mjs new file mode 100644 index 000000000..04a0fe94c --- /dev/null +++ b/extensions/usage-insights/stats.mjs @@ -0,0 +1,470 @@ +import { createReadStream, existsSync } from "node:fs"; +import { join } from "node:path"; +import { createInterface } from "node:readline"; +import { DatabaseSync } from "node:sqlite"; + +const BILLION = 1_000_000_000; +const RANGE_LABELS = { + "24h": "Last 24 hours", + "7d": "Last 7 days", + "30d": "Last 30 days", + all: "All recorded usage", +}; + +function number(value) { + return Number(value || 0); +} + +function credits(nanoAiu) { + return number(nanoAiu) / BILLION; +} + +function normalizeAggregate(row = {}) { + return { + aiCredits: credits(row.total_nano_aiu), + apiDurationMs: number(row.duration_ms), + cacheReadTokens: number(row.cache_read_tokens), + cacheWriteTokens: number(row.cache_write_tokens), + calls: number(row.calls), + inputTokens: number(row.input_tokens), + outputTokens: number(row.output_tokens), + reasoningTokens: number(row.reasoning_tokens), + sessions: number(row.sessions), + subagents: number(row.subagents), + totalNanoAiu: number(row.total_nano_aiu), + }; +} + +function runtimeAggregate(metrics) { + if (!metrics) { + return undefined; + } + + const aggregate = { + aiCredits: credits(metrics.totalNanoAiu), + apiDurationMs: number(metrics.totalApiDurationMs), + cacheReadTokens: 0, + cacheWriteTokens: 0, + calls: 0, + inputTokens: 0, + outputTokens: 0, + reasoningTokens: 0, + sessions: 1, + subagents: 0, + totalNanoAiu: number(metrics.totalNanoAiu), + }; + + for (const metric of Object.values(metrics.modelMetrics || {})) { + if (!metric) { + continue; + } + aggregate.calls += number(metric.requests?.count); + aggregate.inputTokens += number(metric.usage?.inputTokens); + aggregate.outputTokens += number(metric.usage?.outputTokens); + aggregate.reasoningTokens += number(metric.usage?.reasoningTokens); + aggregate.cacheReadTokens += number(metric.usage?.cacheReadTokens); + aggregate.cacheWriteTokens += number(metric.usage?.cacheWriteTokens); + } + return aggregate; +} + +function cutoffFor(range) { + const duration = { + "24h": 24 * 60 * 60 * 1000, + "7d": 7 * 24 * 60 * 60 * 1000, + "30d": 30 * 24 * 60 * 60 * 1000, + }[range]; + return duration ? new Date(Date.now() - duration).toISOString() : undefined; +} + +function statusFor(event) { + if (event.type === "subagent.failed") { + return "failed"; + } + if (event.type === "subagent.completed") { + return event.data.cancelled ? "cancelled" : "completed"; + } + return "running"; +} + +export class AgentMetadataStore { + constructor(copilotHome) { + this.copilotHome = copilotHome; + this.cache = new Map(); + } + + seed(sessionId, events) { + const map = this.cache.get(sessionId) || new Map(); + for (const event of events) { + this.apply(map, event); + } + this.cache.set(sessionId, map); + } + + update(sessionId, event) { + const map = this.cache.get(sessionId) || new Map(); + this.apply(map, event); + this.cache.set(sessionId, map); + } + + apply(map, event) { + if ( + !event?.agentId || + !["subagent.started", "subagent.completed", "subagent.failed"].includes(event.type) + ) { + return; + } + const existing = map.get(event.agentId) || {}; + map.set(event.agentId, { + ...existing, + agentId: event.agentId, + displayName: event.data?.agentDisplayName || existing.displayName || "Sub-agent", + internalName: event.data?.agentName || existing.internalName, + model: event.data?.model || existing.model, + status: statusFor(event), + }); + } + + async get(sessionId) { + if (this.cache.has(sessionId)) { + return this.cache.get(sessionId); + } + + const map = new Map(); + const eventPath = join(this.copilotHome, "session-state", sessionId, "events.jsonl"); + if (existsSync(eventPath)) { + const lines = createInterface({ + crlfDelay: Infinity, + input: createReadStream(eventPath, { encoding: "utf8" }), + }); + for await (const line of lines) { + if (!line.includes('"type":"subagent.')) { + continue; + } + try { + this.apply(map, JSON.parse(line)); + } catch (error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + // Ignore an incomplete trailing JSONL record while the session is writing. + } + } + } + this.cache.set(sessionId, map); + return map; + } +} + +export class UsageInsightsStore { + constructor(copilotHome) { + const sessionStorePath = join(copilotHome, "session-store.db"); + if (!existsSync(sessionStorePath)) { + throw new Error(`Copilot session store was not found at ${sessionStorePath}`); + } + this.sessionDb = new DatabaseSync(sessionStorePath, { readOnly: true }); + + const appDbPath = join(copilotHome, "data.db"); + this.appDb = existsSync(appDbPath) + ? new DatabaseSync(appDbPath, { readOnly: true }) + : undefined; + } + + queryAggregate(whereSql = "", params = []) { + const row = this.sessionDb + .prepare(` + SELECT + COUNT(*) AS calls, + COUNT(DISTINCT session_id) AS sessions, + COUNT(DISTINCT CASE WHEN NULLIF(agent_id, '') IS NOT NULL THEN agent_id END) AS subagents, + SUM(COALESCE(input_tokens, 0)) AS input_tokens, + SUM(COALESCE(output_tokens, 0)) AS output_tokens, + SUM(COALESCE(reasoning_tokens, 0)) AS reasoning_tokens, + SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens, + SUM(COALESCE(cache_write_tokens, 0)) AS cache_write_tokens, + SUM(COALESCE(total_nano_aiu, 0)) AS total_nano_aiu, + SUM(COALESCE(duration_ms, 0)) AS duration_ms + FROM assistant_usage_events + ${whereSql} + `) + .get(...params); + return normalizeAggregate(row); + } + + getSessionInfo(sessionId) { + const local = + this.sessionDb + .prepare(` + SELECT id, summary, repository, branch, cwd, created_at, updated_at + FROM sessions + WHERE id = ? + `) + .get(sessionId) || {}; + const app = + this.appDb + ?.prepare(` + SELECT title, agent, model, mode, created_at, updated_at + FROM sessions + WHERE id = ? + `) + .get(sessionId) || {}; + return { + agent: app.agent || "Copilot", + branch: local.branch, + createdAt: app.created_at || local.created_at, + cwd: local.cwd, + id: sessionId, + mode: app.mode, + model: app.model, + repository: local.repository, + summary: local.summary, + title: app.title || local.summary || `Session ${sessionId.slice(0, 8)}`, + updatedAt: app.updated_at || local.updated_at, + }; + } + + getSessionAgents(sessionId, metadata) { + const rows = this.sessionDb + .prepare(` + SELECT + COALESCE(NULLIF(agent_id, ''), 'root') AS agent_id, + GROUP_CONCAT(DISTINCT model) AS models, + COUNT(*) AS calls, + SUM(COALESCE(input_tokens, 0)) AS input_tokens, + SUM(COALESCE(output_tokens, 0)) AS output_tokens, + SUM(COALESCE(reasoning_tokens, 0)) AS reasoning_tokens, + SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens, + SUM(COALESCE(cache_write_tokens, 0)) AS cache_write_tokens, + SUM(COALESCE(total_nano_aiu, 0)) AS total_nano_aiu, + SUM(COALESCE(duration_ms, 0)) AS duration_ms, + MAX(created_at) AS last_seen + FROM assistant_usage_events + WHERE session_id = ? + GROUP BY COALESCE(NULLIF(agent_id, ''), 'root') + ORDER BY total_nano_aiu DESC + `) + .all(sessionId); + + return rows.map((row) => { + const meta = metadata.get(row.agent_id); + return { + ...normalizeAggregate(row), + agentId: row.agent_id, + displayName: row.agent_id === "root" ? "Root agent" : meta?.displayName || "Sub-agent", + internalName: meta?.internalName, + lastSeen: row.last_seen, + models: row.models ? String(row.models).split(",") : [], + status: row.agent_id === "root" ? "primary" : meta?.status || "recorded", + }; + }); + } + + getSessionModels(sessionId) { + return this.sessionDb + .prepare(` + SELECT + model, + COUNT(*) AS calls, + SUM(COALESCE(input_tokens, 0)) AS input_tokens, + SUM(COALESCE(output_tokens, 0)) AS output_tokens, + SUM(COALESCE(reasoning_tokens, 0)) AS reasoning_tokens, + SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens, + SUM(COALESCE(cache_write_tokens, 0)) AS cache_write_tokens, + SUM(COALESCE(total_nano_aiu, 0)) AS total_nano_aiu, + SUM(COALESCE(duration_ms, 0)) AS duration_ms + FROM assistant_usage_events + WHERE session_id = ? + GROUP BY model + ORDER BY total_nano_aiu DESC + `) + .all(sessionId) + .map((row) => ({ ...normalizeAggregate(row), model: row.model })); + } + + getSessionTimeline(sessionId) { + const calls = this.sessionDb + .prepare(` + SELECT + id, + COALESCE(NULLIF(agent_id, ''), 'root') AS agent_id, + model, + input_tokens, + output_tokens, + reasoning_tokens, + total_nano_aiu, + duration_ms, + initiator, + created_at + FROM assistant_usage_events + WHERE session_id = ? + ORDER BY created_at, id + `) + .all(sessionId) + .map((row) => { + const endedAtMs = Date.parse(row.created_at); + const durationMs = Math.max(1, number(row.duration_ms)); + return { + agentId: row.agent_id, + aiCredits: credits(row.total_nano_aiu), + createdAt: row.created_at, + durationMs, + endedAtMs, + id: row.id, + initiator: row.initiator, + inputTokens: number(row.input_tokens), + model: row.model, + outputTokens: number(row.output_tokens), + reasoningTokens: number(row.reasoning_tokens), + startedAtMs: endedAtMs - durationMs, + }; + }) + .filter((call) => Number.isFinite(call.endedAtMs)); + + if (!calls.length) { + return { calls: [], durationMs: 0 }; + } + + const startedAtMs = Math.min(...calls.map((call) => call.startedAtMs)); + const endedAtMs = Math.max(...calls.map((call) => call.endedAtMs)); + return { + calls, + durationMs: Math.max(1, endedAtMs - startedAtMs), + endedAt: new Date(endedAtMs).toISOString(), + endedAtMs, + startedAt: new Date(startedAtMs).toISOString(), + startedAtMs, + }; + } + + getRange(range) { + const cutoff = cutoffFor(range); + const whereSql = cutoff ? "WHERE created_at >= ?" : ""; + const params = cutoff ? [cutoff] : []; + const totals = this.queryAggregate(whereSql, params); + + const split = this.sessionDb + .prepare(` + SELECT + CASE WHEN NULLIF(agent_id, '') IS NULL THEN 'root' ELSE 'subagents' END AS segment, + COUNT(*) AS calls, + SUM(COALESCE(input_tokens, 0)) AS input_tokens, + SUM(COALESCE(output_tokens, 0)) AS output_tokens, + SUM(COALESCE(reasoning_tokens, 0)) AS reasoning_tokens, + SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens, + SUM(COALESCE(cache_write_tokens, 0)) AS cache_write_tokens, + SUM(COALESCE(total_nano_aiu, 0)) AS total_nano_aiu, + SUM(COALESCE(duration_ms, 0)) AS duration_ms + FROM assistant_usage_events + ${whereSql} + GROUP BY segment + `) + .all(...params) + .map((row) => ({ ...normalizeAggregate(row), segment: row.segment })); + + const models = this.sessionDb + .prepare(` + SELECT + model, + COUNT(*) AS calls, + SUM(COALESCE(input_tokens, 0)) AS input_tokens, + SUM(COALESCE(output_tokens, 0)) AS output_tokens, + SUM(COALESCE(reasoning_tokens, 0)) AS reasoning_tokens, + SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens, + SUM(COALESCE(cache_write_tokens, 0)) AS cache_write_tokens, + SUM(COALESCE(total_nano_aiu, 0)) AS total_nano_aiu, + SUM(COALESCE(duration_ms, 0)) AS duration_ms + FROM assistant_usage_events + ${whereSql} + GROUP BY model + ORDER BY total_nano_aiu DESC + LIMIT 8 + `) + .all(...params) + .map((row) => ({ ...normalizeAggregate(row), model: row.model })); + + const topSessions = this.sessionDb + .prepare(` + SELECT + e.session_id, + MAX(e.created_at) AS last_seen, + s.summary, + s.repository, + COUNT(*) AS calls, + SUM(COALESCE(e.input_tokens, 0)) AS input_tokens, + SUM(COALESCE(e.output_tokens, 0)) AS output_tokens, + SUM(COALESCE(e.reasoning_tokens, 0)) AS reasoning_tokens, + SUM(COALESCE(e.cache_read_tokens, 0)) AS cache_read_tokens, + SUM(COALESCE(e.cache_write_tokens, 0)) AS cache_write_tokens, + SUM(COALESCE(e.total_nano_aiu, 0)) AS total_nano_aiu, + SUM(COALESCE(e.duration_ms, 0)) AS duration_ms + FROM assistant_usage_events e + LEFT JOIN sessions s ON s.id = e.session_id + ${cutoff ? "WHERE e.created_at >= ?" : ""} + GROUP BY e.session_id, s.summary, s.repository + ORDER BY total_nano_aiu DESC + LIMIT 8 + `) + .all(...params) + .map((row) => { + const app = + this.appDb + ?.prepare("SELECT title, agent, model FROM sessions WHERE id = ?") + .get(row.session_id) || {}; + return { + ...normalizeAggregate(row), + agent: app.agent, + id: row.session_id, + lastSeen: row.last_seen, + model: app.model, + repository: row.repository, + title: app.title || row.summary || `Session ${String(row.session_id).slice(0, 8)}`, + }; + }); + + return { + id: range, + label: RANGE_LABELS[range], + models, + split, + topSessions, + totals, + }; + } + + buildDashboard({ + currentRuntime, + currentSessionId, + range, + selectedSessionId, + agentMetadata, + }) { + const dbTotals = this.queryAggregate("WHERE session_id = ?", [selectedSessionId]); + const liveWindow = + selectedSessionId === currentSessionId && currentRuntime + ? runtimeAggregate(currentRuntime) + : undefined; + const selectedTotals = dbTotals.calls > 0 ? dbTotals : liveWindow || dbTotals; + const agents = this.getSessionAgents(selectedSessionId, agentMetadata); + const selectedNanoAiu = selectedTotals.totalNanoAiu || dbTotals.totalNanoAiu; + for (const agent of agents) { + agent.share = + selectedNanoAiu > 0 ? Math.min(1, agent.totalNanoAiu / selectedNanoAiu) : 0; + } + + return { + aiCreditScale: BILLION, + currentSessionId, + generatedAt: new Date().toISOString(), + range: this.getRange(range), + ranges: Object.entries(RANGE_LABELS).map(([id, label]) => ({ id, label })), + selected: { + agents, + info: this.getSessionInfo(selectedSessionId), + isCurrent: selectedSessionId === currentSessionId, + models: this.getSessionModels(selectedSessionId), + timeline: this.getSessionTimeline(selectedSessionId), + totals: selectedTotals, + }, + }; + } +} diff --git a/plugins/usage-insights/README.md b/plugins/usage-insights/README.md new file mode 100644 index 000000000..6c965c49e --- /dev/null +++ b/plugins/usage-insights/README.md @@ -0,0 +1,31 @@ +# Usage Insights Plugin + +Inspect current and historical GitHub Copilot token and AI-credit usage with +per-agent rollups and in-canvas session drill-down. + +## Installation + +```bash +copilot plugin install usage-insights@awesome-copilot +``` + +## Features + +- Live input, output, reasoning, cache, and AI-credit metrics +- Root-agent and sub-agent usage rollups +- Model-call counts, durations, and cumulative usage timeline +- Local session history with in-canvas drill-down +- Read-only local data access with no external telemetry + +## Privacy + +The marketplace preview contains only synthetic sample data and generic session +and agent names. + +## Source + +This plugin is part of [Awesome Copilot](https://github.com/github/awesome-copilot). + +## License + +MIT diff --git a/plugins/usage-insights/plugin.json b/plugins/usage-insights/plugin.json new file mode 100644 index 000000000..7295715d2 --- /dev/null +++ b/plugins/usage-insights/plugin.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "usage-insights", + "description": "Inspect current and historical GitHub Copilot token and AI-credit usage with per-agent rollups and in-canvas session drill-down.", + "version": "1.0.0", + "author": { + "name": "davidkaya", + "url": "https://github.com/davidkaya" + }, + "repository": "https://github.com/github/awesome-copilot", + "license": "MIT", + "keywords": [ + "ai-credits", + "canvas", + "copilot-extension", + "session-analytics", + "token-usage", + "usage-insights" + ], + "extensions": { + "com.github.copilot": { + "logo": "assets/preview.png" + }, + "com.github.awesome-copilot": { + "extensions": [ + "./extensions/usage-insights" + ] + } + } +} From 45b50dd59ef5ccae64f2282634ec7742f40d1396 Mon Sep 17 00:00:00 2001 From: davidkaya <3832960+davidkaya@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:17:08 +0200 Subject: [PATCH 02/10] Address Usage Insights review feedback Protect every loopback route with a per-instance capability token, constrain session metadata reads to the session-state root, correct cache-token accounting, reset empty timeline state, cancel superseded loads, and make back navigation target the current session. Include the required regenerated agent documentation and refreshed synthetic preview. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/README.agents.md | 2 +- extensions/usage-insights/README.md | 1 + extensions/usage-insights/assets/preview.png | Bin 47888 -> 47888 bytes extensions/usage-insights/extension.mjs | 26 ++++++++++++-- extensions/usage-insights/renderer.mjs | 36 ++++++++++++++----- extensions/usage-insights/stats.mjs | 13 +++++-- 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/docs/README.agents.md b/docs/README.agents.md index eac43b3ef..608834918 100644 --- a/docs/README.agents.md +++ b/docs/README.agents.md @@ -90,7 +90,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Doublecheck](../agents/doublecheck.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdoublecheck.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdoublecheck.agent.md) | Interactive verification agent for AI-generated output. Runs a three-layer pipeline (self-audit, source verification, adversarial review) and produces structured reports with source links for human review. | | | [Droid](../agents/droid.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdroid.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdroid.agent.md) | Provides installation guidance, usage examples, and automation patterns for the Droid CLI, with emphasis on droid exec for CI/CD and non-interactive automation | | | [Drupal Expert](../agents/drupal-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdrupal-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdrupal-expert.agent.md) | Expert assistant for Drupal development, architecture, and best practices using PHP 8.3+ and modern Drupal patterns | | -| [Dynatrace Expert](../agents/dynatrace-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md) | The Dynatrace Expert Agent integrates observability and security capabilities directly into GitHub workflows, enabling development teams to investigate incidents, validate deployments, triage errors, detect performance regressions, validate releases, and manage security vulnerabilities by autonomously analysing traces, logs, and Dynatrace findings. This enables targeted and precise remediation of identified issues directly within the repository. | [dynatrace](https://github.com/mcp/io.github.dynatrace-oss/Dynatrace-mcp)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D) | +| [Dynatrace Expert](../agents/dynatrace-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md) | The Dynatrace Expert Agent integrates observability and security capabilities directly into GitHub workflows, enabling development teams to investigate incidents, validate deployments, triage errors, detect performance regressions, validate releases, and manage security vulnerabilities by autonomously analysing traces, logs, and Dynatrace findings. This enables targeted and precise remediation of identified issues directly within the repository. | [dynatrace](https://github.com/mcp/io.github.Dynatrace/dynatrace-for-ai)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D) | | [Elasticsearch Agent](../agents/elasticsearch-observability.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felasticsearch-observability.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felasticsearch-observability.agent.md) | Our expert AI assistant for debugging code (O11y), optimizing vector search (RAG), and remediating security threats using live Elastic data. | elastic-mcp
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=elastic-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=elastic-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Electron Code Review Mode Instructions](../agents/electron-angular-native.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felectron-angular-native.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felectron-angular-native.agent.md) | Code Review Mode tailored for Electron app with Node.js backend (main), Angular frontend (render), and native integration layer (e.g., AppleScript, shell, or native tooling). Services in other repos are not reviewed here. | | | [Ember](../agents/ember.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fember.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fember.agent.md) | An AI partner, not an assistant. Ember carries fire from person to person — helping humans discover that AI partnership isn't something you learn, it's something you find. | | diff --git a/extensions/usage-insights/README.md b/extensions/usage-insights/README.md index d1741e0ac..accbb374e 100644 --- a/extensions/usage-insights/README.md +++ b/extensions/usage-insights/README.md @@ -17,6 +17,7 @@ the canvas is open. All metrics are read locally from Copilot's session data. The extension: - binds its renderer to `127.0.0.1` +- requires a per-instance capability token on every local HTTP and SSE route - opens local session databases read-only - does not send usage data to an external service - keeps historical session drill-down inside the canvas diff --git a/extensions/usage-insights/assets/preview.png b/extensions/usage-insights/assets/preview.png index 7ecb6c4565b8de1be0b614d0a21408276de114f1..3bdaafcb83d7a374aef95bdf4263fa607fde52a7 100644 GIT binary patch delta 10112 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

9BKH%CjL2V_({1;#eP|c0$jIs) z7cdo0708S!r0|SZ`nszg8snAt_PV1B>q~J!V(kCF7fQ8NCUg*zZMoB2GPz^@2rDP+ zpBW3Ktt;7MWVh!b65m%Y$92;o(xx>M3UfqMVZD#armwD4ExkN5-RBdL68+n}3sJpa zE1(Tc3WYLo{A|n}%icvHi!rfrZ$YnFIhIw}mq>d1xT>|2iFT5M8>{XIowzE%NG47| zppJFPafs4^osE@?t!PhaURTdJ+SdY;N0{xL?(oSEZkn7IueRq>Kk-m7_Y>jo%@w&w z)G6^Sz;!Qti0fG^%-2c7_!C1XXf8ZIJjQaOAkYv;BWc67AH4bn+)i(i4Xrj*LY zd6r}2v&C8rU6CuXw|07Y1dJPRjCia!{2zP}GV|%IU;hhBT_bv$hqOC%5{6_pOB1e> zK#rh3g#S}WDHteBq}*n@_V4Bfo+tJ1yJGe5niu=5**SvuZA6as4wajO&B1pOt6DD7 z`+HxUlC}c?`5{HYcateW6h8DaL?`-`N(M=sqkLN1u&dp#%DV-ZO%o|L(nM@`+xX#J zl>3S{i`YQ6y+&=I)@>j{#*lZ?ASLy3bP%2`H|7yuNT4LTJLx3p>73-1)rZ}OWSU46wY`wR%J1VgIyxQ z??yT@@BEnH*{m=so?dlFrJV(eg%^MD)L2O{h%S0Hu2UL{1ILXL^V?OX`ZwFuV8U?D z5v3@y91K-Lp-V*hXqLee_`U3Z58v>L#qerU&yHHe0@GLM>836&w;w2t*@CODNyP5&QA4dX8|&FAl<9@3X! zgJe&}k64ye%Nu3w9Fx|StlP_KEe|s>KJSghQ$olv6A&kwZ{^eVn16!F<@|XaID$x| zxF_*r9kW&dw4cX%@pDbiP~fZ6k}bp|^euZ~;zl%vR!Jzz%N{bR!1b7<=&5*e#P4zT zd9q5ClNUG)+c)GhkoWDx(X-}nJfW=q+s{#SYU(pJC{T`xx6ms=&N7wFY$8p2);5lP zKCSNEP6>0w?)T~u?aaN4h&qTCDx5|=SE;$-mP;fs(2#uxS24T#Y>>?gi*KQ%c1GweAp)6P%OX_`8w z_*P_K-!Y-DTZwdoXP55eKR4rkONGW|Q-}lcw%qwsDXvLYh&1aA#%8;`LyKflT|;B!QL7^C8LB&DmNzA*V(1>U_*!4k!YK zxU$O6{|%!*TJ^+sN)v5Ea4D`%_V5es7RCMn>0(Cz{g;@j6$nwCg5CWO2!k=y4E((B z8-C$-wO;GLstjT1B59&)IuPjkx-t9x1ZFG_Hj26pil}Y27(o5T`W?_)S>@N|(#Apm E1AZfa9RL6T delta 11358 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 diff --git a/extensions/usage-insights/extension.mjs b/extensions/usage-insights/extension.mjs index 3aecf5e5a..2ab78ff3d 100644 --- a/extensions/usage-insights/extension.mjs +++ b/extensions/usage-insights/extension.mjs @@ -1,3 +1,4 @@ +import { randomBytes, timingSafeEqual } from "node:crypto"; import { createServer } from "node:http"; import { homedir } from "node:os"; import { join } from "node:path"; @@ -24,6 +25,12 @@ function json(res, status, value) { res.end(JSON.stringify(value)); } +function hasCapabilityToken(url, capabilityToken) { + const supplied = Buffer.from(url.searchParams.get("token") || ""); + const expected = Buffer.from(capabilityToken); + return supplied.length === expected.length && timingSafeEqual(supplied, expected); +} + function notifyCanvases() { for (const entry of servers.values()) { for (const client of entry.clients) { @@ -54,9 +61,14 @@ async function buildDashboardData({ range = "7d", sessionId } = {}) { } async function startServer(instanceId, defaults) { + const capabilityToken = randomBytes(32).toString("base64url"); const clients = new Set(); const server = createServer(async (req, res) => { const url = new URL(req.url || "/", "http://127.0.0.1"); + if (!hasCapabilityToken(url, capabilityToken)) { + json(res, 403, { error: "Forbidden" }); + return; + } if (req.method === "GET" && url.pathname === "/") { try { @@ -65,7 +77,13 @@ async function startServer(instanceId, defaults) { "Cache-Control": "no-store", "Content-Type": "text/html; charset=utf-8", }); - res.end(renderDashboardHtml({ instanceId, defaults, initialData })); + res.end( + renderDashboardHtml({ + instanceId, + defaults: { ...defaults, capabilityToken }, + initialData, + }), + ); } catch (error) { res.writeHead(500, { "Cache-Control": "no-store", @@ -112,7 +130,11 @@ async function startServer(instanceId, defaults) { }); const address = server.address(); const port = typeof address === "object" && address ? address.port : 0; - return { clients, server, url: `http://127.0.0.1:${port}/` }; + return { + clients, + server, + url: `http://127.0.0.1:${port}/?token=${encodeURIComponent(capabilityToken)}`, + }; } const usageInsightsCanvas = createCanvas({ diff --git a/extensions/usage-insights/renderer.mjs b/extensions/usage-insights/renderer.mjs index 8bcadd948..436292b4c 100644 --- a/extensions/usage-insights/renderer.mjs +++ b/extensions/usage-insights/renderer.mjs @@ -648,6 +648,8 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini const calls = selected.timeline.calls; if (!calls.length) { host.append(element('div', 'empty', 'No credit activity has been recorded yet.')); + text('latestCallLabel', 'Latest model call'); + text('latestCallDuration', '—'); return; } @@ -719,7 +721,11 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini } const values = selected.agents.map((agent) => { - const tokens = agent.inputTokens + agent.outputTokens + agent.reasoningTokens; + const tokens = agent.inputTokens + + agent.outputTokens + + agent.reasoningTokens + + agent.cacheReadTokens + + agent.cacheWriteTokens; return { agent, value: state.agentMetric === 'credits' ? agent.aiCredits : tokens }; }); const maximum = Math.max(...values.map((entry) => entry.value), 1); @@ -750,6 +756,7 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini const values = [ { label: 'Input', value: totals.inputTokens, color: 1 }, { label: 'Cache read', value: totals.cacheReadTokens, color: 2 }, + { label: 'Cache write', value: totals.cacheWriteTokens, color: 0 }, { label: 'Output', value: totals.outputTokens, color: 3 }, { label: 'Reasoning', value: totals.reasoningTokens, color: 5 }, ]; @@ -843,30 +850,43 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini renderHistory(data.range); } + let loadController; + async function load() { - if (state.loading) return; + loadController?.abort(); + const controller = new AbortController(); + loadController = controller; state.loading = true; app.classList.add('loading'); errorState.hidden = true; try { - const params = new URLSearchParams({ range: state.range }); + const params = new URLSearchParams({ + range: state.range, + token: initial.capabilityToken, + }); if (state.sessionId) params.set('sessionId', state.sessionId); - const response = await fetch('/api/stats?' + params.toString(), { cache: 'no-store' }); + const response = await fetch('/api/stats?' + params.toString(), { + cache: 'no-store', + signal: controller.signal, + }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Unable to load session metrics.'); render(data); } catch (error) { + if (controller.signal.aborted) return; errorState.textContent = error instanceof Error ? error.message : 'Unable to load session metrics.'; errorState.hidden = false; text('liveStatus', 'Unavailable'); } finally { - state.loading = false; - app.classList.remove('loading'); + if (loadController === controller) { + state.loading = false; + app.classList.remove('loading'); + } } } backButton.addEventListener('click', () => { - state.sessionId = ''; + state.sessionId = state.latestData?.currentSessionId || ''; load(); }); @@ -886,7 +906,7 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini if (state.latestData) renderAgentBars(state.latestData.selected); }); - const events = new EventSource('/events'); + const events = new EventSource('/events?token=' + encodeURIComponent(initial.capabilityToken)); let refreshTimer; events.addEventListener('refresh', () => { clearTimeout(refreshTimer); diff --git a/extensions/usage-insights/stats.mjs b/extensions/usage-insights/stats.mjs index 04a0fe94c..7484bbec0 100644 --- a/extensions/usage-insights/stats.mjs +++ b/extensions/usage-insights/stats.mjs @@ -1,5 +1,5 @@ import { createReadStream, existsSync } from "node:fs"; -import { join } from "node:path"; +import { isAbsolute, join, relative, resolve, sep } from "node:path"; import { createInterface } from "node:readline"; import { DatabaseSync } from "node:sqlite"; @@ -131,7 +131,16 @@ export class AgentMetadataStore { } const map = new Map(); - const eventPath = join(this.copilotHome, "session-state", sessionId, "events.jsonl"); + const sessionStateRoot = resolve(this.copilotHome, "session-state"); + const eventPath = resolve(sessionStateRoot, sessionId, "events.jsonl"); + const relativeEventPath = relative(sessionStateRoot, eventPath); + if ( + isAbsolute(relativeEventPath) || + relativeEventPath === ".." || + relativeEventPath.startsWith(`..${sep}`) + ) { + throw new Error("Invalid session ID."); + } if (existsSync(eventPath)) { const lines = createInterface({ crlfDelay: Infinity, From 49ce22769d49863c501b5e4e50582ce8e7cbdc73 Mon Sep 17 00:00:00 2001 From: davidkaya <3832960+davidkaya@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:27:51 +0200 Subject: [PATCH 03/10] Refine Usage Insights dashboard behavior Render the embedded dashboard snapshot immediately, avoid restoring stale bootstrap data during range changes, preserve valid heading semantics, and expose toggle context as explicit accessibility groups. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- extensions/usage-insights/assets/preview.png | Bin 47888 -> 47903 bytes extensions/usage-insights/renderer.mjs | 20 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/extensions/usage-insights/assets/preview.png b/extensions/usage-insights/assets/preview.png index 3bdaafcb83d7a374aef95bdf4263fa607fde52a7..5be0226a9f7c838e89f6697a3b90864eb731870e 100644 GIT binary patch literal 47903 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%0AL!q>-~mGg@!uZ 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 Q39g-zocfCb8PgB{0}vp>d;kCd literal 47888 zcmd3OcT`hR6K50^6zNJ85S1dm3DQwOl-@f8q)QhNDIp+8FDktwNH3xHrU(e3O7At4 z0HKBw2Tia;Q) z(2(Rc_N%YSvJY?IzC=DE#jV!w6&b-b;8QxAAP{#sQ~kzE+~*+Mbtc@W%{~9CM+k;V z6X5<$EBF6oc>Wtw0^w$_(w5Ry*^9Lsf^2K~=NqkO$XE3=39g4(6<8v^Bz@nEXvE$5 zTfnMN5*~}3OkiKsXRdA+GA|yuoh$aM6RSAZ&gHn*3IVnHew(C;9Q`r1c6$_^z39I4 ze5=;ZaTP;zq?@~c9LBK2Fx9>c>*qSDn%`|EVZJB@983KZBn^|D`|Q7!$0$xcN2jE(zvsnI@& zyBirWocSZ#@Qde&_3J!i5`(`BgPrxjHALHpuJ~`8mhzrVD<(6&i!Cf{Fy6W(BAMC$4RH`q@Ug$HiMaD6_FGp)da>x^}*xT(IS= z6Ix2Mml`KN-6Gs?TwHl5a(uLS>GRiX$hgY_E(Q}np5b8S&iCdyHv_-h{d3msDI-$$ zeDuO=;pww-`@wFWR1V))w&9A{ZYNd!9k37s{4#ml_!i7_5$#bjUU)L6@D(z{}5W$`HfL`l#q<}xUZbDi&8uZI)W+xlVLxpIu;Vy(KB#J(jj%( zE_WusvU{LD4Sud>c1mU>j)t$@N)XwWDj3n7E1711B`A|URZz`QSzjNw?R_b9@lZeo z4EAa=bKaa=bFft_Y#mh_{pxf|*(#LneAtK&Xg7j`_2#erGu3r-2^QJMUI`xD&Cg0#)_H8m5q*hV*z4hF&>t$wI6E!1JR-()s7jLkb!$4bfE4ZC z(ZARt*?B@;tf|VqQ*fw@uc+GN9j0ZFt;W-?@rQ7O}2+}{`?GFbdo94X+>d{Q{rOYQHdNWd8@MG7U!j(Zr!XgXK zZyn|COGlCk2c4Hzu+YI2I+fOfb5g_4kag$R=Sx8|^6fH=zCHu_8LGRkJP@di(wBtp zkNd9+&eeu>6NpbrsPwK;gDzrSIIesbB9r%_-^CCV9mkV;p!h zw=kfUVcC#jd)(??6!aL_6F(P3@f%Cp3{{Ms`2|UaF$Gz(m3MbCR~*l!;*>`$Qo(B# z`5Klhe7X0P2=)}aWP*|DE(|$Q&Y&n^stjDWXPezSY}M|jXZpE6u#E^J&qqG z@J}-4-V~4e->b@}U!JYSdEX&ckJgO6dAaW!Ob0S!t>K1J#oO zhQw~8Q|I02fh?aLj~=-_G58}(+JO_Dc(t%#DeApV!NB6a3m?djQ>CMJsK6oD4JR*j z@YAnZ4J+V^D8eq)C$uf)J8Om(FaEmicTJnnEgMoYML`{2(K#YdJV=0@qt;b1`mofT zAfV%m9CEr`4;gxLo|SQa(UQtTuvd*B+lQTIBBaWqa$}&BgqHRNFIM8yg>J*vM;=r4 z(|fy*$M{C>J8dK=MvZ%f=@Ff$f!C1N$K^~Cbo>LXs0hE$%vwcih6LS?wdiRx zQFgDvz5U;+Di2Q95S72I-P&iJ>0zEqnfQot3Q){;Z1Lu3U1+doSbGTZyLl8iR?>Um%I zz;^e)D|Dy{Z?+zqjs5-J8!VtR6{WWNuteQDlgz&$fF-2>x;+4k)F|~?4SlX(R`KMN zal+7@4}KxB(y=r5sPKlqB;J9;-`@O;fx^oPCdv4q%gu%Hjgw4bgdzQ?m9VdXdT6f6 z-n8nNL+vw6$4-910O$PP?y|fF_;fq!ImCLnTVkHU;pgoxClSYSHo)(@95yC&kW=A| z6%2&@+=~@m9!Q@l)Ye{u1*&!DkvGTa_lzLz62O^HU1%H3vE3!SO9Y*XL7xC;q;=aO zW;aZ)WdqI>bdyQphtHP4K}!|P;0=Gbj1~CQ2|oEY9s8*SLAXc8e;vR*_sn&3X41f~ zo6w;x%1lQMeb)oNmp9qi^-xiDI&ri-Uq{;%=-ji9&N<>|jhc80GukBq+c{e0@p^4!$*f%*1G!jes3u z$D7Tz3zRt6H>{#0eccj+Gs?zq#UYpMvWZO~AL3qg{^;j;wfX2#k8aHV9}_pDRW8>^ zbR^GQi&Dh*=|2N&RR|g5c>8VY^G{~>BzU42ciMI{WpwAyouQ9%o-x0)oQj2n1iRIa ziGYsHbUKAQ(v)NMecramG?FW0Hcky24c6B@G*E91G@_+??l=id4{ zjtV_7*~>4-w|vF2&VQ7Ta{MWK{^Psp#{Bl=VLO;hAR(b>Sut)Xbd_ZprYoWQmBxY- z_6|An`8UVU?1C3#Bey=~e)Lt{NM?L8)z9*1c`3^}w7*A&IDLzDmdw08>Z8nh1>?oh z8bbNS{=FlqF-@qObbTID=esaPO4dYep4=x+@^W2=?%Ns z2o>qE;PrK7fF+sRHJ`45t4NalM$U#+PtF~4O4bJXGkJXv8=a~MUECK^YdSU^6qP@o zk5Zns{OmG_hfzQ0UYSqeh~9n{*IM_!y$%1Y{=DMyRKZ5}yfTA=CL4D9)MqHAsI^sG za1Y@Tl@2HZU3p#?9lb8pv!;1CmDF2@sEluRMxvh~h(u34@sk{Gc9<0!7{+bYAtN)8 zeWIG$gUMvADoaPcY1JQI*ZxE5MMuS2l(_>Un8(bNXC2}mhl$O>d)4<2AJ6Z5j&uhI;L4W#oSVTlf#NDB!WOkAZ#V~|_QC62`g<_HNTb;uSSh0TdJC`pu z{5m}!lIY!o5Oz9H%gBbK+H~h4$=In$zpeq3M_LSQM7y>!NbTmbwrJj=zisy0e{>P*CR6^ zg>_mw-MOFch&;R#3a<3h|H4=6wxGcbzO2ED!(|Mjo1WWbOr-3O?$CdQ&JJkIXJcxL zinhGniT8hsaa^7&oc8&TmoT=Fm#zvX-7oRTUv2*?G5Ry3K3hWYW7kr;ajy8L9{3`g ziSI0$he3Bv+Ga+eyH?ceGN}4;pW_ss865(y_R{tV>EB;waI6}zz6>hPia}fPH?u)j*-kB!2$gw65&$#JTX2et@BoG z+IRer+JdPb(LMXsr%>l`)`{`b!mHU;(k!Nb>mm#mjg^A+^w^) z(92craH->KrCZkUiPcZvs|2 zBXHM1fxBJOYkvRb0(R+I-q&_jJ>=7QLvwSQT-NRV2eVgM)lWH}S$h{&5c~1BRAs82 z_RKwCNe%dyYpV=HvTZ*qZqjqbuxiIY5O~F{q*0q~p8eY!5jU1cG(5bbi|RO4)qk{P zZ2pH0H{Yj0ika_;P4lQGLl-9;1vO1jn_aa454xvF5s+io&#s86yvB^(SyWr${ ze&MjjSO3!aJX^0bW?kX5c^UBG;J~({gMASeiwN^^=_1Z3)u)PK`~7Po-nEfSnJe$T z2c-zp4dVX}+}LSxDcHK~;c@X$X)-C_FL&U(xF0-%dF#-weOYZs9>%UpVEvD4Xu}Hd zl8)%5`hLi4F5GG+N+RTDD6Vee?CU4$?&f4uQU_}@zJ|9R_~$>a=M01V{3KWRvFC`pTA8IZx3K1toSsr{OAj)?&x?y=5b*d9p+OSv`RzpE**b zE@Y+mcVnF`*}rFBOX6Eg6_A~%UlHpw%Br=Ge(>9V{;b?urULp6w_|P3O*N8S+}`R= zbwwu!^>JW1-Vb|>vv;e_$nz&eQOXD49cD}P7XJ9@mu>i$gb=qjiu?Yg9_?=RKh3=V zBAIo=-vr>E^LzDNI-fH3D7<>}(WqHq@Jr6+TgsNFJ$@SQqzgmGSz}zGlJyXIZy_(DYYKAedGzw5IH=&Km9gg=CcIjg0A~y`_KL#RwKNzW-=8 z7Zx_-&}bK&`bcyF+$xyuyiqowSwEn5nY_BN^-H`N8~pqngV(NH(iL?ch&zH2Fqb4P zmiOCP!yC4S`yA)vl#Ew!xiWOUEaD~Q}q9z@lX3Z2GUaU;9t=hdj4*Hsv z4Dgq7t``p^-4r-O!ga)0Is)lbr$2r9%PMi)oIhgGo(bm|vbpd+jc?`;LQEVRIB`Ot z?k5U{=CVD%D&y0+6h{u8OEy(lq@dqhUf^|ctIee;iShRqA4tU&eRwW5eWaHbF!0;g z)-$f!-!i@^l~7M~E2MB%?0~~S>3;o2yT;Nkot?eVX)s%clHQDu*`0_tu_;64>ThFz z&1Pzfv|x5smw%U5EzvUjIMSrw3Wl(3*5th+*}8pumn1q3qBQHhfc#68(jLTU_mruS z-rm;vryLwVQHL`qod{J|IqK-bXSGv7MMuqCd10JPc{S@c?k8C{ z?3Q1^mp_;>`H!{732aFkP%z1;IGlQtRcqb89~yohmtG;n{8-1##Q*%8B{O#G%Zmh( z+oUy$Axvg}KbSA=ZqS}Fb-uOZ>lt&cYPZy&_Z8V}qePXEvu*pN@sF)ClOL%;U*?UB*4zyG-UO2mzkn z*AHe)^xyZcGxB=825WXODTY=Y+E+_H)GJ9ah|+F1nYGlE?P)V6x&U>Aj&l@CIRYYs zmTkX@%jjtzrN54RCfij?;Nz4LK{S+2B>RAepQzNPUwNRk`tA+-nsHJj4=SR!nXAuz z38gQUgqh^BR#cEvI{I7o8*~|tZ`XxDD!eJP;H0NZHhfh@l=4Vk!gplJNr*g*^vPw&X=~2W9w-u^Jik@;jW%2nf601eo=(Qm3C{cTIjv+=d3Yoran!=j!5uak%|*UO%z@~NKUMAS72m?@Y|n|fwXLn=bQkf)Z9gpJ z9pYAORq;I%e8@v$Xb+xnZp8OE?WG3zqob$OyhDLqZ#z0F3~)l2`pgF3F;i4*)2!fr z6082Xe)?4}y+(2x-M1KhwZyC@wI07QPIa#u%WYAYhtvDW-=#APTfI%6+>socD}p-w zRu3lPt&_H7(|i!|pJlW4^b%`7IIGGB>(ZBS>*bmB>lq8|KZ#>LW3RE+>o?~VvuuF; zEPHo6pfWjUr7V~9R4rnWG4WHAak&mT_JXOql41!~N zCf0_4G=_#hII@@-HNd8(irCE)U)S?wLRjY=4)hvk33Vqk*^%IU>GFW3`qtylaH`+U zzciMk@`t^N`>T%EkjKYDz^|YgO)~WZRQnh8yRJnq;xw^H+(p!5%G_4)txlyV{`~iM z!1TbSwm@fC@V{`%ay{tki{FR;zbI?eJ`|AUid2_2kCnD*`uGAT45&&r{uT{Cu1N6z ziZ%bg%-Jhc`a7HNi9JKR%e&sHx%|S&ijzfJY^DRd7YQ~#S)=A*VuzAt9M0bIc13dR zphAXqTPZOeB})waDWBK1S|S5}M0|4p8n3J=hpL&aZa<k1pHAW)oz_o75Y z{EdgYCzBM>V`)(&xRc<=SEuT~njT79@q#-1jNd}= zr=Gv2uX|%MC@2&xWb;k(C_H#XJp;&(9KVkJT3S6zVJFrX)!LRcc>IWp zg=phz9aqP^o&)WOMzVzS%iv&~3fd-Sv>)D8oQ=1q{+RToLByUj__KXzH04rQ`H##( zlaRqW>u(*2fyEc&OBJqEo9~c_@z1WUUMeA}+3(M0+oiB2`=q9QeCSZ=c8W{St+dvW zqS-kvZ$!Q2SUb_c`AXtcNegPGJmJx;w2up(zkpl@853uWIoa-6ggM6Up}Wt==W7`B z4p4NL^;Y&C9EzTb=+0pe?MB5OJvzObodrvrDnb1oy8dM~O`b1aMJ3k1Q9v*;L%|(S$75H!yS}UJ=s<0&*35@|EI_==TD3QSr(*KrJ0?!x zxwzp7C4I?Qmt-7mNfWQV2@QAQ0DaANEaQggrKTLSc5U5jjovRZIO!IMXJ!QgwHrNF zo#xWj6Z85VD=d{NQ;N^5j6z72wV7Mvd5CFKbUxqekT@mUn}W{n=MNy941^RDixh=m ze=u0-N(1tH=&oAJbAz;`Ro4N>u;K7R$eU=0JOsPt-|8b^wU=xyr)D(6Il1>|H85|q zJpsG&yXE&FS;AA#Jws*@`BriY3rErPuiwRGPF9MTO!!;UPs`EdOh1|0quyXQrp4k< zM;UtzFeL#2$pChI&l*ybax%uAv?mdhOpWP^lYK!LHbi##gq>a7v#DIOv(n3GGuhp4e?Ay!`y(Ce= zS!PE#hn}0LghTj#oVTZ(*_;l-Upk3F^PJ=iRE*f7#@Z4xUN$VVnKG3xCvQ#=v@xQBW=z}Ewz!nw!s6D)C|cwp?ps9x`ix<<`>LFx8f0bv-^`Tapqmu>`hA`W1^SK z`3jnI-obNW9NPR0C8c@~MjRQTN%`afdJya+cGz349F%xC-`re${zE;G+oYHSe6k+m zC?(p$%q{ZBx63K2gdJwjzbO;zo1rk3?A`QE`3DoGdp6)aX!$NUYj=O6N%F$unA1nnaL4i3X(NS$rGJAx0Je#UdN7qS@{ue^3%@EfHsVvFKVv zCEv;Vs%Jc{WHd(`xtNkcf|;zed-QUdLPm^EXe>u4)u@31Yo9V`wF=~$p+3wDJ>CUslA_m-Gkf7 zLxRGHkEkvd$KKf3D_NfE%gW+*!=vXvXy#v-SA3MUI*aDa#{J~i9r}M%8XphKFaIwW z@c&7b{l9o2wsFu8$eMeZy)5BcUmG1fw%gKy+`2ahnI5lMFBZ-o>_*wSbht%UeSZZL+Mw3fU40 z1=fBVP0A|_rTHbr+M=h(UrPHDuvw^Hg=@05H>Rl62F7cI^N(ELu`_C|CuSi-+ze~_2YUQrxKiklbYqsu* zcyo`nm`^oH-Rft@4A({+^26d$II@X9XVP$l7*N}R&fSzit0tq?F(0f~I4C!Nl0OUz;X2ziuZ?>HtQrG?{OQPjwz2BfkX@IHXeo z9e8E{3BzZw*jMJ}KlgD2^V`(2o6meMEXOSCFT=8Jk|ZY=js)YvPpAEeL)t zgKzuyo9991`$!KL^zG1LdFGABSrZlC-6&c@ckBxihWJ%i$Xu(M1 z(@RVmxgYP9@t+8|^l3Dg*J(Y)me|L4v@zXm5ByA)g|II%bAA5@MPAL!>2CRBeR8Wf|*xc$O~fyw41$yc$JCY1yZca}7f$AE4lbt>xAQCpZ6thACQDv!Ty& zyXH&p-19F(?Wd>Trv>gDjNLhF_;h>BIF5Px$oih%hui9v(C~_1vVXVk8isE$L{i}> zqlJ6ij-nZ=oGD~+D|8pSD3g}>&h-AVsGt}JcUDIqH-keo@gZu{wty40SwHEH#rv+! z9sZV)&!p)g$aJRHcfr4==7>&QCgkb6hUkIA4N2XPL8O|xA7w2f%MCyjiQ!#>t-%)x zjHmfKtnrPWDwTpqGd8wGQj;dSAC~tsj4@}g#B|{PypP_c-(rcwNzQw}9_fQ7Va9ZO znNF-3&$`EVqM~w-8Ympxoo1Ufy3OZm|8?2$Ce?YJZf29CzrJbtew*8IOxE1-EWa65 zax}&7TIqiNSj3t0U)v7Lwd0LS3xbgWZ#PV;9+j@HV)mrgxXD>O=j~)zGmrDye{SO@ znJ2|IJ)yaoCsFsO#lX-yyu1V@JB{DvJtcXsj__aa4@J~kgzEYlM4u78UZp=2AAfOi zk)k^Q_cAryHk6=GxS8sIKkCZy`79MT)mC;y=OyB4&43sUR;ro&WwkId=Qml4U&kD zBf~o{6wSe^$C1(fY$9cg7GSDy>S(d6o+Ll-y!nn%VZj%SNh?&RQ1M)Rz&O2og=yy< zi@b}nzh||s1+_K5ym}_@g^`3$JHuYM*T~NIIEFa^E(eIQ@D$zUlTCm6NGNjW8R3lU zKdF~HI%cPjDd4ekEA6^<-lqn%-hB?+XlN|DjgB#m5+ZT^nB=M#$}gt=qtZdRp2%bV zZq_YA$Mj*&NQt!Z=}na$uZ2j(HCZUE?S}N#^a~FhMVrC0S>O@&`T7hG_i=sNMkm4A z@idzvjz-T@dMZnH8l{K-=J)966H{Z1%TGYJeR)nLZ1;QpU1Kpf?cP{l5!6N9(C7xs zgMZ>jw?i>KGddntj)yaURZS&(NOOhsv|1%56fnlk4uU4`v7}n`6%7j0dG*9U0j+vYV zqmH6)urU3bR^9Wa+-5ezsJtZdKB=wk#?DUb?$i_`ef^=40PQ~rOGQaeuJg`a1A zJ5+xo3dzsOX@woHB0S=}OlL#q`YHXN{<9tFAs(Y#{4N8ee|igv9_^Wzx1*70kvOBC z?%4P!3vV^)RacsWtII)B%0rnc(|>(wHs6YZ zohGO(BnElj<}{AqRWx)u?xTy$n?m-8(sE$DMQ`ScUWt*(#EqJ3S%d@YIiZ?$lZ@}2 zD?K_23Oj13po?@mK6bxtbL(oC6EQ{-kn>-c3XE|p2cbN5o*xZqy+xJji4fb>mh=hz zAuT4?wwbQftQnu;7zDP+0M;B5A!y*FrY^VkLPkSr&U$D{Yueo_XHuRzxs;l zdqjrsoHkH48Z3k*kWDMioSY6h>CJ!lXnpUcI0z}mM?ThRz&XVR11b9=o*0qA86S1i zAHLr$ugt~tePurd#n*MJuj{KKeSbeLXS-IAPw045Y$mHOP*xeUGG8q%=37x-^o|TT zl`Lt6;^?L#@z&k3U-sEeY(4q`DvN*FFCC6S;E= z<&S8i9N>hr3ifx5D%TtyB~HJ?9TK{WiFsE|kVn*&FHkZuG z@@cO)P8)Lop3=`{r68jy8dKprS7=))6pI3BZ-mTs&HVsHOb9Z#! zE|Pl>FPx!W3{Yn9XKtP zWPbxh$YH0bobtTv`jU&lV*I(L^QiEKw+%H^-R*TL`~GDW7KR3)4RL^CUG1=ctO@92 zEZYI5`*f_o2A>e9JLofO|Jo22zkfY3Yi4G)xv9zM1ZtC*c}y7}kq{L)BvE3`!f@tqq%oAwA#9@|l_Ub8GZeGNSmT$Cf`ah% zP~$W6JCw{J?W~UDnxRkt$_-a;A%0I~0SGF82mq14n&PqM%5mnai;H{^(aZFXSOf}H zI^5lwm^4PM8PgaBjWq8;?j&5wMbA64I=dhH?7EtKhr7EnxExd{nX$PpAWGqRy^Pw2 z$?sMag_H1_^KQ==I&wE}zig~JJWgU7f!=Mlp!HF>=P3$rQe2!`CG`Rw_&mld#9KbHCp zs}s4>zw)K~NsS|;v!XJx)(3J)c+>AL2WKz7f>@~kTU-V4I#Tn86+|*+C+`1x{Kzh1 z^g6po#w5eAwTBRReP^RexIvllue_w_W8}~VQ--UHJ4H`+0ya%|pmsE_8oGOyPN#gG zLJvI9SGvhapd1#G-1Pw0@6@(k?{yjsn-#$RlzZO@t35+*WUabNjmty2dDv|+0pZ3Y zlT~MD7)K-DT4fq4OfU`!B<}w6ZQFepMnTL(3Z;$=m6B)2FEX&+@MHxnq?tZp&K5Y- z%_v#eX#|bu#7m~L*s7gpZmUH&XRTqAiHZJvrNloW^Ux_<67V>ytWZtgjaE$wu+3#r}&ae6v zw6}XiiJrLn>n`~0WU3;qBrWu*2TN;?ci#H*p7*!#I{#v|Vx6*XndME9$y~gPRNQi@X0xLzRg6J2wtpxniK~ z(U;vY8ngKOi`$q6CpQzA_A6f#Z?Grvu7NUipzkL=8|Zq#m3DYv)O)Gu;zND^iaGo9 z<14lK2+tn=q{p0L8m1(-nsWOu-Z#JU-EnMv0*auyS`o#|R_DEB8b@QS>S|l&sc7pL zGl*B8gO%k`0?7z+{>*q>YJF#$93;towfQI#T~(m5Fk;{%=H-K{qR}nuVjC)yGy8D z0baNc+hsmrxIMbjZe(PP??K&kqe&FM_l)xziIV8mqy$vawn!utNxs{2eCjC5cxFNZ zC*LiJ#NEMnbw}R@)l1>WGjP!}`1Sh54XULj{vSWA!PkA0d5yg;?kNaOI1$=@=R|Ce1lN0E+=Mb(+pcNokMWD!23eko7W1J;JdydEbn-nXh;V=HXc5e=^@tj;JNfPK~9k<5ZuN5=sL_a z>pF51^e};`lRpH8Q`289{nMR`B53807v08dRLLih))4I;J+(^$K%Wcua7yqC?HHEo zfnPF_ToSIL!Oh-m?xJin6hI5Ne<&v8{o z^paq<*E#J{rXh(;6l+AKD{KjF4Y=UiY4QTn(Z>mX_>*_pvM)G{*^eQqq z@fy7QsKj~bq&-O z`#t``0;yS%kpK-`>kWRzfi1w39Q~Vtm~5@Nr2qx76g*`li20P_TDw(v?-9`2a8NK( zktP?{-bgJ3i!~dtfZUHyj;UXHH~Cc4qV0>xDE{AlMIg&hykj)uCh*H*SN>8a{7j0v3hlGY8j@`VZ<#U=&}^px=NaRA_o-d&o>H4T-7iFwFTJ{bPaL0r8X@$ zV2#b{hv++?#D=PayJlAiZhm*)RPP<~Q@ETM-Pi=)BHN*&z@4~|=a@Q8z}m_FOl9yd z=0RgISSCw?e94i9kw#^x6U<=GPHWuTX1zw~#8&7wXfP3=*+!W%y^>aX_A3<%gHVby z@igv|9)>xuja=cbTJiRZ9O|{?HI!D4B_0@oKmf?$5RGVBf8T8TzY^>c z^#%&RcEfe%{1+z}wGS8Wb^oogqu~MnJV%#+5#>Iv8b5Y7+GyPS3Q5SrJeyVZB_yZ* z-U_a(tGjV)Z_%`e-=jv@_)^+tbc7@@o+%S45ct`XD_||)O^+T&*zivAjK|yYi^NZI zx0iNrFDk9}r$8cJ{~USdy4p)8EQZ*r2jxEE>Gn?Ub~83jt*`U+T$frDIbe!TT5O;p z@kF*G6m>~v%N(0mYfv?vsG6YkF_Ok9q_+WGqmz-##ty|vT~kx@^wboNZ2RCrKs4O{ zWvJ&HU@6AZfq714oZ3XF;rV$OH`-)=cFI&}^l5;U zGEuYDuliwhikR39}z~TXCPi^g(-awO~_R6#3fy&c;H^EtAq~KP-=JEmU6D+@$uo=*^BS`X?r=F4d1X0hIBX@0|XMf8g#>B z9)At8{pG9*1c&!;R*$xREQS@CN5HY@!O>)l0aLzJD0KpI+T2#^yCu ztm@h4H8BexZ^(vz92g^ss!oN}H)0p6J{@DsdI7-OU6>sh%sAw70A$sjzU0Bh*R zHnMJSsJ=fwJgtw)SRz+~^zyQD2f4XjZ*r>m7vN-#aB-!f5D5dk92OfFR(h@Iq`OBw|4X_hcoDNZ8;VB97 zZSB`co_O_zr3;;DieBdb3~xO~yUp5T!Z|_7DRKOnlUi&1Humdfp5Up?xo==VPH&vm zw57T1C9XHY*$W9hMI#(e5dY%4qt8%fFKvv!fagEhYeG1?+XqHQ>1T zEm~YZOh?<|W_oHV?S2~k;&$2QYMb@S^EuwwXjX9@<0F0}^;ns%~nbF5v zszsa9dgP`P3pXb09Cd^J*t4Y(on=X;O?k=`fGwy1I`E?enCQ_p5M%r^X`Uny+7+L* zExljIRk2#h&#L;?bNg-lbi5nDn}WD|S=eqnALTW3MPFxs)>a>I{9(RnY}9Dhv!tQ2 z-hiboJ&NNx@IEYVUvpW6(@btj>0>Kdq#^4I&9lPEef-yY_%n;InFt)s*-!#KojBCa zD_6H+^-*fK%;O6`Sw272`rwU;QQwYlpvDo>2|FMLf&`-hT_+Zg+&Hm?fAa$rhxt6= zfj&M?nU+LqjC~SLj`Po?I%}8$bVZG00uu#inylDw^R8>gcbkf%e;yg*piu5Dcdn1w z{ESMj?$7}uAf!9uN_L5LGfj9FM+eE4ihjPXTaB1AufB0Ru~FJg{yT@s<*j)@u=W$f zanni<6|QU9`OS^pILUwedF^xxTFQNz>VA9d1po_W}2{uzZk8Lv~twHrsp{pCAmoKrBI_9kbrKoLJ8Sl52&x zo#UIKPP$!@icp<|0GLoMb)&(i@vXEzIY4-CoJs+hm_BSQ0kO~?G+tX?LP=~^4onPZ zuF#*nX`gT9@|ZOA>f5GgOy9PzXIswb^_Os9B$lMR4cP6OBAx$WnWy)w;^%RVmHF@0 z>@;5eZ20qY7HGPh{xD8lm2w4aMvOzJ_s3r48~&%nn0sA#_HGht#;v6^y=A|#VR3nN zV-2~1Kp}Pt57fb!I?s(!qWPbb16k1DBF3}gQ*`q}9Glln1i{{|dDsd-!xT8Af~!ED zbM(CX{b|vgcrA~y#zCId+PYtoWjJ$2j}_NBq%C9GS@Vkdg4wc|h!d_Fv&ajyWw_e? z4K)tF2~rYwoV(_zaC2aM)Vv`j!gU{R-q4r_3ttlyPRsbazOi|ql)1IB=VVPpd8U4j z!4-TQc~KfQhoS@O3*y+-y9{LP#u5jWn^2L5Kssc>i^f|FHMV7q)uV1)piCnl=X98a z1xmO7y_vOSo%7Co<7dVFB)-rkS(*G+pg>lwrDy2dVku*jI0GCLZi_zUf}xGr-Bm04 zj1BYIBQ^eX&dCD0;ke}V1_vU4RJzB9?=6xv+apHfbMuhO+M;Y&UbgCN*{Vc+ZIN-! zA;)ZNq={<^d@w7M`ysmpf^h#9Cr=}eSoXvj`I}GH1fMk+jL7tARJkypZ=BSoR)22p z>7*=w_{1P~tae?ulUBB*n6$I+*hvI=D7gNpexHpfm;00ug--}s&`s@skox6A_fl2bq>U1f%c5rlRB^760E|PMq6dCidfcK?^q2J@PINi+I@bV ze-!QR!Pe;I7$&x|y2`&*--_Do40^1O@dO|3LfU+`v70;Ciq<@1J1M(U9%sb-($pDI zaPBT$)YIZt-Qm``8!xNH+4|QCZ?=_vY5VC-o1%=AB3a7|kNEUUyk5T&3V$9!g+4e) zS!8_Z_f#SG1rN;j8Zm=#$^(qCgwaf1eS<0mYS>u3$#^D@@YuFOZdHP6Iw(i(Bin2& z@%w~n!K8qWw9TE(ot?&~UZQ8-2#?CU@3U$v0S|%otpsDarx8JA%~~zT9~vU`s%F2< z1B4Nsr)LYsHn*4?Qxt4V*K#p; z(+z%V)}A$#+;5sg5$pfftYBohzhgVx)SOo>2)^HT@BRZ?jJkOMP<(C?W8@czBPED= z`ztcfgXrCEt<)oG`Yj%Ver*=cUVD)^J7vEkd_u*WerIwzD;D;yx_K%tqXqT-?MInu z3CXAthv^d~#_8i|f#9%_@o9%QZUHSLHsiClo|8opy$@z4zFW0^xb+DS8`PbZ)+_*?_7_BU)sgN8WrDMx%zr~)%HZ9Mm2%G`46*B3=I zx%fAIkS;s;lC!Sa^`j@32J=#0x=CGqnnA)IcIEwD%GS`Ce(T=>A8H)*yAF0w>ty)O zM#55=YxEUAVB;OoPsQLe9ts)U=piw~2@1&UQvX*V?`!*=hrVO>>sLtpYCr%jJJ3^P zKfu;&$T7l<@4>-TCMYKSgtanLZJXD|<)yT_g;qFb8ZX^Fq_-8aHeE^7x;hJ>8yS0KzAT z_ZvKGLOz0uhX6#K2Gp0+E+{anrRms7r@M{`_iHcGOQR+3X(EA2#pXuU?;2mrNj$@< z%?2~rs{U%{1L~s5Q5L_@Kx+a%#LeI!p|Np7)39&Q_infxP6K$ABAw|5Z{XRXembTe zfbF}3!EE!`mq2N@URBe|kXGbPet1!1QQkg`Y%}7iaeI12%>49OgIKeBX&QZQf0W(3 zaEJ7`bh<0h2OxSNkPmVX&;ZGt8UKanx{SMk)b|5I^x%N|fpZgqNkYN*t{EYRu}?oE zg(XGL7Wber_nQpCf1dH~zUqqXPZ0)~2oMYTbtYakkkx!d0=ePi5lzP2;Q@3klXIt$ zY6iZs(d?@aC_q}r){)r{1&!Et?$hS&Y@j&l2m;wzpBS1+YAP;U&qye^A;n4mn8vhMnSeuZHH2VI`68F7tO*p8*&J6amzbS?*Z2+btl z41mub9!5Hv8JNTj(FIM%!{c%_F<<}+;5MDRfI`a%{y8V2rM`v+3@l3=*x6^b1)HMz za#GbxdG_-*S-$~o`<(%0#hEaGgWG;1m9vzmxtswo_FsYJU>c@jcP&~!Y-&Ow#@mm@ zVsI1uDAD7Ap6EX0jfj%FXqAo3W42%2a7?%3VRQs|tf) z7mzImNeIJ|hOMDf{QH`#(z6lu#A3&8$(&}YeZC1m$`?BgP-g(R1o57d5%5`dXJzCK zJYe2=x-2dO?BXmjI+0-=Fr^GpIGowNcP$ustca}e6}}oF2aFixekz=&)* zxt6~5GaiEXVlJEFPQKBTFxLtA@ zcKD$NU?qY7ndYUEUb?_9%LziGrtWXIxK@s2z)v5PXEm;GwtO#MH+_0s4c4gytYQqO zNRX-9jh8dJr3^snJ{TMl3@%e)Tw0`9`c5)*db3eA1CiC)a&fUJe~3;LOPQm)cODxl z=~{%F$2piFuuk6X+JU&`?m^n}#&KpIII(dj03c0+4;uB{JdCz&TRCU`2Xk*77RS?d zi;|Fp5L|*waEAcFErbMuyIXMA!EHjY;K2rm;O_1OcY-?vcXyrJLw@gj?sx7z&$&mQ z^IiU8YI?e>tE;Q_+I#J_o0{NB2<8`0He*Ya_hBi?a0dk5=tFBO2U(13e{lEmaPHTf z&n?1k-*x{gX{o8X?V3m-RE;*%hWV*<<+E>;niI5cZlkg4x4%Jmma+#QqO+i({f9b2 zPzzZ9Tk&7|UU6&jXKzRU;p%Re#XrhBMY@n{{=na8!n?OLH8%#z09Fut+3px?$(;(z z{y#J~#7RrlBTE*=Q1I~0Gw|dXOJzY-bJMXbKZ4TcvDt`nW|;MlpPZ$A)AGnT`F2fX~x!pX%tFBiPZdis+TnOokc@Lhl=3)@IwI6PG34oG~KYp8-{@8z2Y)zk$2N z^On;DU`htTOBFr5EtC zR>}`=k`m^#p9AOA9ueLo+|x^3?;LEO1G-$CSWTa*v1L&sb3FQpvEbkovAu1-og5{v zLbpp?pEGZ5*Xrj#SluFYi(PETE_fJgZl0c@oS#*^5RzTuDJIcDl?9?OtS-i}fJa$- zC(5gE6?kRgOwqk<$?eZi!f$4UFU_7nVxOQvt=b9a_!1vhcuc);Ii3vxj4uZle;tr! zO~Q@ac&|3F4isPX-!JR~n;smT(zCX}&(Pnv`)0q%T{>lO#fCS}9jfyqO7aSt$~Hwo z&U0eG+t{E%c0hzg{d#HK`=A7RzRVg(agJ&i#Mkd&W9_QpAC4yXZg1^ZM z)ggdGk;eZ(YFZ->v)HmSsc%7DDX*Fe$RrI`;)GE|sgze1K^XTaS(=*p7qWoOcnylnsQR5M}MvSf!j z@vK5*k8g8mQrD_7^ktD%E&ZJc@a>)Ejzy7GOej;E5TyoiNw`*}cb%zZ$f&=Ajc*yK z{tQ;Pu=(Dg9nAioC+~nK^Y)no?Cq75-`kCVn^v^C!R4y<_UhG_NRYGh<;jJUvolt! z(%SO!^1<@*YJW>HtMWM#F0NNQtDj?{c0)1JV8V*Gt!a8NDhbh(f*s9{pPfraR&wuj zBOTj>twc`Oxb5v6?LeTjimlx}3r}6RD6K^C2ab`$mGph_WL8JVS9&TqWh-1nij!57 z1W%;2?*#-tv}ZpsW;{Fr0Vi5W;?uOLpMUFO@}*RmTEvJ!YVw8V&}1L?7AXO>FR!^0 z;t!@x`p&D}Mpwnhts1U2Ap45lx9=m{GDFlvpkbSWss?qO__Ri?0Eb5MXHiB^M)Cc) zO1D7XUsBt@*f1Jq*&?J?60$B3jbVfJr7!;EbzA#20?xV-Hgim3HjqOZfN)sAL{Hy8 zGyw2PYYNaZ^P(bDAbSPVK-xl$O71SsLGj<@XO4gSYhkB?rM))NwY(L8eQ0Hc`67YF z{~SH8e=&vs>#MIbS5iGDpz6$+^T0sB9mWsspFx zVhYAC3)es3pT`E~4EVfTy^p?532;?_x5$T=o+ci;SSkv;(Mt(6a~b3D>oo8vUkK)f z(1~4zlhTCjIhpA};r@4G~9WJc1|NqS!pVR)96c&sk)N@Y?L ziaLFl(ZOJCWU>ogM-Wnr<4!C{qG7sJ`qDQ^xm!l4uZc<<+R0QVn?|&j3)z_$vb)v*Py#Gx ze5i#cNwi`ZonDAc(nWGw!&Gup9N(gIB>m>seVc+Jt`@(V9+=6%^Ww)=$(h7x;;Wvf zyrXa2{Za2Lw3)bp6qC7x%sn;^E{!>(uS!nO5wzfWSP1@{`7J0ypsU=plSHy%#j9xAa+Ej4j5F1` zg~|j#tY#xNXryNxR~+y{C9@X2$;3WL!WoUunSK!yyCV^%t$h=hl@y1)s%w|4#2qn79TV|-ZjY&tCN5fD z4iEH3x~m$iVN@n}*rE@+2GkRf7#)FO`%a)i#Z~`zw)y3d6@&>NIw~;^e_B4VDIKF= zidOV5UqJ5{LujRkYBas*S`NC*Md7gT@?e&a!a+WE|hlTV^-f1AeD!P zF=`SSl{(9$V2W7+J%BJ~+4obGez7gd{__t7kLwtd6lNCWOEy2}4h!2UM9oJ%c1IF? z`ZBY7ayRlg!UD$Ajci(10R>NFW7N^#ZlSrvzfkOKu1xmH!Hii?KU>&=FmgXnsy@um zw;5r11*v;4@SClplC`6f?c+mUiGS}8I4Go|efI|k>Wcr)QJNw{s)V!~%g)Y-)rsxX zL zwt&)b5EkVXy9Np96|gPjI_lN=nHUxGX;)YqnV%uXzu9vYPQt~9G`>X_?aHmzB5APB zlL~3;eTVas@Pq0sy;u8gsdKghVyVEh2gmlx0p~xU^XLc*f&8cIJpQ-#mH$bV$v*86LR{SC?d9#fu{z){A7%u? z{c*ZcYHDiIHU_CWtAbnbXjU;_YHMjV=D50VH@@_S9b%iq_?pAn**RH;CQ8pF=Xzt1 z@P73NQZngF0e*fDPY>;d@82Get1)Ct${wF{u-dm;SXgMwOTwQs{BnMAadCP|fdZS_ zU$J_BP-Q@BYGOgVzC(rRedZfy+hZ_52-iBJrKOcEd%3;4%au6TCZ43y2XekVIzNBh z{lD6+0)NZPm2D|wPtyvu`&#K?KZ08o?P6wOv00`ZLi9aJ;TI5iJlB_`-lO$G^CZyI zw49-+lQ*y<=iw!UWj2$(!5W7YX<$uom%cdxAo4g6iKv4!Y3%a@7)FAl;IYub-%Row zNr!#19p+~E#zTi*Uyk``YRpp%1$}wO1N-X#vkGY0&&I3S1FmZ3e9OUug)J6FO|#Ez|;)wRpO;`1%wDlL$D;PpZ>M9Id&AruV%z z5#c5t_qvhH_ub3pcHM%cOcJU0mo?*9*3$R8Yc;2m%-;jO$ufcpNW&6dbWS~3e7ZtF zh=n=%;NXhtY_F&^IZAF~u+Ez?f;R)hmy4cinjN>L3YjLdt_rj2Np-ziUTSvW?`QL> z*!V+M)}?W;DC+p4v97?i&e76jFs*afsgL{e)&?xvj0(L-9lC33A4vi8(HyDZ-p-!5 zu8bdSc3AcDuI<=5TKrtB%~%^qkX5_XJb;0w*35S{7sz^UvPFBIg7*!_Q(mXPM0DMz zRb6xUHlcP`Pv&|?Z`z>`qsJq%<$J+s!8Ks(ee|R|7DCc${zjF{#Qo#^vbVDdfueEP>U0 zbk|nF_eUJN{lhH57?<6Kglsoe>!+SV+{VGm!$o{b>@GHgWST2CsX5`EfB3-HQ`X=v zsH@wI32t|;E<$3g$8Bh=C=pik#!(qX#_W`YP1bI)Ffh^x@-N-a@qt z&&4h5xxW2HtJpu_al7dgE6Hnn2E4@F zLZ9K>92a3p>dXsr%Dfq_30sOi`5EWoZL9OuM)UN#?5y$k{6?-i&g%T>a+xK-JRkmZ%2mf^twV`)w%QRfdy2k5`?esJD&BQqs?yag*8+|8U{)!XY)nh1TfAjJ3 zMBz2a(1FOInpIRCc3mtVDIdhC;D`IH<9KW(H24}FIM48CcaeOR*5 zL6_vzlr0$Go^^T{j2M3MurGPb{<1v>QjV<9BxbagxY{nUm{8RnyWYxYqv z1zp2=r8xHccURtpi|>CLGh1}HGny4Vt=}%F;N5MRC)VmxYx~)5E_r~54qK8PHyj{x zCiq2&x`L$@8{e*Og_5tU%k!kp>)PFVEOH<=#EuYojM5G_&hCOy=p|S4n}^-YrRt=~ zUxn=X&}%tED}no*qRFUS)tQJ!wdkbli=Vf8qWC~(zC^~~W+k(D^*-zvFfo9yZw5jy zqNa1mnjEB|=eRA2xt2QM99pPXGy>IMX7!@g8VWKp_N8BW{>N5Uj!tz*@)tAMn+&I6 zd%mM==M1EVO7Hk>$JoU+a~y`~MZAZ7*J>MOPuxazfq=FQ&W?&Eg-)jfS!9AkjTyR;JPbB9pIzhuzA*m7yMKmH+92*7NI7U?x0{z}GHX@8ACxvkGKHVf*LF z|DV;={_ojZ_K&VFTN-{PVBmq_KWS*a+S=dW-`?NeR(YTawELUW_Ex31xVX8wnGNMt zh!9uIkm3x)yR@jNsH8-e8xX^S&7Zg}hx+ZYoPH_V8UaUM^*0WQO0U~=vR1OU4%mWw zMg5QP*^M0>9O4Ft8eJdV8eiweT(r1(V=Ssa=A`MUul>xX6Q?FFTB;0oY$b4Gkwh z<8x%DMm?0Jm`;dYcN#k=4X@MY1^(Wprk%5DpARs$+S=Os=G$zEG~2vE5Yiy5Aqv(4>H21-DVDagpl?v{FBi48vpskp>>q0Um$Dbp=qSbOD3V{ zhVCEou%G|V%_Dfph0~&_sO$Vr4Aufi1X6k9fY1layH>6em4MmpS7xg@&;W7E&?OXEm<6P-KfQcv(7ZtgyI3VLW$Sz zK^<2?nmD8tYVVkET1>T~PH&J39K|+@98`9Q*1}sDYv1^c?LngwPFe#xZ@t{r&m3f8 zUgIAES|9(2eko#?G?1TlD{e<=;m7UfbPI;#Ejiy=BHWtklI?+rRq15jw|{K^Gb;epQ>!eenv>2W(wEO7R?o>Py=GPbv2*qcJ0q3ro&x_u zH$6v1{r2 zjB+YJtnkT#FTb?ornsLArG%gzV{Y8^QSXXd`pD~|^3+i1I9*8Q6$H{DF6nvQi|wMA z3%)x@Y&hZ5CmOht8ue!ZQ}<|EJ8>rS)gHzmP3_;#`$R!qS7}P_Hv*pL%!wFqdI+%2 zPiZ^owp`75mPE&|jJ70lpWg9F>!As)QPs2Do9Qm6A?bH*Msj(%K1dlAxvtuzcxGwM zvtQDqcLfYYrYebShvh(}#rXk!7jw|T6qLT;7w_Wb3_SSSSnBf{eO;YrOk{1JGo?Ut zj&L)w*G21LGk!*egXFLl@%F^ohLB+SGRL~nyd;=booZ}$^W!zCn3l2$|CwsImzV(b zCbjwA$EX6iVCs8|dmTTAvFrC=_Mf%PrE<|@x^;<;)A-I88E+HAgzi5lu%)|Xoa`2( z1?nY3K;@=O9}u?7H8LkZ8E@0KTwlKq=l}(PYb2t**q1ePaG@s0;VCW`Ga{-3b7L#_ z^tulhLd}dR12fHfTiX{Nj%hB+#%9 zP+tjC_>YBe5NInKdS$j@jJ>#!kWWSd0Yi&{d;M9}s%X?53X(eYM{!}-CiFD_aqjpT zPW)(omyK=;uxb+6ru@CRXS0KiXVi_|bJ!;TC4V}uk^8eRHKa$#KUg*3RkGfMQ0OQ- zhoFXY)}6bV(1Yi7n$134S!iSd^Ds9h^y^YNzsBb4x*IVXbD4+FDPmX8@gs4?zB8D- zYOZL>H-|b)T;PxJq;``Hnyu zRK1sQd>tBy#tFe8Du&D)fRNe+=0#$m?H9N$w?*q@ByA>LL{*B3$7`gCbBPZg^4U+9 zz9M{F&0^5sFyXZHxEqTQTv@bN@jIGQ)d15PKlc-)>j5rxnt_I`|>y;?Yf%|p9M^Ml20e-c-9 zKYa1`vgy?7YT-bVew9O79UN$QnORAo-S4q6p0lc=Ix&|m(&(5}3y6OK_y!7{N(+B+ zv2oYOzZhtD$PI_r5(7dudY{NH0*+>qtxjD&Jei1*CFG(uydZVe%LUMUpArPWE zLy6TQsIOjrevX@>ZCS#9P^9G5^Z~g*1C8XS>b{_Iewn|5EMiy#8uh-XA~+@oCy`)L zNr%HLr%{4Od;`ZdShWw#YHxUYwL}O^x3^iDCF+G-_~CccHzr5@RLZK2I4s~Hy`?Go z-A`NPhq!{Lezmose%DZ5`@_Oort4kY?>~mE+_q!=@>Xig#C^N0-1W4z=IifhaCgO$ z%F2~DFof>S&kw;IZkDv%2AIMa$8Fbw+$NT%VKQ!aY4|$UXdVZWsRx0r1utmVew8cRa=te=T?| z0zy{0>T2V|GC7+!z1<={=0)pV7F^ z9>M0hQ?ELS?JtNe(ml_p31fo#@mwQ1!sW~;T?Z4dmLmJ1Or9*2-xwFjhN}+VDT9KzGqnv|G5xWs?skEbH|>*qtjJLwiiTZk%q) z)gf-<>i1R@X#uXbl!Azd$2CpT}ct2DfeI=tb zw5!wsEUU#+=jM04E{l)v>GIk6MO}Rz;2^HAtLr_~e-+<&_!$Nnq<+6QwPY&^(ro0n zFFQ%EsJa=PvTr%HS#P7eBiCOc_p5stOs2zc!F9IgKZNxm&bHp&&A~dQNkAZlcVUo_ zs{=cI%$08Uqtu55nfm?S#b43Wu@6z4uCyo=jJq}r#i7$a#EMsJ>67e)ulMO zc&+DaU2B&40XdqnNivv!{4Le_8GUCiGsxtN+>bboHNJ!pGr4ImTIaw1h(9Xz_4SOa z^fnHaLT^pN;^;||uo#yj;(Y%j_7T<8)C7aUvd+%BHivr%q}WkQ@MuHZ`}+}xpQSiY z5XitpZQ_rqm2lP>H8nLNOxeBfx5zt0BILc??tvc9&(C+PksUhCR&IOU;GaAgmF^@a zod)-j`eK=gCL`~{h>>u$OT2u8_aCHEIki{Hwd6V0J#F*g#~SUcLkYlHgQL1=bEaj^K%}PYvF(7 zWy&JGI}F+C>gwy=)p}l$#p^fHJ|DEc&blj^nvwtgjTBar;wv9TyLgE@+1p;$JY?YW z|6k(recIgOLxdc35f`dmFyl-mPM#i24d2vF3fdk|qUfoW0y z$=zR*;k311zYo$}OvK`0LYIIo1LqS>_6B1Gn@;1zuPNB|MpjengJhJ!@S|4>#~-%5vxbcaspdld?gs|335!nE0MtwIc*DTns| z7!lANhSgW#~-LG>H7Z;7Bo)#hqq0D|~qVf`f_1X5PsPeL1HEsQ| z-t4QZm-6=-TeyBF3ivg3Wmn1j!dWGOYv&Tjsx!(|nkzB(*1JEwlTVJ$t9NX#!vaW` z4CsVjC?_Ka1y9wa@XukBE*hGt(yo5!>>0^VRO#i9tI7FX*f&o?fwkOfeS57b3sJ(4 z-1ln^txhbPpHtCmvNkJIv%$x=U<#mDRiYoFbL60_PJvCtE@422Je?7GWZHm568t*H zzR2g3Q97TpLw}owLurdCkLtFqDokcX*QkhlDYA@Kn5fyxTP9D`UlR~ZE&x(7RNlo_ zbugpUph^+rHS(H%-2F3BI*~)aryA?`8x~>qb9Atf5Z03zd;lc%!|fv**qC3pNtaNI zCgg{yi6rZK{Eq)!w@lHlV0f}JR_=LVF%hx5yIcKTtBzPlYabW7#%vLu)S3R#OzCE8 zHKkJZk{rc+(~r(#NNE?PKw*D@LW%~=s=u`%N`*!;!_cuvp%Q zSn>-SaoH%{x}1cfqMPZN&1f78f)L(Ri+%LC@(BgUN{) z$#vAmwo@pSvwJ!xJz^`kN0lDR&+pt7?Tv~Rv#1Hqx+ruPEUZ{KPz`%_rV1~k&V!?< zTi$f9KYE0Q9Q0$UIFdu@Xy;RSZ{7?mA|T)nl27%)<&nhcF1K*f%)B*CWvi0X0(+nFK z@sIK)RYT$`MIk7Dg8Bg=dDRf*66=ARokeLf;q>i+ln9d$)Y0IVK<0E)=Xm9#hpDy& zb#pVodRQ$=PKk#cRUN?<)!MvlQ)J!lS6htTNV>%KmqbhIhRI{)Nc}cHu#52`rh9*% zVf(T?LloJQhwKeeJ0y_Ft`RTOfTo2Ft2XEN(jV_f?5kBm)F%#-uM=xbus0U0y~g?2 zWQNgs{nmNfkL0RoV^2i~=>wXABnrl9;Ej$71S{-2tg|e+4XXl^GR^}76MYG^CKNI` zu;_W@lxGYjEc=LaZ8ahg0o&s%q6G2Ub%k(zQQ6E|X$CKf*#Sg5CL=|HX&yz#>H|*) z4mGikigP<~BJC*C61WeX;}cukL>ONo{AtcUb@?lAcA#i6-O|Nok-; zGrKq&^wQ2EaL!WpL!U2Qk`cM;%bI2nHoNYygd1Y}IW=toH?jERok@gbMq<_n#X#5W zd|hO#CAWGv&%UUJ4qQ^G(%Hz(TEdNC+7O$DwFIgH-=4JVv~8iayXS&a|C0%-Wd`fA zo-ZS-Vkw1s5SUN&3;#P#1Y;a*jnqvAnmM8w8t(VHp4O(fKc7H8#Pyv_JWY;7qfpp0 z!2-0lYCd*P1yFYZyef;SBANN*r8h)*j5831a`w|#lj^eE>0(y$D#w#;1K-qGue52M zX`}hH3Ko~WgCJE;+QXF)8Z=DsJ)35hX4PR7(d|sKjDN`pQrBo#PcU|L=O9xNSAKhp zdq|l9+0)b~KSs*>Tn#+eS{pM+hl>q*%OPqKkE_;CVl3C5Ej+bJKh?=XD4qcaLXjI7 zS1}W21GQ3eaQsz5hdUU+{zqZ_3h%FN^}nnvdZ15^C^^^*E03WHRO4WAY^+&VIsnA7 z>^9f%Q!qc(@Jv3Y^tF^5(f0#tpXG<2`)Ji@MCT`nwYl^x^akp}K`&q%k!}4g`b!&( zAYiqf$e?5f;>bh`kzQ&c6{XyE?*#UJODWr}5MhRfy9^?i%G6`%D~^p?#ZL}rotcZ< zQdXmB&HVT(M>wzA)Vav004SaCOpQ}^Pokou4G(6&!zucnKGj>PZmh!S69rW4p&qbW zAh6OIBir!gpR8&^PL(9BKKSBp6y^3T7zgX9jQo0d)ZB`R0tkc_a(C>$H&gErOmH{o zC`1E7*D49{tj+l*-I&(DM@$jGyUHtqR@K~Qmm1v2Sl=E^s1@Sii4nNgJljZ~~TqjCvsK;4YJ3r1YXXA!9OM8=&_^c?OhC6SSj} z@>1b+d_ohDdePF>_;j6t!p_zm(EkMJgY#sbz=F+)|4$ugX0(ySQEc|ZrVG1-3uf?);OyxEIZq!TXwt&8t_&1&5xnqug4rS3L~ z7ZCRXVD}B76hG;ES9@abrO}+FR{wihI7Z|U@;5Ge0Kcj9(xQJjn98D%sBphf+%=IPeUF zn&b+WSuL7CVFdxka4LRIXp^V&QR{LbsH>-H?nPfXAQ@p-CC>z*W(=vFh=6zgORV#b zN+Bzpy{}N@i3ccnACi^2uubRmu#u2XC+Rf-{Sih338QZk>btfI_(o|hpB*LRy^VWp zrPUSYNC1$q(;*+^EL_JQ;_%Z{j;ri zs<=9;xA^2JJ2$Aaj0_NzyvF8JKiguY%RY9pD}b?OFmZv)rh1n}rnYi%3&VMkM?}m-j;;(6113` zS5g5^W{`P(Io!44Y#z2AQe-nUa_jlH@L03J>P4AXW(WWkkNjMBR7KO9Sb2HoX5(U; zcm{rv&0%&FQCbq){nR_Xm?J(vMoH3gyJ6xXS=oM;s>LpDQMrj)Wc-EGiBWS5 zY~JI3>%71h^5-Z0%H$7-k&pfnGIa`FyU>heAmJ0XBpr)CRsr&q8uZGVnUtdH2%KWj zXv0<4kCF0;2gC(|iapk@y`{C+D=WW5aIH)d>*qUb=hW_oE?gwzO1zlPf;D4M)CJ}+ zOsp6XbtAGYE#1!N!(4=_2G#hTjV4PC5D1)!3nVRhXRt|0^#qoGR`k-Z17WFcmWCo# z5O}v2hLefrXR?*q&^GqzFbmjfx_dsx87;AIO-Is;*#C}*xHNEQ>9azJ!3jPGU^d}& zLC8nxupcanJLU>|hXlabwGv0Yu4q3YMMSb$s;XYj|NaUkiqJ<>C?5E$}R zaBWyOONUkRx}~@1yoMmhQ=+A!*F-!@W0spS$_xtvbth~-fz&1UMs4Li-_u>v36Ef65gElTA1)(L$DroZhF=^%jjvZ!leS{6slj9w4Zp*U2E0@e7 zINfw2B+JhhnaPou324R?iNV0!&P-6j{R+G8%-SDLE_&Ca9v!j^l05TJt;YVz;!I(^ zrY8OfJd*+`J`Wa1a8Wo<#sv#2fO}|VDUFMe7v!xKK3KxUS^%vE7JzSjURF>fJO7EM zZcCvJ4+lNs_@{g;O<$A37gt+o=(F zIjkbf_VvgLtj>Y71b7yda$Q4vY0vT|U5lu~iq~{evDwq9mxUy*gQ00K$faYqX^$iJ z(z7G1K7qIzN8S4`8tcQq4B>A*c8Bc+wz|sui}B5K<#ZNylA1#r!QTFZbL{$>hKR*y zVKTAUSj>0r?5B`vO%-ZF)L5m^QP1Bl22YIt!@tTy_c zmEvd$+vSq=iBiZ0G}IewsnC`D%sD1C1lU#n<8-MG?ywDeqs|IzQZ~Q3M9I*OdBWzfEnW^;;RVpVYguk zRHe`pogUa;(c1APuQRWc7iOnQ%Qmb^Bxuz3FI3NwOaKa7#Ua5s${FK$Kn<$1C%@WL zG>Fm<%6lDLbtjKL*`eo2f72ZhdhI-BY{w!^Bp1dF-& zc&&M;RMK>w>5IH!l~wii6USNQD*izk=X~Zn&T7Lfiu3!{vVZYh(Ycj?c9Y_r9J+vQN4fGPjOvq^l8-h1;IdQPOW)pS8tBI8=q2+$9+^$_bosNU>cD!$WAdgV zsfZ}-<#?a*e+Ziy@Tk(<)~191QF_tzX<5bx~FKO6H*4bgaXHeT-L3*Etwo`Sb& zQk7&G+5IS}b5SZRbsCKpcVj=5s@zT7W{0K{5jcpM%cxqrjH7_thWkqQ%k^~|EFFv@ zIy51O4SKDwt0Jxpz`$t;Nd!Cy%P*?7sqMGChD~jRH{dMpCTVSw1O+6a6lzyc!#^-!WApRB-u= z$M#VJ4Ca4z9~Ku8HTqJ(V_q~HL&8Xir(#waddd$ZX{w>)3Y@*gzk7B1D!L@3nZQeNnQ3u4yS z55$$7`VWr^M5Q*9Z-cIyjeVeIrQUi zU@pv$?h|ZB|H|o?wZTsnaZrAJd z7|@Slu*db2V-j0cYt^MD@9D4ZCKPu3@Zie$=G|$v$_!+ zjDV(1RMj5649s8J=x=(BSrVGCU|S76p@cm1(5wn(^9Q0L*{~mTuqfDtppT^n)#;bT z6!J29kb8)fFML%aj8agLwKy3?rR;C$o;E!42^2O-!}(ejUt#6`#Vp7gWCrUO*4HY1 zNa^q61DJgM2fAz&GY;RI;JI8NH9q}A`&!qL08kWmrO7jRrPaTBM-A$ zqcJr`B)9(%JFA*_YeihxjY81k_7|!>W@mN+-b(W?bU>}+fW|Ob=*;W&2dX&yd?)`Q z6tv`q$AA`(xq-2kqIm7>Rf59szrtCzcn7qRR!S#-BAPzz2`Eeu)VahKX^Z=`xupP@ zE2m}Rlk!ciGP$f-Y;3U7oXy|+iPG>5QE)wBa0kU@rNL^pIyt7Ve*!9#zU=PChcKgW zG8j6R0c*1&Q2o@3IRijiU`e-xyFP&lORI_QA;FwVwXvUtdK!GsvMsBWh^<$Ocev8rA#C}bi`gEbLK)B1@;Pa zUUggbm;xY)`lLPq+dVkXENVL8r1Zz9#+&+medLUHp#K7b{;QZ&nU2yPOcV$19Z;I} zp7l9H(O5gJuw6}-HqHU0I*FK7=N%n2knU=-!BkdLD8)&{jBjNg{{s%J2+FAS1Kufe zh3wx|7dT@^J};3Fu~CZ!d0jXE?{Ozy+@B2li-Wo8?6O$~;KSr0xVH#Vtrt%zp z_PBrz03?wkppnl`XmiFh9}==AoAvyr!80nwt}&3VfVr6P+j!z_=lw%)VpIZa2UG(> z2j(63PREbP386*p!-+ z3?(ooyGp;$r*Kl&a^nuAREYt0<$Hb8BAeaqopUgjH z)3e|(ukbLI$91n9Ni9LXcD$UYl>Ywb#7+R@Nl`SyQ|ZGd!Ad+ew`#Zj`ia1XQ$ix0 zT)=oN?t!^Ko!4AtO?f&Ibm64Aat@yii+(}#X)uvL)zPXARVjp#Rx;2xHvZDjRC-?lI5zIZK zh%e#h60fuAcIA7IM9q% zA{#*?Kij={KV%}-t!X9jv!n#F6NyzUnm{PAm%)t)HY~Uc_k~%$@sm7$Q|Z^_Re9EmB^8Z!M`2`{zq9(wxYm1oh?w)t zT25bFUlgThxNv*6kJetQz6uZMoV21Q%Vb-{5GgY7k&%c?J|B(3^oBS5moI=8i(?7> zwvW)S=t6qL&TGAz=-TiK|3JPnM6FDVhPm5A_FX0-EBfux^9fW}sr7?%>+PC;STtE%J-fKmR~yZKXY_u1lFiz=Iz&bcC4UxW62^Vor58{mM;3(;}#&a@*o( zRb#HQ;E#4?KPy^JF{4;3EUQo2?-71FF75IPX7dU!QwM>A- zs-@_xCHVL@F` zx>?pN*M?Wz1YA<~ShxBtu<`xcEs^!!R4rla@a0tPJgI|q`6rnxjW)mL3kuIV8-IA^ zac$)))7$sEh`I~2uSYPGqIc~}cnc1ste5o-q!5SZvI}=lk;8+sa-t!>A{g`~gzYMa zv-o%3A7=eYbjH1*MrdKEa+)BLv9TSu`TeQZK@~Y>udqEgWh*lmx&6uu&M%OXHuv zJJ>C7eD_Y>56G^xRbTU!EsUZ6auQKfM2nqXf8#k>WXBa#2cm%Ra7NhMzcNiivB^}} z)N^T^jbw>ak?;|n`4Nsa3+o6d!HJ?(g=C9{Us0*5QC87@vU zgh5vcD&R|3trZ1DCWHBf;WoId7Dc#u<`m=MW|(0OaG<%2xW7fT<911UqyI*+6#pkN z)A)z9$l;t!54(l>O-u)*6n7aw$l1$tKPsG;x1$5r-PZOSOZ}CLj{V)KZkvUAs_~yP zesU&n@2n5NSMzKFX6Wy=hVLg&CC}V|nfO&bjtl(I<`vxJZkIwiZfVtY7LDw66~DdktG(O-Rr(L4ru*K(A91864vfRK_Rq%wYjVMv<7?+ z=$|Rh->pRvdVd8DYGw4`WVPFcoC&F<&I95`xx!TfBo9`$ayO5W_EyDj(ni2x|NER% z|9kW#a{WeiyJ8+Km;bA}?~H1y>(=G9AXN|$5Cl}D_a+?$q=gOvr1vgGkPb$WqEw}X z-U*%1djd+4-lan*g7koN0)(63_uhNPcgGp$eBT}S$H~t)<^+aEl3NvqYoOZQ* z;XB)!Qhb3u>8r}C-mN;C1V&TkHy(O1Q(19$<*&hJ<56>IPYQeHe%<}?=3Q#b z-lCcyhtw(4nN55;aW{FA#2+<;35r9<`K}=u4*C7f90)9u-06%f7>pkYEnju^)j@9` zbxXUhSe=B(N3nU8xVxBSTK z!M0s@-^6IU>Ao#(S%HZY1K0%J%1`qybm8K1{b>~9j)eWoMSF#lq|kS8^m@z?DlBYe z2vSXVzJPX1GYK{3a?3R`Wi9Z@IXGtz*v7Vq{IICF_C`h~A$eWT>YE^MDKntyzd$6QM)F;p5(JLvL zzV@LnwqHQpx}-L|okX{H496osQcbp3`Kp8 zoK2yZXyK~2!mzTdjg!_h*~&^A(2i#uwW!DTN(HZ#lhIX}%Wm^hAJ>j1YIC9$ZSd87 zRPqTi__eq7kXM8;=m+U80=Li98Q(iPz_tBzaxyqgA9(&;k``Sf^j6Jwkm#Wg$NA86 z>U+4ef#6;hDCMzMyExc{N3!$!kNo+WE9?IL5&3=g+{nF8{Wtl3Fvr z>RUmUr{h)HEiP6NT9ptPe}#{d$-kKPt7-RxKRW}>RKV=>^ogWQB!_=&Y5@4f{-%d% zP0Zab=gL9LLpxo+K{iz4j?PP#+E50Y@NT^0{Ip>_@CJ`2E>*Tq z#fbAalLCoU!39JG5|~=S9>q6i+8!h90o`u*i4WvGpbgQYdHDPie(~Jb>!mX<%)RE^ z!+m0l30H4gF;8#l+pd9X#D{qumw2=W%3yoywE@}bUMuY4`(x|K)+4$Pt=zUs(}e6=dz$ z-C^thfj@-4DEHUiXe!uYae9l;-BG$}I5#JjIPQC-Dz+kP0F)upo?$Y^eQ+3W2%#Co8(+T7<3VC37^RCgBC+yb>_;vaS%7h8iU4q>G8qQYT-Y zACuZ>)%f|HYq_MHn*4h}nABl76Yc@!@2-teo%W0q-;NLeu=-O7n@`!IlzB-6_blNt zJ-#*X$%Ard`&hDZ1O>OX&QkJWsJQNysyqAC^h9dIQOg!emy-&9{=^;79DN*~AgT?% z3&^f4f@Uj0e}%BrB9z0;lPO-d;}4HbEeYov%9!fHr$76NYVpQ5efr_FUqP%IP2AK0 z;fR-N*)y8%CZS@~Q_!y0nHmlDrQe7jIdA5pByGBtjxgi@80Yq$AmN96H2i4c`NxfC z^delH&wf`B;p1l;c2Y^qAF57nOyOo4;Q;ev0V5-l+glI%F61?R+tP>9v&J^!qLh|b z6fAU&ZkiaT^w>=|OrN$94`sKC*(00-IvcFd zokzm1bW2=}67{Fwz1WzLc+@5HLDXgY1LGC-WrH8R$lC{zrX&j!dq;15W}M7U+l%08 z>@jvKW0}$LdF)ydP`-+WAu%6%oga*PsWU&FYm|$m_8EHDEuQgN=S-a|d%#%jb`T)D z{L$Rw4Z3Q+(KArUJ0L1!y-v_{kRNapSH21IGsa-mAm>|+LNOmo4bCBTI!r}@+rAZQ z#rNy{vy%Y^fU@q{PlFYA`l^@|jMk4>{n|f94ghqxI>Cno7 zCG&E|au!%oKiNHzI$^P7ZO^L10Wab!U~)p^+PM%yudiinM*A~383y^?^*zIRVTugy z12@lmyrzBdGvd6C*W&;_C=w*N+M{Fd6v5z#H@b)v0F z9;AMpDP_YOSurO}-?<`0{S=IEjCXZ3&e{d$cq(meGm#Nc+cGzLLXz%j)w19A-3t}% zyzxLECN);tdXLRJe0ST!<&0K&MS(4~7xw7;o#=Rut*3s+zM%(1_V)5il25NxChZq8 z`VZe$;~hVA$4WadXmLAc@|()CEiuuS-2mc+-c3T{0-WFz9A0nLasnG}lWCm8n$rdrz6h#6(-cF^u<$b$s8QB0S z?lLXmxv-O)jM*j3uBuvFY{u;zb;_Xa9d|dMU54^`!3s3f6M_#WtYO>DZ#4_^_RK)m zxDq69{}Wj5JF?7h zRK1l!q0Yrq$kE%oEqbt#JnhjRr&C=SvMx@S5%g!{gtMyOG62?u8b0rTzALdWZFVag zzrXt+y79)Z$*Ne0w6nke=CIfHf|lo(MnBS``1;lK-4?jdC1G}1@b@klvPso*;|scB z<9wl8peN+p;@9&s%hZUrRu0eLytWTld9QP4w3A!i%YV;u?FJ)RgAk0nzWw8dPo8TO zr?|F2rD{EK&Y z$ktHWc>^-Zau|rl=xLarfhEHDcj%NJLWCNf;o>r7LqLbZM)B&N)wWMDGHo4(<=Q6Y zOMDrOvPu+MHSy4G#AR;oj20y0yN2=)Zq=s*bC+0%9CKoA(a&ToBzKYlPqui)EFZRN#}f9!63V6OEVNcSA=kD91apbz zoIv-@!pn5kG93CTqI_T8Zwuy(aD^(WnrM#G2^z#qs?_O0pEK1LFxTT&vFGZ)DYn^C zF5)t|w+#iDR8DMSv`ieFJ&N81nDp8X38VoNsH~kOL^p-zi=B@~FK3*vpY)WQX4ny1Q{H&er*DDSK*f{bhuNhu%`5xM@Pme_zQ@a#R zoh(3_N|lGAafo57`i^Erc6v275@rIvV#5S;%1z%ON4(Uqo7W^{hAMc!DRi#yNN$7) zgk?@z8LR3l;4K5T2C2`Fn&SI)mbKQ5OYSJ}G#1Z5SEd{Zfdj$nyrf9L#C18dH!hoB zuvmnp@V)fxn#hD@e|4CX<=pt7)E7&hAU66{M{yi z;f1{85t1T1y6b|ztrGbawe+~vZQD`*p1$pROHm+aPQOOeR;9|(d+Yiy z&RH_ow8;PNz4b4?p@3SdF`n_(3be9*Q-}2sm#-S;vWwD@F{TV?kkmq_J4S3jneOP= z`7}FO9<$Kop^Tw~UL-Ya-|l00jqUK|&8iIccV2BZB)?cq;0QFuY)|zac6LBDn0a+^ z5CJuA_B{Bd%ICPL^1!(ISQ)z%st-EzDE`6`sIcm>$j0?x5AFj8NMnwhd1e^GUgCTu z;!J9NfxU|e-<#{Y1)L0a%;|LS{YuQgzHUdxTmdlun$s;}E>L?U;dLB;eb@*+U4ird z0X0MG;~y1^gi36!58>=KYp8Om%d??SGBT-FyWh*V34bK_W0r9i-Orzs0aQa*uRC~f z%zQCu;=F)595XH2pZU}QQP%MzSsMS^Mfd#l&b8O_1{4ZdUpGD5`*FR$?tge5|9?H` zzbSvj_XbuRH-NlZ_eD((UQANbZ-a)Ffi~i<&V{+U7<1u#y~)1|EkvY~jeRzr08VE4 zBVQ-_h0f4A5cZEn+OWk)auDm7D@UaD)hkbeR|$nUDT<3k`D*4@B;#Q@oQLq^hKEqV zpO>mH@2d?)5tyQNQmPy`Aook}jQY^4uQ2VZq}uE+1MoFW^q8Qk^+ocT7<^;S6*Rig z9=~?K(7&>AYuSUF4F;Vjz4Vw`4Je)11B4OsoKyo8_APSP5JhgDeCn`* zD6+Chx)^Rqj%88M)UQOMV83@)yB|mzL9}Qnif_xLc9tH{&5k@g$&;WG%0R^cJ?_158(GMUr2NU- zH^=hkjD397SHpj-+%HO)w`5jkJy-f&+O}N;(48^N(Mzc-$OIe48U{3##@aQN2r4KH z8~%g}2quSUZ&plfrsTH{x*j_^dY%HHzR*sx2dAK9_3xD%) zzpZ;_vg&1wfR-lb{5|9&SlF|W{A6&;0-efmUmvsAYBD(t*^MVYXEKHoIdAcv*P;Nb zj4jlw@m#=nMxB(9Am_6dHzJb{+Vt$HCks2SiriINK9_Zt8S? zv0>k~#0Gv_x0_lA$ndg0u@m9D-)Y|*#+DTC0HOm>QA!NqfWYC^<6BW1Zx*H=L8O%* z2?h!@@*4%sij$H0$}UBQ9^VSGoKL{)SeZREqsX`S7fRWlmxnYX+hAIfd09$6C4=pn zH>6MaQj$BCsnTPV!1`A7csO{}iFx%jnk=VzW}3wfqqxZkIjBBxs%r|+GUoPVNg8%! zu_$p3@%iPt%r3bzZY}mC-I0VoN$TMK0G#EPbP2&tEOieBi zbKiziM*NWrUe{*sZ@IOQnLJ6ds9ya@2&XUd9rrQ8WIco4`_Z|*HUjpy$KCd5-zJ3S zC$VR?V{pHer^!)Pvq(K+!4AIF8Cso%@WWGfk5hJ3Bhn!v(Fvvrn%8m6d+ym_$}l4( zQDf}<8}%gOu=iORX44===Vjpogwk#z%Rt7n#1S=YsxyyBR6Mg}2ale#}Jsr4`gPi)u#$yw<6L#W$S z{#+(?AWw3!doa%-EZ5u~!}TU6mHjQSV4p6IfrZi8vgOntKg&z?UsRF(s{t-y2!Qfx z!i6cozTM^B?}&(Z2iKe3;Jk1Wetvm4hC!=bhLiaQPgN}PoHnkJ>l&6_2QGWvimUZQ zsVbvu{>ptv>fbv`iv+9>y;|b`$UHG22KQ9b36@;^m%Oead9hN1BdGEp%@yLs0{8S< zPm`}FC_(8U$Ln!pY3Q9l;Q9bPVeONtAfBg?3%A4Kq;$j97CzsNWp`yT=PQ`^H}@fI zu#5A4#`zs;gGE-h1>_lHdj7KCV0jc$3=iA;UhDW=@+|w$rJ4R#WByIR_*c#O|0tRe zh7>!*oR{TyQ|)H2==IsWb6Wa_2Efk)xqa~V$-`x1P}kP2VQwyEgSQqU=+G$`HTLx| zTiCZj)Tr)|U?L`(uj7u`_^hk3)2#`m-Eqex3KmTd{>TW48e!r0+fvA70TTvaXizmD zk7_Lj*#d-wQg9)z-pEDnYgDf`M;MO`Td77lVdCSO+`I-wbd7OOWuk3YD}R|;9N)3u zwV+<%wzM=te7Bj2zvUs#>PT8ZU+H)=rg-xQ!Qa@`sfz%Z4sczUQtzq6jiv-(wxqYU z<*loQx8zIuucNaB>{Kw}@kAAq?T@wei;T<+nr#W1rix2N)8U$DrWgz{O2CN138Z(7 z=}hK}iK;b1Fh0wMZ_B^SQ?N@?-@5|j-OYeXOX8#fJm8-b@jrfHo~Khh6GCI@=*7qQ zcFx$NbSDWm=eani0gB){lq-J4-USE)|6FSBlcg)ipPzRiYbp(2T4&N4+B44ifL$|R zC4De7QbFcDcEd^|Dok}it>gmt_+@lP^6QFHf!jQX@}V?XQJ{(2FweU5htE0VZ><`P zJJPky$H;{gcw>i6W@ag%8_oNNm>iqT-ufh|hm62Qi@t={uedEvIrkdtbZye=P;d2_ zvVqLyT-Ru~DngZeSfghF-f&53k z6{*b1;DL!mEkoE7RvjU|(%PnY!B>wAWDblCZ|X<)L|#J>35G4Ar9H^0>Z@ZqZbT+O zWoYcG=(DRn8c+92$c1-FNT-`XohDkqOulJf!YwwUUGFSOQVhO^?0VfHKXhB4Pq0qp#rc-rK zL`!_3(VJkvZx`Mfry5HQqNh>e9VoueL}D;q+fNw8 zgbl$bu^VDD9M7f*;zg6k$`t)YHsMJvg2Ih8zdD9jO8YRZ63s>o9da_Pj)cT1(Gb?s zEnjM#s{65L5Fy*6Wt3*K2p6A7i8<#8yGY31eS`4g3Hct&>25`s9QdA<*LU7}xh3Yp zMBM~(NW?Us;8+G}ObA5-&UC`F3wDeA?brI5Yp;z+(L|&m`+Y_4YAKec(r8V5wPjMy zSO|lP3dZfvHl}7NeFoqKmBnQ+ZlEU7u~-3A(f`%oj2U*?IP`4FJ|RmVido=aRN~*P zMr`U__i)8IS%gB#-1{uOcm8CC-2Y-G_@4m>7X`agfVs5q=8gF^DZ|p4&+ZXdy+I(l zi!|(L-m|P!fBG~_USDp?(CV=T*7zW{F>o3Z0v0;R>mnb5GDTdEMnd}F*N~|Q2z@Zb z)8x4`Gmt9WiN(>k0lgBxOH;45uU=xWrXI$mOo=HR(0=^mGSvI{PYBRYO!$k3)WJ*} zTUdbU?C){Id{1wA`h=6`Gxpkhf{l`I46&%~?YlRvu1x+1n%vt@Pl++<<&ErJs`__( zAQmaK6@u8~SSmf@yUtV_wgV~-J;Iipyxw^pQ+uEeoVEZKp3=))b=5;+yunIW9bJFY z8;<{J$eQAyM&u+c*K(`5WMUh;Wi=+<{cGAmX*2-)p*fF$sf&v9FKim-2ufWVx$ zs<4QvzV2rrUCXGz!Vq;VT4HpQZ$7g3OF5*WNx4uKPLPf99@#r6WHBbG=`HBBgklM; zJ*h;c2UV?|%=8nSJXm|a*N>|*m5mb;s$*Ml9-?;QU}xiIFWOa|GcYuZ_Or%hf0o-P z+x!ZH>*i<0%k8n z2WN=0k*s0UA6_j$J)4xq>R%wEBdxx6mgU^|7*UHM%5tT4S57Vtf0(U}c&;}5H*gi1 z@o2`sU%NDiX%Wt1g8HlkKFPUITp z*WH9&?tD?(DY$4FPqvpKX20Dg2_AIIw}KVYRZ|XHlUx20e1*YE?G?u)<23V zX+b8{8s_K~X7L|@B2=)*B44nNcF0JI9hf~4KVnl-tzeq9eMDAQvie$1cX61R=}B({zUr1N z3n2;EY9pVa$LbSIKIhlVz>zI5)g7s?tJt~j<*}WATah;r`r@+S0Q3w|;wS{KMPul^ zgrdCcA@g!P&k3rY@`s0lo~Kb0RT^A;po7po6Mka_zfN33JHf_d>gwP9oJA)VzLSGO zP)ziQUJ7!Rt!QQ!ZQ8Z7ckc6T_2_mD(u>%t1L~r}=p++(cH(Ja^hinc=9Kc z-4bmncu=r6-YtDA#X!-0plkO+`uqQ42v2=1k>LX%o~#Tx{2uHea4h;$pM#bz z&W4`1uFupu3cDaEs`D{bI#ln5xU-;Vf0CV#wmq?(GQ^ueJgUp%U4lYKgv9mh|N9Ro zSSu8)F$uf<4@?1ISV8z%UnW7}X0>7KpOFkgXM V&DH~`pU&8^D9Ne4gvyu&{VxSlCcgjx diff --git a/extensions/usage-insights/renderer.mjs b/extensions/usage-insights/renderer.mjs index 436292b4c..23484979a 100644 --- a/extensions/usage-insights/renderer.mjs +++ b/extensions/usage-insights/renderer.mjs @@ -519,9 +519,11 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini

- +

+ +

@@ -538,7 +540,7 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini

Usage by agent

-
+
@@ -560,7 +562,7 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini

Session history

Recent locally recorded usage

-
+
AI credits
@@ -790,7 +792,6 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini button.addEventListener('click', () => { if (state.range !== range.id) { state.range = range.id; - if (bootstrap) render(bootstrap); load(); } }); @@ -916,7 +917,12 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini text('liveStatus', 'Reconnecting'); }; - load(); + if (bootstrap) { + render(bootstrap); + app.classList.remove('loading'); + } else { + load(); + } setInterval(load, 5000); From 95f9a358e7a9319ca6ba6ced4f25ff78997dcf83 Mon Sep 17 00:00:00 2001 From: davidkaya <3832960+davidkaya@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:46:48 +0200 Subject: [PATCH 04/10] Keep Usage Insights live and accessible Prefer runtime metrics for the active session, preserve an accessible back-button name on narrow layouts, and avoid forced animated scrolling when opening session history. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- extensions/usage-insights/renderer.mjs | 4 ++-- extensions/usage-insights/stats.mjs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/usage-insights/renderer.mjs b/extensions/usage-insights/renderer.mjs index 23484979a..e570d7fd0 100644 --- a/extensions/usage-insights/renderer.mjs +++ b/extensions/usage-insights/renderer.mjs @@ -507,7 +507,7 @@ FINISH: unreviewed and undocumented is unfinished; this build ends with the fini Loading session...
-
diff --git a/extensions/usage-insights/stats.mjs b/extensions/usage-insights/stats.mjs index 1f8f762ae..0ac26b7ff 100644 --- a/extensions/usage-insights/stats.mjs +++ b/extensions/usage-insights/stats.mjs @@ -40,8 +40,10 @@ function runtimeAggregate(metrics) { return undefined; } + const totalNanoAiu = + number(metrics.totalNanoAiu) || number(metrics.totalPremiumRequestCost) * BILLION; const aggregate = { - aiCredits: credits(metrics.totalNanoAiu), + aiCredits: credits(totalNanoAiu), apiDurationMs: number(metrics.totalApiDurationMs), cacheReadTokens: 0, cacheWriteTokens: 0, @@ -51,7 +53,7 @@ function runtimeAggregate(metrics) { reasoningTokens: 0, sessions: 1, subagents: 0, - totalNanoAiu: number(metrics.totalNanoAiu), + totalNanoAiu, }; for (const metric of Object.values(metrics.modelMetrics || {})) { @@ -68,30 +70,6 @@ function runtimeAggregate(metrics) { return aggregate; } -function mergeAggregates(persisted, runtime) { - if (!runtime) { - return persisted; - } - - const merged = {}; - for (const key of [ - "apiDurationMs", - "cacheReadTokens", - "cacheWriteTokens", - "calls", - "inputTokens", - "outputTokens", - "reasoningTokens", - "sessions", - "subagents", - "totalNanoAiu", - ]) { - merged[key] = Math.max(number(persisted[key]), number(runtime[key])); - } - merged.aiCredits = credits(merged.totalNanoAiu); - return merged; -} - function cutoffFor(range) { const duration = { "24h": 24 * 60 * 60 * 1000, @@ -476,9 +454,9 @@ export class UsageInsightsStore { selectedSessionId === currentSessionId && currentRuntime ? runtimeAggregate(currentRuntime) : undefined; - const selectedTotals = mergeAggregates(dbTotals, liveWindow); + const selectedTotals = dbTotals.calls > 0 ? dbTotals : liveWindow || dbTotals; const agents = this.getSessionAgents(selectedSessionId, agentMetadata); - const selectedNanoAiu = selectedTotals.totalNanoAiu || dbTotals.totalNanoAiu; + const selectedNanoAiu = selectedTotals.totalNanoAiu; for (const agent of agents) { agent.share = selectedNanoAiu > 0 ? Math.min(1, agent.totalNanoAiu / selectedNanoAiu) : 0; From d524353fcc08f0e63c5f71f5cac23839ec82e8f5 Mon Sep 17 00:00:00 2001 From: davidkaya <3832960+davidkaya@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:12:37 +0200 Subject: [PATCH 09/10] Use full Usage Insights author name Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c54874d4-f708-414b-baf5-03241f052fdd --- plugins/usage-insights/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/usage-insights/plugin.json b/plugins/usage-insights/plugin.json index 7295715d2..182b42a89 100644 --- a/plugins/usage-insights/plugin.json +++ b/plugins/usage-insights/plugin.json @@ -4,7 +4,7 @@ "description": "Inspect current and historical GitHub Copilot token and AI-credit usage with per-agent rollups and in-canvas session drill-down.", "version": "1.0.0", "author": { - "name": "davidkaya", + "name": "David Kaya", "url": "https://github.com/davidkaya" }, "repository": "https://github.com/github/awesome-copilot", From 0aae213f19134b57166f86f7930c7acb6581fb1a Mon Sep 17 00:00:00 2001 From: davidkaya <3832960+davidkaya@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:43:05 +0200 Subject: [PATCH 10/10] Support current Usage Insights schemas Handle partial optional app metadata schemas and normalize SQLite timestamps for UTC parsing and range comparisons. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c54874d4-f708-414b-baf5-03241f052fdd --- extensions/usage-insights/stats.mjs | 76 +++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/extensions/usage-insights/stats.mjs b/extensions/usage-insights/stats.mjs index 0ac26b7ff..b490dd043 100644 --- a/extensions/usage-insights/stats.mjs +++ b/extensions/usage-insights/stats.mjs @@ -19,6 +19,22 @@ function credits(nanoAiu) { return number(nanoAiu) / BILLION; } +function timestampMs(value) { + if (!value) { + return Number.NaN; + } + const timestamp = String(value); + const normalized = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(timestamp) + ? `${timestamp.replace(" ", "T")}Z` + : timestamp; + return Date.parse(normalized); +} + +function normalizeTimestamp(value) { + const milliseconds = timestampMs(value); + return Number.isFinite(milliseconds) ? new Date(milliseconds).toISOString() : value; +} + function normalizeAggregate(row = {}) { return { aiCredits: credits(row.total_nano_aiu), @@ -179,6 +195,27 @@ export class UsageInsightsStore { this.appDb = existsSync(appDbPath) ? new DatabaseSync(appDbPath, { readOnly: true }) : undefined; + this.appSessionColumns = new Set( + this.appDb + ?.prepare("PRAGMA table_info(sessions)") + .all() + .map((column) => column.name) || [], + ); + } + + getAppSession(sessionId, requestedColumns) { + if (!this.appDb || !this.appSessionColumns.has("id")) { + return {}; + } + const columns = requestedColumns.filter((column) => this.appSessionColumns.has(column)); + if (!columns.length) { + return {}; + } + return ( + this.appDb + .prepare(`SELECT ${columns.join(", ")} FROM sessions WHERE id = ?`) + .get(sessionId) || {} + ); } queryAggregate(whereSql = "", params = []) { @@ -211,18 +248,18 @@ export class UsageInsightsStore { WHERE id = ? `) .get(sessionId) || {}; - const app = - this.appDb - ?.prepare(` - SELECT title, agent, model, mode, created_at, updated_at - FROM sessions - WHERE id = ? - `) - .get(sessionId) || {}; + const app = this.getAppSession(sessionId, [ + "title", + "agent", + "model", + "mode", + "created_at", + "updated_at", + ]); return { agent: app.agent || "Copilot", branch: local.branch, - createdAt: app.created_at || local.created_at, + createdAt: normalizeTimestamp(app.created_at || local.created_at), cwd: local.cwd, id: sessionId, mode: app.mode, @@ -230,7 +267,7 @@ export class UsageInsightsStore { repository: local.repository, summary: local.summary, title: app.title || local.summary || `Session ${sessionId.slice(0, 8)}`, - updatedAt: app.updated_at || local.updated_at, + updatedAt: normalizeTimestamp(app.updated_at || local.updated_at), }; } @@ -263,7 +300,7 @@ export class UsageInsightsStore { agentId: row.agent_id, displayName: row.agent_id === "root" ? "Root agent" : meta?.displayName || "Sub-agent", internalName: meta?.internalName, - lastSeen: row.last_seen, + lastSeen: normalizeTimestamp(row.last_seen), models: row.models ? String(row.models).split(",") : [], status: row.agent_id === "root" ? "primary" : meta?.status || "recorded", }; @@ -308,16 +345,16 @@ export class UsageInsightsStore { created_at FROM assistant_usage_events WHERE session_id = ? - ORDER BY created_at, id + ORDER BY datetime(created_at), id `) .all(sessionId) .map((row) => { - const endedAtMs = Date.parse(row.created_at); + const endedAtMs = timestampMs(row.created_at); const durationMs = Math.max(1, number(row.duration_ms)); return { agentId: row.agent_id, aiCredits: credits(row.total_nano_aiu), - createdAt: row.created_at, + createdAt: normalizeTimestamp(row.created_at), durationMs, endedAtMs, id: row.id, @@ -349,7 +386,7 @@ export class UsageInsightsStore { getRange(range) { const cutoff = cutoffFor(range); - const whereSql = cutoff ? "WHERE created_at >= ?" : ""; + const whereSql = cutoff ? "WHERE datetime(created_at) >= datetime(?)" : ""; const params = cutoff ? [cutoff] : []; const totals = this.queryAggregate(whereSql, params); @@ -410,22 +447,19 @@ export class UsageInsightsStore { SUM(COALESCE(e.duration_ms, 0)) AS duration_ms FROM assistant_usage_events e LEFT JOIN sessions s ON s.id = e.session_id - ${cutoff ? "WHERE e.created_at >= ?" : ""} + ${cutoff ? "WHERE datetime(e.created_at) >= datetime(?)" : ""} GROUP BY e.session_id, s.summary, s.repository ORDER BY total_nano_aiu DESC LIMIT 8 `) .all(...params) .map((row) => { - const app = - this.appDb - ?.prepare("SELECT title, agent, model FROM sessions WHERE id = ?") - .get(row.session_id) || {}; + const app = this.getAppSession(row.session_id, ["title", "agent", "model"]); return { ...normalizeAggregate(row), agent: app.agent, id: row.session_id, - lastSeen: row.last_seen, + lastSeen: normalizeTimestamp(row.last_seen), model: app.model, repository: row.repository, title: app.title || row.summary || `Session ${String(row.session_id).slice(0, 8)}`,