@hellcoder/companion 0.112.1-preview.20260728132124.bf82019 → 0.113.0
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-D17hofXo.js → AgentsPage-BBi5x0sS.js} +1 -1
- package/dist/assets/{CronManager-YyyPK6hb.js → CronManager-B1s3pGHK.js} +1 -1
- package/dist/assets/{DashboardPage-ukAmlbn5.js → DashboardPage-DFgvPALP.js} +1 -1
- package/dist/assets/{IntegrationsPage-C5rUwiN9.js → IntegrationsPage-ClHcXTNR.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-6OAQZdqq.js → LinearOAuthSettingsPage-aKHb8Kgv.js} +1 -1
- package/dist/assets/{LinearSettingsPage-BQSZm4Bq.js → LinearSettingsPage-DZbV5ES0.js} +1 -1
- package/dist/assets/{Playground-Bq0Tks1d.js → Playground-Z8gfp_Pg.js} +2 -2
- package/dist/assets/{PromptsPage-Bz9Ml49y.js → PromptsPage-BF5QRCM7.js} +1 -1
- package/dist/assets/{RunsPage-CxlLKCM8.js → RunsPage-Bn7SIAEx.js} +1 -1
- package/dist/assets/{SandboxManager-VUkfrmv0.js → SandboxManager-BNIPWLHz.js} +1 -1
- package/dist/assets/{SettingsPage-BL8vCPyR.js → SettingsPage-CFe6_dW9.js} +1 -1
- package/dist/assets/{TailscalePage-DvlPwchP.js → TailscalePage-DPJGhCUO.js} +1 -1
- package/dist/assets/{index-S0nvfWUo.js → index-B6xqr-v6.js} +44 -44
- package/dist/assets/{sw-register-CZ9DmSD-.js → sw-register-A0FMc-CY.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/claude-adapter.test.ts +71 -0
- package/server/claude-adapter.ts +22 -3
- package/server/session-types.ts +26 -1
- package/server/ws-bridge.test.ts +44 -0
- package/server/ws-bridge.ts +12 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as u}from"./index-
|
|
1
|
+
import{_ as u}from"./index-B6xqr-v6.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-B6xqr-v6.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-C86AT0KJ.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":"4f8bb842832a3076ea5fd36837194001","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-A0FMc-CY.js"},{"revision":null,"url":"assets/index-C86AT0KJ.css"},{"revision":null,"url":"assets/index-B6xqr-v6.js"},{"revision":null,"url":"assets/TailscalePage-DPJGhCUO.js"},{"revision":null,"url":"assets/SettingsPage-CFe6_dW9.js"},{"revision":null,"url":"assets/SandboxManager-BNIPWLHz.js"},{"revision":null,"url":"assets/RunsPage-Bn7SIAEx.js"},{"revision":null,"url":"assets/PromptsPage-BF5QRCM7.js"},{"revision":null,"url":"assets/Playground-Z8gfp_Pg.js"},{"revision":null,"url":"assets/LinearSettingsPage-DZbV5ES0.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-aKHb8Kgv.js"},{"revision":null,"url":"assets/IntegrationsPage-ClHcXTNR.js"},{"revision":null,"url":"assets/DashboardPage-DFgvPALP.js"},{"revision":null,"url":"assets/CronManager-B1s3pGHK.js"},{"revision":null,"url":"assets/AgentsPage-BBi5x0sS.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.
|
|
3
|
+
"version": "0.113.0",
|
|
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",
|
|
@@ -55,6 +55,7 @@ vi.mock("./settings-manager.js", () => ({
|
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
57
|
import { ClaudeAdapter } from "./claude-adapter.js";
|
|
58
|
+
import type { BrowserIncomingMessage } from "./session-types.js";
|
|
58
59
|
import { log } from "./logger.js";
|
|
59
60
|
|
|
60
61
|
// ─── Mock socket factory ────────────────────────────────────────────────────
|
|
@@ -2319,3 +2320,73 @@ describe("stderr tail on kill warnings", () => {
|
|
|
2319
2320
|
expect(wedgeWarning()).toBeDefined();
|
|
2320
2321
|
});
|
|
2321
2322
|
});
|
|
2323
|
+
|
|
2324
|
+
/**
|
|
2325
|
+
* rate_limit_event was previously discarded with the comment "no user-facing
|
|
2326
|
+
* action needed". Recorded traffic disagrees: across 200 events there were 14
|
|
2327
|
+
* `allowed_warning` and 3 `rejected`, none of which reached the UI or the logs.
|
|
2328
|
+
* It is also the only signal we get about plan-window usage.
|
|
2329
|
+
*/
|
|
2330
|
+
describe("rate_limit_event surfacing", () => {
|
|
2331
|
+
it("forwards the info to the browser", async () => {
|
|
2332
|
+
const emitted: BrowserIncomingMessage[] = [];
|
|
2333
|
+
const a = new ClaudeAdapter("rl-1");
|
|
2334
|
+
a.onBrowserMessage((m) => emitted.push(m));
|
|
2335
|
+
const { proc, pushStdout } = createMockProc();
|
|
2336
|
+
a.attachStdio(proc);
|
|
2337
|
+
|
|
2338
|
+
pushStdout(JSON.stringify({
|
|
2339
|
+
type: "rate_limit_event",
|
|
2340
|
+
rate_limit_info: {
|
|
2341
|
+
status: "allowed_warning",
|
|
2342
|
+
utilization: 0.95,
|
|
2343
|
+
rateLimitType: "five_hour",
|
|
2344
|
+
resetsAt: 1784441400,
|
|
2345
|
+
},
|
|
2346
|
+
uuid: "rl-uuid-1",
|
|
2347
|
+
session_id: "s",
|
|
2348
|
+
}) + "\n");
|
|
2349
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
2350
|
+
|
|
2351
|
+
const msg = emitted.find((m) => m.type === "rate_limit") as
|
|
2352
|
+
| { type: "rate_limit"; info: Record<string, unknown> }
|
|
2353
|
+
| undefined;
|
|
2354
|
+
expect(msg).toBeDefined();
|
|
2355
|
+
expect(msg?.info.status).toBe("allowed_warning");
|
|
2356
|
+
// Utilization must survive intact — it drives the meter fill.
|
|
2357
|
+
expect(msg?.info.utilization).toBe(0.95);
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
it("still forwards allowed events so the UI can clear a stale warning", async () => {
|
|
2361
|
+
// If only warnings were forwarded, a session would stay visually throttled
|
|
2362
|
+
// after recovering.
|
|
2363
|
+
const emitted: BrowserIncomingMessage[] = [];
|
|
2364
|
+
const a = new ClaudeAdapter("rl-2");
|
|
2365
|
+
a.onBrowserMessage((m) => emitted.push(m));
|
|
2366
|
+
const { proc, pushStdout } = createMockProc();
|
|
2367
|
+
a.attachStdio(proc);
|
|
2368
|
+
|
|
2369
|
+
pushStdout(JSON.stringify({
|
|
2370
|
+
type: "rate_limit_event",
|
|
2371
|
+
rate_limit_info: { status: "allowed", rateLimitType: "five_hour" },
|
|
2372
|
+
session_id: "s",
|
|
2373
|
+
}) + "\n");
|
|
2374
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
2375
|
+
|
|
2376
|
+
expect(emitted.some((m) => m.type === "rate_limit")).toBe(true);
|
|
2377
|
+
});
|
|
2378
|
+
|
|
2379
|
+
it("tolerates a missing rate_limit_info without throwing", async () => {
|
|
2380
|
+
// A throw here would kill the stdout reader for the life of the process —
|
|
2381
|
+
// the exact failure the e2e harness hit with Bun.hash.
|
|
2382
|
+
const a = new ClaudeAdapter("rl-3");
|
|
2383
|
+
const { proc, pushStdout } = createMockProc();
|
|
2384
|
+
a.attachStdio(proc);
|
|
2385
|
+
|
|
2386
|
+
expect(() => {
|
|
2387
|
+
pushStdout(JSON.stringify({ type: "rate_limit_event", session_id: "s" }) + "\n");
|
|
2388
|
+
}).not.toThrow();
|
|
2389
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
2390
|
+
expect(a.isConnected()).toBe(true);
|
|
2391
|
+
});
|
|
2392
|
+
});
|
package/server/claude-adapter.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
CLIControlResponseMessage,
|
|
31
31
|
CLIAuthStatusMessage,
|
|
32
32
|
CLIControlCancelRequestMessage,
|
|
33
|
+
CLIRateLimitEventMessage,
|
|
33
34
|
CLIStreamlinedTextMessage,
|
|
34
35
|
CLIStreamlinedToolUseSummaryMessage,
|
|
35
36
|
CLIPromptSuggestionMessage,
|
|
@@ -1394,10 +1395,28 @@ export class ClaudeAdapter implements IBackendAdapter {
|
|
|
1394
1395
|
// to avoid rendering raw tool_result JSON in the chat UI.
|
|
1395
1396
|
break;
|
|
1396
1397
|
|
|
1397
|
-
case "rate_limit_event":
|
|
1398
|
-
//
|
|
1399
|
-
//
|
|
1398
|
+
case "rate_limit_event": {
|
|
1399
|
+
// Previously discarded. It is the only signal we get about plan-window
|
|
1400
|
+
// usage, and recorded traffic shows it does fire in anger (14 warning
|
|
1401
|
+
// and 3 rejected events across 200 samples) — while the UI said nothing
|
|
1402
|
+
// and the logs kept no record.
|
|
1403
|
+
const info = (msg as CLIRateLimitEventMessage).rate_limit_info ?? {};
|
|
1404
|
+
// Only non-`allowed` states are logged: `allowed` arrives constantly and
|
|
1405
|
+
// would drown the log, whereas a warning or rejection is exactly what a
|
|
1406
|
+
// later investigation needs to find.
|
|
1407
|
+
if (info.status && info.status !== "allowed") {
|
|
1408
|
+
log.warn("claude-adapter", "API rate limit", {
|
|
1409
|
+
sessionId: this.sessionId,
|
|
1410
|
+
status: info.status,
|
|
1411
|
+
utilization: info.utilization,
|
|
1412
|
+
rateLimitType: info.rateLimitType,
|
|
1413
|
+
resetsAt: info.resetsAt,
|
|
1414
|
+
isUsingOverage: info.isUsingOverage,
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
this.browserMessageCb?.({ type: "rate_limit", info });
|
|
1400
1418
|
break;
|
|
1419
|
+
}
|
|
1401
1420
|
|
|
1402
1421
|
case "control_cancel_request":
|
|
1403
1422
|
this.handleControlCancelRequest(msg as CLIControlCancelRequestMessage);
|
package/server/session-types.ts
CHANGED
|
@@ -297,10 +297,34 @@ export interface CLIUserEchoMessage {
|
|
|
297
297
|
session_id?: string;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Rate-limit status from the Claude API.
|
|
302
|
+
*
|
|
303
|
+
* Field shapes verified against 200 recorded events: status was `allowed`
|
|
304
|
+
* (183), `allowed_warning` (14) and `rejected` (3); `utilization` and
|
|
305
|
+
* `surpassedThreshold` appear on warning events, and `rateLimitType` was
|
|
306
|
+
* always `five_hour` — the plan window, not the per-minute RPM/TPM cap.
|
|
307
|
+
*/
|
|
308
|
+
export interface RateLimitInfo {
|
|
309
|
+
/** `allowed` | `allowed_warning` | `rejected`, per observed traffic. */
|
|
310
|
+
status?: string;
|
|
311
|
+
/** Fraction of the window consumed, 0-1 (e.g. 0.95). Warning events only. */
|
|
312
|
+
utilization?: number;
|
|
313
|
+
/** The threshold that tripped this warning, 0-1 (e.g. 0.9). */
|
|
314
|
+
surpassedThreshold?: number;
|
|
315
|
+
/** Unix seconds at which the window resets. */
|
|
316
|
+
resetsAt?: number;
|
|
317
|
+
/** e.g. "five_hour". */
|
|
318
|
+
rateLimitType?: string;
|
|
319
|
+
overageStatus?: string;
|
|
320
|
+
overageDisabledReason?: string;
|
|
321
|
+
isUsingOverage?: boolean;
|
|
322
|
+
}
|
|
323
|
+
|
|
300
324
|
/** Rate-limit status from Claude API (allowed/throttled). */
|
|
301
325
|
export interface CLIRateLimitEventMessage {
|
|
302
326
|
type: "rate_limit_event";
|
|
303
|
-
rate_limit_info: Record<string, unknown>;
|
|
327
|
+
rate_limit_info: RateLimitInfo & Record<string, unknown>;
|
|
304
328
|
uuid?: string;
|
|
305
329
|
}
|
|
306
330
|
|
|
@@ -414,6 +438,7 @@ export type BrowserIncomingMessageBase =
|
|
|
414
438
|
| { type: "session_name_update"; name: string }
|
|
415
439
|
| { type: "pr_status_update"; pr: import("./github-pr.js").GitHubPRInfo | null; available: boolean }
|
|
416
440
|
| { type: "mcp_status"; servers: McpServerDetail[] }
|
|
441
|
+
| { type: "rate_limit"; info: RateLimitInfo }
|
|
417
442
|
| { type: "session_phase"; phase: SessionPhase; previousPhase: SessionPhase }
|
|
418
443
|
| { type: "prompt_suggestion"; suggestions: string[] }
|
|
419
444
|
| { type: "streamlined_text"; text: string }
|
package/server/ws-bridge.test.ts
CHANGED
|
@@ -1996,6 +1996,50 @@ describe("CLI message routing", () => {
|
|
|
1996
1996
|
}
|
|
1997
1997
|
});
|
|
1998
1998
|
|
|
1999
|
+
/**
|
|
2000
|
+
* Regression: the server-owned-adapter disconnect path serves BOTH stdio
|
|
2001
|
+
* Claude and Codex, but every label on it said "Codex" — so a Claude
|
|
2002
|
+
* session's disconnects were filed under the wrong backend and any forensic
|
|
2003
|
+
* search for them looked under the wrong term. Observed live:
|
|
2004
|
+
* reason=codex_disconnect_confirmed backendType=claude
|
|
2005
|
+
*/
|
|
2006
|
+
it("labels adapter disconnects with the session's actual backend", async () => {
|
|
2007
|
+
vi.useFakeTimers();
|
|
2008
|
+
try {
|
|
2009
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
2010
|
+
const session = bridge.getOrCreateSession("s1");
|
|
2011
|
+
expect(session.backendType).toBe("claude");
|
|
2012
|
+
|
|
2013
|
+
const browser = makeBrowserSocket("s1");
|
|
2014
|
+
bridge.handleBrowserOpen(browser, "s1");
|
|
2015
|
+
|
|
2016
|
+
let disconnectCb: (() => void) | undefined;
|
|
2017
|
+
const adapter = {
|
|
2018
|
+
isConnected: () => false,
|
|
2019
|
+
send: () => true,
|
|
2020
|
+
disconnect: async () => {},
|
|
2021
|
+
onBrowserMessage: () => {},
|
|
2022
|
+
onSessionMeta: () => {},
|
|
2023
|
+
onDisconnect: (cb: () => void) => { disconnectCb = cb; },
|
|
2024
|
+
onInitError: () => {},
|
|
2025
|
+
};
|
|
2026
|
+
bridge.attachBackendAdapter("s1", adapter as never, "claude");
|
|
2027
|
+
|
|
2028
|
+
disconnectCb!();
|
|
2029
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
2030
|
+
|
|
2031
|
+
const banner = warnSpy.mock.calls
|
|
2032
|
+
.map(([a]) => String(a))
|
|
2033
|
+
.find((l) => l.includes("UI: CLI disconnected banner shown"));
|
|
2034
|
+
expect(banner).toBeDefined();
|
|
2035
|
+
// A Claude session must never be filed under codex_*.
|
|
2036
|
+
expect(banner).toContain("claude_disconnect_confirmed");
|
|
2037
|
+
expect(banner).not.toContain("codex_disconnect_confirmed");
|
|
2038
|
+
} finally {
|
|
2039
|
+
vi.useRealTimers();
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
|
|
1999
2043
|
it("tool_progress: broadcasts", async () => {
|
|
2000
2044
|
const msg = JSON.stringify({
|
|
2001
2045
|
type: "tool_progress",
|
package/server/ws-bridge.ts
CHANGED
|
@@ -669,9 +669,17 @@ export class WsBridge {
|
|
|
669
669
|
// "reconnecting" with a short debounce (5s vs 15s for the legacy WS
|
|
670
670
|
// Claude transport, since there's no WebSocket cycling).
|
|
671
671
|
session.backendAdapter = null;
|
|
672
|
-
|
|
672
|
+
// This path serves BOTH stdio Claude and Codex, but every label here used
|
|
673
|
+
// to say "Codex" — so a Claude session's disconnects were filed under the
|
|
674
|
+
// wrong backend in the logs, and forensics silently searched the wrong
|
|
675
|
+
// term. Label from the session's actual backend instead.
|
|
676
|
+
const backend = session.backendType;
|
|
677
|
+
session.stateMachine.transition("reconnecting", `${backend}_adapter_disconnected`);
|
|
673
678
|
this.persistSession(session);
|
|
674
|
-
log.info("ws-bridge", "
|
|
679
|
+
log.info("ws-bridge", "Backend adapter disconnected, starting debounce", {
|
|
680
|
+
sessionId,
|
|
681
|
+
backendType: backend,
|
|
682
|
+
});
|
|
675
683
|
|
|
676
684
|
const existing = this.disconnectTimers.get(sessionId);
|
|
677
685
|
if (existing) clearTimeout(existing);
|
|
@@ -680,14 +688,14 @@ export class WsBridge {
|
|
|
680
688
|
// Check if a new adapter reconnected during the grace period
|
|
681
689
|
if (session.backendAdapter?.isConnected()) return;
|
|
682
690
|
|
|
683
|
-
log.warn("ws-bridge", "
|
|
691
|
+
log.warn("ws-bridge", "Backend disconnect confirmed", { sessionId, backendType: backend });
|
|
684
692
|
for (const [reqId] of session.pendingPermissions) {
|
|
685
693
|
this.broadcastToBrowsers(session, { type: "permission_cancelled", request_id: reqId });
|
|
686
694
|
}
|
|
687
695
|
session.pendingPermissions.clear();
|
|
688
696
|
session.stateMachine.transition("terminated", "disconnect_confirmed");
|
|
689
697
|
this.persistSession(session);
|
|
690
|
-
this.broadcastCliDisconnected(session,
|
|
698
|
+
this.broadcastCliDisconnected(session, `${backend}_disconnect_confirmed`);
|
|
691
699
|
|
|
692
700
|
// Request auto-relaunch regardless of browser state — proactive
|
|
693
701
|
// keepalive in the orchestrator ensures headless sessions stay alive.
|