@hellcoder/companion 0.102.1-preview.20260616092710.1b57e0b → 0.102.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/assets/{AgentsPage-uXDPdxng.js → AgentsPage-DMd0B7DV.js} +1 -1
  2. package/dist/assets/{CronManager-BHTueSuv.js → CronManager-Cc1g1uc_.js} +1 -1
  3. package/dist/assets/{IntegrationsPage-BIAuw3fS.js → IntegrationsPage-CmhW-5Oz.js} +1 -1
  4. package/dist/assets/{LinearOAuthSettingsPage-CTlEsf3M.js → LinearOAuthSettingsPage-Zj44jouA.js} +1 -1
  5. package/dist/assets/{LinearSettingsPage-BErRFO3Z.js → LinearSettingsPage-Berx9B0o.js} +1 -1
  6. package/dist/assets/{Playground-Bj6JwBpM.js → Playground-Cv0nNeBH.js} +1 -1
  7. package/dist/assets/{PromptsPage-BEvGho2w.js → PromptsPage-CEVBQ8dD.js} +1 -1
  8. package/dist/assets/{RunsPage-CLUH_HTH.js → RunsPage-CAOTAlme.js} +1 -1
  9. package/dist/assets/SandboxManager-K86fOiMy.js +1 -0
  10. package/dist/assets/{SettingsPage-CreWaJD6.js → SettingsPage-CNeDZmJh.js} +1 -1
  11. package/dist/assets/{TailscalePage-BqCQBn1C.js → TailscalePage-DHn8CkNO.js} +1 -1
  12. package/dist/assets/index-DToro0X1.js +134 -0
  13. package/dist/assets/{index-BVs_mQXt.css → index-P1hpzChp.css} +1 -1
  14. package/dist/assets/{sw-register-CeyFsabK.js → sw-register-CDS-EAiG.js} +1 -1
  15. package/dist/index.html +2 -2
  16. package/dist/sw.js +1 -1
  17. package/package.json +1 -1
  18. package/server/feature-flags.test.ts +31 -0
  19. package/server/feature-flags.ts +18 -0
  20. package/server/image-pull-manager.test.ts +17 -0
  21. package/server/image-pull-manager.ts +12 -0
  22. package/server/routes/env-routes.test.ts +2 -0
  23. package/server/routes/env-routes.ts +4 -0
  24. package/server/routes/sandbox-routes.test.ts +2 -0
  25. package/server/routes/sandbox-routes.ts +5 -0
  26. package/server/routes/system-routes.ts +4 -2
  27. package/server/session-creation-service.test.ts +16 -0
  28. package/server/session-creation-service.ts +5 -2
  29. package/server/session-orchestrator.test.ts +2 -0
  30. package/server/session-orchestrator.ts +5 -2
  31. package/dist/assets/SandboxManager-BbBqapwZ.js +0 -8
  32. package/dist/assets/index-CO5E0NEG.js +0 -134
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":"c924d9f84eb51b5b320a5fc233cd5299","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-CeyFsabK.js"},{"revision":null,"url":"assets/index-CO5E0NEG.js"},{"revision":null,"url":"assets/index-BVs_mQXt.css"},{"revision":null,"url":"assets/TailscalePage-BqCQBn1C.js"},{"revision":null,"url":"assets/SettingsPage-CreWaJD6.js"},{"revision":null,"url":"assets/SandboxManager-BbBqapwZ.js"},{"revision":null,"url":"assets/RunsPage-CLUH_HTH.js"},{"revision":null,"url":"assets/PromptsPage-BEvGho2w.js"},{"revision":null,"url":"assets/Playground-Bj6JwBpM.js"},{"revision":null,"url":"assets/LinearSettingsPage-BErRFO3Z.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-CTlEsf3M.js"},{"revision":null,"url":"assets/IntegrationsPage-BIAuw3fS.js"},{"revision":null,"url":"assets/CronManager-BHTueSuv.js"},{"revision":null,"url":"assets/AgentsPage-uXDPdxng.js"}]);ne();T(new oe(re("index.html"),{denylist:[/^\/api/,/^\/ws/]}));
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":"0c3c541750418120b23599c83babb648","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-CDS-EAiG.js"},{"revision":null,"url":"assets/index-P1hpzChp.css"},{"revision":null,"url":"assets/index-DToro0X1.js"},{"revision":null,"url":"assets/TailscalePage-DHn8CkNO.js"},{"revision":null,"url":"assets/SettingsPage-CNeDZmJh.js"},{"revision":null,"url":"assets/SandboxManager-K86fOiMy.js"},{"revision":null,"url":"assets/RunsPage-CAOTAlme.js"},{"revision":null,"url":"assets/PromptsPage-CEVBQ8dD.js"},{"revision":null,"url":"assets/Playground-Cv0nNeBH.js"},{"revision":null,"url":"assets/LinearSettingsPage-Berx9B0o.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-Zj44jouA.js"},{"revision":null,"url":"assets/IntegrationsPage-CmhW-5Oz.js"},{"revision":null,"url":"assets/CronManager-Cc1g1uc_.js"},{"revision":null,"url":"assets/AgentsPage-DMd0B7DV.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.102.1-preview.20260616092710.1b57e0b",
3
+ "version": "0.102.1",
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",
@@ -0,0 +1,31 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { isSandboxEnabled } from "./feature-flags.js";
3
+
4
+ // The sandbox kill switch defaults OFF (the upstream image is unmaintained) and
5
+ // is opt-in via COMPANION_SANDBOX_ENABLED. It must read the env live so runtime
6
+ // toggles take effect.
7
+ describe("isSandboxEnabled", () => {
8
+ afterEach(() => {
9
+ delete process.env.COMPANION_SANDBOX_ENABLED;
10
+ });
11
+
12
+ it("is disabled by default", () => {
13
+ delete process.env.COMPANION_SANDBOX_ENABLED;
14
+ expect(isSandboxEnabled()).toBe(false);
15
+ });
16
+
17
+ it('is enabled when set to "1"', () => {
18
+ process.env.COMPANION_SANDBOX_ENABLED = "1";
19
+ expect(isSandboxEnabled()).toBe(true);
20
+ });
21
+
22
+ it('is enabled when set to "true"', () => {
23
+ process.env.COMPANION_SANDBOX_ENABLED = "true";
24
+ expect(isSandboxEnabled()).toBe(true);
25
+ });
26
+
27
+ it("is disabled for any other value", () => {
28
+ process.env.COMPANION_SANDBOX_ENABLED = "yes";
29
+ expect(isSandboxEnabled()).toBe(false);
30
+ });
31
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Feature flags for the fork.
3
+ *
4
+ * SANDBOX / container support is DISABLED by default because it depends on the
5
+ * upstream, no-longer-maintained `docker.io/stangirard/the-companion` image.
6
+ * Running an unmaintained image (privileged, no less) is a security risk, so the
7
+ * fork ships with it off.
8
+ *
9
+ * The code is intentionally kept intact: set COMPANION_SANDBOX_ENABLED=1 (or
10
+ * "true") to re-enable it — e.g. once we publish and point at our own image.
11
+ *
12
+ * Read as a function (not a cached const) so tests and runtime env changes take
13
+ * effect without reimporting the module.
14
+ */
15
+ export function isSandboxEnabled(): boolean {
16
+ const v = process.env.COMPANION_SANDBOX_ENABLED;
17
+ return v === "1" || v === "true";
18
+ }
@@ -44,6 +44,8 @@ async function createManager() {
44
44
 
45
45
  describe("ImagePullManager", () => {
46
46
  beforeEach(() => {
47
+ // Sandbox is force-disabled by default; enable it so the pull paths run.
48
+ process.env.COMPANION_SANDBOX_ENABLED = "1";
47
49
  vi.clearAllMocks();
48
50
  mockImageExists.mockReturnValue(false);
49
51
  mockPullImage.mockResolvedValue(true);
@@ -125,6 +127,21 @@ describe("ImagePullManager", () => {
125
127
 
126
128
  expect(mockPullImage).toHaveBeenCalledOnce();
127
129
  });
130
+
131
+ it("refuses to pull the upstream image when sandbox support is disabled (hard backstop)", async () => {
132
+ // Security backstop: with the flag off, the unmaintained upstream image must
133
+ // never be pulled, regardless of which call path requested it.
134
+ delete process.env.COMPANION_SANDBOX_ENABLED;
135
+ mockImageExists.mockReturnValue(false);
136
+ const manager = await createManager();
137
+
138
+ manager.ensureImage("the-companion:latest");
139
+
140
+ const state = manager.getState("the-companion:latest");
141
+ expect(state.status).toBe("error");
142
+ expect(state.error).toMatch(/disabled/i);
143
+ expect(mockPullImage).not.toHaveBeenCalled();
144
+ });
128
145
  });
129
146
 
130
147
  describe("waitForReady", () => {
@@ -2,6 +2,7 @@ import { join, dirname } from "node:path";
2
2
  import { existsSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { containerManager, ContainerManager } from "./container-manager.js";
5
+ import { isSandboxEnabled } from "./feature-flags.js";
5
6
 
6
7
  // ---------------------------------------------------------------------------
7
8
  // Types
@@ -171,6 +172,17 @@ class ImagePullManager {
171
172
  // Determine if we can pull from registry
172
173
  const registryImage = ContainerManager.getRegistryImage(image);
173
174
 
175
+ // Hard backstop: while sandbox support is disabled, never pull the upstream
176
+ // (unmaintained) registry image, regardless of which call path asked. Local
177
+ // Dockerfile builds are still allowed so a future self-built image works.
178
+ if (registryImage && !isSandboxEnabled()) {
179
+ this.markError(
180
+ image,
181
+ "Sandbox/container support is disabled in this build. Set COMPANION_SANDBOX_ENABLED=1 to re-enable.",
182
+ );
183
+ return;
184
+ }
185
+
174
186
  if (registryImage) {
175
187
  this.doPullFromRegistry(image, registryImage);
176
188
  } else {
@@ -42,6 +42,8 @@ import { registerEnvRoutes } from "./env-routes.js";
42
42
  let app: Hono;
43
43
 
44
44
  beforeEach(() => {
45
+ // Sandbox is force-disabled by default; enable it so build-base runs.
46
+ process.env.COMPANION_SANDBOX_ENABLED = "1";
45
47
  vi.clearAllMocks();
46
48
 
47
49
  app = new Hono();
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import * as envManager from "../env-manager.js";
5
5
  import { containerManager } from "../container-manager.js";
6
6
  import { imagePullManager } from "../image-pull-manager.js";
7
+ import { isSandboxEnabled } from "../feature-flags.js";
7
8
 
8
9
  export function registerEnvRoutes(
9
10
  api: Hono,
@@ -59,6 +60,9 @@ export function registerEnvRoutes(
59
60
  });
60
61
 
61
62
  api.post("/docker/build-base", async (c) => {
63
+ if (!isSandboxEnabled()) {
64
+ return c.json({ error: "Sandbox support is disabled in this build." }, 403);
65
+ }
62
66
  if (!containerManager.checkDocker()) return c.json({ error: "Docker is not available" }, 503);
63
67
  const dockerfilePath = join(options.webDir, "docker", "Dockerfile.the-companion");
64
68
  if (!existsSync(dockerfilePath)) {
@@ -38,6 +38,8 @@ import { registerSandboxRoutes } from "./sandbox-routes.js";
38
38
  let app: Hono;
39
39
 
40
40
  beforeEach(() => {
41
+ // Sandbox is force-disabled by default; enable it so test-init runs.
42
+ process.env.COMPANION_SANDBOX_ENABLED = "1";
41
43
  vi.clearAllMocks();
42
44
 
43
45
  app = new Hono().basePath("/api");
@@ -3,6 +3,7 @@ import type { Hono } from "hono";
3
3
  import * as sandboxManager from "../sandbox-manager.js";
4
4
  import { containerManager, type ContainerConfig } from "../container-manager.js";
5
5
  import { imagePullManager } from "../image-pull-manager.js";
6
+ import { isSandboxEnabled } from "../feature-flags.js";
6
7
 
7
8
  export function registerSandboxRoutes(
8
9
  api: Hono,
@@ -62,6 +63,10 @@ export function registerSandboxRoutes(
62
63
  // Accepts an optional `initScript` body param to test unsaved content
63
64
  // without persisting it first. Falls back to the stored script.
64
65
  api.post("/sandboxes/:slug/test-init", async (c) => {
66
+ // Sandbox containers use the unmaintained upstream image; refuse when disabled.
67
+ if (!isSandboxEnabled()) {
68
+ return c.json({ error: "Sandbox support is disabled in this build." }, 403);
69
+ }
65
70
  const slug = c.req.param("slug");
66
71
  const body = await c.req.json().catch(() => ({}));
67
72
  const rawCwd = body.cwd;
@@ -12,6 +12,7 @@ import {
12
12
  import { refreshServiceDefinition } from "../service.js";
13
13
  import { getSettings, updateSettings } from "../settings-manager.js";
14
14
  import { imagePullManager } from "../image-pull-manager.js";
15
+ import { isSandboxEnabled } from "../feature-flags.js";
15
16
  import { checkCompat, getCompatState } from "../claude-compat-checker.js";
16
17
  import { pinToVersion, patchBinary, unpatch } from "../claude-patcher.js";
17
18
  import {
@@ -145,8 +146,9 @@ export function registerSystemRoutes(
145
146
  return;
146
147
  }
147
148
 
148
- // Re-pull Docker image if auto-update is enabled
149
- if (getSettings().dockerAutoUpdate) {
149
+ // Re-pull Docker image if auto-update is enabled (and sandbox isn't
150
+ // disabled — we never refresh the unmaintained upstream image).
151
+ if (isSandboxEnabled() && getSettings().dockerAutoUpdate) {
150
152
  try {
151
153
  console.log("[update] Re-pulling Docker image (dockerAutoUpdate enabled)...");
152
154
  imagePullManager.pull("the-companion:latest");
@@ -150,6 +150,8 @@ describe("executeSessionCreation", () => {
150
150
  let deps: SessionCreationDeps;
151
151
 
152
152
  beforeEach(() => {
153
+ // Sandbox is force-disabled by default; enable it so sandbox paths run.
154
+ process.env.COMPANION_SANDBOX_ENABLED = "1";
153
155
  vi.clearAllMocks();
154
156
  deps = makeDeps();
155
157
  });
@@ -215,6 +217,20 @@ describe("executeSessionCreation", () => {
215
217
  }
216
218
  });
217
219
 
220
+ it("ignores sandboxEnabled entirely when the feature flag is off (kill switch)", async () => {
221
+ // Security kill switch: with sandbox disabled, a request asking for a sandbox
222
+ // (even an invalid one) is treated as a plain session — no 404, no image
223
+ // pull, no container creation.
224
+ delete process.env.COMPANION_SANDBOX_ENABLED;
225
+ const result = await executeSessionCreation(
226
+ { cwd: "/workspace", sandboxEnabled: true, sandboxSlug: "missing" },
227
+ deps,
228
+ );
229
+ expect(result.session).toBeDefined();
230
+ expect(imagePullManager.ensureImage).not.toHaveBeenCalled();
231
+ expect(containerManager.createContainer).not.toHaveBeenCalled();
232
+ });
233
+
218
234
  // -- Branch validation --
219
235
  it("rejects invalid branch names", async () => {
220
236
  try {
@@ -10,6 +10,7 @@ import { containerManager } from "./container-manager.js";
10
10
  import { hasContainerClaudeAuth } from "./claude-container-auth.js";
11
11
  import { hasContainerCodexAuth } from "./codex-container-auth.js";
12
12
  import { imagePullManager } from "./image-pull-manager.js";
13
+ import { isSandboxEnabled } from "./feature-flags.js";
13
14
  import { getConnection } from "./linear-connections.js";
14
15
  import { buildLinearSystemPrompt } from "./linear-prompt-builder.js";
15
16
  import { discoverCommandsAndSkills } from "./commands-discovery.js";
@@ -100,8 +101,10 @@ export async function executeSessionCreation(
100
101
  console.warn(`[session-creation] Environment "${body.envSlug}" not found, ignoring`);
101
102
  }
102
103
 
103
- // Resolve sandbox configuration
104
- const sandboxEnabled = body.sandboxEnabled === true;
104
+ // Resolve sandbox configuration. Sandbox is force-disabled when the feature
105
+ // flag is off (the upstream image is unmaintained), so no image is pulled and
106
+ // no privileged container is launched regardless of the request.
107
+ const sandboxEnabled = isSandboxEnabled() && body.sandboxEnabled === true;
105
108
  const companionSandbox = body.sandboxSlug ? sandboxManager.getSandbox(body.sandboxSlug as string) : null;
106
109
  if (sandboxEnabled && body.sandboxSlug && !companionSandbox) {
107
110
  throw new SessionCreationError(`Sandbox "${body.sandboxSlug}" not found`, 404, "resolving_env");
@@ -220,6 +220,8 @@ describe("SessionOrchestrator", () => {
220
220
  let orchestrator: SessionOrchestrator;
221
221
 
222
222
  beforeEach(() => {
223
+ // Sandbox is force-disabled by default; enable it so sandbox paths run.
224
+ process.env.COMPANION_SANDBOX_ENABLED = "1";
223
225
  vi.clearAllMocks();
224
226
  companionBus.clear();
225
227
  mockImagePullIsReady.mockReturnValue(true);
@@ -7,6 +7,7 @@ import type { BackendType, CreationStepId } from "./session-types.js";
7
7
  import type { ContainerConfig, ContainerInfo } from "./container-manager.js";
8
8
  import { containerManager } from "./container-manager.js";
9
9
  import { imagePullManager } from "./image-pull-manager.js";
10
+ import { isSandboxEnabled } from "./feature-flags.js";
10
11
  import * as envManager from "./env-manager.js";
11
12
  import * as sandboxManager from "./sandbox-manager.js";
12
13
  import * as gitUtils from "./git-utils.js";
@@ -314,8 +315,10 @@ export class SessionOrchestrator {
314
315
  envVars = { ...envVars, OPENAI_API_KEY: globalSettings.openaiApiKey };
315
316
  }
316
317
 
317
- // Resolve sandbox configuration
318
- const sandboxEnabled = body.sandboxEnabled === true;
318
+ // Resolve sandbox configuration. Sandbox is force-disabled when the feature
319
+ // flag is off (the upstream image is unmaintained), so no image is pulled
320
+ // and no privileged container is launched regardless of the request.
321
+ const sandboxEnabled = isSandboxEnabled() && body.sandboxEnabled === true;
319
322
  const companionSandbox = body.sandboxSlug ? sandboxManager.getSandbox(body.sandboxSlug) : null;
320
323
  if (sandboxEnabled && body.sandboxSlug && !companionSandbox) {
321
324
  return { ok: false, error: `Sandbox "${body.sandboxSlug}" not found`, status: 404 };
@@ -1,8 +0,0 @@
1
- import{r as n,a as c,j as e}from"./index-CO5E0NEG.js";function X({embedded:f=!1}){const[u,C]=n.useState([]),[V,W]=n.useState(!0),[v,l]=n.useState(""),[o,M]=n.useState(!1),[k,R]=n.useState(!1),[b,B]=n.useState(null),[s,E]=n.useState(null),i=n.useRef(null),[p,I]=n.useState(""),[T,D]=n.useState(""),[j,y]=n.useState(null),[P,$]=n.useState(""),[N,H]=n.useState(""),[h,w]=n.useState(null),[m,d]=n.useState(null),x=n.useRef({}),[S,z]=n.useState(""),g=n.useCallback(()=>{c.listSandboxes().then(C).catch(()=>{}).finally(()=>W(!1))},[]);n.useEffect(()=>{g(),c.getContainerStatus().then(t=>B(t.available)).catch(()=>B(!1)),c.getImageStatus("the-companion:latest").then(t=>E(t)).catch(()=>{}),c.getHome().then(({cwd:t})=>z(t)).catch(()=>{})},[g]),n.useEffect(()=>{if(!s||s.status!=="pulling"){i.current&&(clearInterval(i.current),i.current=null);return}return i.current||(i.current=setInterval(()=>{c.getImageStatus("the-companion:latest").then(t=>E(t)).catch(()=>{})},2e3)),()=>{i.current&&(clearInterval(i.current),i.current=null)}},[s]);function L(){c.pullImage("the-companion:latest").then(t=>{t.state&&E(t.state)}).catch(()=>{})}async function A(){const t=p.trim();if(t){R(!0);try{await c.createSandbox(t,{initScript:T||void 0}),I(""),D(""),M(!1),l(""),g()}catch(r){l(r instanceof Error?r.message:String(r))}finally{R(!1)}}}function Z(t){x.current={},y(t.slug),$(t.name),H(t.initScript||""),d(null),l("")}function q(){x.current={},y(null),d(null),w(null),l("")}async function K(){if(j){x.current={},w(null);try{await c.updateSandbox(j,{name:P.trim()||void 0,initScript:N||void 0}),y(null),d(null),l(""),g()}catch(t){l(t instanceof Error?t.message:String(t))}}}async function U(t){try{await c.deleteSandbox(t),j===t&&y(null),g()}catch(r){l(r instanceof Error?r.message:String(r))}}async function F(t){if(!S)return;const r={};x.current=r,w(t),d(null);try{const a=await c.testInitScript(t,S,N);if(x.current!==r)return;d(a)}catch(a){if(x.current!==r)return;d({success:!1,exitCode:-1,output:a instanceof Error?a.message:String(a)})}finally{x.current===r&&w(null)}}const G=b===null?null:b?e.jsx("span",{className:"text-[10px] px-2 py-1 rounded-md bg-green-500/10 text-green-500 font-medium",children:"Docker"}):e.jsx("span",{className:"text-[10px] px-2 py-1 rounded-md bg-amber-500/10 text-amber-500 font-medium",children:"No Docker"});function J(){if(!b)return null;const t=(s==null?void 0:s.status)==="pulling";return e.jsxs("div",{className:"rounded-xl bg-cc-card p-3 sm:p-4 mb-5",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[e.jsx("span",{className:"text-[11px] font-medium text-cc-muted",children:"Base Image"}),e.jsx("code",{className:"text-[10px] font-mono-code text-cc-fg",children:"the-companion:latest"}),(s==null?void 0:s.status)==="ready"&&e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-green-500/10 text-green-500",children:"Ready"}),(s==null?void 0:s.status)==="pulling"&&e.jsxs("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-amber-500/10 text-amber-500 flex items-center gap-1",children:[e.jsx("span",{className:"w-2.5 h-2.5 border border-amber-500/30 border-t-amber-500 rounded-full animate-spin"}),"Pulling..."]}),(s==null?void 0:s.status)==="error"&&e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-cc-error/10 text-cc-error",children:"Pull failed"}),(!s||s.status==="idle")&&e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-cc-hover text-cc-muted",children:"Not downloaded"})]}),(s==null?void 0:s.status)!=="ready"&&e.jsx("button",{onClick:L,disabled:t,className:`text-xs px-3 py-2 min-h-[36px] rounded-lg transition-colors shrink-0 ${t?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 cursor-pointer"}`,children:t?"Pulling...":"Pull"})]}),t&&(s==null?void 0:s.progress)&&s.progress.length>0&&e.jsx("pre",{className:"mt-2 px-3 py-2 text-[10px] font-mono-code bg-cc-code-bg rounded-lg text-cc-muted max-h-[120px] overflow-auto whitespace-pre-wrap",children:s.progress.slice(-20).join(`
2
- `)}),(s==null?void 0:s.status)==="error"&&s.error&&e.jsx("p",{className:"mt-2 text-[10px] text-cc-error",children:s.error})]})}return f?e.jsx("div",{className:"h-full bg-cc-bg text-cc-fg font-sans-ui antialiased overflow-y-auto overflow-x-hidden",children:e.jsxs("div",{className:"max-w-2xl mx-auto px-4 sm:px-6 py-6 sm:py-10 pb-safe",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-2",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("h1",{className:"text-lg font-semibold text-cc-fg",children:"Sandboxes"}),e.jsx("p",{className:"mt-0.5 text-[13px] text-cc-muted leading-relaxed",children:"Reusable sandbox configurations for containerized sessions."})]}),e.jsx("div",{className:"flex items-center gap-2 shrink-0",children:G})]}),J(),e.jsxs("div",{className:"flex items-center gap-2 mt-4 mb-5",children:[e.jsx("div",{className:"flex-1"}),e.jsxs("button",{onClick:()=>M(!o),className:`flex items-center gap-1.5 px-3.5 py-2.5 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer shrink-0 ${o?"bg-cc-active text-cc-fg":"bg-cc-primary hover:bg-cc-primary-hover text-white"}`,children:[e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"w-4 h-4",children:o?e.jsx("path",{d:"M18 6 6 18M6 6l12 12"}):e.jsx("path",{d:"M12 5v14M5 12h14"})}),e.jsx("span",{className:"hidden sm:inline",children:o?"Cancel":"New Sandbox"})]})]}),o&&e.jsxs("div",{className:"mb-6 rounded-xl bg-cc-card p-4 sm:p-5 space-y-3",style:{animation:"fadeSlideIn 150ms ease-out"},children:[e.jsx("input",{type:"text",value:p,onChange:t=>I(t.target.value),placeholder:"Sandbox name (e.g. node-project)",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow",onKeyDown:t=>{t.key==="Enter"&&p.trim()&&A()}}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[11px] font-medium text-cc-muted mb-1",children:"Init Script (optional)"}),e.jsx("textarea",{value:T,onChange:t=>D(t.target.value),placeholder:`# Runs inside the container before Claude starts
3
- # Example:
4
- bun install
5
- pip install -r requirements.txt`,rows:5,className:"w-full px-3 py-2.5 text-[11px] font-mono-code bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 resize-y transition-shadow",style:{minHeight:"80px"}})]}),v&&o&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-error/10 text-xs text-cc-error",children:v}),e.jsxs("div",{className:"flex items-center justify-between pt-1",children:[e.jsxs("p",{className:"text-[11px] text-cc-muted",children:["Stored in ",e.jsx("code",{className:"text-[10px]",children:"~/.companion/sandboxes/"})]}),e.jsx("button",{onClick:A,disabled:!p.trim()||k,className:`px-4 py-2.5 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${p.trim()&&!k?"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer":"bg-cc-hover text-cc-muted cursor-not-allowed"}`,children:k?"Creating...":"Create"})]})]}),e.jsx("div",{className:"flex items-center gap-2 mb-3 text-[12px] text-cc-muted",children:e.jsxs("span",{children:[u.length," sandbox",u.length!==1?"es":""]})}),V?e.jsx("div",{className:"py-12 text-center text-sm text-cc-muted",children:"Loading sandboxes..."}):u.length===0?e.jsx("div",{className:"py-12 text-center text-sm text-cc-muted",children:"No sandboxes yet."}):e.jsx("div",{className:"space-y-1",children:u.map(t=>j===t.slug?e.jsxs("div",{className:"rounded-xl bg-cc-card p-4 space-y-3",style:{animation:"fadeSlideIn 150ms ease-out"},children:[e.jsx("input",{type:"text",value:P,onChange:a=>$(a.target.value),placeholder:"Sandbox name",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow"}),e.jsxs("div",{children:[e.jsx("div",{className:"flex items-center justify-between mb-1",children:e.jsx("div",{className:"text-[11px] font-medium text-cc-muted",children:"Init Script (optional)"})}),e.jsx("textarea",{value:N,onChange:a=>{H(a.target.value),d(null)},placeholder:`# Runs inside the container before Claude starts
6
- # Example:
7
- bun install
8
- pip install -r requirements.txt`,rows:5,className:"w-full px-3 py-2.5 text-[11px] font-mono-code bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 resize-y transition-shadow",style:{minHeight:"80px"}})]}),h===t.slug&&e.jsxs("div",{className:"flex items-center gap-2 px-3 py-2 rounded-lg bg-amber-500/10 text-xs text-amber-500",children:[e.jsx("span",{className:"w-3 h-3 border-2 border-amber-500/30 border-t-amber-500 rounded-full animate-spin"}),"Testing init script..."]}),m&&h!==t.slug&&e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:`flex items-center gap-2 px-3 py-2 rounded-lg text-xs ${m.success?"bg-green-500/10 text-green-500":"bg-cc-error/10 text-cc-error"}`,children:m.success?"Test passed":`Test failed (exit ${m.exitCode})`}),m.output&&e.jsx("pre",{className:"px-3 py-2 text-[10px] font-mono-code bg-cc-code-bg rounded-lg text-cc-muted max-h-[200px] overflow-auto whitespace-pre-wrap",children:m.output})]}),e.jsxs("div",{className:"flex justify-end gap-2",children:[N.trim()&&b&&e.jsx("button",{onClick:()=>void F(t.slug),disabled:h===t.slug||!S,className:`px-3 py-2.5 min-h-[44px] text-sm rounded-lg font-medium transition-colors ${h===t.slug||!S?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 cursor-pointer"}`,children:h===t.slug?"Testing...":"Test Init Script"}),e.jsx("button",{onClick:q,className:"px-3 py-2.5 min-h-[44px] text-sm rounded-lg text-cc-muted hover:text-cc-fg hover:bg-cc-hover transition-colors cursor-pointer",children:"Cancel"}),e.jsx("button",{onClick:()=>void K(),className:"px-4 py-2.5 min-h-[44px] text-sm rounded-lg font-medium bg-cc-primary hover:bg-cc-primary-hover text-white transition-colors cursor-pointer",children:"Save"})]})]},t.slug):e.jsx(O,{sandbox:t,onStartEdit:()=>Z(t),onDelete:()=>void U(t.slug)},t.slug))}),v&&!o&&e.jsx("div",{className:"mt-4 px-3 py-2 rounded-lg bg-cc-error/10 text-xs text-cc-error",children:v})]})}):e.jsx("div",{className:"h-full bg-cc-bg text-cc-fg font-sans-ui antialiased overflow-y-auto overflow-x-hidden",children:e.jsx("div",{className:"max-w-2xl mx-auto px-4 sm:px-6 py-6 sm:py-10 pb-safe",children:e.jsx("div",{className:"py-12 text-center text-sm text-cc-muted",children:"Use embedded mode to view sandboxes."})})})}function O({sandbox:f,onStartEdit:u,onDelete:C}){return e.jsxs("div",{className:"group flex items-start gap-3 px-3 py-3 min-h-[44px] rounded-lg hover:bg-cc-hover/60 transition-colors",children:[e.jsx("div",{className:"shrink-0 mt-0.5 w-7 h-7 rounded-md bg-cc-primary/10 flex items-center justify-center",children:e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",className:"w-3.5 h-3.5 text-cc-primary",children:[e.jsx("path",{d:"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"}),e.jsx("path",{d:"M3.27 6.96 12 12.01l8.73-5.05M12 22.08V12"})]})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"flex items-center gap-2",children:e.jsx("span",{className:"text-sm font-medium text-cc-fg truncate",children:f.name})}),e.jsx("p",{className:"mt-0.5 text-xs text-cc-muted",children:f.initScript?"init script":"no init script"})]}),e.jsxs("div",{className:"shrink-0 flex items-center gap-0.5 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity",children:[e.jsx("button",{onClick:u,className:"p-2 min-h-[44px] min-w-[44px] sm:min-h-0 sm:min-w-0 sm:p-1.5 rounded-md text-cc-muted hover:text-cc-fg hover:bg-cc-active transition-colors cursor-pointer","aria-label":"Edit",children:e.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",className:"w-3.5 h-3.5",children:[e.jsx("path",{d:"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"}),e.jsx("path",{d:"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5Z"})]})}),e.jsx("button",{onClick:C,className:"p-2 min-h-[44px] min-w-[44px] sm:min-h-0 sm:min-w-0 sm:p-1.5 rounded-md text-cc-muted hover:text-cc-error hover:bg-cc-error/10 transition-colors cursor-pointer","aria-label":"Delete",children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.5",className:"w-3.5 h-3.5",children:e.jsx("path",{d:"M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14Z"})})})]})]})}export{X as SandboxManager};