@hienlh/ppm 0.8.89 → 0.8.91

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 (43) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/web/assets/{browser-tab-DSWumOSG.js → browser-tab-Bt91e0v_.js} +1 -1
  3. package/dist/web/assets/chat-tab-BY1ovPns.js +8 -0
  4. package/dist/web/assets/{code-editor-DLTcPb55.js → code-editor-CAHcH0N-.js} +1 -1
  5. package/dist/web/assets/{database-viewer-BrpPlYG7.js → database-viewer-DzEoA-r6.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-Dx96kcTu.js → diff-viewer-Co7JUnvw.js} +1 -1
  7. package/dist/web/assets/{git-graph-CoN6voTp.js → git-graph-B139k04F.js} +1 -1
  8. package/dist/web/assets/{index-DRdx_Wqn.js → index-CXJneRo7.js} +10 -10
  9. package/dist/web/assets/index-CqhIj4Ko.css +2 -0
  10. package/dist/web/assets/keybindings-store-C8WA_lZu.js +1 -0
  11. package/dist/web/assets/{markdown-renderer-BqsXIW9n.js → markdown-renderer-C6phS0NU.js} +1 -1
  12. package/dist/web/assets/{postgres-viewer-Lw8xaGfc.js → postgres-viewer-Bb1N6-J2.js} +1 -1
  13. package/dist/web/assets/{settings-tab-DDCC58we.js → settings-tab-CZp_PyJ9.js} +1 -1
  14. package/dist/web/assets/{sqlite-viewer-DECA802J.js → sqlite-viewer-Bzgj_M05.js} +1 -1
  15. package/dist/web/assets/{terminal-tab-DneNM6WP.js → terminal-tab-Bi4qWzTP.js} +1 -1
  16. package/dist/web/assets/{use-monaco-theme-CrtYAJMR.js → use-monaco-theme-D7s2hmIL.js} +1 -1
  17. package/dist/web/index.html +2 -2
  18. package/dist/web/sw.js +1 -1
  19. package/package.json +1 -1
  20. package/src/providers/claude-agent-sdk.ts +59 -1
  21. package/src/providers/cli-provider-base.ts +6 -0
  22. package/src/server/routes/chat.ts +31 -10
  23. package/src/server/routes/settings.ts +27 -0
  24. package/src/server/ws/chat.ts +7 -1
  25. package/src/services/cloud-ws.service.ts +1 -0
  26. package/src/services/cloud.service.ts +1 -0
  27. package/src/services/db.service.ts +8 -0
  28. package/src/services/supervisor.ts +22 -2
  29. package/src/types/api.ts +1 -0
  30. package/src/types/chat.ts +2 -0
  31. package/src/web/app.tsx +3 -2
  32. package/src/web/components/chat/chat-history-bar.tsx +21 -7
  33. package/src/web/components/chat/chat-tab.tsx +4 -1
  34. package/src/web/components/chat/message-list.tsx +2 -2
  35. package/src/web/components/chat/session-picker.tsx +1 -0
  36. package/src/web/components/layout/upgrade-banner.tsx +15 -5
  37. package/src/web/components/settings/change-password-section.tsx +128 -0
  38. package/src/web/components/settings/settings-tab.tsx +4 -0
  39. package/src/web/hooks/use-chat.ts +17 -0
  40. package/test-session-ops.mjs +444 -0
  41. package/dist/web/assets/chat-tab-Ccwf-c6M.js +0 -8
  42. package/dist/web/assets/index-CtbNK_ih.css +0 -2
  43. package/dist/web/assets/keybindings-store-DHGoLYnP.js +0 -1
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":"8d3ea9bac3eb342673fb75bf8da6c0be","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/xychartDiagram-JWTSCODW-DylHYNtJ.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-ywK7LMaH.js"},{"revision":null,"url":"assets/utils-DMiycH3O.js"},{"revision":null,"url":"assets/use-monaco-theme-CrtYAJMR.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-C9TYRE0k.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-A4PN_Efm.js"},{"revision":null,"url":"assets/terminal-tab-DneNM6WP.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-Q2dZiSPX.js"},{"revision":null,"url":"assets/table-CQVQM2SB.js"},{"revision":null,"url":"assets/tab-store--SlERlDs.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-C4EMl6jf.js"},{"revision":null,"url":"assets/src-Dw4QhedI.js"},{"revision":null,"url":"assets/sqlite-viewer-DECA802J.js"},{"revision":null,"url":"assets/settings-tab-DDCC58we.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-DM-tMAhx.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-RolPi8bU.js"},{"revision":null,"url":"assets/rough.esm-nHaDi0Kw.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-B9F_Cx_p.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-dom-Bpkvzu3U.js"},{"revision":null,"url":"assets/react-SKk5z-bm.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-Z-Tr5wtS.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-CdjGIDfw.js"},{"revision":null,"url":"assets/preload-helper-Bf_JiD2A.js"},{"revision":null,"url":"assets/postgres-viewer-Lw8xaGfc.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-At5Kz0KK.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-CvXHKAzp.js"},{"revision":null,"url":"assets/path-DIKpVbHL.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-IVa5F-go.js"},{"revision":null,"url":"assets/ordinal-LFEjVtwQ.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-BaOBwb-W.js"},{"revision":null,"url":"assets/mermaid-parser.core-8u2leTXI.js"},{"revision":null,"url":"assets/math-y9zN1W-N.js"},{"revision":null,"url":"assets/markdown-renderer-BqsXIW9n.js"},{"revision":null,"url":"assets/linear-Bcjv9FQt.js"},{"revision":null,"url":"assets/line-B75-Rx70.js"},{"revision":null,"url":"assets/lib-BeaDXEkP.js"},{"revision":null,"url":"assets/keybindings-store-DHGoLYnP.js"},{"revision":null,"url":"assets/katex-DzXRfQ_m.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-h4g10UHL.js"},{"revision":null,"url":"assets/jsx-runtime-BRW_vwa9.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-CgDI-UG4.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js"},{"revision":null,"url":"assets/isEmpty-B9L-Ge-H.js"},{"revision":null,"url":"assets/isArrayLikeObject-CGBoxvCD.js"},{"revision":null,"url":"assets/init-C0r9Gk5G.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js"},{"revision":null,"url":"assets/info-3K5VOQVL-BDzTLc11.js"},{"revision":null,"url":"assets/index-DRdx_Wqn.js"},{"revision":null,"url":"assets/index-CtbNK_ih.css"},{"revision":null,"url":"assets/graphlib-Duh_bWLa.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-CNlas3Rz.js"},{"revision":null,"url":"assets/git-graph-CoN6voTp.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-BdjmoMLS.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-CRxlE9Sr.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-DLeYhAAT.js"},{"revision":null,"url":"assets/dist-DGDPTxs1.js"},{"revision":null,"url":"assets/dist-Cep75xXf.js"},{"revision":null,"url":"assets/dist-CALwEtco.js"},{"revision":null,"url":"assets/diff-viewer-Dx96kcTu.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO-hzmp0GHK.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-sqTog_XV.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-DxPjK7_c.js"},{"revision":null,"url":"assets/defaultLocale-CrJzLgRD.js"},{"revision":null,"url":"assets/database-viewer-BrpPlYG7.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-C3O-MTLf.js"},{"revision":null,"url":"assets/dagre-BFcnKyBF.js"},{"revision":null,"url":"assets/cytoscape.esm-CWPXKqbJ.js"},{"revision":null,"url":"assets/csv-preview-DUbHtTAS.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-qudEiMCT.js"},{"revision":null,"url":"assets/columns-2-DbesTfa7.js"},{"revision":null,"url":"assets/code-editor-DLTcPb55.js"},{"revision":null,"url":"assets/clone-B2hUek6n.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-av5aeHLq.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-Cb0iqycX.js"},{"revision":null,"url":"assets/chunk-XPW4576I-BPEX8KhL.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-DRJEb7Zb.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-CXuQvlyu.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-CMY0PkRK.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-DFKFM_C1.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-C7Gry6md.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-DX0xW7kO.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-rG0P22U9.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-BsUWb9d0.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-DXUTQ-BL.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-C2UEioMs.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-HG_eMj_C.js"},{"revision":null,"url":"assets/chunk-KYZI473N-Djw13C-3.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-DP36BDiU.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-BBmymCjA.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-BBw_z0fW.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-DBdWQ3zy.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-tDjHsAUs.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-D23YVTOU.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-Cpr87sBR.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-CtqKiH4q.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-Dvbyu4Zw.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-CkFGv6Zs.js"},{"revision":null,"url":"assets/chunk-55IACEB6-D5cABeB9.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-C3aZvW7B.js"},{"revision":null,"url":"assets/chevron-right-CHnjJt4E.js"},{"revision":null,"url":"assets/chat-tab-Ccwf-c6M.js"},{"revision":null,"url":"assets/channel-C2fMafck.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW--pF1r5lr.js"},{"revision":null,"url":"assets/browser-tab-DSWumOSG.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-h3cDF2vI.js"},{"revision":null,"url":"assets/arrow-up--LjUXLEt.js"},{"revision":null,"url":"assets/array-DqLCdDFv.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-DqAZP_F6.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-CFzkFKEL.js"},{"revision":null,"url":"assets/arc-B9n1Gvb5.js"},{"revision":null,"url":"assets/api-settings-Bid0NHuI.js"},{"revision":null,"url":"assets/api-client-BKIT_Qeg.js"},{"revision":null,"url":"assets/_baseUniq-Yy35llnn.js"},{"revision":null,"url":"assets/_basePickBy-3Xe18azI.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":"583c98c2d25ebf1f3adaebbf961a8347","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/xychartDiagram-JWTSCODW-DylHYNtJ.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-ywK7LMaH.js"},{"revision":null,"url":"assets/utils-DMiycH3O.js"},{"revision":null,"url":"assets/use-monaco-theme-D7s2hmIL.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-C9TYRE0k.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-A4PN_Efm.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-Bi4qWzTP.js"},{"revision":null,"url":"assets/tag-Q2dZiSPX.js"},{"revision":null,"url":"assets/table-CQVQM2SB.js"},{"revision":null,"url":"assets/tab-store--SlERlDs.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-C4EMl6jf.js"},{"revision":null,"url":"assets/src-Dw4QhedI.js"},{"revision":null,"url":"assets/sqlite-viewer-Bzgj_M05.js"},{"revision":null,"url":"assets/settings-tab-CZp_PyJ9.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-DM-tMAhx.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-RolPi8bU.js"},{"revision":null,"url":"assets/rough.esm-nHaDi0Kw.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-B9F_Cx_p.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-dom-Bpkvzu3U.js"},{"revision":null,"url":"assets/react-SKk5z-bm.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-Z-Tr5wtS.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-CdjGIDfw.js"},{"revision":null,"url":"assets/preload-helper-Bf_JiD2A.js"},{"revision":null,"url":"assets/postgres-viewer-Bb1N6-J2.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-At5Kz0KK.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-CvXHKAzp.js"},{"revision":null,"url":"assets/path-DIKpVbHL.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-IVa5F-go.js"},{"revision":null,"url":"assets/ordinal-LFEjVtwQ.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-BaOBwb-W.js"},{"revision":null,"url":"assets/mermaid-parser.core-8u2leTXI.js"},{"revision":null,"url":"assets/math-y9zN1W-N.js"},{"revision":null,"url":"assets/markdown-renderer-C6phS0NU.js"},{"revision":null,"url":"assets/linear-Bcjv9FQt.js"},{"revision":null,"url":"assets/line-B75-Rx70.js"},{"revision":null,"url":"assets/lib-BeaDXEkP.js"},{"revision":null,"url":"assets/keybindings-store-C8WA_lZu.js"},{"revision":null,"url":"assets/katex-DzXRfQ_m.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-h4g10UHL.js"},{"revision":null,"url":"assets/jsx-runtime-BRW_vwa9.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-CgDI-UG4.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js"},{"revision":null,"url":"assets/isEmpty-B9L-Ge-H.js"},{"revision":null,"url":"assets/isArrayLikeObject-CGBoxvCD.js"},{"revision":null,"url":"assets/init-C0r9Gk5G.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js"},{"revision":null,"url":"assets/info-3K5VOQVL-BDzTLc11.js"},{"revision":null,"url":"assets/index-CqhIj4Ko.css"},{"revision":null,"url":"assets/index-CXJneRo7.js"},{"revision":null,"url":"assets/graphlib-Duh_bWLa.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-CNlas3Rz.js"},{"revision":null,"url":"assets/git-graph-B139k04F.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-BdjmoMLS.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-CRxlE9Sr.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-DLeYhAAT.js"},{"revision":null,"url":"assets/dist-DGDPTxs1.js"},{"revision":null,"url":"assets/dist-Cep75xXf.js"},{"revision":null,"url":"assets/dist-CALwEtco.js"},{"revision":null,"url":"assets/diff-viewer-Co7JUnvw.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO-hzmp0GHK.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-sqTog_XV.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-DxPjK7_c.js"},{"revision":null,"url":"assets/defaultLocale-CrJzLgRD.js"},{"revision":null,"url":"assets/database-viewer-DzEoA-r6.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-C3O-MTLf.js"},{"revision":null,"url":"assets/dagre-BFcnKyBF.js"},{"revision":null,"url":"assets/cytoscape.esm-CWPXKqbJ.js"},{"revision":null,"url":"assets/csv-preview-DUbHtTAS.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-qudEiMCT.js"},{"revision":null,"url":"assets/columns-2-DbesTfa7.js"},{"revision":null,"url":"assets/code-editor-CAHcH0N-.js"},{"revision":null,"url":"assets/clone-B2hUek6n.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-av5aeHLq.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-Cb0iqycX.js"},{"revision":null,"url":"assets/chunk-XPW4576I-BPEX8KhL.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-DRJEb7Zb.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-CXuQvlyu.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-CMY0PkRK.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-DFKFM_C1.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-C7Gry6md.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-DX0xW7kO.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-rG0P22U9.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-BsUWb9d0.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-DXUTQ-BL.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-C2UEioMs.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-HG_eMj_C.js"},{"revision":null,"url":"assets/chunk-KYZI473N-Djw13C-3.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-DP36BDiU.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-BBmymCjA.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-BBw_z0fW.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-DBdWQ3zy.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-tDjHsAUs.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-D23YVTOU.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-Cpr87sBR.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-CtqKiH4q.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-Dvbyu4Zw.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-CkFGv6Zs.js"},{"revision":null,"url":"assets/chunk-55IACEB6-D5cABeB9.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-C3aZvW7B.js"},{"revision":null,"url":"assets/chevron-right-CHnjJt4E.js"},{"revision":null,"url":"assets/chat-tab-BY1ovPns.js"},{"revision":null,"url":"assets/channel-C2fMafck.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW--pF1r5lr.js"},{"revision":null,"url":"assets/browser-tab-Bt91e0v_.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-h3cDF2vI.js"},{"revision":null,"url":"assets/arrow-up--LjUXLEt.js"},{"revision":null,"url":"assets/array-DqLCdDFv.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-DqAZP_F6.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-CFzkFKEL.js"},{"revision":null,"url":"assets/arc-B9n1Gvb5.js"},{"revision":null,"url":"assets/api-settings-Bid0NHuI.js"},{"revision":null,"url":"assets/api-client-BKIT_Qeg.js"},{"revision":null,"url":"assets/_baseUniq-Yy35llnn.js"},{"revision":null,"url":"assets/_basePickBy-3Xe18azI.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.8.89",
3
+ "version": "0.8.91",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -19,9 +19,11 @@ import { getSessionMapping, setSessionMapping, getSessionTitles } from "../servi
19
19
  import { accountSelector } from "../services/account-selector.service.ts";
