@hellcoder/companion 0.116.0 → 0.116.1-preview.20260814041931.d191f06
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-BBi92OuO.js → AgentsPage-BAalV1v6.js} +5 -5
- package/dist/assets/CronManager-B2Vo1YmB.js +1 -0
- package/dist/assets/{DashboardPage-BvuDoRlp.js → DashboardPage-BOvDiP1W.js} +1 -1
- package/dist/assets/{IntegrationsPage-CK_ThzWg.js → IntegrationsPage-kOXLH5KU.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-InTf-E7u.js → LinearOAuthSettingsPage-H-uUJ-ub.js} +1 -1
- package/dist/assets/{LinearSettingsPage-BwjC9Vd-.js → LinearSettingsPage-CD_x4AGr.js} +1 -1
- package/dist/assets/MagicUIDashboard-9Jgiv4rb.js +1119 -0
- package/dist/assets/MagicUIView-BLEcS9XO.js +1 -0
- package/dist/assets/{Playground-DlzomEOu.js → Playground-QLrd9q16.js} +10 -9
- package/dist/assets/{PromptsPage-hRZhLUYR.js → PromptsPage-11eJ6ak7.js} +1 -1
- package/dist/assets/{RunsPage-zsVmbFoi.js → RunsPage-Bz3udIL2.js} +1 -1
- package/dist/assets/{SandboxManager-Bq_aXA1z.js → SandboxManager-Dv47pbC6.js} +1 -1
- package/dist/assets/SettingsPage-C_2ftG-C.js +1 -0
- package/dist/assets/{TailscalePage-fE7Z9uiF.js → TailscalePage-DzMX_7XA.js} +1 -1
- package/dist/assets/index-DSCRqqpK.css +1 -0
- package/dist/assets/index-DspbwgSL.js +140 -0
- package/dist/assets/{sw-register-Cjt2BQTW.js → sw-register-CarYQEeI.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/package.json +4 -1
- package/server/async-message-queue.ts +38 -0
- package/server/auto-namer.test.ts +8 -0
- package/server/claude-sdk-adapter.ts +1 -36
- package/server/event-bus-types.ts +32 -1
- package/server/index.ts +7 -0
- package/server/linear-agent-bridge.ts +6 -72
- package/server/linear-connections.test.ts +8 -0
- package/server/magic-ui-ops.test.ts +239 -0
- package/server/magic-ui-ops.ts +365 -0
- package/server/magic-ui-settings.test.ts +56 -0
- package/server/magic-ui-settings.ts +38 -0
- package/server/magic-ui-store.ts +94 -0
- package/server/magic-ui-types.ts +138 -0
- package/server/magic-ui-watcher.test.ts +288 -0
- package/server/magic-ui-watcher.ts +623 -0
- package/server/message-extract.ts +79 -0
- package/server/routes/settings-routes.ts +22 -0
- package/server/routes.test.ts +59 -0
- package/server/session-types.ts +15 -1
- package/server/settings-manager.test.ts +6 -0
- package/server/settings-manager.ts +24 -1
- package/server/ws-bridge-browser-ingest.ts +1 -0
- package/server/ws-bridge-codex.test.ts +12 -0
- package/server/ws-bridge-controls.ts +9 -0
- package/server/ws-bridge.ts +65 -0
- package/dist/assets/CronManager-DxLaNh9T.js +0 -1
- package/dist/assets/SettingsPage-BvrrlEKj.js +0 -1
- package/dist/assets/index--SnXduR0.js +0 -139
- package/dist/assets/index-DcQxHfE7.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as u}from"./index
|
|
1
|
+
import{_ as u}from"./index-DspbwgSL.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,8 +11,8 @@
|
|
|
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
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-DspbwgSL.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DSCRqqpK.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body class="bg-cc-bg text-cc-fg">
|
|
18
18
|
<div id="root"></div>
|
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":"435beaa5ccfe53f5052e5e930e8e8f3f","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-CarYQEeI.js"},{"revision":null,"url":"assets/index-DspbwgSL.js"},{"revision":null,"url":"assets/index-DSCRqqpK.css"},{"revision":null,"url":"assets/TailscalePage-DzMX_7XA.js"},{"revision":null,"url":"assets/SettingsPage-C_2ftG-C.js"},{"revision":null,"url":"assets/SandboxManager-Dv47pbC6.js"},{"revision":null,"url":"assets/RunsPage-Bz3udIL2.js"},{"revision":null,"url":"assets/PromptsPage-11eJ6ak7.js"},{"revision":null,"url":"assets/Playground-QLrd9q16.js"},{"revision":null,"url":"assets/MagicUIView-BLEcS9XO.js"},{"revision":null,"url":"assets/MagicUIDashboard-9Jgiv4rb.js"},{"revision":null,"url":"assets/LinearSettingsPage-CD_x4AGr.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-H-uUJ-ub.js"},{"revision":null,"url":"assets/IntegrationsPage-kOXLH5KU.js"},{"revision":null,"url":"assets/DashboardPage-BOvDiP1W.js"},{"revision":null,"url":"assets/CronManager-B2Vo1YmB.js"},{"revision":null,"url":"assets/AgentsPage-BAalV1v6.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.116.
|
|
3
|
+
"version": "0.116.1-preview.20260814041931.d191f06",
|
|
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",
|
|
@@ -67,12 +67,14 @@
|
|
|
67
67
|
"@codemirror/view": "6.39.15",
|
|
68
68
|
"@uiw/react-codemirror": "4.25.4",
|
|
69
69
|
"axe-core": "^4.11.1",
|
|
70
|
+
"chart.js": "^4.5.1",
|
|
70
71
|
"croner": "^10.0.1",
|
|
71
72
|
"diff": "^8.0.3",
|
|
72
73
|
"fzf": "^0.5.2",
|
|
73
74
|
"hono": "^4.7.0",
|
|
74
75
|
"posthog-js": "^1.347.2",
|
|
75
76
|
"qrcode": "^1.5.4",
|
|
77
|
+
"sanitize-html": "^2.17.7",
|
|
76
78
|
"ws": "^8.19.0"
|
|
77
79
|
},
|
|
78
80
|
"devDependencies": {
|
|
@@ -84,6 +86,7 @@
|
|
|
84
86
|
"@types/qrcode": "^1.5.6",
|
|
85
87
|
"@types/react": "^19.0.0",
|
|
86
88
|
"@types/react-dom": "^19.0.0",
|
|
89
|
+
"@types/sanitize-html": "^2.16.1",
|
|
87
90
|
"@types/ws": "^8.18.1",
|
|
88
91
|
"@vitejs/plugin-react": "^4.4.0",
|
|
89
92
|
"@vitest/coverage-v8": "^4.0.18",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal async queue that adapts push-style sends to a pull-style
|
|
3
|
+
* AsyncIterable — the input primitive for the Agent SDK's `query()`
|
|
4
|
+
* streaming prompt. Shared by SdkClaudeAdapter and the MagicUI watcher.
|
|
5
|
+
*/
|
|
6
|
+
export class AsyncMessageQueue<T> implements AsyncIterable<T> {
|
|
7
|
+
private buffer: T[] = [];
|
|
8
|
+
private waiters: Array<(r: IteratorResult<T>) => void> = [];
|
|
9
|
+
private closed = false;
|
|
10
|
+
|
|
11
|
+
push(item: T): void {
|
|
12
|
+
if (this.closed) return;
|
|
13
|
+
const waiter = this.waiters.shift();
|
|
14
|
+
if (waiter) waiter({ value: item, done: false });
|
|
15
|
+
else this.buffer.push(item);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
close(): void {
|
|
19
|
+
this.closed = true;
|
|
20
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
21
|
+
waiter({ value: undefined as never, done: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
26
|
+
return {
|
|
27
|
+
next: (): Promise<IteratorResult<T>> => {
|
|
28
|
+
if (this.buffer.length > 0) {
|
|
29
|
+
return Promise.resolve({ value: this.buffer.shift() as T, done: false });
|
|
30
|
+
}
|
|
31
|
+
if (this.closed) {
|
|
32
|
+
return Promise.resolve({ value: undefined as never, done: true });
|
|
33
|
+
}
|
|
34
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -38,6 +38,8 @@ beforeEach(() => {
|
|
|
38
38
|
dashboardModel: "claude-haiku-4-5",
|
|
39
39
|
dashboardRunHour: 3,
|
|
40
40
|
dashboardMaxSessionsPerRun: 30,
|
|
41
|
+
magicUiEnabled: false,
|
|
42
|
+
magicUiModel: "claude-haiku-4-5",
|
|
41
43
|
publicUrl: "",
|
|
42
44
|
updateChannel: "stable",
|
|
43
45
|
dockerAutoUpdate: false,
|
|
@@ -89,6 +91,8 @@ describe("generateSessionTitle", () => {
|
|
|
89
91
|
dashboardModel: "claude-haiku-4-5",
|
|
90
92
|
dashboardRunHour: 3,
|
|
91
93
|
dashboardMaxSessionsPerRun: 30,
|
|
94
|
+
magicUiEnabled: false,
|
|
95
|
+
magicUiModel: "claude-haiku-4-5",
|
|
92
96
|
publicUrl: "",
|
|
93
97
|
updateChannel: "stable",
|
|
94
98
|
dockerAutoUpdate: false,
|
|
@@ -147,6 +151,8 @@ describe("generateSessionTitle", () => {
|
|
|
147
151
|
dashboardModel: "claude-haiku-4-5",
|
|
148
152
|
dashboardRunHour: 3,
|
|
149
153
|
dashboardMaxSessionsPerRun: 30,
|
|
154
|
+
magicUiEnabled: false,
|
|
155
|
+
magicUiModel: "claude-haiku-4-5",
|
|
150
156
|
publicUrl: "",
|
|
151
157
|
updateChannel: "stable",
|
|
152
158
|
dockerAutoUpdate: false,
|
|
@@ -234,6 +240,8 @@ describe("generateSessionTitle", () => {
|
|
|
234
240
|
dashboardModel: "claude-haiku-4-5",
|
|
235
241
|
dashboardRunHour: 3,
|
|
236
242
|
dashboardMaxSessionsPerRun: 30,
|
|
243
|
+
magicUiEnabled: false,
|
|
244
|
+
magicUiModel: "claude-haiku-4-5",
|
|
237
245
|
publicUrl: "",
|
|
238
246
|
updateChannel: "stable",
|
|
239
247
|
dockerAutoUpdate: false,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
39
39
|
import { ClaudeAdapter } from "./claude-adapter.js";
|
|
40
40
|
import { log } from "./logger.js";
|
|
41
|
+
import { AsyncMessageQueue } from "./async-message-queue.js";
|
|
41
42
|
|
|
42
43
|
export interface SdkAttachOptions {
|
|
43
44
|
model?: string;
|
|
@@ -57,42 +58,6 @@ export interface SdkAttachOptions {
|
|
|
57
58
|
claudeBinary?: string;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
/** Minimal async queue that adapts push-style sends to the SDK's
|
|
61
|
-
* pull-style AsyncIterable input. */
|
|
62
|
-
class AsyncMessageQueue<T> implements AsyncIterable<T> {
|
|
63
|
-
private buffer: T[] = [];
|
|
64
|
-
private waiters: Array<(r: IteratorResult<T>) => void> = [];
|
|
65
|
-
private closed = false;
|
|
66
|
-
|
|
67
|
-
push(item: T): void {
|
|
68
|
-
if (this.closed) return;
|
|
69
|
-
const waiter = this.waiters.shift();
|
|
70
|
-
if (waiter) waiter({ value: item, done: false });
|
|
71
|
-
else this.buffer.push(item);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
close(): void {
|
|
75
|
-
this.closed = true;
|
|
76
|
-
for (const waiter of this.waiters.splice(0)) {
|
|
77
|
-
waiter({ value: undefined as never, done: true });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
82
|
-
return {
|
|
83
|
-
next: (): Promise<IteratorResult<T>> => {
|
|
84
|
-
if (this.buffer.length > 0) {
|
|
85
|
-
return Promise.resolve({ value: this.buffer.shift() as T, done: false });
|
|
86
|
-
}
|
|
87
|
-
if (this.closed) {
|
|
88
|
-
return Promise.resolve({ value: undefined as never, done: true });
|
|
89
|
-
}
|
|
90
|
-
return new Promise((resolve) => this.waiters.push(resolve));
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
61
|
export class SdkClaudeAdapter extends ClaudeAdapter {
|
|
97
62
|
private sdkQuery: Query | null = null;
|
|
98
63
|
private inputQueue = new AsyncMessageQueue<SDKUserMessage>();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Typed event map for the Companion internal event bus.
|
|
2
2
|
// Each key is a namespaced event name; values are the payload passed to handlers.
|
|
3
3
|
|
|
4
|
-
import type { BrowserIncomingMessage } from "./session-types.js";
|
|
4
|
+
import type { BrowserIncomingMessage, PermissionRequest } from "./session-types.js";
|
|
5
5
|
import type { CodexAdapter } from "./codex-adapter.js";
|
|
6
6
|
import type { ClaudeAdapter } from "./claude-adapter.js";
|
|
7
7
|
import type { SessionPhase } from "./session-state-machine.js";
|
|
@@ -76,4 +76,35 @@ export interface CompanionEventMap {
|
|
|
76
76
|
|
|
77
77
|
/** A result (turn completion) was processed and broadcast to browsers. */
|
|
78
78
|
"message:result": { sessionId: string; message: BrowserIncomingMessage };
|
|
79
|
+
|
|
80
|
+
// ── Permission flow (transport-agnostic; emitted at bridge level) ───
|
|
81
|
+
|
|
82
|
+
/** A permission request was stored and broadcast to browsers. */
|
|
83
|
+
"permission:requested": { sessionId: string; request: PermissionRequest };
|
|
84
|
+
|
|
85
|
+
/** A permission request was resolved — by the user or by AI validation. */
|
|
86
|
+
"permission:resolved": {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
requestId: string;
|
|
89
|
+
behavior: "allow" | "deny";
|
|
90
|
+
resolvedBy: "user" | "ai";
|
|
91
|
+
toolName?: string;
|
|
92
|
+
/** For AskUserQuestion allows: the answers keyed by question text. */
|
|
93
|
+
answers?: Record<string, string>;
|
|
94
|
+
reason?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** A permission request was cancelled by the CLI (e.g. interrupt). */
|
|
98
|
+
"permission:cancelled": { sessionId: string; requestId: string };
|
|
99
|
+
|
|
100
|
+
// ── MagicUI ────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/** Per-session MagicUI opt-in changed via set_magic_ui. */
|
|
103
|
+
"magic-ui:setting-changed": {
|
|
104
|
+
sessionId: string;
|
|
105
|
+
magicUiActive: boolean | null | undefined;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** A browser asked for the current dashboard snapshot (join/resync). */
|
|
109
|
+
"magic-ui:sync-requested": { sessionId: string };
|
|
79
110
|
}
|
package/server/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { migrateCronJobsToAgents } from "./agent-cron-migrator.js";
|
|
|
32
32
|
import { migrateLinearCredentialsToAgents } from "./linear-credential-migration.js";
|
|
33
33
|
import { authenticateManagedWebSocket } from "./ws-auth.js";
|
|
34
34
|
import { LinearAgentBridge } from "./linear-agent-bridge.js";
|
|
35
|
+
import { MagicUiWatcherManager } from "./magic-ui-watcher.js";
|
|
35
36
|
import { NoVncProxy } from "./novnc-proxy.js";
|
|
36
37
|
|
|
37
38
|
import { startPeriodicCheck, setServiceMode, getCurrentVersion } from "./update-checker.js";
|
|
@@ -72,6 +73,12 @@ const recorder = new RecorderManager();
|
|
|
72
73
|
const cronScheduler = new CronScheduler(launcher, wsBridge);
|
|
73
74
|
const agentExecutor = new AgentExecutor(launcher, wsBridge);
|
|
74
75
|
const linearAgentBridge = new LinearAgentBridge(agentExecutor, wsBridge);
|
|
76
|
+
// MagicUI watcher fleet: one Haiku session per opted-in companion session.
|
|
77
|
+
const magicUiManager = new MagicUiWatcherManager({
|
|
78
|
+
broadcast: (sessionId, msg) => wsBridge.broadcastToSession(sessionId, msg),
|
|
79
|
+
getSessionState: (sessionId) => wsBridge.getSession(sessionId)?.state,
|
|
80
|
+
});
|
|
81
|
+
void magicUiManager; // owned for process lifetime; bus-driven
|
|
75
82
|
|
|
76
83
|
const orchestrator = new SessionOrchestrator({
|
|
77
84
|
launcher, wsBridge, sessionStore, worktreeTracker,
|
|
@@ -19,82 +19,16 @@ import { buildLinearOAuthSystemPrompt } from "./linear-prompt-builder.js";
|
|
|
19
19
|
import { getSettings } from "./settings-manager.js";
|
|
20
20
|
import { companionBus } from "./event-bus.js";
|
|
21
21
|
import { findOAuthConnectionByClientId, getOAuthConnection, updateOAuthConnection } from "./linear-oauth-connections.js";
|
|
22
|
+
import {
|
|
23
|
+
extractTextDeltaFromStreamEvent,
|
|
24
|
+
extractTextFromAssistant,
|
|
25
|
+
extractToolResults,
|
|
26
|
+
extractToolUses,
|
|
27
|
+
} from "./message-extract.js";
|
|
22
28
|
|
|
23
29
|
/** Interval (ms) for flushing intermediate progress as ephemeral thoughts. */
|
|
24
30
|
const PROGRESS_FLUSH_INTERVAL_MS = 30_000;
|
|
25
31
|
|
|
26
|
-
/** Safely extract the content array from an assistant-type message. */
|
|
27
|
-
function getAssistantContent(msg: BrowserIncomingMessage): unknown[] | null {
|
|
28
|
-
if (msg.type !== "assistant") return null;
|
|
29
|
-
// Assistant messages carry content blocks at msg.message.content
|
|
30
|
-
const raw = msg as Record<string, unknown>;
|
|
31
|
-
const message = raw.message;
|
|
32
|
-
if (!message || typeof message !== "object") return null;
|
|
33
|
-
const content = (message as Record<string, unknown>).content;
|
|
34
|
-
return Array.isArray(content) ? content : null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Extract text from assistant message content blocks */
|
|
38
|
-
function extractTextFromAssistant(msg: BrowserIncomingMessage): string {
|
|
39
|
-
const content = getAssistantContent(msg);
|
|
40
|
-
if (!content) return "";
|
|
41
|
-
return content
|
|
42
|
-
.filter((b): b is { type: string; text: string } =>
|
|
43
|
-
typeof b === "object" && b !== null && (b as Record<string, unknown>).type === "text" && typeof (b as Record<string, unknown>).text === "string")
|
|
44
|
-
.map((b) => b.text)
|
|
45
|
-
.join("\n");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Extract text deltas from stream events. */
|
|
49
|
-
function extractTextDeltaFromStreamEvent(msg: BrowserIncomingMessage): string {
|
|
50
|
-
if (msg.type !== "stream_event") return "";
|
|
51
|
-
const event = msg.event as Record<string, unknown> | undefined;
|
|
52
|
-
if (!event || event.type !== "content_block_delta") return "";
|
|
53
|
-
const delta = event.delta as Record<string, unknown> | undefined;
|
|
54
|
-
if (!delta || delta.type !== "text_delta" || typeof delta.text !== "string") return "";
|
|
55
|
-
return delta.text;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Extract all tool use blocks from assistant message content (with raw input for plan extraction) */
|
|
59
|
-
function extractToolUses(msg: BrowserIncomingMessage): Array<{ id?: string; name: string; input: string; rawInput?: Record<string, unknown> }> {
|
|
60
|
-
const content = getAssistantContent(msg);
|
|
61
|
-
if (!content) return [];
|
|
62
|
-
return content
|
|
63
|
-
.filter((b): b is { type: string; id?: string; name: string; input?: Record<string, unknown> } =>
|
|
64
|
-
typeof b === "object" && b !== null
|
|
65
|
-
&& (b as Record<string, unknown>).type === "tool_use"
|
|
66
|
-
&& typeof (b as Record<string, unknown>).name === "string")
|
|
67
|
-
.map((toolBlock) => ({
|
|
68
|
-
id: typeof toolBlock.id === "string" ? toolBlock.id : undefined,
|
|
69
|
-
name: toolBlock.name,
|
|
70
|
-
input: toolBlock.input ? JSON.stringify(toolBlock.input).slice(0, 200) : "",
|
|
71
|
-
rawInput: toolBlock.input,
|
|
72
|
-
}));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Extract tool_result blocks from assistant message content. */
|
|
76
|
-
function extractToolResults(msg: BrowserIncomingMessage): Array<{ tool_use_id: string; content: string }> {
|
|
77
|
-
const content = getAssistantContent(msg);
|
|
78
|
-
if (!content) return [];
|
|
79
|
-
return content
|
|
80
|
-
.filter((b): b is { type: string; tool_use_id: string; content?: unknown } =>
|
|
81
|
-
typeof b === "object" && b !== null
|
|
82
|
-
&& (b as Record<string, unknown>).type === "tool_result"
|
|
83
|
-
&& typeof (b as Record<string, unknown>).tool_use_id === "string")
|
|
84
|
-
.map((block) => ({
|
|
85
|
-
tool_use_id: block.tool_use_id,
|
|
86
|
-
content: typeof block.content === "string"
|
|
87
|
-
? block.content.slice(0, 500)
|
|
88
|
-
: Array.isArray(block.content)
|
|
89
|
-
? (block.content as Array<{ type?: string; text?: string }>)
|
|
90
|
-
.filter((c) => c.type === "text" && typeof c.text === "string")
|
|
91
|
-
.map((c) => c.text)
|
|
92
|
-
.join("\n")
|
|
93
|
-
.slice(0, 500)
|
|
94
|
-
: "",
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
32
|
/** Map TodoWrite status values to Linear plan item status values. */
|
|
99
33
|
function mapTodoStatus(status: string): AgentPlanItem["status"] {
|
|
100
34
|
if (status === "in_progress") return "inProgress";
|
|
@@ -64,6 +64,8 @@ beforeEach(() => {
|
|
|
64
64
|
dashboardModel: "claude-haiku-4-5",
|
|
65
65
|
dashboardRunHour: 3,
|
|
66
66
|
dashboardMaxSessionsPerRun: 30,
|
|
67
|
+
magicUiEnabled: false,
|
|
68
|
+
magicUiModel: "claude-haiku-4-5",
|
|
67
69
|
publicUrl: "",
|
|
68
70
|
updateChannel: "stable",
|
|
69
71
|
dockerAutoUpdate: false,
|
|
@@ -203,6 +205,8 @@ describe("linear-connections", () => {
|
|
|
203
205
|
dashboardModel: "claude-haiku-4-5",
|
|
204
206
|
dashboardRunHour: 3,
|
|
205
207
|
dashboardMaxSessionsPerRun: 30,
|
|
208
|
+
magicUiEnabled: false,
|
|
209
|
+
magicUiModel: "claude-haiku-4-5",
|
|
206
210
|
publicUrl: "",
|
|
207
211
|
updateChannel: "stable",
|
|
208
212
|
dockerAutoUpdate: false,
|
|
@@ -252,6 +256,8 @@ describe("linear-connections", () => {
|
|
|
252
256
|
dashboardModel: "claude-haiku-4-5",
|
|
253
257
|
dashboardRunHour: 3,
|
|
254
258
|
dashboardMaxSessionsPerRun: 30,
|
|
259
|
+
magicUiEnabled: false,
|
|
260
|
+
magicUiModel: "claude-haiku-4-5",
|
|
255
261
|
publicUrl: "",
|
|
256
262
|
updateChannel: "stable",
|
|
257
263
|
dockerAutoUpdate: false,
|
|
@@ -320,6 +326,8 @@ describe("linear-connections", () => {
|
|
|
320
326
|
dashboardModel: "claude-haiku-4-5",
|
|
321
327
|
dashboardRunHour: 3,
|
|
322
328
|
dashboardMaxSessionsPerRun: 30,
|
|
329
|
+
magicUiEnabled: false,
|
|
330
|
+
magicUiModel: "claude-haiku-4-5",
|
|
323
331
|
publicUrl: "",
|
|
324
332
|
updateChannel: "stable",
|
|
325
333
|
dockerAutoUpdate: false,
|