@hellcoder/companion 0.113.0 → 0.113.1-preview.20260728154019.dc990d1
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-BBi5x0sS.js → AgentsPage-DoMIvK5c.js} +1 -1
- package/dist/assets/{CronManager-B1s3pGHK.js → CronManager-DA1peJy7.js} +1 -1
- package/dist/assets/{DashboardPage-DFgvPALP.js → DashboardPage-C4B7XxBJ.js} +1 -1
- package/dist/assets/{IntegrationsPage-ClHcXTNR.js → IntegrationsPage-D4FtQBUj.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-aKHb8Kgv.js → LinearOAuthSettingsPage-BSfT0Ulr.js} +1 -1
- package/dist/assets/{LinearSettingsPage-DZbV5ES0.js → LinearSettingsPage-g23UlNTx.js} +1 -1
- package/dist/assets/{Playground-Z8gfp_Pg.js → Playground-By14cDox.js} +1 -1
- package/dist/assets/{PromptsPage-BF5QRCM7.js → PromptsPage-9YIF_Pkw.js} +1 -1
- package/dist/assets/{RunsPage-Bn7SIAEx.js → RunsPage-CeuQAzD-.js} +1 -1
- package/dist/assets/{SandboxManager-BNIPWLHz.js → SandboxManager-CoHOUMFN.js} +1 -1
- package/dist/assets/{SettingsPage-CFe6_dW9.js → SettingsPage-B6pfdIlj.js} +1 -1
- package/dist/assets/{TailscalePage-DPJGhCUO.js → TailscalePage-B6WlB5tj.js} +1 -1
- package/dist/assets/{index-B6xqr-v6.js → index-By5tzX2N.js} +8 -8
- package/dist/assets/{sw-register-A0FMc-CY.js → sw-register-E2OIBgNb.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/ws-bridge-types.ts +16 -0
- package/server/ws-bridge.test.ts +107 -0
- package/server/ws-bridge.ts +37 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as u}from"./index-
|
|
1
|
+
import{_ as u}from"./index-By5tzX2N.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-By5tzX2N.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":"f61d2f9dcbdeb06894be02d8dba85fd3","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-E2OIBgNb.js"},{"revision":null,"url":"assets/index-C86AT0KJ.css"},{"revision":null,"url":"assets/index-By5tzX2N.js"},{"revision":null,"url":"assets/TailscalePage-B6WlB5tj.js"},{"revision":null,"url":"assets/SettingsPage-B6pfdIlj.js"},{"revision":null,"url":"assets/SandboxManager-CoHOUMFN.js"},{"revision":null,"url":"assets/RunsPage-CeuQAzD-.js"},{"revision":null,"url":"assets/PromptsPage-9YIF_Pkw.js"},{"revision":null,"url":"assets/Playground-By14cDox.js"},{"revision":null,"url":"assets/LinearSettingsPage-g23UlNTx.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-BSfT0Ulr.js"},{"revision":null,"url":"assets/IntegrationsPage-D4FtQBUj.js"},{"revision":null,"url":"assets/DashboardPage-C4B7XxBJ.js"},{"revision":null,"url":"assets/CronManager-DA1peJy7.js"},{"revision":null,"url":"assets/AgentsPage-DoMIvK5c.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.113.
|
|
3
|
+
"version": "0.113.1-preview.20260728154019.dc990d1",
|
|
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",
|
|
@@ -75,6 +75,22 @@ export interface Session {
|
|
|
75
75
|
* new user_message is dispatched.
|
|
76
76
|
*/
|
|
77
77
|
inFlightTurnReplayed?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* True from the moment a user message is dispatched until its `result`
|
|
80
|
+
* arrives, regardless of how much output was produced in between.
|
|
81
|
+
*
|
|
82
|
+
* `inFlightUserTurn` is cleared as soon as the first `assistant`/
|
|
83
|
+
* `stream_event` lands, so it only ever covers turns that die BEFORE
|
|
84
|
+
* producing output. A turn interrupted mid-answer had already cleared it and
|
|
85
|
+
* was therefore never recovered — the session simply idled until a human
|
|
86
|
+
* typed "continue". This flag is what makes that case detectable.
|
|
87
|
+
*/
|
|
88
|
+
turnAwaitingResult?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* One-shot guard for the mid-answer continuation nudge, so a turn that keeps
|
|
91
|
+
* dying cannot be nudged in a loop. Reset when a new user_message is sent.
|
|
92
|
+
*/
|
|
93
|
+
continuationSent?: boolean;
|
|
78
94
|
/**
|
|
79
95
|
* Set when the last turn failed authentication (expired/invalid credentials).
|
|
80
96
|
* Auto-relaunch is skipped while this is set — relaunching cannot fix auth —
|
package/server/ws-bridge.test.ts
CHANGED
|
@@ -2040,6 +2040,113 @@ describe("CLI message routing", () => {
|
|
|
2040
2040
|
}
|
|
2041
2041
|
});
|
|
2042
2042
|
|
|
2043
|
+
/**
|
|
2044
|
+
* Recovery for turns interrupted MID-ANSWER.
|
|
2045
|
+
*
|
|
2046
|
+
* `inFlightUserTurn` is cleared on the first assistant/stream_event, so the
|
|
2047
|
+
* existing replay only ever covered turns that died before producing output.
|
|
2048
|
+
* A turn cut off mid-answer was not recovered at all: the session idled until
|
|
2049
|
+
* a human typed "continue" by hand. Reported in production as 15 wedge-kills
|
|
2050
|
+
* across 11 sessions in ~34h.
|
|
2051
|
+
*/
|
|
2052
|
+
describe("mid-answer turn continuation", () => {
|
|
2053
|
+
async function startTurnWithOutput(sessionId: string) {
|
|
2054
|
+
const cli = makeCliSocket(sessionId);
|
|
2055
|
+
bridge.handleCLIOpen(cli, sessionId);
|
|
2056
|
+
await bridge.handleCLIMessage(cli, makeInitMsg());
|
|
2057
|
+
const browser = makeBrowserSocket(sessionId);
|
|
2058
|
+
bridge.handleBrowserOpen(browser, sessionId);
|
|
2059
|
+
|
|
2060
|
+
await bridge.handleBrowserMessage(browser, JSON.stringify({
|
|
2061
|
+
type: "user_message",
|
|
2062
|
+
content: "do a big task",
|
|
2063
|
+
}));
|
|
2064
|
+
// First output clears inFlightUserTurn — this is what made the case
|
|
2065
|
+
// invisible to the original replay logic.
|
|
2066
|
+
await bridge.handleCLIMessage(cli, JSON.stringify({
|
|
2067
|
+
type: "stream_event",
|
|
2068
|
+
event: { type: "content_block_delta", delta: { type: "text_delta", text: "work" } },
|
|
2069
|
+
session_id: "cli-123",
|
|
2070
|
+
uuid: "se-1",
|
|
2071
|
+
}));
|
|
2072
|
+
return { cli, browser, session: bridge.getSession(sessionId)! };
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
it("nudges a turn that was interrupted after it began answering", async () => {
|
|
2076
|
+
const { session } = await startTurnWithOutput("s1");
|
|
2077
|
+
expect(session.inFlightUserTurn).toBeUndefined();
|
|
2078
|
+
expect(session.turnAwaitingResult).toBe(true);
|
|
2079
|
+
|
|
2080
|
+
// Relaunch: a fresh adapter attaches.
|
|
2081
|
+
const sent: string[] = [];
|
|
2082
|
+
bridge.attachBackendAdapter("s1", {
|
|
2083
|
+
isConnected: () => true,
|
|
2084
|
+
send: (m: unknown) => { sent.push(typeof m === "string" ? m : JSON.stringify(m)); return true; },
|
|
2085
|
+
disconnect: async () => {},
|
|
2086
|
+
onBrowserMessage: () => {},
|
|
2087
|
+
onSessionMeta: () => {},
|
|
2088
|
+
onDisconnect: () => {},
|
|
2089
|
+
onInitError: () => {},
|
|
2090
|
+
} as never, "claude");
|
|
2091
|
+
|
|
2092
|
+
const nudge = sent.map((m) => JSON.parse(m)).find((m) => m.type === "user_message");
|
|
2093
|
+
expect(nudge, `sent: ${JSON.stringify(sent)}`).toBeDefined();
|
|
2094
|
+
expect(nudge.content).toMatch(/continue from where you left off/i);
|
|
2095
|
+
// It must not re-send the original prompt, which would redo tool work.
|
|
2096
|
+
expect(nudge.content).not.toBe("do a big task");
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
it("does not nudge twice for the same turn", async () => {
|
|
2100
|
+
// A turn that keeps dying must not accumulate nudges.
|
|
2101
|
+
const { session } = await startTurnWithOutput("s1");
|
|
2102
|
+
const sent: string[] = [];
|
|
2103
|
+
const attach = () => bridge.attachBackendAdapter("s1", {
|
|
2104
|
+
isConnected: () => true,
|
|
2105
|
+
send: (m: unknown) => { sent.push(typeof m === "string" ? m : JSON.stringify(m)); return true; },
|
|
2106
|
+
disconnect: async () => {},
|
|
2107
|
+
onBrowserMessage: () => {},
|
|
2108
|
+
onSessionMeta: () => {},
|
|
2109
|
+
onDisconnect: () => {},
|
|
2110
|
+
onInitError: () => {},
|
|
2111
|
+
} as never, "claude");
|
|
2112
|
+
|
|
2113
|
+
attach();
|
|
2114
|
+
attach();
|
|
2115
|
+
|
|
2116
|
+
const nudges = sent
|
|
2117
|
+
.map((m) => JSON.parse(m))
|
|
2118
|
+
.filter((m) => m.type === "user_message" && /continue from where/i.test(m.content || ""));
|
|
2119
|
+
expect(nudges).toHaveLength(1);
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
it("does not nudge once the turn has completed", async () => {
|
|
2123
|
+
// A finished turn is not an interrupted one — nudging here would send an
|
|
2124
|
+
// unprompted message to an idle session.
|
|
2125
|
+
const { cli, session } = await startTurnWithOutput("s1");
|
|
2126
|
+
await bridge.handleCLIMessage(cli, JSON.stringify({
|
|
2127
|
+
type: "result", subtype: "success", is_error: false,
|
|
2128
|
+
duration_ms: 10, num_turns: 1, session_id: "cli-123", uuid: "r-1",
|
|
2129
|
+
}));
|
|
2130
|
+
expect(session.turnAwaitingResult).toBe(false);
|
|
2131
|
+
|
|
2132
|
+
const sent: string[] = [];
|
|
2133
|
+
bridge.attachBackendAdapter("s1", {
|
|
2134
|
+
isConnected: () => true,
|
|
2135
|
+
send: (m: unknown) => { sent.push(typeof m === "string" ? m : JSON.stringify(m)); return true; },
|
|
2136
|
+
disconnect: async () => {},
|
|
2137
|
+
onBrowserMessage: () => {},
|
|
2138
|
+
onSessionMeta: () => {},
|
|
2139
|
+
onDisconnect: () => {},
|
|
2140
|
+
onInitError: () => {},
|
|
2141
|
+
} as never, "claude");
|
|
2142
|
+
|
|
2143
|
+
const nudges = sent
|
|
2144
|
+
.map((m) => JSON.parse(m))
|
|
2145
|
+
.filter((m) => m.type === "user_message" && /continue from where/i.test(m.content || ""));
|
|
2146
|
+
expect(nudges).toHaveLength(0);
|
|
2147
|
+
});
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2043
2150
|
it("tool_progress: broadcasts", async () => {
|
|
2044
2151
|
const msg = JSON.stringify({
|
|
2045
2152
|
type: "tool_progress",
|
package/server/ws-bridge.ts
CHANGED
|
@@ -527,6 +527,8 @@ export class WsBridge {
|
|
|
527
527
|
const resultData = msg.data;
|
|
528
528
|
// The turn completed (even if it errored) — no replay needed.
|
|
529
529
|
session.inFlightUserTurn = undefined;
|
|
530
|
+
// The turn is genuinely finished, so a later relaunch must not nudge it.
|
|
531
|
+
session.turnAwaitingResult = false;
|
|
530
532
|
// Detect auth failures (expired/invalid credentials). The CLI keeps
|
|
531
533
|
// running in stream-json mode, so this is the primary auth signal — not
|
|
532
534
|
// a process exit. Flag the session so auto-relaunch is skipped (the
|
|
@@ -729,6 +731,36 @@ export class WsBridge {
|
|
|
729
731
|
type: "error",
|
|
730
732
|
message: "Reconnected — re-sending your last message, which the previous session didn't answer.",
|
|
731
733
|
});
|
|
734
|
+
} else if (
|
|
735
|
+
!isLegacyWsClaude &&
|
|
736
|
+
// Only when output had already begun: inFlightUserTurn is cleared by the
|
|
737
|
+
// first token, so its absence is what distinguishes "died mid-answer"
|
|
738
|
+
// from "died before answering". Without this check a poison prompt that
|
|
739
|
+
// has exhausted its one replay would get nudged on every subsequent
|
|
740
|
+
// relaunch, defeating inFlightTurnReplayed.
|
|
741
|
+
!session.inFlightUserTurn &&
|
|
742
|
+
session.turnAwaitingResult &&
|
|
743
|
+
!session.continuationSent
|
|
744
|
+
) {
|
|
745
|
+
// The turn died AFTER it had begun answering. `--resume` restores the
|
|
746
|
+
// transcript but does not restart the turn, so without this the session
|
|
747
|
+
// idles forever and a human eventually types "continue" by hand.
|
|
748
|
+
//
|
|
749
|
+
// Re-sending the original prompt is wrong here: work already happened and
|
|
750
|
+
// replaying it would redo tool calls. A continuation nudge asks the model
|
|
751
|
+
// to finish from where the transcript stops instead.
|
|
752
|
+
session.continuationSent = true;
|
|
753
|
+
session.pendingMessages.unshift(JSON.stringify({
|
|
754
|
+
type: "user_message",
|
|
755
|
+
content: "Your previous response was interrupted before it finished. Continue from where you left off — do not repeat work that is already in the transcript.",
|
|
756
|
+
}));
|
|
757
|
+
log.info("ws-bridge", "Nudging interrupted turn to continue after relaunch", {
|
|
758
|
+
sessionId,
|
|
759
|
+
});
|
|
760
|
+
this.broadcastToBrowsers(session, {
|
|
761
|
+
type: "error",
|
|
762
|
+
message: "Reconnected — the previous answer was cut off, asking it to continue.",
|
|
763
|
+
});
|
|
732
764
|
}
|
|
733
765
|
|
|
734
766
|
// Flush pending messages for server-owned backends (Codex + stdio Claude),
|
|
@@ -1257,6 +1289,11 @@ export class WsBridge {
|
|
|
1257
1289
|
// one-shot replay guard.
|
|
1258
1290
|
session.inFlightUserTurn = JSON.stringify(msg);
|
|
1259
1291
|
session.inFlightTurnReplayed = false;
|
|
1292
|
+
// Tracked separately from inFlightUserTurn, which is cleared on the first
|
|
1293
|
+
// token: this stays set until the turn actually finishes, so an interrupt
|
|
1294
|
+
// *mid-answer* is still recognisable as an unfinished turn.
|
|
1295
|
+
session.turnAwaitingResult = true;
|
|
1296
|
+
session.continuationSent = false;
|
|
1260
1297
|
// A fresh turn is an attempt to make progress (e.g. after the user
|
|
1261
1298
|
// re-authenticated), so clear any stale auth-block — a genuine crash on
|
|
1262
1299
|
// this new turn should be allowed to auto-relaunch again.
|