@hellcoder/companion 0.104.2-preview.20260618073757.2ec188a → 0.104.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AgentsPage-D-hzqgey.js → AgentsPage-H1WOCJ_U.js} +1 -1
- package/dist/assets/{CronManager-DwUEHVgT.js → CronManager-Cqnqj1KO.js} +1 -1
- package/dist/assets/{IntegrationsPage-Bw1U5mwv.js → IntegrationsPage-Nd6In9Av.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-BX9mUmcG.js → LinearOAuthSettingsPage-D7RQfyFI.js} +1 -1
- package/dist/assets/{LinearSettingsPage-xtk6Iyr7.js → LinearSettingsPage-CGVs5jG6.js} +1 -1
- package/dist/assets/{Playground-D8ylR37H.js → Playground-M3TI9s_p.js} +2 -2
- package/dist/assets/{PromptsPage-B0d6-WKV.js → PromptsPage-QXhg1mXT.js} +1 -1
- package/dist/assets/{RunsPage-DdLaWyao.js → RunsPage-C1XiccgQ.js} +1 -1
- package/dist/assets/{SandboxManager---T4q5LN.js → SandboxManager-DYmKXcTM.js} +1 -1
- package/dist/assets/{SettingsPage-DshSGWUD.js → SettingsPage-DaJfAHDI.js} +1 -1
- package/dist/assets/{TailscalePage-DI91T6d8.js → TailscalePage-CmH09EMI.js} +1 -1
- package/dist/assets/{index-DVj8r0rF.js → index-CKx0s4-A.js} +14 -14
- package/dist/assets/{sw-register-DMu12Jt3.js → sw-register-DkUWP5b0.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/session-orchestrator.test.ts +75 -0
- package/server/session-orchestrator.ts +50 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as u}from"./index-
|
|
1
|
+
import{_ as u}from"./index-CKx0s4-A.js";function w(r={}){const{immediate:i=!1,onNeedReload:d,onNeedRefresh:_,onOfflineReady:s,onRegistered:n,onRegisteredSW:o,onRegisterError:t}=r;let a,c;const l=async(e=!0)=>{await c};async function f(){if("serviceWorker"in navigator){if(a=await u(async()=>{const{Workbox:e}=await import("./workbox-window.prod.es5-BBnX5xw4.js");return{Workbox:e}},[]).then(({Workbox:e})=>new e("/sw.js",{scope:"/",type:"classic"})).catch(e=>{t==null||t(e)}),!a)return;a.addEventListener("activated",e=>{(e.isUpdate||e.isExternal)&&(d?d():window.location.reload())}),a.addEventListener("installed",e=>{e.isUpdate||s==null||s()}),a.register({immediate:i}).then(e=>{o?o("/sw.js",e):n==null||n(e)}).catch(e=>{t==null||t(e)})}}return c=f(),l}w({onRegisteredSW(r,i){i&&setInterval(()=>{i.update()},3600*1e3)},onOfflineReady(){console.log("[SW] Offline-ready: all assets precached")}});
|
package/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-title" content="Companion ME" />
|
|
12
12
|
<meta name="theme-color" content="#d97757" />
|
|
13
13
|
<title>The Companion — Moritz Edition</title>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-CKx0s4-A.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-psWeuMrk.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body class="bg-cc-bg text-cc-fg">
|
package/dist/sw.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
try{self["workbox:core:7.4.0"]&&_()}catch{}const x=(a,...e)=>{let t=a;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},N=x;class l extends Error{constructor(e,t){const s=N(e,t);super(s),this.name=e,this.details=t}}const E=new Set,f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},b=a=>[f.prefix,a,f.suffix].filter(e=>e&&e.length>0).join("-"),O=a=>{for(const e of Object.keys(f))a(e)},C={updateDetails:a=>{O(e=>{typeof a[e]=="string"&&(f[e]=a[e])})},getGoogleAnalyticsName:a=>a||b(f.googleAnalytics),getPrecacheName:a=>a||b(f.precache),getPrefix:()=>f.prefix,getRuntimeName:a=>a||b(f.runtime),getSuffix:()=>f.suffix};function P(a,e){const t=new URL(a);for(const s of e)t.searchParams.delete(s);return t.href}async function I(a,e,t,s){const n=P(e.url,t);if(e.url===n)return a.match(e,s);const r=Object.assign(Object.assign({},s),{ignoreSearch:!0}),c=await a.keys(e,r);for(const i of c){const o=P(i.url,t);if(n===o)return a.match(i,s)}}let y;function M(){if(y===void 0){const a=new Response("");if("body"in a)try{new Response(a.body),y=!0}catch{y=!1}y=!1}return y}class D{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}async function S(){for(const a of E)await a()}const W=a=>new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),"");function A(a){return new Promise(e=>setTimeout(e,a))}function K(a,e){const t=e();return a.waitUntil(t),t}async function q(a,e){let t=null;if(a.url&&(t=new URL(a.url).origin),t!==self.location.origin)throw new l("cross-origin-copy-response",{origin:t});const s=a.clone(),r={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},c=M()?s.body:await s.blob();return new Response(c,r)}function j(){self.addEventListener("activate",()=>self.clients.claim())}try{self["workbox:precaching:7.4.0"]&&_()}catch{}const H="__WB_REVISION__";function F(a){if(!a)throw new l("add-to-cache-list-unexpected-type",{entry:a});if(typeof a=="string"){const r=new URL(a,location.href);return{cacheKey:r.href,url:r.href}}const{revision:e,url:t}=a;if(!t)throw new l("add-to-cache-list-unexpected-type",{entry:a});if(!e){const r=new URL(t,location.href);return{cacheKey:r.href,url:r.href}}const s=new URL(t,location.href),n=new URL(t,location.href);return s.searchParams.set(H,e),{cacheKey:s.href,url:n.href}}class B{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const n=t.originalRequest.url;s?this.notUpdatedURLs.push(n):this.updatedURLs.push(n)}return s}}}class ${constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const n=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return n?new Request(n,{headers:t.headers}):t},this._precacheController=e}}try{self["workbox:strategies:7.4.0"]&&_()}catch{}function m(a){return typeof a=="string"?new Request(a):a}class G{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new D,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=m(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const c=await t.preloadResponse;if(c)return c}const n=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const c of this.iterateCallbacks("requestWillFetch"))s=await c({request:s.clone(),event:t})}catch(c){if(c instanceof Error)throw new l("plugin-error-request-will-fetch",{thrownErrorMessage:c.message})}const r=s.clone();try{let c;c=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const i of this.iterateCallbacks("fetchDidSucceed"))c=await i({event:t,request:r,response:c});return c}catch(c){throw n&&await this.runCallbacks("fetchDidFail",{error:c,event:t,originalRequest:n.clone(),request:r.clone()}),c}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=m(e);let s;const{cacheName:n,matchOptions:r}=this._strategy,c=await this.getCacheKey(t,"read"),i=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(c,i);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:n,matchOptions:r,cachedResponse:s,request:c,event:this.event})||void 0;return s}async cachePut(e,t){const s=m(e);await A(0);const n=await this.getCacheKey(s,"write");if(!t)throw new l("cache-put-with-no-response",{url:W(n.url)});const r=await this._ensureResponseSafeToCache(t);if(!r)return!1;const{cacheName:c,matchOptions:i}=this._strategy,o=await self.caches.open(c),h=this.hasCallback("cacheDidUpdate"),p=h?await I(o,n.clone(),["__WB_REVISION__"],i):null;try{await o.put(n,h?r.clone():r)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await S(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:c,oldResponse:p,newResponse:r.clone(),request:n,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let n=e;for(const r of this.iterateCallbacks("cacheKeyWillBeUsed"))n=m(await r({mode:t,request:n,event:this.event,params:this.params}));this._cacheKeys[s]=n}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield r=>{const c=Object.assign(Object.assign({},r),{state:s});return t[e](c)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(n=>n.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const n of this.iterateCallbacks("cacheWillUpdate"))if(t=await n({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class V{constructor(e={}){this.cacheName=C.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,n="params"in e?e.params:void 0,r=new G(this,{event:t,request:s,params:n}),c=this._getResponse(r,s,t),i=this._awaitComplete(c,r,s,t);return[c,i]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let n;try{if(n=await this._handle(t,e),!n||n.type==="error")throw new l("no-response",{url:t.url})}catch(r){if(r instanceof Error){for(const c of e.iterateCallbacks("handlerDidError"))if(n=await c({error:r,event:s,request:t}),n)break}if(!n)throw r}for(const r of e.iterateCallbacks("handlerWillRespond"))n=await r({event:s,request:t,response:n});return n}async _awaitComplete(e,t,s,n){let r,c;try{r=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await t.doneWaiting()}catch(i){i instanceof Error&&(c=i)}if(await t.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:c}),t.destroy(),c)throw c}}class d extends V{constructor(e={}){e.cacheName=C.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(d.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const n=t.params||{};if(this._fallbackToNetwork){const r=n.integrity,c=e.integrity,i=!c||c===r;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?c||r:void 0})),r&&i&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new l("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new l("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,n]of this.plugins.entries())n!==d.copyRedirectedCacheableResponsesPlugin&&(n===d.defaultPrecacheCacheabilityPlugin&&(e=s),n.cacheWillUpdate&&t++);t===0?this.plugins.push(d.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}d.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:a}){return!a||a.status>=400?null:a}};d.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:a}){return a.redirected?await q(a):a}};class Q{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new d({cacheName:C.getPrecacheName(e),plugins:[...t,new $({precacheController:this})],fallbackToNetwork:s}),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),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:n,url:r}=F(s),c=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(r)&&this._urlsToCacheKeys.get(r)!==n)throw new l("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(r),secondEntry:n});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(n)&&this._cacheKeysToIntegrities.get(n)!==s.integrity)throw new l("add-to-cache-list-conflicting-integrities",{url:r});this._cacheKeysToIntegrities.set(n,s.integrity)}if(this._urlsToCacheKeys.set(r,n),this._urlsToCacheModes.set(r,c),t.length>0){const i=`Workbox is precaching URLs without revision info: ${t.join(", ")}
|
|
2
|
-
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(i)}}}install(e){return K(e,async()=>{const t=new B;this.strategy.plugins.push(t);for(const[r,c]of this._urlsToCacheKeys){const i=this._cacheKeysToIntegrities.get(c),o=this._urlsToCacheModes.get(r),h=new Request(r,{integrity:i,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:c},request:h,event:e}))}const{updatedURLs:s,notUpdatedURLs:n}=t;return{updatedURLs:s,notUpdatedURLs:n}})}activate(e){return K(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(const c of s)n.has(c.url)||(await t.delete(c),r.push(c.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let U;const L=()=>(U||(U=new Q),U);try{self["workbox:routing:7.4.0"]&&_()}catch{}const v="GET",R=a=>a&&typeof a=="object"?a:{handle:a};class g{constructor(e,t,s=v){this.handler=R(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=R(e)}}class z extends g{constructor(e,t,s){const n=({url:r})=>{const c=e.exec(r.href);if(c&&!(r.origin!==location.origin&&c.index!==0))return c.slice(1)};super(n,t,s)}}class J{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(n=>{typeof n=="string"&&(n=[n]);const r=new Request(...n);return this.handleRequest({request:r,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:c}=this.findMatchingRoute({event:t,request:e,sameOrigin:n,url:s});let i=c&&c.handler;const o=e.method;if(!i&&this._defaultHandlerMap.has(o)&&(i=this._defaultHandlerMap.get(o)),!i)return;let h;try{h=i.handle({url:s,request:e,event:t,params:r})}catch(u){h=Promise.reject(u)}const p=c&&c.catchHandler;return h instanceof Promise&&(this._catchHandler||p)&&(h=h.catch(async u=>{if(p)try{return await p.handle({url:s,request:e,event:t,params:r})}catch(k){k instanceof Error&&(u=k)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),h}findMatchingRoute({url:e,sameOrigin:t,request:s,event:n}){const r=this._routes.get(s.method)||[];for(const c of r){let i;const o=c.match({url:e,sameOrigin:t,request:s,event:n});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:c,params:i}}return{}}setDefaultHandler(e,t=v){this._defaultHandlerMap.set(t,R(e))}setCatchHandler(e){this._catchHandler=R(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 l("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}let w;const X=()=>(w||(w=new J,w.addFetchListener(),w.addCacheListener()),w);function T(a,e,t){let s;if(typeof a=="string"){const r=new URL(a,location.href),c=({url:i})=>i.href===r.href;s=new g(c,e,t)}else if(a instanceof RegExp)s=new z(a,e,t);else if(typeof a=="function")s=new g(a,e,t);else if(a instanceof g)s=a;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return X().registerRoute(s),s}function Y(a,e=[]){for(const t of[...a.searchParams.keys()])e.some(s=>s.test(t))&&a.searchParams.delete(t);return a}function*Z(a,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:n}={}){const r=new URL(a,location.href);r.hash="",yield r.href;const c=Y(r,e);if(yield c.href,t&&c.pathname.endsWith("/")){const i=new URL(c.href);i.pathname+=t,yield i.href}if(s){const i=new URL(c.href);i.pathname+=".html",yield i.href}if(n){const i=n({url:r});for(const o of i)yield o.href}}class ee extends g{constructor(e,t){const s=({request:n})=>{const r=e.getURLsToCacheKeys();for(const c of Z(n.url,t)){const i=r.get(c);if(i){const o=e.getIntegrityForCacheKey(i);return{cacheKey:i,integrity:o}}}};super(s,e.strategy)}}function te(a){const e=L(),t=new ee(e,a);T(t)}const se="-precache-",ae=async(a,e=se)=>{const s=(await self.caches.keys()).filter(n=>n.includes(e)&&n.includes(self.registration.scope)&&n!==a);return await Promise.all(s.map(n=>self.caches.delete(n))),s};function ne(){self.addEventListener("activate",(a=>{const e=C.getPrecacheName();a.waitUntil(ae(e).then(t=>{}))}))}function re(a){return L().createHandlerBoundToURL(a)}function ce(a){L().precache(a)}function ie(a,e){ce(a),te(e)}class oe extends g{constructor(e,{allowlist:t=[/./],denylist:s=[]}={}){super(n=>this._match(n),e),this._allowlist=t,this._denylist=s}_match({url:e,request:t}){if(t&&t.mode!=="navigate")return!1;const s=e.pathname+e.search;for(const n of this._denylist)if(n.test(s))return!1;return!!this._allowlist.some(n=>n.test(s))}}self.skipWaiting();j();ie([{"revision":"fab346dacb0b9b9af12554e9a59f004d","url":"logo.svg"},{"revision":"623840baec64a3438663496bb1050d30","url":"logo-docker.svg"},{"revision":"c5c8611b432190ec28522f1a2dfcbb7f","url":"logo-codex.svg"},{"revision":"
|
|
2
|
+
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(i)}}}install(e){return K(e,async()=>{const t=new B;this.strategy.plugins.push(t);for(const[r,c]of this._urlsToCacheKeys){const i=this._cacheKeysToIntegrities.get(c),o=this._urlsToCacheModes.get(r),h=new Request(r,{integrity:i,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:c},request:h,event:e}))}const{updatedURLs:s,notUpdatedURLs:n}=t;return{updatedURLs:s,notUpdatedURLs:n}})}activate(e){return K(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(const c of s)n.has(c.url)||(await t.delete(c),r.push(c.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let U;const L=()=>(U||(U=new Q),U);try{self["workbox:routing:7.4.0"]&&_()}catch{}const v="GET",R=a=>a&&typeof a=="object"?a:{handle:a};class g{constructor(e,t,s=v){this.handler=R(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=R(e)}}class z extends g{constructor(e,t,s){const n=({url:r})=>{const c=e.exec(r.href);if(c&&!(r.origin!==location.origin&&c.index!==0))return c.slice(1)};super(n,t,s)}}class J{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(n=>{typeof n=="string"&&(n=[n]);const r=new Request(...n);return this.handleRequest({request:r,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:c}=this.findMatchingRoute({event:t,request:e,sameOrigin:n,url:s});let i=c&&c.handler;const o=e.method;if(!i&&this._defaultHandlerMap.has(o)&&(i=this._defaultHandlerMap.get(o)),!i)return;let h;try{h=i.handle({url:s,request:e,event:t,params:r})}catch(u){h=Promise.reject(u)}const p=c&&c.catchHandler;return h instanceof Promise&&(this._catchHandler||p)&&(h=h.catch(async u=>{if(p)try{return await p.handle({url:s,request:e,event:t,params:r})}catch(k){k instanceof Error&&(u=k)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),h}findMatchingRoute({url:e,sameOrigin:t,request:s,event:n}){const r=this._routes.get(s.method)||[];for(const c of r){let i;const o=c.match({url:e,sameOrigin:t,request:s,event:n});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:c,params:i}}return{}}setDefaultHandler(e,t=v){this._defaultHandlerMap.set(t,R(e))}setCatchHandler(e){this._catchHandler=R(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 l("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}let w;const X=()=>(w||(w=new J,w.addFetchListener(),w.addCacheListener()),w);function T(a,e,t){let s;if(typeof a=="string"){const r=new URL(a,location.href),c=({url:i})=>i.href===r.href;s=new g(c,e,t)}else if(a instanceof RegExp)s=new z(a,e,t);else if(typeof a=="function")s=new g(a,e,t);else if(a instanceof g)s=a;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return X().registerRoute(s),s}function Y(a,e=[]){for(const t of[...a.searchParams.keys()])e.some(s=>s.test(t))&&a.searchParams.delete(t);return a}function*Z(a,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:n}={}){const r=new URL(a,location.href);r.hash="",yield r.href;const c=Y(r,e);if(yield c.href,t&&c.pathname.endsWith("/")){const i=new URL(c.href);i.pathname+=t,yield i.href}if(s){const i=new URL(c.href);i.pathname+=".html",yield i.href}if(n){const i=n({url:r});for(const o of i)yield o.href}}class ee extends g{constructor(e,t){const s=({request:n})=>{const r=e.getURLsToCacheKeys();for(const c of Z(n.url,t)){const i=r.get(c);if(i){const o=e.getIntegrityForCacheKey(i);return{cacheKey:i,integrity:o}}}};super(s,e.strategy)}}function te(a){const e=L(),t=new ee(e,a);T(t)}const se="-precache-",ae=async(a,e=se)=>{const s=(await self.caches.keys()).filter(n=>n.includes(e)&&n.includes(self.registration.scope)&&n!==a);return await Promise.all(s.map(n=>self.caches.delete(n))),s};function ne(){self.addEventListener("activate",(a=>{const e=C.getPrecacheName();a.waitUntil(ae(e).then(t=>{}))}))}function re(a){return L().createHandlerBoundToURL(a)}function ce(a){L().precache(a)}function ie(a,e){ce(a),te(e)}class oe extends g{constructor(e,{allowlist:t=[/./],denylist:s=[]}={}){super(n=>this._match(n),e),this._allowlist=t,this._denylist=s}_match({url:e,request:t}){if(t&&t.mode!=="navigate")return!1;const s=e.pathname+e.search;for(const n of this._denylist)if(n.test(s))return!1;return!!this._allowlist.some(n=>n.test(s))}}self.skipWaiting();j();ie([{"revision":"fab346dacb0b9b9af12554e9a59f004d","url":"logo.svg"},{"revision":"623840baec64a3438663496bb1050d30","url":"logo-docker.svg"},{"revision":"c5c8611b432190ec28522f1a2dfcbb7f","url":"logo-codex.svg"},{"revision":"db0bd8f0032bf19eae29a0b176ceb4cc","url":"index.html"},{"revision":"1421e9c6cad8218ef5689b85034d3a95","url":"icon-512.png"},{"revision":"b96da5a0b6636012517f54b0e69c304d","url":"icon-192.png"},{"revision":"c64ca8ae8596d4ffe13d0f591beb2274","url":"favicon.svg"},{"revision":"6481face04bd08e6f0bfc2a633840b57","url":"apple-touch-icon.png"},{"revision":"83304194a3c8be2b5b61242eeb1c1046","url":"fonts/MesloLGSNerdFontMono-Regular.woff2"},{"revision":"60fafe18cbcb717c51cdabf87f9490f0","url":"fonts/MesloLGSNerdFontMono-Bold.woff2"},{"revision":null,"url":"assets/workbox-window.prod.es5-BBnX5xw4.js"},{"revision":null,"url":"assets/sw-register-DkUWP5b0.js"},{"revision":null,"url":"assets/index-psWeuMrk.css"},{"revision":null,"url":"assets/index-CKx0s4-A.js"},{"revision":null,"url":"assets/TailscalePage-CmH09EMI.js"},{"revision":null,"url":"assets/SettingsPage-DaJfAHDI.js"},{"revision":null,"url":"assets/SandboxManager-DYmKXcTM.js"},{"revision":null,"url":"assets/RunsPage-C1XiccgQ.js"},{"revision":null,"url":"assets/PromptsPage-QXhg1mXT.js"},{"revision":null,"url":"assets/Playground-M3TI9s_p.js"},{"revision":null,"url":"assets/LinearSettingsPage-CGVs5jG6.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-D7RQfyFI.js"},{"revision":null,"url":"assets/IntegrationsPage-Nd6In9Av.js"},{"revision":null,"url":"assets/CronManager-Cqnqj1KO.js"},{"revision":null,"url":"assets/AgentsPage-H1WOCJ_U.js"}]);ne();T(new oe(re("index.html"),{denylist:[/^\/api/,/^\/ws/]}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hellcoder/companion",
|
|
3
|
-
"version": "0.104.2
|
|
3
|
+
"version": "0.104.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web UI for launching and interacting with Claude Code agents — Moritz Edition (fork of the-companion)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -480,6 +480,81 @@ describe("SessionOrchestrator", () => {
|
|
|
480
480
|
|
|
481
481
|
vi.useRealTimers();
|
|
482
482
|
});
|
|
483
|
+
|
|
484
|
+
it("pauses a session that keeps crashing even though each relaunch briefly recovers", async () => {
|
|
485
|
+
// The crux of the swallowed-turn loop: a CLI that crashes every minute or
|
|
486
|
+
// two stays connected just long enough (> HEALTHY_UPTIME_MS) for
|
|
487
|
+
// scheduleHealthyReset to wipe the per-attempt budget every cycle. The
|
|
488
|
+
// per-attempt cap therefore NEVER trips, so under the old code the session
|
|
489
|
+
// relaunched forever and the user only saw an endless "reconnecting"
|
|
490
|
+
// banner. The rolling-window cap must accumulate across these fake
|
|
491
|
+
// recoveries and eventually pause auto-restart with a visible message.
|
|
492
|
+
vi.useFakeTimers();
|
|
493
|
+
deps.launcher.getSession.mockReturnValue({ archived: false, state: "exited", pid: undefined } as any);
|
|
494
|
+
deps.launcher.relaunch.mockResolvedValue({ ok: true });
|
|
495
|
+
orchestrator.initialize();
|
|
496
|
+
|
|
497
|
+
// 7 crash→relaunch→"recover for 61s" cycles. Each 61s recovery fires the
|
|
498
|
+
// healthy-reset (clearing the per-attempt count), so only the rolling cap
|
|
499
|
+
// can stop the loop.
|
|
500
|
+
for (let i = 0; i < 7; i++) {
|
|
501
|
+
deps.wsBridge.isCliConnected.mockReturnValue(false);
|
|
502
|
+
companionBus.emit("session:exited", { sessionId: "s1", exitCode: 143 });
|
|
503
|
+
await vi.advanceTimersByTimeAsync(45_000); // grace + backoff + cooldown → relaunch fires
|
|
504
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
505
|
+
// Session comes back and stays up past HEALTHY_UPTIME_MS, so the
|
|
506
|
+
// per-attempt budget is restored — but the rolling history is not.
|
|
507
|
+
deps.wsBridge.isCliConnected.mockReturnValue(true);
|
|
508
|
+
await vi.advanceTimersByTimeAsync(61_000);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// It relaunched up to the rolling cap, then stopped — NOT all 7 times.
|
|
512
|
+
expect(deps.launcher.relaunch).toHaveBeenCalledTimes(5);
|
|
513
|
+
// ...and surfaced a distinct "keeps crashing / paused" message to the user.
|
|
514
|
+
const errorBroadcasts = deps.wsBridge.broadcastToSession.mock.calls.filter(
|
|
515
|
+
([, msg]: [string, any]) => msg?.type === "error",
|
|
516
|
+
);
|
|
517
|
+
expect(errorBroadcasts.length).toBeGreaterThanOrEqual(1);
|
|
518
|
+
expect(errorBroadcasts[errorBroadcasts.length - 1][1].message).toMatch(/restarted .* times in the last .* minutes/i);
|
|
519
|
+
|
|
520
|
+
vi.useRealTimers();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("a genuine long recovery (history ages out of the window) earns a fresh budget", async () => {
|
|
524
|
+
// The rolling cap must not be a permanent death sentence: if a session
|
|
525
|
+
// recovers and stays healthy *longer than the window*, its old relaunch
|
|
526
|
+
// timestamps age out and a later, unrelated crash streak is not penalized.
|
|
527
|
+
vi.useFakeTimers();
|
|
528
|
+
deps.launcher.getSession.mockReturnValue({ archived: false, state: "exited", pid: undefined } as any);
|
|
529
|
+
deps.launcher.relaunch.mockResolvedValue({ ok: true });
|
|
530
|
+
orchestrator.initialize();
|
|
531
|
+
|
|
532
|
+
// Build up 4 relaunches across brief recoveries (just under the rolling cap).
|
|
533
|
+
for (let i = 0; i < 4; i++) {
|
|
534
|
+
deps.wsBridge.isCliConnected.mockReturnValue(false);
|
|
535
|
+
companionBus.emit("session:exited", { sessionId: "s1", exitCode: 143 });
|
|
536
|
+
await vi.advanceTimersByTimeAsync(45_000);
|
|
537
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
538
|
+
deps.wsBridge.isCliConnected.mockReturnValue(true);
|
|
539
|
+
await vi.advanceTimersByTimeAsync(61_000);
|
|
540
|
+
}
|
|
541
|
+
expect(deps.launcher.relaunch).toHaveBeenCalledTimes(4);
|
|
542
|
+
|
|
543
|
+
// Now it genuinely recovers and stays up well past the 10-minute window,
|
|
544
|
+
// so every prior relaunch timestamp ages out.
|
|
545
|
+
deps.wsBridge.isCliConnected.mockReturnValue(true);
|
|
546
|
+
await vi.advanceTimersByTimeAsync(11 * 60_000);
|
|
547
|
+
|
|
548
|
+
// A fresh crash relaunches normally instead of being blocked by the cap.
|
|
549
|
+
deps.launcher.relaunch.mockClear();
|
|
550
|
+
deps.wsBridge.isCliConnected.mockReturnValue(false);
|
|
551
|
+
companionBus.emit("session:exited", { sessionId: "s1", exitCode: 143 });
|
|
552
|
+
await vi.advanceTimersByTimeAsync(45_000);
|
|
553
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
554
|
+
expect(deps.launcher.relaunch).toHaveBeenCalledTimes(1);
|
|
555
|
+
|
|
556
|
+
vi.useRealTimers();
|
|
557
|
+
});
|
|
483
558
|
});
|
|
484
559
|
|
|
485
560
|
// ── Session Creation ──────────────────────────────────────────────────────
|
|
@@ -45,6 +45,19 @@ const KEEPALIVE_BASE_DELAY_MS = 3_000;
|
|
|
45
45
|
// terminates with a visible "keeps crashing" message instead.
|
|
46
46
|
const HEALTHY_UPTIME_MS = 60_000;
|
|
47
47
|
|
|
48
|
+
// Long-horizon backstop for "slow" crash loops. The per-attempt budget above
|
|
49
|
+
// only catches *fast* re-crashes (a resume that dies before HEALTHY_UPTIME_MS).
|
|
50
|
+
// A session can also crash every minute or two — staying up just long enough
|
|
51
|
+
// each time for scheduleHealthyReset to wipe the per-attempt budget — and so
|
|
52
|
+
// relaunch forever, invisibly, never reaching MAX_AUTO_RELAUNCHES. To stop that,
|
|
53
|
+
// we also count relaunches within a rolling window that healthy-resets do NOT
|
|
54
|
+
// clear; once a session exceeds MAX_RELAUNCHES_PER_WINDOW relaunches inside
|
|
55
|
+
// RELAUNCH_WINDOW_MS, auto-restart is paused with a visible message. Entries
|
|
56
|
+
// age out by time, so a session that genuinely recovers (stays up well past the
|
|
57
|
+
// window) earns a clean slate without any explicit reset.
|
|
58
|
+
const RELAUNCH_WINDOW_MS = 10 * 60_000;
|
|
59
|
+
const MAX_RELAUNCHES_PER_WINDOW = 5;
|
|
60
|
+
|
|
48
61
|
const VSCODE_EDITOR_CONTAINER_PORT = 13337;
|
|
49
62
|
const CODEX_APP_SERVER_CONTAINER_PORT = Number(
|
|
50
63
|
process.env.COMPANION_CODEX_CONTAINER_WS_PORT || "4502",
|
|
@@ -152,6 +165,11 @@ export class SessionOrchestrator {
|
|
|
152
165
|
// for HEALTHY_UPTIME_MS after a relaunch. Cancelled if it re-crashes first, so
|
|
153
166
|
// a crash-loop accumulates toward MAX_AUTO_RELAUNCHES instead of resetting.
|
|
154
167
|
private healthyResetTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
168
|
+
// Timestamps of recent relaunch attempts, used for the rolling-window cap.
|
|
169
|
+
// Unlike autoRelaunchCounts this is NOT cleared by scheduleHealthyReset — a
|
|
170
|
+
// session that briefly recovers between crashes must still accumulate toward
|
|
171
|
+
// the long-horizon cap. Entries are pruned by age (RELAUNCH_WINDOW_MS).
|
|
172
|
+
private relaunchHistory = new Map<string, number[]>();
|
|
155
173
|
|
|
156
174
|
// Idempotency guard for initialize()
|
|
157
175
|
private _initialized = false;
|
|
@@ -748,6 +766,7 @@ export class SessionOrchestrator {
|
|
|
748
766
|
this.wsBridge.closeSession(sessionId);
|
|
749
767
|
this.autoRelaunchCounts.delete(sessionId);
|
|
750
768
|
this.relaunchExhaustedNotified.delete(sessionId);
|
|
769
|
+
this.relaunchHistory.delete(sessionId);
|
|
751
770
|
this.relaunchingSet.delete(sessionId);
|
|
752
771
|
this.intentionalKills.delete(sessionId);
|
|
753
772
|
return { ok: true, worktree: worktreeResult };
|
|
@@ -766,6 +785,7 @@ export class SessionOrchestrator {
|
|
|
766
785
|
clearAutoRelaunchCount(sessionId: string): void {
|
|
767
786
|
this.autoRelaunchCounts.delete(sessionId);
|
|
768
787
|
this.relaunchExhaustedNotified.delete(sessionId);
|
|
788
|
+
this.relaunchHistory.delete(sessionId);
|
|
769
789
|
this.cancelHealthyReset(sessionId);
|
|
770
790
|
}
|
|
771
791
|
|
|
@@ -848,8 +868,38 @@ export class SessionOrchestrator {
|
|
|
848
868
|
return;
|
|
849
869
|
}
|
|
850
870
|
|
|
871
|
+
// Long-horizon cap: even when each relaunch briefly recovers (long enough for
|
|
872
|
+
// scheduleHealthyReset to clear the per-attempt count above), a session that
|
|
873
|
+
// keeps crashing should not relaunch forever. Count relaunches in a rolling
|
|
874
|
+
// window that healthy-resets do not clear, and pause once it's clearly a loop.
|
|
875
|
+
const now = Date.now();
|
|
876
|
+
const recent = (this.relaunchHistory.get(sessionId) ?? []).filter(
|
|
877
|
+
(ts) => now - ts < RELAUNCH_WINDOW_MS,
|
|
878
|
+
);
|
|
879
|
+
if (recent.length >= MAX_RELAUNCHES_PER_WINDOW) {
|
|
880
|
+
metricsCollector.recordRelaunchExhausted();
|
|
881
|
+
log.warn("orchestrator", "Relaunch rolling-window cap reached", {
|
|
882
|
+
sessionId,
|
|
883
|
+
count: recent.length,
|
|
884
|
+
windowMs: RELAUNCH_WINDOW_MS,
|
|
885
|
+
});
|
|
886
|
+
this.wsBridge.broadcastToSession(sessionId, {
|
|
887
|
+
type: "error",
|
|
888
|
+
message:
|
|
889
|
+
`This session has restarted ${recent.length} times in the last ` +
|
|
890
|
+
`${Math.round(RELAUNCH_WINDOW_MS / 60_000)} minutes — it keeps crashing a ` +
|
|
891
|
+
`short while after each resume. Auto-restart is paused so it stops looping. ` +
|
|
892
|
+
`Use Reconnect to try again; if it keeps crashing, edit or remove the last message and resend.`,
|
|
893
|
+
});
|
|
894
|
+
this.relaunchExhaustedNotified.add(sessionId);
|
|
895
|
+
this.relaunchHistory.set(sessionId, recent);
|
|
896
|
+
this.relaunchingSet.delete(sessionId);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
851
900
|
if (freshInfo && freshInfo.state !== "starting") {
|
|
852
901
|
this.autoRelaunchCounts.set(sessionId, count + 1);
|
|
902
|
+
this.relaunchHistory.set(sessionId, [...recent, now]);
|
|
853
903
|
metricsCollector.recordRelaunchAttempted();
|
|
854
904
|
log.info("orchestrator", "Auto-relaunching CLI", { sessionId, attempt: count + 1, maxAttempts: MAX_AUTO_RELAUNCHES });
|
|
855
905
|
const session = this.wsBridge.getSession(sessionId);
|