@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/http-api.md +1 -1
  4. package/dist/web/assets/{ai-settings-section-DR5BueEL.js → ai-settings-section-DN4egS8e.js} +1 -1
  5. package/dist/web/assets/architecture-PBZL5I3N-CZBayZMd.js +1 -0
  6. package/dist/web/assets/{audio-preview-DwyrUe-V.js → audio-preview-CrTLA4VQ.js} +1 -1
  7. package/dist/web/assets/chat-tab-DFCOXFk8.js +12 -0
  8. package/dist/web/assets/code-editor-J864BoOW.js +8 -0
  9. package/dist/web/assets/{conflict-editor-C8vTvS9w.js → conflict-editor-BIwUtzO5.js} +1 -1
  10. package/dist/web/assets/{csv-preview-Bo-N3GHl.js → csv-preview-BIfojSWd.js} +1 -1
  11. package/dist/web/assets/{data-grid-overlay-editor-DqcDQ9st.js → data-grid-overlay-editor-DZIqEOsz.js} +1 -1
  12. package/dist/web/assets/{database-viewer-_RTlPC26.js → database-viewer-DhawNQtp.js} +1 -1
  13. package/dist/web/assets/{diff-viewer-P2Dc__bQ.js → diff-viewer-nupJr1AG.js} +1 -1
  14. package/dist/web/assets/{esm-Dvc8oJly.js → esm-UZtw2QcY.js} +1 -1
  15. package/dist/web/assets/{extension-webview-CHqtkQBd.js → extension-webview-BXDYtTXe.js} +2 -2
  16. package/dist/web/assets/gitGraph-HDMCJU4V-CboO1wK8.js +1 -0
  17. package/dist/web/assets/{glide-data-grid-9TPVejSQ.js → glide-data-grid-DttB_tob.js} +6 -6
  18. package/dist/web/assets/{image-preview--nh-wHgF.js → image-preview-Dh11TP_j.js} +1 -1
  19. package/dist/web/assets/index-C5sLGvFC.css +2 -0
  20. package/dist/web/assets/index-CPcnZtNl.js +27 -0
  21. package/dist/web/assets/info-3K5VOQVL-D_qKNgUf.js +1 -0
  22. package/dist/web/assets/keybindings-store-DvBC5IaA.js +1 -0
  23. package/dist/web/assets/{markdown-renderer-Bsow9WVr.js → markdown-renderer-Bwpgzn7n.js} +3 -3
  24. package/dist/web/assets/notification-store-D1sxDh0s.js +1 -0
  25. package/dist/web/assets/{number-overlay-editor-XTjjEXtk.js → number-overlay-editor-CewUR5pB.js} +1 -1
  26. package/dist/web/assets/packet-RMMSAZCW-XtGc2GdX.js +1 -0
  27. package/dist/web/assets/{pdf-preview-BqntOcNA.js → pdf-preview-CI-lrcdD.js} +1 -1
  28. package/dist/web/assets/pie-UPGHQEXC-DNZ5YtCW.js +1 -0
  29. package/dist/web/assets/{port-forwarding-tab-WWRLWcTB.js → port-forwarding-tab-DOEfu8ca.js} +1 -1
  30. package/dist/web/assets/{postgres-viewer-g7-3kOzD.js → postgres-viewer-Bb3RwFMj.js} +3 -3
  31. package/dist/web/assets/radar-KQ55EAFF-uCGpAvZE.js +1 -0
  32. package/dist/web/assets/{settings-store-D2MtC9tm.js → settings-store-CVrIYYCB.js} +2 -2
  33. package/dist/web/assets/settings-tab-i8KAi1LY.js +1 -0
  34. package/dist/web/assets/{sql-query-editor-CultKZsI.js → sql-query-editor-C3ZrhqZr.js} +1 -1
  35. package/dist/web/assets/sqlite-viewer-Cucs41S6.js +1 -0
  36. package/dist/web/assets/terminal-tab-upGE8feC.js +1 -0
  37. package/dist/web/assets/treemap-KZPCXAKY-DQvivjBa.js +1 -0
  38. package/dist/web/assets/{use-monaco-theme-CugUkORI.js → use-monaco-theme-BePWbY58.js} +1 -1
  39. package/dist/web/assets/{vendor-mermaid-CPtQ2zua.js → vendor-mermaid-Cl50p6TB.js} +2 -2
  40. package/dist/web/assets/{video-preview-C4PxtiOc.js → video-preview-CSdxf4fH.js} +1 -1
  41. package/dist/web/index.html +6 -6
  42. package/dist/web/sw.js +1 -1
  43. package/package.json +1 -1
  44. package/src/providers/claude-agent-sdk.ts +13 -1
  45. package/src/server/routes/chat.ts +73 -3
  46. package/src/server/routes/database.ts +11 -2
  47. package/src/server/ws/chat.ts +12 -3
  48. package/src/services/autostart-generator.ts +2 -2
  49. package/src/services/autostart-register.ts +6 -3
  50. package/src/services/db.service.ts +41 -1
  51. package/src/services/supervisor.ts +20 -7
  52. package/src/web/app.tsx +12 -0
  53. package/src/web/components/chat/chat-history-bar.tsx +43 -13
  54. package/src/web/components/chat/chat-tab.tsx +3 -0
  55. package/src/web/components/chat/session-list-panel.tsx +15 -8
  56. package/src/web/components/chat/session-picker.tsx +33 -5
  57. package/src/web/components/database/connection-list.tsx +55 -205
  58. package/src/web/components/database/connection-row.tsx +104 -0
  59. package/src/web/components/database/database-sidebar.tsx +1 -1
  60. package/src/web/components/database/schema-table-tree.tsx +98 -0
  61. package/src/web/components/database/use-connections.ts +9 -6
  62. package/src/web/components/layout/command-palette.tsx +10 -2
  63. package/src/web/components/layout/editor-panel.tsx +16 -39
  64. package/src/web/components/layout/tab-pool.tsx +196 -0
  65. package/src/web/hooks/use-chat.ts +9 -2
  66. package/src/web/hooks/use-debounced-value.ts +10 -0
  67. package/src/web/stores/notification-store.ts +42 -0
  68. package/dist/web/assets/architecture-PBZL5I3N-7JKY4P1L.js +0 -1
  69. package/dist/web/assets/chat-tab-DqS9Qk3O.js +0 -12
  70. package/dist/web/assets/code-editor-DwdeigGe.js +0 -8
  71. package/dist/web/assets/gitGraph-HDMCJU4V-Daf9rhiF.js +0 -1
  72. package/dist/web/assets/index-nC9UURj4.css +0 -2
  73. package/dist/web/assets/index-xpTdWKsA.js +0 -27
  74. package/dist/web/assets/info-3K5VOQVL-gn0pjNiT.js +0 -1
  75. package/dist/web/assets/keybindings-store-C0XkvJcm.js +0 -1
  76. package/dist/web/assets/packet-RMMSAZCW-Csaeizjc.js +0 -1
  77. package/dist/web/assets/pie-UPGHQEXC-DatkjxTH.js +0 -1
  78. package/dist/web/assets/radar-KQ55EAFF-BnGB20hR.js +0 -1
  79. package/dist/web/assets/settings-tab-DO3s244B.js +0 -1
  80. package/dist/web/assets/sqlite-viewer-BtYh66b0.js +0 -1
  81. package/dist/web/assets/terminal-tab-C25rc_34.js +0 -1
  82. package/dist/web/assets/treemap-KZPCXAKY-CgEYv38e.js +0 -1
  83. /package/dist/web/assets/{api-settings-DowGyuVy.js → api-settings-DnHv6JgF.js} +0 -0
  84. /package/dist/web/assets/{data-grid-types-DqqspyVw.js → data-grid-types-BISkUXAY.js} +0 -0
  85. /package/dist/web/assets/{dist-_jZs3YZC.js → dist-B1I_4Jtc.js} +0 -0
  86. /package/dist/web/assets/{dist-D1SZxtVS.js → dist-CcDNqGjt.js} +0 -0
  87. /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
  88. /package/dist/web/assets/{lib-Dub8DlCJ.js → lib-Bu71-TFS.js} +0 -0
  89. /package/dist/web/assets/{use-blob-url-DGY5qKiT.js → use-blob-url-QX-XajU8.js} +0 -0
  90. /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{B as n}from"./index-xpTdWKsA.js";import{t as r}from"./use-blob-url-DGY5qKiT.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};
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};
@@ -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-xpTdWKsA.js"></script>
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-CPtQ2zua.js">
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-D2MtC9tm.js">
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-DowGyuVy.js">
58
- <link rel="modulepreload" crossorigin href="/assets/ai-settings-section-DR5BueEL.js">
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-nC9UURj4.css">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.13.20",
3
+ "version": "0.13.22",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -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 missing = findMissingSessions(dir, knownIds, this.id);
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
- const filtered = filterTagId !== null ? enriched.filter((s) => s.tag?.id === filterTagId) : enriched;
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}".` : "";
@@ -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}`
@@ -128,8 +128,8 @@ Wants=network-online.target
128
128
  Type=notify
129
129
  NotifyAccess=all
130
130
  ExecStart=${execStart}
131
- Restart=on-failure
132
- RestartSec=5
131
+ Restart=always
132
+ RestartSec=3
133
133
  TimeoutStartSec=60
134
134
  TimeoutStopSec=10
135
135
  KillMode=mixed
@@ -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. Currently flags units missing Type=notify (introduced to fix
356
- * the WSL/systemd upgrade-kill bug). Linux-only; returns false elsewhere.
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
- return !content.includes("Type=notify");
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 spawning new supervisor (tunnel kept alive)");
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>