20
20
  import { accountService } from "../services/account.service.ts";
21
21
  import { resolve } from "node:path";
22
- import { existsSync } from "node:fs";
22
+ import { existsSync, readdirSync, unlinkSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
 
25
+ const CLAUDE_PROJECTS_DIR = resolve(homedir(), ".claude/projects");
26
+
25
27
  function getSdkSessionId(ppmId: string): string {
26
28
  return getSessionMapping(ppmId) ?? ppmId;
27
29
  }
@@ -317,6 +319,21 @@ export class ClaudeAgentSdkProvider implements AIProvider {
317
319
  this.closeStreamingSession(sessionId);
318
320
  this.activeSessions.delete(sessionId);
319
321
  this.messageCount.delete(sessionId);
322
+ this.pendingApprovals.delete(sessionId);
323
+ this.forkSources.delete(sessionId);
324
+
325
+ // Best-effort: delete JSONL from ~/.claude/projects/
326
+ const sdkId = getSessionMapping(sessionId) ?? sessionId;
327
+ try {
328
+ if (existsSync(CLAUDE_PROJECTS_DIR)) {
329
+ const projectDirs = readdirSync(CLAUDE_PROJECTS_DIR);
330
+ for (const dir of projectDirs) {
331
+ if (dir.includes("..") || dir.includes("/")) continue; // safety
332
+ const jsonlPath = resolve(CLAUDE_PROJECTS_DIR, dir, `${sdkId}.jsonl`);
333
+ if (existsSync(jsonlPath)) { unlinkSync(jsonlPath); break; }
334
+ }
335
+ }
336
+ } catch { /* best-effort */ }
320
337
  }
321
338
 
322
339
  /**
@@ -335,6 +352,29 @@ export class ClaudeAgentSdkProvider implements AIProvider {
335
352
  this.forkSources.set(sessionId, sourceSessionId);
336
353
  }
337
354
 
355
+ /** Fork a session at a specific message using SDK forkSession() */
356
+ async forkAtMessage(
357
+ sessionId: string,
358
+ messageId: string,
359
+ opts?: { title?: string; dir?: string },
360
+ ): Promise<{ sessionId: string }> {
361
+ const sdkId = getSessionMapping(sessionId) ?? sessionId;
362
+ // Dynamic import: Bun's ESM linker fails to resolve forkSession as a static named export
363
+ // in certain test configurations. Lazy import avoids the module linking issue.
364
+ const { forkSession } = await import("@anthropic-ai/claude-agent-sdk");
365
+ const result = await forkSession(sdkId, {
366
+ upToMessageId: messageId,
367
+ title: opts?.title,
368
+ dir: opts?.dir,
369
+ });
370
+ return { sessionId: result.sessionId };
371
+ }
372
+
373
+ /** Mark session as resumed so next sendMessage uses resume path */
374
+ markAsResumed(sessionId: string): void {
375
+ this.messageCount.set(sessionId, 1);
376
+ }
377
+
338
378
  async listModels(): Promise<ModelOption[]> {
339
379
  return [
340
380
  { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
@@ -698,6 +738,24 @@ export class ClaudeAgentSdkProvider implements AIProvider {
698
738
  }
699
739
  }
700
740
 
741
+ // Detect compacting status
742
+ if (subtype === "status") {
743
+ const status = (msg as any).status;
744
+ if (status === "compacting") {
745
+ console.log(`[sdk] session=${sessionId} COMPACTING`);
746
+ yield { type: "system" as const, subtype: "compacting" } as ChatEvent;
747
+ continue;
748
+ }
749
+ }
750
+
751
+ // Detect compact boundary (compact finished, messages replaced in JSONL)
752
+ if (subtype === "compact_boundary") {
753
+ const meta = (msg as any).compact_metadata;
754
+ console.log(`[sdk] session=${sessionId} COMPACT_BOUNDARY trigger=${meta?.trigger} pre_tokens=${meta?.pre_tokens}`);
755
+ yield { type: "system" as const, subtype: "compact_done" } as ChatEvent;
756
+ continue;
757
+ }
758
+
701
759
  // Yield system events so streaming loop can transition phases
702
760
  // (e.g. connecting → thinking when hooks/init arrive)
703
761
  yield { type: "system" as any, subtype } as any;
@@ -84,7 +84,13 @@ export abstract class CliProvider implements AIProvider {
84
84
  }));
85
85
  }
