@hellcoder/companion 0.105.3 → 0.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AgentsPage-Chj3i8hp.js → AgentsPage-DbvkUXVp.js} +5 -5
- package/dist/assets/{CronManager-CnSV7pph.js → CronManager-BLIoyqgi.js} +1 -1
- package/dist/assets/{IntegrationsPage-D7He8DJZ.js → IntegrationsPage-DIG0VC9S.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-BhbSx1L1.js → LinearOAuthSettingsPage-VDcZL-vG.js} +1 -1
- package/dist/assets/{LinearSettingsPage-xYU0CnhC.js → LinearSettingsPage-N9lB7uAD.js} +1 -1
- package/dist/assets/{Playground-ZMOfN3kG.js → Playground-egZO3vjk.js} +1 -1
- package/dist/assets/{PromptsPage-C-YET0qF.js → PromptsPage-ByCRKIIK.js} +1 -1
- package/dist/assets/{RunsPage-D8p_NPsc.js → RunsPage-DRLiiAa4.js} +1 -1
- package/dist/assets/{SandboxManager-DNct4A2p.js → SandboxManager-D0UrXCUj.js} +1 -1
- package/dist/assets/SettingsPage-BESpvcQy.js +1 -0
- package/dist/assets/{TailscalePage-CVXN6STS.js → TailscalePage-hiU_mAeJ.js} +1 -1
- package/dist/assets/{index-B_8Ih1pQ.js → index-Ct0BP0Nf.js} +49 -49
- package/dist/assets/{sw-register-Gj6h8GCi.js → sw-register-DqHi_6mH.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/agent-executor.test.ts +99 -2
- package/server/agent-executor.ts +34 -4
- package/server/agent-types.ts +10 -0
- package/server/auto-namer.test.ts +4 -0
- package/server/linear-connections.test.ts +4 -0
- package/server/routes/agent-routes.ts +2 -1
- package/server/routes/settings-routes.ts +10 -0
- package/server/routes.test.ts +52 -0
- package/server/session-orchestrator.test.ts +44 -1
- package/server/session-orchestrator.ts +14 -0
- package/server/settings-manager.test.ts +3 -0
- package/server/settings-manager.ts +10 -1
- package/server/ws-bridge-codex.test.ts +6 -0
- package/dist/assets/SettingsPage-Bc33muUi.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as u}from"./index-
|
|
1
|
+
import{_ as u}from"./index-Ct0BP0Nf.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-Ct0BP0Nf.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-D4AeOLki.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":"2d7031149d01133258a09051c022c4c1","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-DqHi_6mH.js"},{"revision":null,"url":"assets/index-D4AeOLki.css"},{"revision":null,"url":"assets/index-Ct0BP0Nf.js"},{"revision":null,"url":"assets/TailscalePage-hiU_mAeJ.js"},{"revision":null,"url":"assets/SettingsPage-BESpvcQy.js"},{"revision":null,"url":"assets/SandboxManager-D0UrXCUj.js"},{"revision":null,"url":"assets/RunsPage-DRLiiAa4.js"},{"revision":null,"url":"assets/PromptsPage-ByCRKIIK.js"},{"revision":null,"url":"assets/Playground-egZO3vjk.js"},{"revision":null,"url":"assets/LinearSettingsPage-N9lB7uAD.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-VDcZL-vG.js"},{"revision":null,"url":"assets/IntegrationsPage-DIG0VC9S.js"},{"revision":null,"url":"assets/CronManager-BLIoyqgi.js"},{"revision":null,"url":"assets/AgentsPage-DbvkUXVp.js"}]);ne();T(new oe(re("index.html"),{denylist:[/^\/api/,/^\/ws/]}));
|
package/package.json
CHANGED
|
@@ -438,10 +438,11 @@ describe("AgentExecutor", () => {
|
|
|
438
438
|
expect(launcher.launch).toHaveBeenCalledOnce();
|
|
439
439
|
});
|
|
440
440
|
|
|
441
|
-
it("skips when previous execution is still
|
|
442
|
-
// Simulate an agent whose previous session is still alive
|
|
441
|
+
it("skips when previous execution is still open and restartMode is 'skip'", async () => {
|
|
442
|
+
// Simulate an agent (restartMode "skip") whose previous session is still alive
|
|
443
443
|
const agent = makeAgent({
|
|
444
444
|
id: "overlapping",
|
|
445
|
+
restartMode: "skip",
|
|
445
446
|
lastSessionId: "still-running-session",
|
|
446
447
|
});
|
|
447
448
|
mockAgentStore.getAgent.mockReturnValue(agent);
|
|
@@ -723,6 +724,102 @@ describe("AgentExecutor", () => {
|
|
|
723
724
|
});
|
|
724
725
|
});
|
|
725
726
|
|
|
727
|
+
// =========================================================================
|
|
728
|
+
// restartMode — what happens when a previous run's session is still open
|
|
729
|
+
// =========================================================================
|
|
730
|
+
describe("restartMode (leftover session handling)", () => {
|
|
731
|
+
it("default 'terminate': archives the leftover session, then launches a fresh one", async () => {
|
|
732
|
+
// Regression test for the core bug: a scheduled agent's session stays alive
|
|
733
|
+
// after its turn finishes, so isAlive() is permanently true. With the default
|
|
734
|
+
// terminate mode we archive the leftover and start a new run instead of
|
|
735
|
+
// blocking forever.
|
|
736
|
+
const archiveSpy = vi.fn<(id: string) => Promise<void>>().mockResolvedValue(undefined);
|
|
737
|
+
executor.setArchivePreviousSession(archiveSpy);
|
|
738
|
+
|
|
739
|
+
const agent = makeAgent({
|
|
740
|
+
id: "terminate-agent",
|
|
741
|
+
// no restartMode set -> defaults to "terminate"
|
|
742
|
+
lastSessionId: "leftover-session",
|
|
743
|
+
});
|
|
744
|
+
mockAgentStore.getAgent.mockReturnValue(agent);
|
|
745
|
+
launcher.isAlive.mockReturnValue(true);
|
|
746
|
+
|
|
747
|
+
const result = await executor.executeAgent("terminate-agent", undefined, { triggerType: "schedule" });
|
|
748
|
+
|
|
749
|
+
// The leftover session should have been archived (intentional kill)...
|
|
750
|
+
expect(archiveSpy).toHaveBeenCalledWith("leftover-session");
|
|
751
|
+
// ...and a new session launched anyway.
|
|
752
|
+
expect(launcher.launch).toHaveBeenCalledOnce();
|
|
753
|
+
expect(result).toBeDefined();
|
|
754
|
+
expect(result!.sessionId).toBe("session-123");
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it("does not archive when no previous session is alive", async () => {
|
|
758
|
+
const archiveSpy = vi.fn<(id: string) => Promise<void>>().mockResolvedValue(undefined);
|
|
759
|
+
executor.setArchivePreviousSession(archiveSpy);
|
|
760
|
+
|
|
761
|
+
const agent = makeAgent({ id: "fresh-agent", lastSessionId: "old-session" });
|
|
762
|
+
mockAgentStore.getAgent.mockReturnValue(agent);
|
|
763
|
+
// Previous session already exited.
|
|
764
|
+
launcher.isAlive.mockReturnValue(false);
|
|
765
|
+
|
|
766
|
+
await executor.executeAgent("fresh-agent");
|
|
767
|
+
|
|
768
|
+
expect(archiveSpy).not.toHaveBeenCalled();
|
|
769
|
+
expect(launcher.launch).toHaveBeenCalledOnce();
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it("terminate mode still launches even if archiving the leftover fails", async () => {
|
|
773
|
+
// A failed archive should be logged but must not block the new run.
|
|
774
|
+
const archiveSpy = vi.fn<(id: string) => Promise<void>>().mockRejectedValue(new Error("kill failed"));
|
|
775
|
+
executor.setArchivePreviousSession(archiveSpy);
|
|
776
|
+
|
|
777
|
+
const agent = makeAgent({ id: "resilient-agent", lastSessionId: "leftover-session" });
|
|
778
|
+
mockAgentStore.getAgent.mockReturnValue(agent);
|
|
779
|
+
launcher.isAlive.mockReturnValue(true);
|
|
780
|
+
|
|
781
|
+
const result = await executor.executeAgent("resilient-agent");
|
|
782
|
+
|
|
783
|
+
expect(archiveSpy).toHaveBeenCalledWith("leftover-session");
|
|
784
|
+
expect(launcher.launch).toHaveBeenCalledOnce();
|
|
785
|
+
expect(result).toBeDefined();
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it("terminate mode without a wired archive callback warns but still launches", async () => {
|
|
789
|
+
// No setArchivePreviousSession() called -> callback undefined.
|
|
790
|
+
const agent = makeAgent({ id: "no-callback-agent", lastSessionId: "leftover-session" });
|
|
791
|
+
mockAgentStore.getAgent.mockReturnValue(agent);
|
|
792
|
+
launcher.isAlive.mockReturnValue(true);
|
|
793
|
+
|
|
794
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
795
|
+
|
|
796
|
+
const result = await executor.executeAgent("no-callback-agent");
|
|
797
|
+
|
|
798
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("no archive callback wired"));
|
|
799
|
+
// Still launches a fresh session rather than getting stuck.
|
|
800
|
+
expect(launcher.launch).toHaveBeenCalledOnce();
|
|
801
|
+
expect(result).toBeDefined();
|
|
802
|
+
|
|
803
|
+
warnSpy.mockRestore();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("skip mode with force=true bypasses the skip and launches", async () => {
|
|
807
|
+
// Manual/forced runs should never be blocked by the skip guard.
|
|
808
|
+
const agent = makeAgent({
|
|
809
|
+
id: "skip-force-agent",
|
|
810
|
+
restartMode: "skip",
|
|
811
|
+
lastSessionId: "leftover-session",
|
|
812
|
+
});
|
|
813
|
+
mockAgentStore.getAgent.mockReturnValue(agent);
|
|
814
|
+
launcher.isAlive.mockReturnValue(true);
|
|
815
|
+
|
|
816
|
+
const result = await executor.executeAgent("skip-force-agent", undefined, { force: true });
|
|
817
|
+
|
|
818
|
+
expect(launcher.launch).toHaveBeenCalledOnce();
|
|
819
|
+
expect(result).toBeDefined();
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
|
|
726
823
|
// =========================================================================
|
|
727
824
|
// waitForCLIConnection (tested indirectly via executeAgent)
|
|
728
825
|
// =========================================================================
|
package/server/agent-executor.ts
CHANGED
|
@@ -34,11 +34,24 @@ export class AgentExecutor {
|
|
|
34
34
|
/** Persistent execution store (JSONL on disk) */
|
|
35
35
|
private executionStore = new ExecutionStore();
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Callback used to intentionally terminate a leftover session before starting
|
|
39
|
+
* a new run (restartMode "terminate"). Wired by the orchestrator to its
|
|
40
|
+
* archiveSession() so the kill is registered as intentional — otherwise the
|
|
41
|
+
* keepalive relaunch would immediately respawn the process.
|
|
42
|
+
*/
|
|
43
|
+
private archivePreviousSession?: (sessionId: string) => Promise<void>;
|
|
44
|
+
|
|
37
45
|
constructor(launcher: CliLauncher, wsBridge: WsBridge) {
|
|
38
46
|
this.launcher = launcher;
|
|
39
47
|
this.wsBridge = wsBridge;
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
/** Wire the callback used to archive a leftover session (see field docs). */
|
|
51
|
+
setArchivePreviousSession(fn: (sessionId: string) => Promise<void>): void {
|
|
52
|
+
this.archivePreviousSession = fn;
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
/** Start all enabled agents with schedule triggers from disk. Called once at server startup. */
|
|
43
56
|
startAll(): void {
|
|
44
57
|
const agents = agentStore.listAgents();
|
|
@@ -123,10 +136,27 @@ export class AgentExecutor {
|
|
|
123
136
|
if (!agent) return;
|
|
124
137
|
if (!agent.enabled && !opts?.force) return;
|
|
125
138
|
|
|
126
|
-
// Overlap
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
// Overlap handling: a previous run's session may still be open. Because the
|
|
140
|
+
// CLI process stays alive after a turn finishes (it only exits on
|
|
141
|
+
// archive/kill), we can't distinguish "still working" from "idle but open"
|
|
142
|
+
// via liveness alone — so the agent's restartMode decides what to do.
|
|
143
|
+
if (agent.lastSessionId && this.launcher.isAlive(agent.lastSessionId)) {
|
|
144
|
+
const restartMode = agent.restartMode ?? "terminate";
|
|
145
|
+
if (restartMode === "terminate") {
|
|
146
|
+
// Terminate the leftover session (intentional kill) before starting fresh.
|
|
147
|
+
console.log(`[agent-executor] Terminating leftover session for "${agent.name}" before new run (${agent.lastSessionId})`);
|
|
148
|
+
if (this.archivePreviousSession) {
|
|
149
|
+
await this.archivePreviousSession(agent.lastSessionId).catch((err) => {
|
|
150
|
+
console.error(`[agent-executor] Failed to terminate leftover session ${agent.lastSessionId}:`, err);
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
console.warn(`[agent-executor] restartMode="terminate" but no archive callback wired; leftover session ${agent.lastSessionId} left running`);
|
|
154
|
+
}
|
|
155
|
+
} else if (!opts?.force) {
|
|
156
|
+
// restartMode "skip": don't start while the previous run is still open.
|
|
157
|
+
console.log(`[agent-executor] Skipping "${agent.name}" — previous execution still open (${agent.lastSessionId})`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
130
160
|
}
|
|
131
161
|
|
|
132
162
|
const triggerType = opts?.triggerType || "manual";
|
package/server/agent-types.ts
CHANGED
|
@@ -37,6 +37,16 @@ export interface AgentConfig {
|
|
|
37
37
|
allowedTools?: string[];
|
|
38
38
|
/** Codex-specific: internet access */
|
|
39
39
|
codexInternetAccess?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* What to do when a previous run's session is still open (alive) at the moment
|
|
42
|
+
* this agent is triggered again:
|
|
43
|
+
* - "terminate" (default): archive/kill the leftover session, then start fresh.
|
|
44
|
+
* - "skip": don't start a new run while the previous one is still open.
|
|
45
|
+
* Because the CLI process stays alive after a turn finishes (it only exits on
|
|
46
|
+
* archive/kill), "skip" would otherwise block every future scheduled run until
|
|
47
|
+
* the session is manually archived.
|
|
48
|
+
*/
|
|
49
|
+
restartMode?: "skip" | "terminate";
|
|
40
50
|
|
|
41
51
|
// ── Prompt ──
|
|
42
52
|
/** Prompt template. Use {{input}} as placeholder for trigger-provided input */
|
|
@@ -37,6 +37,7 @@ beforeEach(() => {
|
|
|
37
37
|
publicUrl: "",
|
|
38
38
|
updateChannel: "stable",
|
|
39
39
|
dockerAutoUpdate: false,
|
|
40
|
+
proactiveKeepaliveEnabled: true,
|
|
40
41
|
updatedAt: 0,
|
|
41
42
|
});
|
|
42
43
|
});
|
|
@@ -80,6 +81,7 @@ describe("generateSessionTitle", () => {
|
|
|
80
81
|
publicUrl: "",
|
|
81
82
|
updateChannel: "stable",
|
|
82
83
|
dockerAutoUpdate: false,
|
|
84
|
+
proactiveKeepaliveEnabled: true,
|
|
83
85
|
updatedAt: 0,
|
|
84
86
|
});
|
|
85
87
|
|
|
@@ -130,6 +132,7 @@ describe("generateSessionTitle", () => {
|
|
|
130
132
|
publicUrl: "",
|
|
131
133
|
updateChannel: "stable",
|
|
132
134
|
dockerAutoUpdate: false,
|
|
135
|
+
proactiveKeepaliveEnabled: true,
|
|
133
136
|
updatedAt: 0,
|
|
134
137
|
});
|
|
135
138
|
mockFetch.mockResolvedValueOnce({
|
|
@@ -209,6 +212,7 @@ describe("generateSessionTitle", () => {
|
|
|
209
212
|
publicUrl: "",
|
|
210
213
|
updateChannel: "stable",
|
|
211
214
|
dockerAutoUpdate: false,
|
|
215
|
+
proactiveKeepaliveEnabled: true,
|
|
212
216
|
updatedAt: 0,
|
|
213
217
|
});
|
|
214
218
|
mockFetch.mockResolvedValueOnce({
|
|
@@ -63,6 +63,7 @@ beforeEach(() => {
|
|
|
63
63
|
publicUrl: "",
|
|
64
64
|
updateChannel: "stable",
|
|
65
65
|
dockerAutoUpdate: false,
|
|
66
|
+
proactiveKeepaliveEnabled: true,
|
|
66
67
|
updatedAt: 0,
|
|
67
68
|
});
|
|
68
69
|
});
|
|
@@ -194,6 +195,7 @@ describe("linear-connections", () => {
|
|
|
194
195
|
publicUrl: "",
|
|
195
196
|
updateChannel: "stable",
|
|
196
197
|
dockerAutoUpdate: false,
|
|
198
|
+
proactiveKeepaliveEnabled: true,
|
|
197
199
|
updatedAt: 0,
|
|
198
200
|
});
|
|
199
201
|
// Need to reset so migration runs with updated mock
|
|
@@ -235,6 +237,7 @@ describe("linear-connections", () => {
|
|
|
235
237
|
publicUrl: "",
|
|
236
238
|
updateChannel: "stable",
|
|
237
239
|
dockerAutoUpdate: false,
|
|
240
|
+
proactiveKeepaliveEnabled: true,
|
|
238
241
|
updatedAt: 0,
|
|
239
242
|
});
|
|
240
243
|
_resetForTest(join(tempDir, "linear-connections-migrate.json"));
|
|
@@ -295,6 +298,7 @@ describe("linear-connections", () => {
|
|
|
295
298
|
publicUrl: "",
|
|
296
299
|
updateChannel: "stable",
|
|
297
300
|
dockerAutoUpdate: false,
|
|
301
|
+
proactiveKeepaliveEnabled: true,
|
|
298
302
|
updatedAt: 0,
|
|
299
303
|
});
|
|
300
304
|
|
|
@@ -11,7 +11,7 @@ import { getOAuthConnection, createOAuthConnection } from "../linear-oauth-conne
|
|
|
11
11
|
const EDITABLE_FIELDS = [
|
|
12
12
|
"name", "description", "icon", "version",
|
|
13
13
|
"backendType", "model", "permissionMode", "cwd",
|
|
14
|
-
"envSlug", "env", "allowedTools", "codexInternetAccess",
|
|
14
|
+
"envSlug", "env", "allowedTools", "codexInternetAccess", "restartMode",
|
|
15
15
|
"prompt", "mcpServers", "skills",
|
|
16
16
|
"container", "branch", "createBranch", "useWorktree",
|
|
17
17
|
"triggers", "enabled",
|
|
@@ -42,6 +42,7 @@ function buildCreateInput(
|
|
|
42
42
|
env: body.env as Record<string, string> | undefined,
|
|
43
43
|
allowedTools: body.allowedTools as string[] | undefined,
|
|
44
44
|
codexInternetAccess: body.codexInternetAccess as boolean | undefined,
|
|
45
|
+
restartMode: body.restartMode as AgentConfig["restartMode"] | undefined,
|
|
45
46
|
prompt: (body.prompt as string | undefined) || "",
|
|
46
47
|
mcpServers: body.mcpServers as AgentConfig["mcpServers"] | undefined,
|
|
47
48
|
skills: body.skills as string[] | undefined,
|
|
@@ -29,6 +29,7 @@ export function registerSettingsRoutes(api: Hono): void {
|
|
|
29
29
|
publicUrl: settings.publicUrl,
|
|
30
30
|
updateChannel: settings.updateChannel,
|
|
31
31
|
dockerAutoUpdate: settings.dockerAutoUpdate,
|
|
32
|
+
proactiveKeepaliveEnabled: settings.proactiveKeepaliveEnabled,
|
|
32
33
|
cliBridgeMode: settings.cliBridgeMode,
|
|
33
34
|
});
|
|
34
35
|
});
|
|
@@ -104,6 +105,9 @@ export function registerSettingsRoutes(api: Hono): void {
|
|
|
104
105
|
if (body.dockerAutoUpdate !== undefined && typeof body.dockerAutoUpdate !== "boolean") {
|
|
105
106
|
return c.json({ error: "dockerAutoUpdate must be a boolean" }, 400);
|
|
106
107
|
}
|
|
108
|
+
if (body.proactiveKeepaliveEnabled !== undefined && typeof body.proactiveKeepaliveEnabled !== "boolean") {
|
|
109
|
+
return c.json({ error: "proactiveKeepaliveEnabled must be a boolean" }, 400);
|
|
110
|
+
}
|
|
107
111
|
if (body.cliBridgeMode !== undefined && body.cliBridgeMode !== "loopback" && body.cliBridgeMode !== "jsonHandoff") {
|
|
108
112
|
return c.json({ error: "cliBridgeMode must be 'loopback' or 'jsonHandoff'" }, 400);
|
|
109
113
|
}
|
|
@@ -121,6 +125,7 @@ export function registerSettingsRoutes(api: Hono): void {
|
|
|
121
125
|
|| body.publicUrl !== undefined
|
|
122
126
|
|| body.updateChannel !== undefined
|
|
123
127
|
|| body.dockerAutoUpdate !== undefined
|
|
128
|
+
|| body.proactiveKeepaliveEnabled !== undefined
|
|
124
129
|
|| body.cliBridgeMode !== undefined;
|
|
125
130
|
if (!hasAnyField) {
|
|
126
131
|
return c.json({ error: "At least one settings field is required" }, 400);
|
|
@@ -215,6 +220,10 @@ export function registerSettingsRoutes(api: Hono): void {
|
|
|
215
220
|
typeof body.dockerAutoUpdate === "boolean"
|
|
216
221
|
? body.dockerAutoUpdate
|
|
217
222
|
: undefined,
|
|
223
|
+
proactiveKeepaliveEnabled:
|
|
224
|
+
typeof body.proactiveKeepaliveEnabled === "boolean"
|
|
225
|
+
? body.proactiveKeepaliveEnabled
|
|
226
|
+
: undefined,
|
|
218
227
|
cliBridgeMode:
|
|
219
228
|
body.cliBridgeMode === "loopback" || body.cliBridgeMode === "jsonHandoff"
|
|
220
229
|
? (body.cliBridgeMode as CliBridgeMode)
|
|
@@ -243,6 +252,7 @@ export function registerSettingsRoutes(api: Hono): void {
|
|
|
243
252
|
publicUrl: settings.publicUrl,
|
|
244
253
|
updateChannel: settings.updateChannel,
|
|
245
254
|
dockerAutoUpdate: settings.dockerAutoUpdate,
|
|
255
|
+
proactiveKeepaliveEnabled: settings.proactiveKeepaliveEnabled,
|
|
246
256
|
cliBridgeMode: settings.cliBridgeMode,
|
|
247
257
|
});
|
|
248
258
|
});
|
package/server/routes.test.ts
CHANGED
|
@@ -1149,6 +1149,7 @@ describe("GET /api/sessions/:id/archive-info", () => {
|
|
|
1149
1149
|
publicUrl: "",
|
|
1150
1150
|
updateChannel: "stable",
|
|
1151
1151
|
dockerAutoUpdate: false,
|
|
1152
|
+
proactiveKeepaliveEnabled: true,
|
|
1152
1153
|
updatedAt: 0,
|
|
1153
1154
|
});
|
|
1154
1155
|
const res = await app.request("/api/sessions/s1/archive-info", { method: "GET" });
|
|
@@ -1519,6 +1520,7 @@ describe("GET /api/settings", () => {
|
|
|
1519
1520
|
publicUrl: "",
|
|
1520
1521
|
updateChannel: "stable",
|
|
1521
1522
|
dockerAutoUpdate: false,
|
|
1523
|
+
proactiveKeepaliveEnabled: true,
|
|
1522
1524
|
updatedAt: 123,
|
|
1523
1525
|
});
|
|
1524
1526
|
|
|
@@ -1547,6 +1549,7 @@ describe("GET /api/settings", () => {
|
|
|
1547
1549
|
publicUrl: "",
|
|
1548
1550
|
updateChannel: "stable",
|
|
1549
1551
|
dockerAutoUpdate: false,
|
|
1552
|
+
proactiveKeepaliveEnabled: true,
|
|
1550
1553
|
});
|
|
1551
1554
|
});
|
|
1552
1555
|
|
|
@@ -1575,6 +1578,7 @@ describe("GET /api/settings", () => {
|
|
|
1575
1578
|
publicUrl: "",
|
|
1576
1579
|
updateChannel: "stable",
|
|
1577
1580
|
dockerAutoUpdate: false,
|
|
1581
|
+
proactiveKeepaliveEnabled: true,
|
|
1578
1582
|
updatedAt: 123,
|
|
1579
1583
|
});
|
|
1580
1584
|
|
|
@@ -1603,6 +1607,7 @@ describe("GET /api/settings", () => {
|
|
|
1603
1607
|
publicUrl: "",
|
|
1604
1608
|
updateChannel: "stable",
|
|
1605
1609
|
dockerAutoUpdate: false,
|
|
1610
|
+
proactiveKeepaliveEnabled: true,
|
|
1606
1611
|
});
|
|
1607
1612
|
});
|
|
1608
1613
|
|
|
@@ -1632,6 +1637,7 @@ describe("GET /api/settings", () => {
|
|
|
1632
1637
|
publicUrl: "https://example.com",
|
|
1633
1638
|
updateChannel: "stable",
|
|
1634
1639
|
dockerAutoUpdate: false,
|
|
1640
|
+
proactiveKeepaliveEnabled: true,
|
|
1635
1641
|
updatedAt: 100,
|
|
1636
1642
|
});
|
|
1637
1643
|
|
|
@@ -1669,6 +1675,7 @@ describe("PUT /api/settings", () => {
|
|
|
1669
1675
|
publicUrl: "",
|
|
1670
1676
|
updateChannel: "stable",
|
|
1671
1677
|
dockerAutoUpdate: false,
|
|
1678
|
+
proactiveKeepaliveEnabled: true,
|
|
1672
1679
|
updatedAt: 456,
|
|
1673
1680
|
});
|
|
1674
1681
|
|
|
@@ -1719,6 +1726,7 @@ describe("PUT /api/settings", () => {
|
|
|
1719
1726
|
publicUrl: "",
|
|
1720
1727
|
updateChannel: "stable",
|
|
1721
1728
|
dockerAutoUpdate: false,
|
|
1729
|
+
proactiveKeepaliveEnabled: true,
|
|
1722
1730
|
});
|
|
1723
1731
|
});
|
|
1724
1732
|
|
|
@@ -1747,6 +1755,7 @@ describe("PUT /api/settings", () => {
|
|
|
1747
1755
|
publicUrl: "",
|
|
1748
1756
|
updateChannel: "stable",
|
|
1749
1757
|
dockerAutoUpdate: false,
|
|
1758
|
+
proactiveKeepaliveEnabled: true,
|
|
1750
1759
|
updatedAt: 789,
|
|
1751
1760
|
});
|
|
1752
1761
|
|
|
@@ -1792,6 +1801,7 @@ describe("PUT /api/settings", () => {
|
|
|
1792
1801
|
publicUrl: "",
|
|
1793
1802
|
updateChannel: "stable",
|
|
1794
1803
|
dockerAutoUpdate: false,
|
|
1804
|
+
proactiveKeepaliveEnabled: true,
|
|
1795
1805
|
updatedAt: 999,
|
|
1796
1806
|
});
|
|
1797
1807
|
|
|
@@ -1888,6 +1898,7 @@ describe("PUT /api/settings", () => {
|
|
|
1888
1898
|
publicUrl: "https://my-server.com",
|
|
1889
1899
|
updateChannel: "stable",
|
|
1890
1900
|
dockerAutoUpdate: false,
|
|
1901
|
+
proactiveKeepaliveEnabled: true,
|
|
1891
1902
|
updatedAt: 500,
|
|
1892
1903
|
});
|
|
1893
1904
|
|
|
@@ -1993,6 +2004,31 @@ describe("PUT /api/settings", () => {
|
|
|
1993
2004
|
const json = await res.json();
|
|
1994
2005
|
expect(json).toEqual({ error: "dockerAutoUpdate must be a boolean" });
|
|
1995
2006
|
});
|
|
2007
|
+
|
|
2008
|
+
// Validates that proactiveKeepaliveEnabled must be a boolean
|
|
2009
|
+
it("returns 400 for non-boolean proactiveKeepaliveEnabled", async () => {
|
|
2010
|
+
const res = await app.request("/api/settings", {
|
|
2011
|
+
method: "PUT",
|
|
2012
|
+
headers: { "Content-Type": "application/json" },
|
|
2013
|
+
body: JSON.stringify({ proactiveKeepaliveEnabled: "nope" }),
|
|
2014
|
+
});
|
|
2015
|
+
expect(res.status).toBe(400);
|
|
2016
|
+
const json = await res.json();
|
|
2017
|
+
expect(json).toEqual({ error: "proactiveKeepaliveEnabled must be a boolean" });
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
// Accepts a boolean proactiveKeepaliveEnabled and forwards it to updateSettings
|
|
2021
|
+
it("accepts proactiveKeepaliveEnabled and forwards it to updateSettings", async () => {
|
|
2022
|
+
const res = await app.request("/api/settings", {
|
|
2023
|
+
method: "PUT",
|
|
2024
|
+
headers: { "Content-Type": "application/json" },
|
|
2025
|
+
body: JSON.stringify({ proactiveKeepaliveEnabled: false }),
|
|
2026
|
+
});
|
|
2027
|
+
expect(res.status).toBe(200);
|
|
2028
|
+
expect(settingsManager.updateSettings).toHaveBeenCalledWith(
|
|
2029
|
+
expect.objectContaining({ proactiveKeepaliveEnabled: false }),
|
|
2030
|
+
);
|
|
2031
|
+
});
|
|
1996
2032
|
});
|
|
1997
2033
|
|
|
1998
2034
|
describe("POST /api/settings/anthropic/verify", () => {
|
|
@@ -2114,6 +2150,7 @@ describe("GET /api/linear/issues", () => {
|
|
|
2114
2150
|
publicUrl: "",
|
|
2115
2151
|
updateChannel: "stable",
|
|
2116
2152
|
dockerAutoUpdate: false,
|
|
2153
|
+
proactiveKeepaliveEnabled: true,
|
|
2117
2154
|
updatedAt: 0,
|
|
2118
2155
|
});
|
|
2119
2156
|
vi.mocked(resolveApiKey).mockReturnValue(null);
|
|
@@ -2149,6 +2186,7 @@ describe("GET /api/linear/issues", () => {
|
|
|
2149
2186
|
publicUrl: "",
|
|
2150
2187
|
updateChannel: "stable",
|
|
2151
2188
|
dockerAutoUpdate: false,
|
|
2189
|
+
proactiveKeepaliveEnabled: true,
|
|
2152
2190
|
updatedAt: 0,
|
|
2153
2191
|
});
|
|
2154
2192
|
|
|
@@ -2237,6 +2275,7 @@ describe("GET /api/linear/issues", () => {
|
|
|
2237
2275
|
publicUrl: "",
|
|
2238
2276
|
updateChannel: "stable",
|
|
2239
2277
|
dockerAutoUpdate: false,
|
|
2278
|
+
proactiveKeepaliveEnabled: true,
|
|
2240
2279
|
updatedAt: 0,
|
|
2241
2280
|
});
|
|
2242
2281
|
|
|
@@ -2332,6 +2371,7 @@ describe("GET /api/linear/issues", () => {
|
|
|
2332
2371
|
publicUrl: "",
|
|
2333
2372
|
updateChannel: "stable",
|
|
2334
2373
|
dockerAutoUpdate: false,
|
|
2374
|
+
proactiveKeepaliveEnabled: true,
|
|
2335
2375
|
updatedAt: 0,
|
|
2336
2376
|
});
|
|
2337
2377
|
|
|
@@ -2392,6 +2432,7 @@ describe("GET /api/linear/connection", () => {
|
|
|
2392
2432
|
publicUrl: "",
|
|
2393
2433
|
updateChannel: "stable",
|
|
2394
2434
|
dockerAutoUpdate: false,
|
|
2435
|
+
proactiveKeepaliveEnabled: true,
|
|
2395
2436
|
updatedAt: 0,
|
|
2396
2437
|
});
|
|
2397
2438
|
vi.mocked(resolveApiKey).mockReturnValue(null);
|
|
@@ -2427,6 +2468,7 @@ describe("GET /api/linear/connection", () => {
|
|
|
2427
2468
|
publicUrl: "",
|
|
2428
2469
|
updateChannel: "stable",
|
|
2429
2470
|
dockerAutoUpdate: false,
|
|
2471
|
+
proactiveKeepaliveEnabled: true,
|
|
2430
2472
|
updatedAt: 0,
|
|
2431
2473
|
});
|
|
2432
2474
|
|
|
@@ -2484,6 +2526,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
|
|
|
2484
2526
|
publicUrl: "",
|
|
2485
2527
|
updateChannel: "stable",
|
|
2486
2528
|
dockerAutoUpdate: false,
|
|
2529
|
+
proactiveKeepaliveEnabled: true,
|
|
2487
2530
|
updatedAt: 0,
|
|
2488
2531
|
});
|
|
2489
2532
|
|
|
@@ -2523,6 +2566,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
|
|
|
2523
2566
|
publicUrl: "",
|
|
2524
2567
|
updateChannel: "stable",
|
|
2525
2568
|
dockerAutoUpdate: false,
|
|
2569
|
+
proactiveKeepaliveEnabled: true,
|
|
2526
2570
|
updatedAt: 0,
|
|
2527
2571
|
});
|
|
2528
2572
|
|
|
@@ -2561,6 +2605,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
|
|
|
2561
2605
|
publicUrl: "",
|
|
2562
2606
|
updateChannel: "stable",
|
|
2563
2607
|
dockerAutoUpdate: false,
|
|
2608
|
+
proactiveKeepaliveEnabled: true,
|
|
2564
2609
|
updatedAt: 0,
|
|
2565
2610
|
});
|
|
2566
2611
|
vi.mocked(resolveApiKey).mockReturnValue(null);
|
|
@@ -2601,6 +2646,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
|
|
|
2601
2646
|
publicUrl: "",
|
|
2602
2647
|
updateChannel: "stable",
|
|
2603
2648
|
dockerAutoUpdate: false,
|
|
2649
|
+
proactiveKeepaliveEnabled: true,
|
|
2604
2650
|
updatedAt: 0,
|
|
2605
2651
|
});
|
|
2606
2652
|
|
|
@@ -2675,6 +2721,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
|
|
|
2675
2721
|
publicUrl: "",
|
|
2676
2722
|
updateChannel: "stable",
|
|
2677
2723
|
dockerAutoUpdate: false,
|
|
2724
|
+
proactiveKeepaliveEnabled: true,
|
|
2678
2725
|
updatedAt: 0,
|
|
2679
2726
|
});
|
|
2680
2727
|
|
|
@@ -2728,6 +2775,7 @@ describe("GET /api/linear/projects", () => {
|
|
|
2728
2775
|
publicUrl: "",
|
|
2729
2776
|
updateChannel: "stable",
|
|
2730
2777
|
dockerAutoUpdate: false,
|
|
2778
|
+
proactiveKeepaliveEnabled: true,
|
|
2731
2779
|
updatedAt: 0,
|
|
2732
2780
|
});
|
|
2733
2781
|
vi.mocked(resolveApiKey).mockReturnValue(null);
|
|
@@ -2763,6 +2811,7 @@ describe("GET /api/linear/projects", () => {
|
|
|
2763
2811
|
publicUrl: "",
|
|
2764
2812
|
updateChannel: "stable",
|
|
2765
2813
|
dockerAutoUpdate: false,
|
|
2814
|
+
proactiveKeepaliveEnabled: true,
|
|
2766
2815
|
updatedAt: 0,
|
|
2767
2816
|
});
|
|
2768
2817
|
|
|
@@ -2828,6 +2877,7 @@ describe("GET /api/linear/project-issues", () => {
|
|
|
2828
2877
|
publicUrl: "",
|
|
2829
2878
|
updateChannel: "stable",
|
|
2830
2879
|
dockerAutoUpdate: false,
|
|
2880
|
+
proactiveKeepaliveEnabled: true,
|
|
2831
2881
|
updatedAt: 0,
|
|
2832
2882
|
});
|
|
2833
2883
|
vi.mocked(resolveApiKey).mockReturnValue(null);
|
|
@@ -2863,6 +2913,7 @@ describe("GET /api/linear/project-issues", () => {
|
|
|
2863
2913
|
publicUrl: "",
|
|
2864
2914
|
updateChannel: "stable",
|
|
2865
2915
|
dockerAutoUpdate: false,
|
|
2916
|
+
proactiveKeepaliveEnabled: true,
|
|
2866
2917
|
updatedAt: 0,
|
|
2867
2918
|
});
|
|
2868
2919
|
|
|
@@ -2943,6 +2994,7 @@ describe("GET /api/linear/project-issues", () => {
|
|
|
2943
2994
|
publicUrl: "",
|
|
2944
2995
|
updateChannel: "stable",
|
|
2945
2996
|
dockerAutoUpdate: false,
|
|
2997
|
+
proactiveKeepaliveEnabled: true,
|
|
2946
2998
|
updatedAt: 0,
|
|
2947
2999
|
});
|
|
2948
3000
|
|