@hienlh/ppm 0.13.20 → 0.13.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{ai-settings-section-DR5BueEL.js → ai-settings-section-DN4egS8e.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-CZBayZMd.js +1 -0
- package/dist/web/assets/{audio-preview-DwyrUe-V.js → audio-preview-CrTLA4VQ.js} +1 -1
- package/dist/web/assets/chat-tab-DFCOXFk8.js +12 -0
- package/dist/web/assets/code-editor-J864BoOW.js +8 -0
- package/dist/web/assets/{conflict-editor-C8vTvS9w.js → conflict-editor-BIwUtzO5.js} +1 -1
- package/dist/web/assets/{csv-preview-Bo-N3GHl.js → csv-preview-BIfojSWd.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-DqcDQ9st.js → data-grid-overlay-editor-DZIqEOsz.js} +1 -1
- package/dist/web/assets/{database-viewer-_RTlPC26.js → database-viewer-DhawNQtp.js} +1 -1
- package/dist/web/assets/{diff-viewer-P2Dc__bQ.js → diff-viewer-nupJr1AG.js} +1 -1
- package/dist/web/assets/{esm-Dvc8oJly.js → esm-UZtw2QcY.js} +1 -1
- package/dist/web/assets/{extension-webview-CHqtkQBd.js → extension-webview-BXDYtTXe.js} +2 -2
- package/dist/web/assets/gitGraph-HDMCJU4V-CboO1wK8.js +1 -0
- package/dist/web/assets/{glide-data-grid-9TPVejSQ.js → glide-data-grid-DttB_tob.js} +6 -6
- package/dist/web/assets/{image-preview--nh-wHgF.js → image-preview-Dh11TP_j.js} +1 -1
- package/dist/web/assets/index-C5sLGvFC.css +2 -0
- package/dist/web/assets/index-CPcnZtNl.js +27 -0
- package/dist/web/assets/info-3K5VOQVL-D_qKNgUf.js +1 -0
- package/dist/web/assets/keybindings-store-DvBC5IaA.js +1 -0
- package/dist/web/assets/{markdown-renderer-Bsow9WVr.js → markdown-renderer-Bwpgzn7n.js} +3 -3
- package/dist/web/assets/notification-store-D1sxDh0s.js +1 -0
- package/dist/web/assets/{number-overlay-editor-XTjjEXtk.js → number-overlay-editor-CewUR5pB.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-XtGc2GdX.js +1 -0
- package/dist/web/assets/{pdf-preview-BqntOcNA.js → pdf-preview-CI-lrcdD.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DNZ5YtCW.js +1 -0
- package/dist/web/assets/{port-forwarding-tab-WWRLWcTB.js → port-forwarding-tab-DOEfu8ca.js} +1 -1
- package/dist/web/assets/{postgres-viewer-g7-3kOzD.js → postgres-viewer-Bb3RwFMj.js} +3 -3
- package/dist/web/assets/radar-KQ55EAFF-uCGpAvZE.js +1 -0
- package/dist/web/assets/{settings-store-D2MtC9tm.js → settings-store-CVrIYYCB.js} +2 -2
- package/dist/web/assets/settings-tab-i8KAi1LY.js +1 -0
- package/dist/web/assets/{sql-query-editor-CultKZsI.js → sql-query-editor-C3ZrhqZr.js} +1 -1
- package/dist/web/assets/sqlite-viewer-Cucs41S6.js +1 -0
- package/dist/web/assets/terminal-tab-upGE8feC.js +1 -0
- package/dist/web/assets/treemap-KZPCXAKY-DQvivjBa.js +1 -0
- package/dist/web/assets/{use-monaco-theme-CugUkORI.js → use-monaco-theme-BePWbY58.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CPtQ2zua.js → vendor-mermaid-Cl50p6TB.js} +2 -2
- package/dist/web/assets/{video-preview-C4PxtiOc.js → video-preview-CSdxf4fH.js} +1 -1
- package/dist/web/index.html +6 -6
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +13 -1
- package/src/server/routes/chat.ts +73 -3
- package/src/server/routes/database.ts +11 -2
- package/src/server/ws/chat.ts +12 -3
- package/src/services/autostart-generator.ts +2 -2
- package/src/services/autostart-register.ts +6 -3
- package/src/services/db.service.ts +41 -1
- package/src/services/supervisor.ts +20 -7
- package/src/web/app.tsx +12 -0
- package/src/web/components/chat/chat-history-bar.tsx +43 -13
- package/src/web/components/chat/chat-tab.tsx +3 -0
- package/src/web/components/chat/session-list-panel.tsx +15 -8
- package/src/web/components/chat/session-picker.tsx +33 -5
- package/src/web/components/database/connection-list.tsx +55 -205
- package/src/web/components/database/connection-row.tsx +104 -0
- package/src/web/components/database/database-sidebar.tsx +1 -1
- package/src/web/components/database/schema-table-tree.tsx +98 -0
- package/src/web/components/database/use-connections.ts +9 -6
- package/src/web/components/layout/command-palette.tsx +10 -2
- package/src/web/components/layout/editor-panel.tsx +16 -39
- package/src/web/components/layout/tab-pool.tsx +196 -0
- package/src/web/hooks/use-chat.ts +9 -2
- package/src/web/hooks/use-debounced-value.ts +10 -0
- package/src/web/stores/notification-store.ts +42 -0
- package/dist/web/assets/architecture-PBZL5I3N-7JKY4P1L.js +0 -1
- package/dist/web/assets/chat-tab-DqS9Qk3O.js +0 -12
- package/dist/web/assets/code-editor-DwdeigGe.js +0 -8
- package/dist/web/assets/gitGraph-HDMCJU4V-Daf9rhiF.js +0 -1
- package/dist/web/assets/index-nC9UURj4.css +0 -2
- package/dist/web/assets/index-xpTdWKsA.js +0 -27
- package/dist/web/assets/info-3K5VOQVL-gn0pjNiT.js +0 -1
- package/dist/web/assets/keybindings-store-C0XkvJcm.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-Csaeizjc.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-DatkjxTH.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-BnGB20hR.js +0 -1
- package/dist/web/assets/settings-tab-DO3s244B.js +0 -1
- package/dist/web/assets/sqlite-viewer-BtYh66b0.js +0 -1
- package/dist/web/assets/terminal-tab-C25rc_34.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-CgEYv38e.js +0 -1
- /package/dist/web/assets/{api-settings-DowGyuVy.js → api-settings-DnHv6JgF.js} +0 -0
- /package/dist/web/assets/{data-grid-types-DqqspyVw.js → data-grid-types-BISkUXAY.js} +0 -0
- /package/dist/web/assets/{dist-_jZs3YZC.js → dist-B1I_4Jtc.js} +0 -0
- /package/dist/web/assets/{dist-D1SZxtVS.js → dist-CcDNqGjt.js} +0 -0
- /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
- /package/dist/web/assets/{lib-Dub8DlCJ.js → lib-Bu71-TFS.js} +0 -0
- /package/dist/web/assets/{use-blob-url-DGY5qKiT.js → use-blob-url-QX-XajU8.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-Dyfw49hJ.js → vendor-xterm-K3_Xwigj.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-BwzaQ50n.js";import"./api-client-DIhJ5qVW.js";import{
|
|
1
|
+
import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-BwzaQ50n.js";import"./api-client-DIhJ5qVW.js";import{W as n}from"./index-CPcnZtNl.js";import{t as r}from"./use-blob-url-QX-XajU8.js";var i=e();function a({filePath:e,projectName:a}){let{blobUrl:o,error:s}=r(e,a);return s?(0,i.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,i.jsx)(t,{className:`size-10 text-text-subtle`}),(0,i.jsx)(`p`,{className:`text-sm`,children:`Failed to load video.`})]}):o?(0,i.jsx)(`div`,{className:`flex items-center justify-center h-full p-4 bg-surface overflow-auto`,children:(0,i.jsx)(`video`,{src:o,controls:!0,className:`max-w-full max-h-full`})}):(0,i.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,i.jsx)(n,{className:`size-5 animate-spin text-text-subtle`})})}export{a as VideoPreview};
|
package/dist/web/index.html
CHANGED
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
40
40
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
41
41
|
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
42
|
-
<script type="module" crossorigin src="/assets/index-
|
|
42
|
+
<script type="module" crossorigin src="/assets/index-CPcnZtNl.js"></script>
|
|
43
43
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-FhOqtrmT.js">
|
|
44
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-
|
|
44
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-Cl50p6TB.js">
|
|
45
45
|
<link rel="modulepreload" crossorigin href="/assets/vendor-markdown-0Mxgxy0L.js">
|
|
46
46
|
<link rel="modulepreload" crossorigin href="/assets/vendor-ui-B-89Uj8i.js">
|
|
47
47
|
<link rel="modulepreload" crossorigin href="/assets/utils-CQux7CsO.js">
|
|
@@ -50,18 +50,18 @@
|
|
|
50
50
|
<link rel="modulepreload" crossorigin href="/assets/input-4ll___Gh.js">
|
|
51
51
|
<link rel="modulepreload" crossorigin href="/assets/react-DMIOAtcX.js">
|
|
52
52
|
<link rel="modulepreload" crossorigin href="/assets/api-client-DIhJ5qVW.js">
|
|
53
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-store-
|
|
53
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-store-CVrIYYCB.js">
|
|
54
54
|
<link rel="modulepreload" crossorigin href="/assets/scroll-area-iv39O3VN.js">
|
|
55
55
|
<link rel="modulepreload" crossorigin href="/assets/dist-CaKCIxem.js">
|
|
56
56
|
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-BjrAbUJe.js">
|
|
57
|
-
<link rel="modulepreload" crossorigin href="/assets/api-settings-
|
|
58
|
-
<link rel="modulepreload" crossorigin href="/assets/ai-settings-section-
|
|
57
|
+
<link rel="modulepreload" crossorigin href="/assets/api-settings-DnHv6JgF.js">
|
|
58
|
+
<link rel="modulepreload" crossorigin href="/assets/ai-settings-section-DN4egS8e.js">
|
|
59
59
|
<link rel="modulepreload" crossorigin href="/assets/database-DOWH9-Vv.js">
|
|
60
60
|
<link rel="modulepreload" crossorigin href="/assets/chevron-right-DnHIvvcy.js">
|
|
61
61
|
<link rel="modulepreload" crossorigin href="/assets/search-tM8K5zWU.js">
|
|
62
62
|
<link rel="modulepreload" crossorigin href="/assets/file-store-DOxcU_7s.js">
|
|
63
63
|
<link rel="modulepreload" crossorigin href="/assets/tab-store-Dow2Ztto.js">
|
|
64
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
64
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C5sLGvFC.css">
|
|
65
65
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
66
66
|
<body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
|
|
67
67
|
<div id="portal" style="position: fixed; left: 0; top: 0; z-index: 9999;" /></div>
|
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"78c747f84e33be621795ec09811abc8e","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/refresh-cw-BjrAbUJe.js"},{"revision":null,"url":"assets/postgres-viewer-g7-3kOzD.js"},{"revision":null,"url":"assets/code-editor-DwdeigGe.js"},{"revision":null,"url":"assets/dist-D1SZxtVS.js"},{"revision":null,"url":"assets/csv-preview-Bo-N3GHl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/use-monaco-theme-CugUkORI.js"},{"revision":null,"url":"assets/diff-viewer-P2Dc__bQ.js"},{"revision":null,"url":"assets/pdf-preview-BqntOcNA.js"},{"revision":null,"url":"assets/tab-store-Dow2Ztto.js"},{"revision":null,"url":"assets/data-grid-types-DqqspyVw.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-DatkjxTH.js"},{"revision":null,"url":"assets/input-4ll___Gh.js"},{"revision":null,"url":"assets/settings-tab-DO3s244B.js"},{"revision":null,"url":"assets/chat-tab-DqS9Qk3O.js"},{"revision":null,"url":"assets/table-BzjWcs87.js"},{"revision":null,"url":"assets/text-wrap-DJz9Bgpa.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/sparkles-CulWHe4c.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-7JKY4P1L.js"},{"revision":null,"url":"assets/search-tM8K5zWU.js"},{"revision":null,"url":"assets/markdown-renderer-Bsow9WVr.js"},{"revision":null,"url":"assets/database-viewer-_RTlPC26.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/info-3K5VOQVL-gn0pjNiT.js"},{"revision":null,"url":"assets/database-DOWH9-Vv.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-CgEYv38e.js"},{"revision":null,"url":"assets/keybindings-store-C0XkvJcm.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/glide-data-grid-nthEL3fk.css"},{"revision":null,"url":"assets/number-overlay-editor-XTjjEXtk.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/sql-query-editor-CultKZsI.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/video-preview-C4PxtiOc.js"},{"revision":null,"url":"assets/file-store-DOxcU_7s.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/radar-KQ55EAFF-BnGB20hR.js"},{"revision":null,"url":"assets/use-blob-url-DGY5qKiT.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/esm-Dvc8oJly.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/react-DMIOAtcX.js"},{"revision":null,"url":"assets/utils-CQux7CsO.js"},{"revision":null,"url":"assets/data-grid-overlay-editor-DqcDQ9st.js"},{"revision":null,"url":"assets/csv-parser-Dly5nqE1.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/dist-_jZs3YZC.js"},{"revision":null,"url":"assets/port-forwarding-tab-WWRLWcTB.js"},{"revision":null,"url":"assets/code-DGBecc50.js"},{"revision":null,"url":"assets/extension-webview-CHqtkQBd.js"},{"revision":null,"url":"assets/glide-data-grid-9TPVejSQ.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/ai-settings-section-DR5BueEL.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-Csaeizjc.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/arrow-up-Rcw6_KKu.js"},{"revision":null,"url":"assets/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/api-client-DIhJ5qVW.js"},{"revision":null,"url":"assets/vendor-xterm-Dyfw49hJ.js"},{"revision":null,"url":"assets/api-settings-DowGyuVy.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/vendor-mermaid-CPtQ2zua.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/index-nC9UURj4.css"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/dist-CaKCIxem.js"},{"revision":null,"url":"assets/x-BPReZWnP.js"},{"revision":null,"url":"assets/scroll-area-iv39O3VN.js"},{"revision":null,"url":"assets/settings-store-D2MtC9tm.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Daf9rhiF.js"},{"revision":null,"url":"assets/chevron-right-DnHIvvcy.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/audio-preview-DwyrUe-V.js"},{"revision":null,"url":"assets/conflict-editor-C8vTvS9w.js"},{"revision":null,"url":"assets/lib-Dub8DlCJ.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/index-xpTdWKsA.js"},{"revision":null,"url":"assets/image-preview--nh-wHgF.js"},{"revision":null,"url":"assets/sqlite-viewer-BtYh66b0.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/katex-DzXRfQ_m.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/terminal-tab-C25rc_34.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"b82aa0072fcaf46d8f176a34168de7a9","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/refresh-cw-BjrAbUJe.js"},{"revision":null,"url":"assets/data-grid-overlay-editor-DZIqEOsz.js"},{"revision":null,"url":"assets/terminal-tab-upGE8feC.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/use-blob-url-QX-XajU8.js"},{"revision":null,"url":"assets/vendor-mermaid-Cl50p6TB.js"},{"revision":null,"url":"assets/tab-store-Dow2Ztto.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-uCGpAvZE.js"},{"revision":null,"url":"assets/data-grid-types-BISkUXAY.js"},{"revision":null,"url":"assets/index-C5sLGvFC.css"},{"revision":null,"url":"assets/input-4ll___Gh.js"},{"revision":null,"url":"assets/database-viewer-DhawNQtp.js"},{"revision":null,"url":"assets/table-BzjWcs87.js"},{"revision":null,"url":"assets/text-wrap-DJz9Bgpa.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/sparkles-CulWHe4c.js"},{"revision":null,"url":"assets/search-tM8K5zWU.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/database-DOWH9-Vv.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-CboO1wK8.js"},{"revision":null,"url":"assets/sqlite-viewer-Cucs41S6.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chat-tab-DFCOXFk8.js"},{"revision":null,"url":"assets/settings-store-CVrIYYCB.js"},{"revision":null,"url":"assets/glide-data-grid-nthEL3fk.css"},{"revision":null,"url":"assets/api-settings-DnHv6JgF.js"},{"revision":null,"url":"assets/info-3K5VOQVL-D_qKNgUf.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/sql-query-editor-C3ZrhqZr.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/file-store-DOxcU_7s.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/katex-Bqvo_ZG0.js"},{"revision":null,"url":"assets/port-forwarding-tab-DOEfu8ca.js"},{"revision":null,"url":"assets/vendor-xterm-K3_Xwigj.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/ai-settings-section-DN4egS8e.js"},{"revision":null,"url":"assets/extension-webview-BXDYtTXe.js"},{"revision":null,"url":"assets/keybindings-store-DvBC5IaA.js"},{"revision":null,"url":"assets/index-CPcnZtNl.js"},{"revision":null,"url":"assets/esm-UZtw2QcY.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/treemap-KZPCXAKY-DQvivjBa.js"},{"revision":null,"url":"assets/react-DMIOAtcX.js"},{"revision":null,"url":"assets/lib-Bu71-TFS.js"},{"revision":null,"url":"assets/utils-CQux7CsO.js"},{"revision":null,"url":"assets/csv-parser-Dly5nqE1.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/dist-CcDNqGjt.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-XtGc2GdX.js"},{"revision":null,"url":"assets/code-DGBecc50.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/video-preview-CSdxf4fH.js"},{"revision":null,"url":"assets/diff-viewer-nupJr1AG.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/arrow-up-Rcw6_KKu.js"},{"revision":null,"url":"assets/code-editor-J864BoOW.js"},{"revision":null,"url":"assets/number-overlay-editor-CewUR5pB.js"},{"revision":null,"url":"assets/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/api-client-DIhJ5qVW.js"},{"revision":null,"url":"assets/glide-data-grid-DttB_tob.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-DNZ5YtCW.js"},{"revision":null,"url":"assets/postgres-viewer-Bb3RwFMj.js"},{"revision":null,"url":"assets/use-monaco-theme-BePWbY58.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/dist-B1I_4Jtc.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/notification-store-D1sxDh0s.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-CZBayZMd.js"},{"revision":null,"url":"assets/csv-preview-BIfojSWd.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/settings-tab-i8KAi1LY.js"},{"revision":null,"url":"assets/audio-preview-CrTLA4VQ.js"},{"revision":null,"url":"assets/pdf-preview-CI-lrcdD.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/dist-CaKCIxem.js"},{"revision":null,"url":"assets/x-BPReZWnP.js"},{"revision":null,"url":"assets/scroll-area-iv39O3VN.js"},{"revision":null,"url":"assets/image-preview-Dh11TP_j.js"},{"revision":null,"url":"assets/chevron-right-DnHIvvcy.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/conflict-editor-BIwUtzO5.js"},{"revision":null,"url":"assets/markdown-renderer-Bwpgzn7n.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/package.json
CHANGED
|
@@ -384,9 +384,17 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
384
384
|
|
|
385
385
|
// SDK's listSessions drops sessions whose first user message exceeds its
|
|
386
386
|
// 64KB head buffer (e.g. large pasted docs). Scan JSONL dir to recover them.
|
|
387
|
+
// Cached with TTL to avoid thousands of sync FS reads per request.
|
|
387
388
|
if (dir && offset === 0) {
|
|
388
389
|
const knownIds = new Set(sessions.map((s) => s.id));
|
|
389
|
-
const
|
|
390
|
+
const cached = missingSessionsCache.get(dir);
|
|
391
|
+
const missing = (cached && Date.now() - cached.ts < MISSING_SESSIONS_TTL_MS)
|
|
392
|
+
? cached.sessions.filter((s) => !knownIds.has(s.id))
|
|
393
|
+
: (() => {
|
|
394
|
+
const result = findMissingSessions(dir, knownIds, this.id);
|
|
395
|
+
missingSessionsCache.set(dir, { sessions: result, ts: Date.now() });
|
|
396
|
+
return result;
|
|
397
|
+
})();
|
|
390
398
|
if (missing.length > 0) {
|
|
391
399
|
const missingIds = missing.map((s) => s.id);
|
|
392
400
|
const missingDbTitles = getSessionTitles(missingIds);
|
|
@@ -1594,6 +1602,10 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1594
1602
|
* (e.g., pasted API docs) can't be parsed and are silently dropped.
|
|
1595
1603
|
* We extract title from queue-operation content or first user message.
|
|
1596
1604
|
*/
|
|
1605
|
+
// Cache findMissingSessions results to avoid thousands of sync FS reads per request
|
|
1606
|
+
const missingSessionsCache = new Map<string, { sessions: SessionInfo[]; ts: number }>();
|
|
1607
|
+
const MISSING_SESSIONS_TTL_MS = 60_000; // 60 seconds
|
|
1608
|
+
|
|
1597
1609
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
1598
1610
|
function findMissingSessions(
|
|
1599
1611
|
dir: string,
|
|
@@ -10,7 +10,7 @@ import { upsertSlashRecent, getSlashRecents } from "../../services/db.service.ts
|
|
|
10
10
|
import { getCachedUsage, refreshUsageNow } from "../../services/claude-usage.service.ts";
|
|
11
11
|
import { getSessionLog } from "../../services/session-log.service.ts";
|
|
12
12
|
import { parseJsonlTranscript, validateJsonlPath } from "../../services/jsonl-transcript-parser.ts";
|
|
13
|
-
import { getSessionProjectPath, setSessionMetadata, setSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionMetadata, deleteSessionTitle } from "../../services/db.service.ts";
|
|
13
|
+
import { getSessionProjectPath, setSessionMetadata, setSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionMetadata, deleteSessionTitle, getAllUnread, clearSessionUnread } from "../../services/db.service.ts";
|
|
14
14
|
import { setSessionTag, bulkSetSessionTag, getTagById, getSessionTags, getProjectDefaultTagId } from "../../services/tag.service.ts";
|
|
15
15
|
import { ok, err } from "../../types/api.ts";
|
|
16
16
|
|
|
@@ -106,6 +106,7 @@ chatRoutes.get("/sessions", async (c) => {
|
|
|
106
106
|
const providerId = c.req.query("providerId");
|
|
107
107
|
const tagIdParam = c.req.query("tag_id");
|
|
108
108
|
const filterTagId = tagIdParam ? parseInt(tagIdParam, 10) : null;
|
|
109
|
+
const searchQuery = c.req.query("q")?.toLowerCase().trim() || "";
|
|
109
110
|
const limit = Math.min(parseInt(c.req.query("limit") ?? "50", 10) || 50, 200);
|
|
110
111
|
const offset = parseInt(c.req.query("offset") ?? "0", 10) || 0;
|
|
111
112
|
|
|
@@ -143,8 +144,10 @@ chatRoutes.get("/sessions", async (c) => {
|
|
|
143
144
|
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
144
145
|
});
|
|
145
146
|
|
|
146
|
-
// Server-side tag filter
|
|
147
|
-
|
|
147
|
+
// Server-side search + tag filter
|
|
148
|
+
let filtered = enriched;
|
|
149
|
+
if (searchQuery) filtered = filtered.filter((s) => (s.title || "").toLowerCase().includes(searchQuery));
|
|
150
|
+
if (filterTagId !== null) filtered = filtered.filter((s) => s.tag?.id === filterTagId);
|
|
148
151
|
const hasMore = sessions.length >= limit;
|
|
149
152
|
return c.json(ok({ sessions: filtered, hasMore }));
|
|
150
153
|
} catch (e) {
|
|
@@ -184,6 +187,50 @@ chatRoutes.post("/sessions", async (c) => {
|
|
|
184
187
|
}
|
|
185
188
|
});
|
|
186
189
|
|
|
190
|
+
/** DELETE /chat/sessions — bulk delete sessions older than N days */
|
|
191
|
+
chatRoutes.delete("/sessions", async (c) => {
|
|
192
|
+
try {
|
|
193
|
+
const projectPath = c.get("projectPath");
|
|
194
|
+
const providerId = c.req.query("providerId") ?? "claude";
|
|
195
|
+
const olderThanDays = parseInt(c.req.query("olderThanDays") ?? "0", 10);
|
|
196
|
+
if (!olderThanDays || olderThanDays < 1) return c.json(err("olderThanDays must be >= 1"), 400);
|
|
197
|
+
|
|
198
|
+
const cutoff = new Date(Date.now() - olderThanDays * 86400_000);
|
|
199
|
+
// Fetch all sessions (paginate through) to find old ones
|
|
200
|
+
const allSessions: { id: string; createdAt: string; providerId: string }[] = [];
|
|
201
|
+
let offset = 0;
|
|
202
|
+
const batchSize = 200;
|
|
203
|
+
while (true) {
|
|
204
|
+
const batch = await chatService.listSessions(providerId, projectPath, { limit: batchSize, offset });
|
|
205
|
+
allSessions.push(...batch);
|
|
206
|
+
if (batch.length < batchSize) break;
|
|
207
|
+
offset += batchSize;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const pinnedIds = getPinnedSessionIds();
|
|
211
|
+
const toDelete = allSessions.filter((s) =>
|
|
212
|
+
new Date(s.createdAt) < cutoff && !pinnedIds.has(s.id),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
let deleted = 0;
|
|
216
|
+
for (const s of toDelete) {
|
|
217
|
+
try {
|
|
218
|
+
await chatService.deleteSession(s.providerId ?? providerId, s.id);
|
|
219
|
+
deleteSessionMapping(s.id);
|
|
220
|
+
setSessionTag(s.id, null, projectPath);
|
|
221
|
+
deleteSessionMetadata(s.id);
|
|
222
|
+
deleteSessionTitle(s.id);
|
|
223
|
+
unpinSession(s.id);
|
|
224
|
+
deleted++;
|
|
225
|
+
} catch { /* skip individual failures */ }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return c.json(ok({ deleted, total: toDelete.length }));
|
|
229
|
+
} catch (e) {
|
|
230
|
+
return c.json(err((e as Error).message), 500);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
187
234
|
/** DELETE /chat/sessions/:id — delete a session */
|
|
188
235
|
chatRoutes.delete("/sessions/:id", async (c) => {
|
|
189
236
|
try {
|
|
@@ -246,6 +293,29 @@ chatRoutes.delete("/sessions/:id/pin", (c) => {
|
|
|
246
293
|
}
|
|
247
294
|
});
|
|
248
295
|
|
|
296
|
+
/** GET /chat/sessions/unread — get all sessions with unread notifications */
|
|
297
|
+
chatRoutes.get("/sessions/unread", (c) => {
|
|
298
|
+
try {
|
|
299
|
+
return c.json(ok(getAllUnread()));
|
|
300
|
+
} catch (e) {
|
|
301
|
+
return c.json(err((e as Error).message), 500);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/** POST /chat/sessions/:id/read — mark a session as read */
|
|
306
|
+
chatRoutes.post("/sessions/:id/read", async (c) => {
|
|
307
|
+
try {
|
|
308
|
+
const id = c.req.param("id");
|
|
309
|
+
clearSessionUnread(id);
|
|
310
|
+
// Broadcast to all WS clients so other tabs/devices sync
|
|
311
|
+
const { broadcastGlobalEvent } = await import("../ws/chat.ts");
|
|
312
|
+
broadcastGlobalEvent({ type: "session:unread_changed", sessionId: id, unreadCount: 0, unreadType: null, projectName: "" });
|
|
313
|
+
return c.json(ok({ id, unreadCount: 0 }));
|
|
314
|
+
} catch (e) {
|
|
315
|
+
return c.json(err((e as Error).message), 500);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
249
319
|
/** PATCH /chat/sessions/bulk-tag — assign tag to multiple sessions (MUST be before /sessions/:id) */
|
|
250
320
|
chatRoutes.patch("/sessions/bulk-tag", async (c) => {
|
|
251
321
|
try {
|
|
@@ -31,6 +31,11 @@ function isValidHex(color: string): boolean {
|
|
|
31
31
|
return /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(color);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Validate SQL identifier (table/column/schema name) to prevent injection */
|
|
35
|
+
function isSafeIdentifier(name: string): boolean {
|
|
36
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
/** Resolve connection + parse config, return 404 on miss */
|
|
35
40
|
function resolveConn(id: string) {
|
|
36
41
|
const numId = parseInt(id, 10);
|
|
@@ -396,10 +401,14 @@ databaseRoutes.post("/connections/:id/row", async (c) => {
|
|
|
396
401
|
return c.json(err("table and values are required"), 400);
|
|
397
402
|
}
|
|
398
403
|
|
|
404
|
+
// Validate identifiers to prevent SQL injection
|
|
405
|
+
if (!isSafeIdentifier(body.table)) return c.json(err("Invalid table name"), 400);
|
|
406
|
+
if (body.schema && !isSafeIdentifier(body.schema)) return c.json(err("Invalid schema name"), 400);
|
|
407
|
+
const cols = Object.keys(body.values);
|
|
408
|
+
if (cols.some((col) => !isSafeIdentifier(col))) return c.json(err("Invalid column name"), 400);
|
|
409
|
+
|
|
399
410
|
const config = decryptConfig(conn.connection_config);
|
|
400
411
|
const adapter = getAdapter(conn.type);
|
|
401
|
-
// Build and execute INSERT query
|
|
402
|
-
const cols = Object.keys(body.values);
|
|
403
412
|
const vals = Object.values(body.values);
|
|
404
413
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
405
414
|
const schema = body.schema && conn.type === "postgres" ? `"${body.schema}".` : "";
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { providerRegistry } from "../../providers/registry.ts";
|
|
|
3
3
|
import { resolveProjectPath } from "../helpers/resolve-project.ts";
|
|
4
4
|
import { logSessionEvent } from "../../services/session-log.service.ts";
|
|
5
5
|
import { listSessions as sdkListSessions } from "@anthropic-ai/claude-agent-sdk";
|
|
6
|
-
import { getSessionTitle } from "../../services/db.service.ts";
|
|
6
|
+
import { getSessionTitle, incrementSessionUnread, clearSessionUnread } from "../../services/db.service.ts";
|
|
7
7
|
import type { ChatWsClientMessage, SessionPhase } from "../../types/api.ts";
|
|
8
8
|
import { startWatching, stopWatching, onFileChange } from "../../services/file-watcher.service.ts";
|
|
9
9
|
import { bashOutputSpy } from "../../services/bash-output-spy.ts";
|
|
@@ -370,6 +370,10 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
370
370
|
if (session) session.title = title;
|
|
371
371
|
}
|
|
372
372
|
}).catch(() => {});
|
|
373
|
+
// Persist unread to DB + broadcast to all tabs/devices
|
|
374
|
+
incrementSessionUnread(sessionId, "done");
|
|
375
|
+
broadcastGlobalEvent({ type: "session:unread_changed", sessionId, unreadCount: -1, unreadType: "done", projectName: entry.projectName || "" });
|
|
376
|
+
|
|
373
377
|
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
374
378
|
const project = entry.projectName || "Project";
|
|
375
379
|
const session = chatService.getSession(sessionId);
|
|
@@ -384,12 +388,17 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
384
388
|
}).catch(() => {});
|
|
385
389
|
} else if (evType === "approval_request") {
|
|
386
390
|
entry.pendingApprovalEvent = ev;
|
|
391
|
+
|
|
392
|
+
const isQuestion = ev.tool === "AskUserQuestion";
|
|
393
|
+
const nType = isQuestion ? "question" : "approval_request";
|
|
394
|
+
// Persist unread to DB + broadcast to all tabs/devices
|
|
395
|
+
incrementSessionUnread(sessionId, nType);
|
|
396
|
+
broadcastGlobalEvent({ type: "session:unread_changed", sessionId, unreadCount: -1, unreadType: nType, projectName: entry.projectName || "" });
|
|
397
|
+
|
|
387
398
|
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
388
399
|
const project = entry.projectName || "Project";
|
|
389
400
|
const session = chatService.getSession(sessionId);
|
|
390
401
|
const sTitle = session?.title || `Session ${sessionId.slice(0, 8)}`;
|
|
391
|
-
const isQuestion = ev.tool === "AskUserQuestion";
|
|
392
|
-
const nType = isQuestion ? "question" : "approval_request";
|
|
393
402
|
const title = isQuestion ? "AI has a question" : "Waiting for approval";
|
|
394
403
|
const body = isQuestion
|
|
395
404
|
? `${project} — ${sTitle}`
|
|
@@ -352,8 +352,9 @@ export function getAutoStartStatus(): AutoStartStatus {
|
|
|
352
352
|
|
|
353
353
|
/**
|
|
354
354
|
* Detect whether an existing systemd unit file is outdated and needs
|
|
355
|
-
* regeneration.
|
|
356
|
-
*
|
|
355
|
+
* regeneration. Flags units missing Type=notify or still using Restart=on-failure
|
|
356
|
+
* (should be Restart=always to survive upgrade-induced systemd restarts).
|
|
357
|
+
* Linux-only; returns false elsewhere.
|
|
357
358
|
*/
|
|
358
359
|
export function isAutoStartUnitStale(): boolean {
|
|
359
360
|
if (process.platform !== "linux") return false;
|
|
@@ -361,7 +362,9 @@ export function isAutoStartUnitStale(): boolean {
|
|
|
361
362
|
const path = getServicePath();
|
|
362
363
|
if (!existsSync(path)) return false;
|
|
363
364
|
const content = readFileSync(path, "utf-8");
|
|
364
|
-
|
|
365
|
+
if (!content.includes("Type=notify")) return true;
|
|
366
|
+
if (content.includes("Restart=on-failure")) return true;
|
|
367
|
+
return false;
|
|
365
368
|
} catch {
|
|
366
369
|
return false;
|
|
367
370
|
}
|
|
@@ -599,6 +599,12 @@ function runMigrations(database: Database): void {
|
|
|
599
599
|
}
|
|
600
600
|
database.exec("PRAGMA user_version = 21");
|
|
601
601
|
}
|
|
602
|
+
|
|
603
|
+
if (current < 22) {
|
|
604
|
+
try { database.exec("ALTER TABLE session_metadata ADD COLUMN unread_count INTEGER NOT NULL DEFAULT 0"); } catch { /* column exists */ }
|
|
605
|
+
try { database.exec("ALTER TABLE session_metadata ADD COLUMN unread_type TEXT"); } catch { /* column exists */ }
|
|
606
|
+
database.exec("PRAGMA user_version = 22");
|
|
607
|
+
}
|
|
602
608
|
}
|
|
603
609
|
|
|
604
610
|
// ---------------------------------------------------------------------------
|
|
@@ -755,6 +761,39 @@ export function deleteSessionMetadata(sessionId: string): void {
|
|
|
755
761
|
getDb().query("DELETE FROM session_metadata WHERE session_id = ?").run(sessionId);
|
|
756
762
|
}
|
|
757
763
|
|
|
764
|
+
// ---------------------------------------------------------------------------
|
|
765
|
+
// Unread tracking
|
|
766
|
+
// ---------------------------------------------------------------------------
|
|
767
|
+
|
|
768
|
+
export interface UnreadEntry {
|
|
769
|
+
sessionId: string;
|
|
770
|
+
unreadCount: number;
|
|
771
|
+
unreadType: string | null;
|
|
772
|
+
projectName: string | null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Increment unread count for a session and set the notification type */
|
|
776
|
+
export function incrementSessionUnread(sessionId: string, type: string): void {
|
|
777
|
+
getDb().query(
|
|
778
|
+
"UPDATE session_metadata SET unread_count = unread_count + 1, unread_type = ? WHERE session_id = ?",
|
|
779
|
+
).run(type, sessionId);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** Mark a session as read (reset unread count) */
|
|
783
|
+
export function clearSessionUnread(sessionId: string): void {
|
|
784
|
+
getDb().query(
|
|
785
|
+
"UPDATE session_metadata SET unread_count = 0, unread_type = NULL WHERE session_id = ?",
|
|
786
|
+
).run(sessionId);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** Get all sessions with unread > 0 */
|
|
790
|
+
export function getAllUnread(): UnreadEntry[] {
|
|
791
|
+
const rows = getDb().query(
|
|
792
|
+
"SELECT session_id, unread_count, unread_type, project_name FROM session_metadata WHERE unread_count > 0",
|
|
793
|
+
).all() as { session_id: string; unread_count: number; unread_type: string | null; project_name: string | null }[];
|
|
794
|
+
return rows.map((r) => ({ sessionId: r.session_id, unreadCount: r.unread_count, unreadType: r.unread_type, projectName: r.project_name }));
|
|
795
|
+
}
|
|
796
|
+
|
|
758
797
|
// ---------------------------------------------------------------------------
|
|
759
798
|
// Session title helpers (user-set titles persisted in PPM DB)
|
|
760
799
|
// ---------------------------------------------------------------------------
|
|
@@ -1074,7 +1113,7 @@ export function deleteConnection(nameOrId: string): boolean {
|
|
|
1074
1113
|
}
|
|
1075
1114
|
|
|
1076
1115
|
export function updateConnection(
|
|
1077
|
-
id: number, updates: { name?: string; config?: ConnectionConfig; groupName?: string | null; color?: string | null; readonly?: number },
|
|
1116
|
+
id: number, updates: { name?: string; config?: ConnectionConfig; groupName?: string | null; color?: string | null; readonly?: number; sortOrder?: number },
|
|
1078
1117
|
): void {
|
|
1079
1118
|
const sets: string[] = [];
|
|
1080
1119
|
const vals: unknown[] = [];
|
|
@@ -1083,6 +1122,7 @@ export function updateConnection(
|
|
|
1083
1122
|
if (updates.groupName !== undefined) { sets.push("group_name = ?"); vals.push(updates.groupName); }
|
|
1084
1123
|
if (updates.color !== undefined) { sets.push("color = ?"); vals.push(updates.color); }
|
|
1085
1124
|
if (updates.readonly !== undefined) { sets.push("readonly = ?"); vals.push(updates.readonly); }
|
|
1125
|
+
if (updates.sortOrder !== undefined) { sets.push("sort_order = ?"); vals.push(updates.sortOrder); }
|
|
1086
1126
|
if (sets.length === 0) return;
|
|
1087
1127
|
sets.push("updated_at = datetime('now')");
|
|
1088
1128
|
vals.push(id);
|
|
@@ -425,6 +425,7 @@ function killStaleTunnel() {
|
|
|
425
425
|
/** Spawn new supervisor from updated code, wait for it to be healthy, then exit */
|
|
426
426
|
async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
427
427
|
log("INFO", "Starting self-replace for upgrade");
|
|
428
|
+
const underSystemd = !!process.env.INVOCATION_ID && process.platform === "linux";
|
|
428
429
|
const currentSupervisorPid = process.pid;
|
|
429
430
|
|
|
430
431
|
try {
|
|
@@ -454,7 +455,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
454
455
|
|
|
455
456
|
// Kill server child to free the port; keep tunnel alive for domain continuity
|
|
456
457
|
// Use SIGKILL + process group kill to ensure grandchildren (SDK subprocesses) die too
|
|
457
|
-
log("INFO", "Stopping server before
|
|
458
|
+
log("INFO", "Stopping server before upgrade (tunnel kept alive)");
|
|
458
459
|
if (serverChild) {
|
|
459
460
|
const pid = serverChild.pid;
|
|
460
461
|
try { process.kill(-pid, "SIGKILL"); } catch {} // kill process group
|
|
@@ -462,6 +463,24 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
462
463
|
serverChild = null;
|
|
463
464
|
}
|
|
464
465
|
if (healthTimer) { clearInterval(healthTimer); healthTimer = null; }
|
|
466
|
+
|
|
467
|
+
// ── systemd path: exit cleanly, let Restart=always bring us back ────
|
|
468
|
+
// The old approach (Bun.spawn new supervisor + sd_notify MAINPID) causes
|
|
469
|
+
// systemd to lose track ("not our child"), leading to service death on
|
|
470
|
+
// daemon-reload. Instead, just exit — systemd restarts us with new code.
|
|
471
|
+
if (underSystemd) {
|
|
472
|
+
log("INFO", "Under systemd: exiting for automatic restart with updated code");
|
|
473
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
474
|
+
if (upgradeCheckTimer) clearInterval(upgradeCheckTimer);
|
|
475
|
+
if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
|
|
476
|
+
if (cloudMonitorTimer) clearInterval(cloudMonitorTimer);
|
|
477
|
+
// Disconnect Cloud WS so new supervisor can reconnect cleanly
|
|
478
|
+
try { const { disconnect } = await import("./cloud-ws.service.ts"); disconnect(); } catch {}
|
|
479
|
+
// Don't kill tunnel — it lives in its own systemd-run scope and survives cgroup teardown
|
|
480
|
+
process.exit(0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ── Non-systemd path: spawn new supervisor directly (macOS/Windows) ─
|
|
465
484
|
// Poll until port is actually free (max 10s) — never guess with fixed sleep
|
|
466
485
|
const portFreeStart = Date.now();
|
|
467
486
|
while (Date.now() - portFreeStart < 10_000) {
|
|
@@ -495,15 +514,9 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
495
514
|
const data = JSON.parse(readFileSync(STATUS_FILE(), "utf-8"));
|
|
496
515
|
if (data.supervisorPid && data.supervisorPid !== currentSupervisorPid) {
|
|
497
516
|
log("INFO", `New supervisor detected (PID: ${data.supervisorPid}), handing off MainPID to systemd`);
|
|
498
|
-
// Tell systemd the new supervisor is now MainPID — required so that
|
|
499
|
-
// systemd does NOT tear down the ppm.service cgroup when this old
|
|
500
|
-
// supervisor exits 0. Needs NotifyAccess=all in unit file.
|
|
501
|
-
// No-op on non-systemd platforms (NOTIFY_SOCKET unset).
|
|
502
517
|
await sdNotify(`MAINPID=${data.supervisorPid}`);
|
|
503
|
-
// Small delay so systemd processes the datagram before our exit.
|
|
504
518
|
await Bun.sleep(300);
|
|
505
519
|
log("INFO", `Old supervisor exiting`);
|
|
506
|
-
// Children already killed, just clear remaining timers and exit
|
|
507
520
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
508
521
|
if (upgradeCheckTimer) clearInterval(upgradeCheckTimer);
|
|
509
522
|
if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
|
package/src/web/app.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from "react";
|
|
|
2
2
|
import { Toaster } from "@/components/ui/sonner";
|
|
3
3
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
4
|
import { PanelLayout } from "@/components/layout/panel-layout";
|
|
5
|
+
import { TabPool } from "@/components/layout/tab-pool";
|
|
5
6
|
import { Sidebar } from "@/components/layout/sidebar";
|
|
6
7
|
import { ProjectBar } from "@/components/layout/project-bar";
|
|
7
8
|
import { MobileNav } from "@/components/layout/mobile-nav";
|
|
@@ -204,6 +205,14 @@ export function App() {
|
|
|
204
205
|
useTabStore.getState().switchProject(projectName);
|
|
205
206
|
}, [activeProject?.name]);
|
|
206
207
|
|
|
208
|
+
// Hydrate unread notification state from server (persisted across refresh / tabs)
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (authState !== "authenticated" || !activeProject?.name) return;
|
|
211
|
+
import("@/stores/notification-store").then(({ useNotificationStore }) => {
|
|
212
|
+
useNotificationStore.getState().loadFromServer(activeProject.name);
|
|
213
|
+
});
|
|
214
|
+
}, [authState, activeProject?.name]);
|
|
215
|
+
|
|
207
216
|
// Keep-alive: mount workspace on first visit, never unmount
|
|
208
217
|
useEffect(() => {
|
|
209
218
|
const projectName = activeProject?.name ?? "__global__";
|
|
@@ -281,6 +290,9 @@ export function App() {
|
|
|
281
290
|
<PanelLayout projectName={projectName} />
|
|
282
291
|
</div>
|
|
283
292
|
))}
|
|
293
|
+
{/* TabPool renders all tab components persistently and portals them into panel slots.
|
|
294
|
+
Placed after PanelLayout so slot refs are registered before portals render. */}
|
|
295
|
+
<TabPool />
|
|
284
296
|
<StatusBar />
|
|
285
297
|
</div>
|
|
286
298
|
</div>
|