86
86
 
87
+ markAsResumed(sessionId: string): void {
88
+ this.messageCount.set(sessionId, 1);
89
+ }
90
+
87
91
  async deleteSession(sessionId: string): Promise<void> {
92
+ const proc = this.activeProcesses.get(sessionId);
93
+ if (proc) { proc.kill(); this.activeProcesses.delete(sessionId); }
88
94
  this.sessions.delete(sessionId);
89
95
  this.messageCount.delete(sessionId);
90
96
  }
@@ -8,7 +8,7 @@ import { renameSession as sdkRenameSession } from "@anthropic-ai/claude-agent-sd
8
8
  import { listSlashItems } from "../../services/slash-items.service.ts";
9
9
  import { getCachedUsage, refreshUsageNow } from "../../services/claude-usage.service.ts";
10
10
  import { getSessionLog } from "../../services/session-log.service.ts";
11
- import { getSessionMapping, setSessionTitle, getPinnedSessionIds, pinSession, unpinSession } from "../../services/db.service.ts";
11
+ import { getSessionMapping, setSessionMapping, setSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionTitle } from "../../services/db.service.ts";
12
12
  import { ok, err } from "../../types/api.ts";
13
13
 
14
14
  type Env = { Variables: { projectPath: string; projectName: string } };
@@ -125,7 +125,13 @@ chatRoutes.delete("/sessions/:id", async (c) => {
125
125
  try {
126
126
  const id = c.req.param("id");
127
127
  const providerId = c.req.query("providerId") ?? "claude";
128
+ const sdkId = getSessionMapping(id) ?? id;
129
+ // Provider-specific cleanup (JSONL, process, etc.)
128
130
  await chatService.deleteSession(providerId, id);
131
+ // Shared DB cleanup
132
+ deleteSessionMapping(id);
133
+ deleteSessionTitle(sdkId);
134
+ unpinSession(sdkId);
129
135
  return c.json(ok({ deleted: id }));
130
136
  } catch (e) {
131
137
  return c.json(err((e as Error).message), 404);
@@ -184,16 +190,31 @@ chatRoutes.post("/sessions/:id/fork", async (c) => {
184
190
  const projectName = c.get("projectName");
185
191
  const projectPath = c.get("projectPath");
186
192
  const providerId = c.req.query("providerId") ?? "claude";
187
- // Create a new PPM session that will fork from sourceId on first message
188
- const session = await chatService.createSession(providerId, {
189
- projectName,
190
- projectPath,
191
- title: "Forked Chat",
192
- });
193
- // Store fork source so WS handler knows to use forkSession on first message
193
+ const body = await c.req.json<{ messageId?: string }>().catch(() => ({} as { messageId?: string }));
194
194
  const provider = providerRegistry.get(providerId);
195
- provider?.setForkSource?.(session.id, sourceId);
196
- return c.json(ok({ ...session, forkedFrom: sourceId }), 201);
195
+ if (!provider) return c.json(err("Provider not found"), 404);
196
+
197
+ if (body.messageId && provider.forkAtMessage) {
198
+ // Mid-fork: SDK fork first, then create PPM session only on success
199
+ const result = await provider.forkAtMessage(sourceId, body.messageId, {
200
+ title: "Forked Chat", dir: projectPath,
201
+ });
202
+ const session = await chatService.createSession(providerId, {
203
+ projectName, projectPath, title: "Forked Chat",
204
+ });
205
+ setSessionMapping(session.id, result.sessionId);
206
+ provider.markAsResumed?.(session.id);
207
+ return c.json(ok({ ...session, forkedFrom: sourceId }), 201);
208
+ } else if (provider.setForkSource) {
209
+ // Deferred fork from end (full history copy on first msg)
210
+ const session = await chatService.createSession(providerId, {
211
+ projectName, projectPath, title: "Forked Chat",
212
+ });
213
+ provider.setForkSource(session.id, sourceId);
214
+ return c.json(ok({ ...session, forkedFrom: sourceId }), 201);
215
+ } else {
216
+ return c.json(err("Provider does not support forking"), 400);
217
+ }
197
218
  } catch (e) {
198
219
  return c.json(err((e as Error).message), 500);
199
220
  }
@@ -252,6 +252,33 @@ settingsRoutes.post("/telegram/test", async (c) => {
252
252
  }
253
253
  });
254
254
 
255
+ // ── Auth / Password ──────────────────────────────────────────────────
256
+
257
+ /** PUT /settings/auth/password — change the access password (token) */
258
+ settingsRoutes.put("/auth/password", async (c) => {
259
+ try {
260
+ const { password, confirm } = await c.req.json<{ password: string; confirm: string }>();
261
+ if (typeof password !== "string" || !password.trim()) {
262
+ return c.json(err("Password is required"), 400);
263
+ }
264
+ if (password !== confirm) {
265
+ return c.json(err("Passwords do not match"), 400);
266
+ }
267
+ const trimmed = password.trim();
268
+ if (trimmed.length < 4) {
269
+ return c.json(err("Password must be at least 4 characters"), 400);
270
+ }
271
+
272
+ const auth = configService.get("auth");
273
+ configService.set("auth", { ...auth, token: trimmed });
274
+ configService.save();
275
+
276
+ return c.json(ok({ token: trimmed }));
277
+ } catch (e) {
278
+ return c.json(err((e as Error).message), 400);
279
+ }
280
+ });
281
+
255
282
  // ── Proxy ────────────────────────────────────────────────────────────
256
283
 
257
284
  /** GET /settings/proxy — proxy status */
@@ -218,8 +218,14 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
218
218
  continue;
219
219
  }
220
220
 
221
- // System events → transition connecting → thinking
221
+ // System events → transition connecting → thinking, forward compact events
222
222
  if (evType === "system") {
223
+ const sub = (ev as any).subtype;
224
+ if (sub === "compacting") {
225
+ broadcast(sessionId, { type: "compact_status", status: "compacting" });
226
+ } else if (sub === "compact_done") {
227
+ broadcast(sessionId, { type: "compact_status", status: "done" });
228
+ }
223
229
  if (!firstEventReceived) {
224
230
  if (heartbeat) clearInterval(heartbeat);
225
231
  setPhase(sessionId, "thinking");
@@ -21,6 +21,7 @@ interface HeartbeatMsg extends WsMessage {
21
21
  availableVersion: string | null;
22
22
  serverPid: number | null;
23
23
  uptime: number;
24
+ deviceName?: string;
24
25
  }
25
26
 
26
27
  interface StateChangeMsg extends WsMessage {
@@ -354,6 +354,7 @@ export async function sendHeartbeat(tunnelUrl: string): Promise<boolean> {
354
354
  secret_key: device.secret_key,
355
355
  tunnel_url: tunnelUrl,
356
356
  status: "online",
357
+ name: device.name,
357
358
  }),
358
359
  });
359
360
  return res.ok;
@@ -424,6 +424,14 @@ export function getPinnedSessionIds(): Set<string> {
424
424
  return new Set(rows.map((r) => r.session_id));
425
425
  }
426
426
 
427
+ export function deleteSessionMapping(ppmId: string): void {
428
+ getDb().query("DELETE FROM session_map WHERE ppm_id = ?").run(ppmId);
429
+ }
430
+
431
+ export function deleteSessionTitle(sessionId: string): void {
432
+ getDb().query("DELETE FROM session_titles WHERE session_id = ?").run(sessionId);
433
+ }
434
+
427
435
  // ---------------------------------------------------------------------------
428
436
  // Push subscription helpers
429
437
  // ---------------------------------------------------------------------------
@@ -378,17 +378,33 @@ function adoptTunnel(): boolean {
378
378
  const status = readStatus();
379
379
  const pid = status.tunnelPid as number;
380
380
  const url = status.shareUrl as string;
381
- if (!pid || !url) return false;
381
+ if (!pid || !url) {
382
+ log("DEBUG", `adoptTunnel: missing tunnelPid(${pid}) or shareUrl(${url}) in status`);
383
+ return false;
384
+ }
382
385
  process.kill(pid, 0); // throws if process is dead
383
386
  adoptedTunnelPid = pid;
384
387
  tunnelUrl = url;
385
388
  log("INFO", `Adopted existing tunnel (PID: ${pid}, URL: ${url})`);
386
389
  return true;
387
- } catch {
390
+ } catch (e) {
391
+ log("WARN", `adoptTunnel: tunnel PID ${(readStatus().tunnelPid)} unreachable: ${e}`);
388
392
  return false;
389
393
  }
390
394
  }
391
395
 
396
+ /** Kill stale tunnel PID from status.json (cleanup after failed adoption) */
397
+ function killStaleTunnel() {
398
+ try {
399
+ const status = readStatus();
400
+ const pid = status.tunnelPid as number;
401
+ if (!pid) return;
402
+ try { process.kill(pid, "SIGTERM"); } catch {}
403
+ log("INFO", `Killed stale tunnel (PID: ${pid})`);
404
+ } catch {}
405
+ updateStatus({ tunnelPid: null, shareUrl: null });
406
+ }
407
+
392
408
  /** Spawn new supervisor from updated code, wait for it to be healthy, then exit */
393
409
  async function selfReplace(): Promise<{ success: boolean; error?: string }> {
394
410
  log("INFO", "Starting self-replace for upgrade");
@@ -490,6 +506,8 @@ async function connectCloud(opts: { port: number }, serverArgs: string[], logFd:
490
506
  secretKey: device.secret_key,
491
507
  heartbeatFn: () => {
492
508
  const status = readStatus();
509
+ // Re-read device file each heartbeat to pick up name changes
510
+ const currentDevice = getCloudDevice();
493
511
  return {
494
512
  type: "heartbeat" as const,
495
513
  tunnelUrl,
@@ -499,6 +517,7 @@ async function connectCloud(opts: { port: number }, serverArgs: string[], logFd:
499
517
  availableVersion: (status.availableVersion as string) || null,
500
518
  serverPid: serverChild?.pid ?? null,
501
519
  uptime: Math.floor((Date.now() - startTime) / 1000),
520
+ deviceName: currentDevice?.name ?? device.name,
502
521
  timestamp: new Date().toISOString(),
503
522
  };
504
523
  },
@@ -706,6 +725,7 @@ export async function runSupervisor(opts: {
706
725
  startTunnelProbe(opts.port);
707
726
  // Try adopting tunnel kept alive from previous upgrade; spawn new if dead
708
727
  if (!adoptTunnel()) {
728
+ killStaleTunnel(); // kill orphaned tunnel before spawning new one
709
729
  promises.push(spawnTunnel(opts.port));
710
730
  }
711
731
  }
package/src/types/api.ts CHANGED
@@ -44,4 +44,5 @@ export type ChatWsServerMessage =
44
44
  | { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: { requestId: string; tool: string; input: unknown } | null; sessionTitle: string | null }
45
45
  | { type: "turn_events"; events: unknown[] }
46
46
  | { type: "title_updated"; title: string }
47
+ | { type: "compact_status"; status: "compacting" | "done" }
47
48
  | { type: "ping" };
package/src/types/chat.ts CHANGED
@@ -29,6 +29,8 @@ export interface AIProvider {
29
29
  listSessionsByDir?(dir: string): Promise<SessionInfo[]>;
30
30
  ensureProjectPath?(sessionId: string, path: string): void;
31
31
  setForkSource?(sessionId: string, sourceSessionId: string): void;
32
+ forkAtMessage?(sessionId: string, messageId: string, opts?: { title?: string; dir?: string }): Promise<{ sessionId: string }>;
33
+ markAsResumed?(sessionId: string): void;
32
34
  isAvailable?(): Promise<boolean>;
33
35
  listModels?(): Promise<ModelOption[]>;
34
36
  }
package/src/web/app.tsx CHANGED
@@ -37,6 +37,7 @@ type AuthState = "checking" | "authenticated" | "unauthenticated";
37
37
 
38
38
  export function App() {
39
39
  const [authState, setAuthState] = useState<AuthState>("checking");
40
+ const [upgradeBannerVisible, setUpgradeBannerVisible] = useState(false);
40
41
  const [drawerOpen, setDrawerOpen] = useState(false);
41
42
  const [drawerTab, setDrawerTab] = useState<"explorer" | "git" | "settings" | undefined>();
42
43
  const [projectSheetOpen, setProjectSheetOpen] = useState(false);
@@ -229,11 +230,11 @@ export function App() {
229
230
  <TooltipProvider>
230
231
  <div className="h-dvh flex flex-col bg-background text-foreground overflow-hidden relative">
231
232
  {/* Upgrade banner — shown when new version available */}
232
- <UpgradeBanner />
233
+ <UpgradeBanner onVisibilityChange={setUpgradeBannerVisible} />
233
234
 
234
235
  {/* Mobile device name badge — floating top-left */}
235
236
  {deviceName && (
236
- <div className="md:hidden fixed top-0 left-0 z-50 px-2 py-0.5 bg-primary/80 text-primary-foreground text-[10px] font-medium rounded-br">
237
+ <div className={cn("md:hidden fixed left-0 z-50 px-2 py-0.5 bg-primary/80 text-primary-foreground text-[10px] font-medium rounded-br transition-[top]", upgradeBannerVisible ? "top-7" : "top-0")}>
237
238
  {deviceName}
238
239
  </div>
239
240
  )}
@@ -16,6 +16,7 @@ interface ChatHistoryBarProps {
16
16
  projectName: string;
17
17
  usageInfo: UsageInfo;
18
18
  contextWindowPct?: number | null;
19
+ compactStatus?: "compacting" | null;
19
20
  usageLoading?: boolean;
20
21
  refreshUsage?: () => void;
21
22
  lastFetchedAt?: string | null;
@@ -79,7 +80,7 @@ function DebugCopyButton({ sessionId, projectName }: { sessionId: string; projec
79
80
  }
80
81
 
81
82
  export function ChatHistoryBar({
82
- projectName, usageInfo, contextWindowPct, usageLoading, refreshUsage, lastFetchedAt,
83
+ projectName, usageInfo, contextWindowPct, compactStatus, usageLoading, refreshUsage, lastFetchedAt,
83
84
  sessionId, providerId, onSelectSession, onBugReport, isConnected, onReconnect,
84
85
  }: ChatHistoryBarProps) {
85
86
  const [activePanel, setActivePanel] = useState<PanelType>(null);
@@ -240,14 +241,27 @@ export function ChatHistoryBar({
240
241
  <span className={pctColor(contextWindowPct)}>Ctx:{contextWindowPct}%</span>
241
242
  </>
242
243
  )}
244
+ {compactStatus === "compacting" && (
245
+ <>
246
+ <span className="text-text-subtle">·</span>
247
+ <span className="text-blue-400 animate-pulse">compacting...</span>
248
+ </>
249
+ )}
243
250
  </button>
244
251
  ) : (
245
- contextWindowPct != null && (
246
- <span className={`flex items-center gap-1 px-1.5 py-0.5 text-[11px] font-medium tabular-nums ${pctColor(contextWindowPct)}`}>
247
- <Activity className="size-3" />
248
- <span>Ctx:{contextWindowPct}%</span>
249
- </span>
250
- )
252
+ <>
253
+ {contextWindowPct != null && (
254
+ <span className={`flex items-center gap-1 px-1.5 py-0.5 text-[11px] font-medium tabular-nums ${pctColor(contextWindowPct)}`}>
255
+ <Activity className="size-3" />
256
+ <span>Ctx:{contextWindowPct}%</span>
257
+ </span>
258
+ )}
259
+ {compactStatus === "compacting" && (
260
+ <span className="text-[11px] px-1.5 py-0.5 text-blue-400 animate-pulse">
261
+ compacting...
262
+ </span>
263
+ )}
264
+ </>
251
265
  )}
252
266
 
253
267
  {/* Spacer */}
@@ -89,6 +89,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
89
89
  connectingElapsed,
90
90
  pendingApproval,
91
91
  contextWindowPct,
92
+ compactStatus,
92
93
  sessionTitle,
93
94
  migratedSessionId,
94
95
  sendMessage,
@@ -162,12 +163,13 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
162
163
  }, [tabId, updateTab]);
163
164
 
164
165
  /** Fork current session and open new tab with the forked session, resending userMessage */
165
- const handleFork = useCallback(async (userMessage: string) => {
166
+ const handleFork = useCallback(async (userMessage: string, messageId?: string) => {
166
167
  if (!sessionId || !projectName) return;
167
168
  try {
168
169
  const { api, projectUrl } = await import("@/lib/api-client");
169
170
  const forked = await api.post<{ id: string; forkedFrom: string }>(
170
171
  `${projectUrl(projectName)}/chat/sessions/${sessionId}/fork?providerId=${providerId}`,
172
+ { messageId },
171
173
  );
172
174
  // Open new chat tab with forked session — it will send userMessage on connect
173
175
  useTabStore.getState().openTab({
@@ -350,6 +352,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
350
352
  projectName={projectName}
351
353
  usageInfo={usageInfo}
352
354
  contextWindowPct={contextWindowPct}
355
+ compactStatus={compactStatus}
353
356
  usageLoading={usageLoading}
354
357
  refreshUsage={refreshUsage}
355
358
  lastFetchedAt={lastFetchedAt}
@@ -43,7 +43,7 @@ interface MessageListProps {
43
43
  connectingElapsed?: number;
44
44
  projectName?: string;
45
45
  /** Called when user clicks Fork/Rewind — opens new forked chat tab */
46
- onFork?: (userMessage: string) => void;
46
+ onFork?: (userMessage: string, messageId?: string) => void;
47
47
  }
48
48
 
49
49
  export function MessageList({
@@ -96,7 +96,7 @@ export function MessageList({
96
96
  message={msg}
97
97
  isStreaming={isStreaming && msg.id.startsWith("streaming-")}
98
98
  projectName={projectName}
99
- onFork={msg.role === "user" && onFork ? () => onFork(msg.content) : undefined}
99
+ onFork={msg.role === "user" && onFork ? () => onFork(msg.content, msg.id) : undefined}
100
100
  />
101
101
  ))}
102
102
 
@@ -47,6 +47,7 @@ export function SessionPicker({
47
47
 
48
48
  const handleDelete = async (e: React.MouseEvent, session: SessionInfo) => {
49
49
  e.stopPropagation();
50
+ if (!window.confirm("Delete this session? This cannot be undone.")) return;
50
51
  try {
51
52
  if (!projectName) return;
52
53
  await api.del(