@crouton-kit/crouter 0.3.81 → 0.3.83
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/builtin-pi-packages/pi-mode-switch/README.md +3 -3
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +12 -4
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +7 -3
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +104 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +16 -44
- package/dist/clients/attach/attach-cmd.d.ts +1 -2
- package/dist/clients/attach/attach-cmd.js +960 -961
- package/dist/clients/attach/attach-def.d.ts +2 -0
- package/dist/clients/attach/attach-def.js +93 -0
- package/dist/clients/attach/canvas-panels.d.ts +10 -3
- package/dist/clients/attach/canvas-panels.js +42 -16
- package/dist/clients/attach/chat-view.d.ts +20 -1
- package/dist/clients/attach/chat-view.js +85 -2
- package/dist/commands/__tests__/human.test.js +7 -22
- package/dist/commands/attention.js +31 -30
- package/dist/commands/human/prompts.js +51 -38
- package/dist/commands/human/queue.d.ts +1 -28
- package/dist/commands/human/queue.js +156 -520
- package/dist/commands/human/shared.d.ts +28 -68
- package/dist/commands/human/shared.js +37 -120
- package/dist/commands/human.js +13 -17
- package/dist/commands/node.js +4 -2
- package/dist/commands/surface.js +1 -1
- package/dist/commands/sys/__tests__/setup-core.test.js +1 -1
- package/dist/commands/sys/__tests__/setup-front-door.test.js +98 -0
- package/dist/commands/sys/setup-core.d.ts +22 -0
- package/dist/commands/sys/setup-core.js +62 -0
- package/dist/commands/sys/setup.js +8 -1
- package/dist/core/__tests__/human-deliver-e2e.test.js +95 -0
- package/dist/core/__tests__/human-deliver.test.js +332 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +5 -4
- package/dist/core/__tests__/pid-identity-match.test.js +83 -0
- package/dist/core/canvas/__tests__/attention.test.d.ts +1 -0
- package/dist/core/canvas/__tests__/attention.test.js +126 -0
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +4 -4
- package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
- package/dist/core/canvas/attention.d.ts +16 -14
- package/dist/core/canvas/attention.js +34 -29
- package/dist/core/canvas/boot.d.ts +1 -0
- package/dist/core/canvas/boot.js +1 -1
- package/dist/core/canvas/browse/app.js +3 -3
- package/dist/core/canvas/pid.d.ts +43 -17
- package/dist/core/canvas/pid.js +104 -35
- package/dist/core/canvas/remote-canvas-source.d.ts +1 -1
- package/dist/core/canvas/remote-canvas-source.js +4 -2
- package/dist/core/canvas/render.d.ts +5 -5
- package/dist/core/canvas/render.js +14 -14
- package/dist/core/canvas/source.d.ts +2 -2
- package/dist/core/canvas/source.js +3 -3
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +3 -2
- package/dist/core/preview-registry.js +22 -29
- package/dist/core/profiles/select.js +4 -4
- package/dist/core/runtime/front-door.js +19 -6
- package/dist/core/runtime/package-health.d.ts +13 -0
- package/dist/core/runtime/package-health.js +18 -0
- package/dist/core/runtime/pi-vendored.js +15 -1
- package/dist/core/runtime/placement.d.ts +1 -10
- package/dist/core/runtime/placement.js +43 -26
- package/dist/core/runtime/resume.js +72 -4
- package/dist/core/runtime/session-list-cache.d.ts +22 -2
- package/dist/core/runtime/session-list-cache.js +92 -24
- package/dist/core/runtime/spawn.js +9 -0
- package/dist/core/spawn.d.ts +0 -59
- package/dist/core/spawn.js +5 -163
- package/dist/daemon/crtrd.js +7 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/web-client/assets/index-DZdKS-FB.js +78 -0
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +2 -2
- package/dist/core/__tests__/full/dead-pane-regression.test.js +0 -154
- package/dist/core/__tests__/full/human-new-window-regression.test.js +0 -104
- package/dist/core/__tests__/full/review-render-pane-regression.test.js +0 -133
- package/dist/core/__tests__/human-stranded-deliver.test.js +0 -145
- package/dist/core/__tests__/human-surface-target.test.js +0 -98
- package/dist/web-client/assets/index--md2ylfi.js +0 -78
- /package/dist/{core/__tests__/full/dead-pane-regression.test.d.ts → clients/attach/__tests__/chat-view-snapshot-ordering.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/human-new-window-regression.test.d.ts → commands/sys/__tests__/setup-front-door.test.d.ts} +0 -0
- /package/dist/core/__tests__/{full/review-render-pane-regression.test.d.ts → human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-stranded-deliver.test.d.ts → human-deliver.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-surface-target.test.d.ts → pid-identity-match.test.d.ts} +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
9
9
|
<link rel="icon" href="/icon-192.png" />
|
|
10
10
|
<title>crouter</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DZdKS-FB.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-BUdm9s9s.css">
|
|
13
13
|
<link rel="manifest" href="/manifest.webmanifest"></head>
|
|
14
14
|
<body>
|
package/dist/web-client/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.4.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.4.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.4.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),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),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.4.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});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:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(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 t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}var B=`-precache-`,V=async(e,t=B)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function H(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(V(t).then(e=>{}))}))}function U(e){return O().createHandlerBoundToURL(e)}function W(e){O().precache(e)}function G(e,t){W(e),z(t)}var K=class extends A{constructor(e,{allowlist:t=[/./],denylist:n=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=n}_match({url:e,request:t}){if(t&&t.mode!==`navigate`)return!1;let n=e.pathname+e.search;for(let e of this._denylist)if(e.test(n))return!1;return!!this._allowlist.some(e=>e.test(n))}},q=class extends w{constructor(e={}){super(e),this._networkTimeoutSeconds=e.networkTimeoutSeconds||0}async _handle(e,n){let r,i;try{let t=[n.fetch(e)];if(this._networkTimeoutSeconds){let e=x(this._networkTimeoutSeconds*1e3);t.push(e)}if(i=await Promise.race(t),!i)throw Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`)}catch(e){e instanceof Error&&(r=e)}if(!i)throw new t(`no-response`,{url:e.url,error:r});return i}};function J(e,t,n){let r=new URLSearchParams;t!==void 0&&t!==``&&r.set(`ask`,t),n!==void 0&&n!==``&&r.set(`token`,n);let i=r.toString();return`/c/${encodeURIComponent(e)}${i===``?``:`?${i}`}`}var Y=`crtr-push-token-v1`,X=`/__push-token`;async function Z(){if(typeof caches>`u`)return;let e=await(await caches.open(Y)).match(X);if(e===void 0)return;let t=await e.text();return t===``?void 0:t}self.addEventListener(`message`,e=>{e.data?.type===`SKIP_WAITING`&&self.skipWaiting()}),G([{"revision":"14c9d1b4f235b9b66a4c2f60932c463e","url":"index.html"},{"revision":"d1609f5483f95b4a260d09d0ab185c33","url":"icon-maskable-512.png"},{"revision":"b275ba8590c62c4dd8459e88e911e4b2","url":"icon-512.png"},{"revision":"b1b16d2a1130ca68196bf7ada05ab5bd","url":"icon-192.png"},{"revision":"943e387ec5184bd044444386ecf60b48","url":"apple-touch-icon.png"},{"revision":null,"url":"assets/workbox-window.prod.es5-Bd17z0YL.js"},{"revision":null,"url":"assets/martian-mono-latin-wght-normal-5W32yIyr.woff2"},{"revision":null,"url":"assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2"},{"revision":null,"url":"assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2"},{"revision":null,"url":"assets/index-BUdm9s9s.css"},{"revision":null,"url":"assets/index--md2ylfi.js"},{"revision":null,"url":"assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff"},{"revision":null,"url":"assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2"},{"revision":null,"url":"assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2"},{"revision":null,"url":"assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff"},{"revision":"943e387ec5184bd044444386ecf60b48","url":"apple-touch-icon.png"},{"revision":"b1b16d2a1130ca68196bf7ada05ab5bd","url":"icon-192.png"},{"revision":"b275ba8590c62c4dd8459e88e911e4b2","url":"icon-512.png"},{"revision":"d1609f5483f95b4a260d09d0ab185c33","url":"icon-maskable-512.png"},{"revision":"660a37964e8a469ae61a2cfad652cc5a","url":"manifest.webmanifest"}]),H(),F(new K(U(`index.html`),{denylist:[/^\/__crtr\//,/^\/node\//,/[?&]token=/]})),F(({url:e})=>e.pathname.startsWith(`/__crtr/`),new q,`GET`),F(({url:e})=>e.pathname.startsWith(`/node/`),new q,`GET`);function Q(e){if(typeof e!=`object`||!e)return!1;let t=e;return!(typeof t.conversationId!=`string`||t.conversationId===``||t.kind!==`needs-you`&&t.kind!==`done`||typeof t.title!=`string`||typeof t.snippet!=`string`||t.askId!==void 0&&typeof t.askId!=`string`)}self.addEventListener(`push`,e=>{let t;try{t=e.data?.json()}catch{return}if(!Q(t))return;let n={body:t.snippet,tag:`crtr:${t.conversationId}`,renotify:!0,icon:`/icon-192.png`,badge:`/icon-192.png`,data:{conversationId:t.conversationId,askId:t.askId}};e.waitUntil(self.registration.showNotification(t.title,n))}),self.addEventListener(`notificationclick`,e=>{e.notification.close();let t=e.notification.data;if(typeof t?.conversationId!=`string`||t.conversationId===``)return;let n=t.conversationId,r=typeof t.askId==`string`?t.askId:void 0;e.waitUntil($(n,r))});async function $(e,t){let n=(await self.clients.matchAll({type:`window`,includeUncontrolled:!0}))[0];if(n!==void 0){n.postMessage({type:`crtr-deep-link`,conversationId:e,askId:t}),await n.focus();return}let r=await Z();await self.clients.openWindow(J(e,t,r))}
|
|
1
|
+
try{self[`workbox:core:7.4.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.4.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.4.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),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),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.4.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});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:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(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 t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}var B=`-precache-`,V=async(e,t=B)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function H(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(V(t).then(e=>{}))}))}function U(e){return O().createHandlerBoundToURL(e)}function W(e){O().precache(e)}function G(e,t){W(e),z(t)}var K=class extends A{constructor(e,{allowlist:t=[/./],denylist:n=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=n}_match({url:e,request:t}){if(t&&t.mode!==`navigate`)return!1;let n=e.pathname+e.search;for(let e of this._denylist)if(e.test(n))return!1;return!!this._allowlist.some(e=>e.test(n))}},q=class extends w{constructor(e={}){super(e),this._networkTimeoutSeconds=e.networkTimeoutSeconds||0}async _handle(e,n){let r,i;try{let t=[n.fetch(e)];if(this._networkTimeoutSeconds){let e=x(this._networkTimeoutSeconds*1e3);t.push(e)}if(i=await Promise.race(t),!i)throw Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`)}catch(e){e instanceof Error&&(r=e)}if(!i)throw new t(`no-response`,{url:e.url,error:r});return i}};function J(e,t,n){let r=new URLSearchParams;t!==void 0&&t!==``&&r.set(`ask`,t),n!==void 0&&n!==``&&r.set(`token`,n);let i=r.toString();return`/c/${encodeURIComponent(e)}${i===``?``:`?${i}`}`}var Y=`crtr-push-token-v1`,X=`/__push-token`;async function Z(){if(typeof caches>`u`)return;let e=await(await caches.open(Y)).match(X);if(e===void 0)return;let t=await e.text();return t===``?void 0:t}self.addEventListener(`message`,e=>{e.data?.type===`SKIP_WAITING`&&self.skipWaiting()}),G([{"revision":"ea3b22a1e805dc90caef43dd05304522","url":"index.html"},{"revision":"d1609f5483f95b4a260d09d0ab185c33","url":"icon-maskable-512.png"},{"revision":"b275ba8590c62c4dd8459e88e911e4b2","url":"icon-512.png"},{"revision":"b1b16d2a1130ca68196bf7ada05ab5bd","url":"icon-192.png"},{"revision":"943e387ec5184bd044444386ecf60b48","url":"apple-touch-icon.png"},{"revision":null,"url":"assets/workbox-window.prod.es5-Bd17z0YL.js"},{"revision":null,"url":"assets/martian-mono-latin-wght-normal-5W32yIyr.woff2"},{"revision":null,"url":"assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2"},{"revision":null,"url":"assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2"},{"revision":null,"url":"assets/index-DZdKS-FB.js"},{"revision":null,"url":"assets/index-BUdm9s9s.css"},{"revision":null,"url":"assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff"},{"revision":null,"url":"assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2"},{"revision":null,"url":"assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2"},{"revision":null,"url":"assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff"},{"revision":"943e387ec5184bd044444386ecf60b48","url":"apple-touch-icon.png"},{"revision":"b1b16d2a1130ca68196bf7ada05ab5bd","url":"icon-192.png"},{"revision":"b275ba8590c62c4dd8459e88e911e4b2","url":"icon-512.png"},{"revision":"d1609f5483f95b4a260d09d0ab185c33","url":"icon-maskable-512.png"},{"revision":"660a37964e8a469ae61a2cfad652cc5a","url":"manifest.webmanifest"}]),H(),F(new K(U(`index.html`),{denylist:[/^\/__crtr\//,/^\/node\//,/[?&]token=/]})),F(({url:e})=>e.pathname.startsWith(`/__crtr/`),new q,`GET`),F(({url:e})=>e.pathname.startsWith(`/node/`),new q,`GET`);function Q(e){if(typeof e!=`object`||!e)return!1;let t=e;return!(typeof t.conversationId!=`string`||t.conversationId===``||t.kind!==`needs-you`&&t.kind!==`done`||typeof t.title!=`string`||typeof t.snippet!=`string`||t.askId!==void 0&&typeof t.askId!=`string`)}self.addEventListener(`push`,e=>{let t;try{t=e.data?.json()}catch{return}if(!Q(t))return;let n={body:t.snippet,tag:`crtr:${t.conversationId}`,renotify:!0,icon:`/icon-192.png`,badge:`/icon-192.png`,data:{conversationId:t.conversationId,askId:t.askId}};e.waitUntil(self.registration.showNotification(t.title,n))}),self.addEventListener(`notificationclick`,e=>{e.notification.close();let t=e.notification.data;if(typeof t?.conversationId!=`string`||t.conversationId===``)return;let n=t.conversationId,r=typeof t.askId==`string`?t.askId:void 0;e.waitUntil($(n,r))});async function $(e,t){let n=(await self.clients.matchAll({type:`window`,includeUncontrolled:!0}))[0];if(n!==void 0){n.postMessage({type:`crtr-deep-link`,conversationId:e,askId:t}),await n.focus();return}let r=await Z();await self.clients.openWindow(J(e,t,r))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crouton-kit/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.83",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"license": "MIT",
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@crouton-kit/humanloop": "^0.3.
|
|
57
|
+
"@crouton-kit/humanloop": "^0.3.35",
|
|
58
58
|
"@earendil-works/pi-agent-core": "0.80.6",
|
|
59
59
|
"@earendil-works/pi-ai": "0.80.6",
|
|
60
60
|
"@earendil-works/pi-coding-agent": "0.80.6",
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/dead-pane-regression.test.ts
|
|
2
|
-
//
|
|
3
|
-
// PLACEMENT-FAMILY DEAD-PANE REGRESSION GUARD.
|
|
4
|
-
//
|
|
5
|
-
// The bug (fixed this session, committed in HEAD `src/core/spawn.ts`): the
|
|
6
|
-
// `human` command tree opens its humanloop TUI in a tmux pane via
|
|
7
|
-
// `spawnAndDetach` (a `split-window` into the CURRENT window). The canvas
|
|
8
|
-
// runtime arms `remain-on-exit on` at the WINDOW scope on a node's vehicle /
|
|
9
|
-
// focus window (the F3 freeze, `runtime/tmux.ts` setRemainOnExit). A
|
|
10
|
-
// `split-window` pane opened into such a window INHERITS the `on`, so when the
|
|
11
|
-
// TUI worker (`crtr human _run`) exits 0 (clean), tmux keeps the pane around as
|
|
12
|
-
// a dead "[Exited]" pane instead of destroying it — a lingering dead pane.
|
|
13
|
-
//
|
|
14
|
-
// The fix: after the split, `spawnAndDetach` forces `remain-on-exit off` at PANE
|
|
15
|
-
// scope on the new pane (`set-option -p -t <pane> remain-on-exit off`). That
|
|
16
|
-
// destroys the pane on a clean exit WITHOUT touching the window's value (so a
|
|
17
|
-
// focus freeze still works) or the user's global config.
|
|
18
|
-
//
|
|
19
|
-
// This file drives the REAL production `spawnAndDetach` against a REAL but
|
|
20
|
-
// isolated tmux session — no mocks. It is ADDITIVE: it adds no harness/fixture
|
|
21
|
-
// capability and edits no production file. The canvas harness
|
|
22
|
-
// (`helpers/harness.ts`) cannot reach this path — it drives canvas nodes via
|
|
23
|
-
// `openNodeWindow` (new-window backstage), never the `spawnAndDetach`
|
|
24
|
-
// split-window/human-pane path — so the tmux drivers below are LOCAL to this
|
|
25
|
-
// file (candidates for harness consolidation: a `splitDetachPane` + pane-scoped
|
|
26
|
-
// `paneAlive`/`paneIsDead`/`waitForPaneGone` keyed on a raw %pane id).
|
|
27
|
-
import { test } from 'node:test';
|
|
28
|
-
import assert from 'node:assert/strict';
|
|
29
|
-
import { spawnSync } from 'node:child_process';
|
|
30
|
-
import { hasTmux, TMUX_TIMEOUT_MS } from '../helpers/harness.js';
|
|
31
|
-
import { spawnAndDetach } from '../../spawn.js';
|
|
32
|
-
const SKIP = !hasTmux();
|
|
33
|
-
// --- LOCAL tmux drivers (keyed on a raw %pane id, not a canvas node) --------
|
|
34
|
-
function tmux(args) {
|
|
35
|
-
const r = spawnSync('tmux', args, { encoding: 'utf8', timeout: TMUX_TIMEOUT_MS });
|
|
36
|
-
return { code: r.status ?? -1, out: (r.stdout ?? '').trim(), err: (r.stderr ?? '').trim() };
|
|
37
|
-
}
|
|
38
|
-
/** Every pane in the session (across windows) with its dead flag. */
|
|
39
|
-
function sessionPanes(session) {
|
|
40
|
-
const r = tmux(['list-panes', '-s', '-t', session, '-F', '#{pane_id} #{pane_dead}']);
|
|
41
|
-
if (r.code !== 0)
|
|
42
|
-
return [];
|
|
43
|
-
return r.out
|
|
44
|
-
.split('\n')
|
|
45
|
-
.filter((l) => l.trim() !== '')
|
|
46
|
-
.map((l) => {
|
|
47
|
-
const [id, dead] = l.split(' ');
|
|
48
|
-
return { id: id, dead: dead === '1' };
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
function paneAlive(session, pane) {
|
|
52
|
-
return sessionPanes(session).some((p) => p.id === pane && !p.dead);
|
|
53
|
-
}
|
|
54
|
-
function paneExists(session, pane) {
|
|
55
|
-
return sessionPanes(session).some((p) => p.id === pane);
|
|
56
|
-
}
|
|
57
|
-
function paneIsDead(session, pane) {
|
|
58
|
-
return sessionPanes(session).find((p) => p.id === pane)?.dead ?? false;
|
|
59
|
-
}
|
|
60
|
-
async function waitUntil(probe, label, timeoutMs = 15_000, intervalMs = 100) {
|
|
61
|
-
const deadline = Date.now() + timeoutMs;
|
|
62
|
-
for (;;) {
|
|
63
|
-
if (probe())
|
|
64
|
-
return;
|
|
65
|
-
if (Date.now() > deadline)
|
|
66
|
-
throw new Error(`waitUntil timed out: ${label}`);
|
|
67
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
test('dead-pane regression: spawnAndDetach into a remain-on-exit-ON window is DESTROYED on clean exit', { skip: SKIP ? 'tmux unavailable' : false, timeout: 60_000 }, async () => {
|
|
71
|
-
const session = `crtr-deadpane-${process.pid}-${Date.now().toString(36)}`;
|
|
72
|
-
const origTmux = process.env['TMUX'];
|
|
73
|
-
let tmuxOverridden = false;
|
|
74
|
-
try {
|
|
75
|
-
// --- isolated session on whatever server the current env targets -------
|
|
76
|
-
const created = tmux(['new-session', '-d', '-s', session, '-c', process.cwd(), 'sleep 100000']);
|
|
77
|
-
assert.equal(created.code, 0, `create isolated session failed: ${created.err}`);
|
|
78
|
-
const info = tmux([
|
|
79
|
-
'display-message', '-p', '-t', `${session}:`,
|
|
80
|
-
'#{pane_id}\t#{window_id}\t#{socket_path}',
|
|
81
|
-
]);
|
|
82
|
-
assert.equal(info.code, 0, `display-message failed: ${info.err}`);
|
|
83
|
-
const [basePane, baseWindow, socketPath] = info.out.split('\t');
|
|
84
|
-
assert.ok(basePane && baseWindow && socketPath, `parsed session info: ${info.out}`);
|
|
85
|
-
// --- arm the BUG PRECONDITION: window default remain-on-exit ON --------
|
|
86
|
-
// (mirrors the canvas runtime's F3 freeze on a node's vehicle/focus window).
|
|
87
|
-
const armed = tmux(['set-window-option', '-t', baseWindow, 'remain-on-exit', 'on']);
|
|
88
|
-
assert.equal(armed.code, 0, `arm window remain-on-exit on failed: ${armed.err}`);
|
|
89
|
-
assert.equal(tmux(['show-window-options', '-t', baseWindow, 'remain-on-exit']).out, 'remain-on-exit on', 'precondition: the window into which we split has remain-on-exit ON');
|
|
90
|
-
// spawnAndDetach gates on isInTmux() (Boolean(process.env.TMUX)) and shells
|
|
91
|
-
// `tmux` with no -L, so point TMUX at THIS session's socket — both our
|
|
92
|
-
// helpers and spawnAndDetach then drive the same server holding `session`.
|
|
93
|
-
process.env['TMUX'] = `${socketPath},0,0`;
|
|
94
|
-
tmuxOverridden = true;
|
|
95
|
-
// ====================================================================
|
|
96
|
-
// MAIN — the REAL production spawnAndDetach split into the ON window.
|
|
97
|
-
// ====================================================================
|
|
98
|
-
const res = spawnAndDetach({
|
|
99
|
-
command: 'sleep 1', // a clean-exiting (status 0) worker — stands in for `crtr human _run`
|
|
100
|
-
cwd: process.cwd(),
|
|
101
|
-
placement: 'split-h',
|
|
102
|
-
killAfterSeconds: 0, // no self-kill of the originating pane
|
|
103
|
-
targetPane: basePane, // pin the split to our isolated window (never the user's pane)
|
|
104
|
-
});
|
|
105
|
-
assert.equal(res.status, 'spawned', `spawnAndDetach should spawn: ${res.message}`);
|
|
106
|
-
const pane = res.paneId;
|
|
107
|
-
assert.ok(pane && pane.startsWith('%'), `spawnAndDetach returned a %pane id: ${pane}`);
|
|
108
|
-
// (a) the pane landed alive in the remain-on-exit-ON window.
|
|
109
|
-
assert.equal(paneAlive(session, pane), true, 'spawned pane is alive right after the split');
|
|
110
|
-
// (b) THE FIX: the spawned pane carries a PANE-SCOPED remain-on-exit OFF
|
|
111
|
-
// override — the exact line under regression
|
|
112
|
-
// (`set-option -p -t <pane> remain-on-exit off`). If a refactor drops
|
|
113
|
-
// it, this fails loudly.
|
|
114
|
-
assert.equal(tmux(['show-options', '-p', '-t', pane, 'remain-on-exit']).out, 'remain-on-exit off', 'FIX present: pane-scoped remain-on-exit OFF on the spawnAndDetach pane');
|
|
115
|
-
// (c) the override is pane-scoped only — the WINDOW value is untouched, so
|
|
116
|
-
// a real focus-freeze on this window still works.
|
|
117
|
-
assert.equal(tmux(['show-window-options', '-t', baseWindow, 'remain-on-exit']).out, 'remain-on-exit on', 'window remain-on-exit untouched (override is pane-scoped, focus freeze intact)');
|
|
118
|
-
// (d) THE GUARANTEE: on the worker's CLEAN exit the pane is DESTROYED,
|
|
119
|
-
// never lingering as a dead [Exited] pane.
|
|
120
|
-
await waitUntil(() => !paneExists(session, pane), 'spawnAndDetach pane destroyed on clean exit');
|
|
121
|
-
assert.equal(paneExists(session, pane), false, 'spawnAndDetach pane DESTROYED on clean exit (not lingering)');
|
|
122
|
-
// ====================================================================
|
|
123
|
-
// CONTROL — prove the guard is NON-VACUOUS: the SAME split WITHOUT the
|
|
124
|
-
// pane-scoped override (i.e. pre-fix behavior) lingers as a dead pane.
|
|
125
|
-
// ====================================================================
|
|
126
|
-
const ctl = tmux([
|
|
127
|
-
'split-window', '-h', '-d', '-P', '-F', '#{pane_id}',
|
|
128
|
-
'-t', basePane, '-c', process.cwd(), 'sleep 1',
|
|
129
|
-
]);
|
|
130
|
-
assert.equal(ctl.code, 0, `control split failed: ${ctl.err}`);
|
|
131
|
-
const ctlPane = ctl.out;
|
|
132
|
-
assert.ok(ctlPane.startsWith('%'), `control pane id: ${ctlPane}`);
|
|
133
|
-
// No pane-scoped override → inherits the window's remain-on-exit ON →
|
|
134
|
-
// after its clean exit the pane is kept as DEAD rather than destroyed.
|
|
135
|
-
await waitUntil(() => paneIsDead(session, ctlPane), 'control pane reaches dead state after clean exit');
|
|
136
|
-
assert.equal(paneExists(session, ctlPane), true, 'CONTROL: un-fixed pane LINGERS (still listed) after clean exit');
|
|
137
|
-
assert.equal(paneIsDead(session, ctlPane), true, 'CONTROL: the lingering pane is DEAD — confirms remain-on-exit ON causes the exact bug the fix prevents');
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
// Tear down on the SAME server the session lives on (TMUX still points there),
|
|
141
|
-
// then restore the original TMUX env.
|
|
142
|
-
tmux(['kill-session', '-t', session]);
|
|
143
|
-
if (tmuxOverridden) {
|
|
144
|
-
if (origTmux === undefined)
|
|
145
|
-
delete process.env['TMUX'];
|
|
146
|
-
else
|
|
147
|
-
process.env['TMUX'] = origTmux;
|
|
148
|
-
}
|
|
149
|
-
assert.equal(spawnSync('tmux', ['has-session', '-t', session], {
|
|
150
|
-
stdio: 'ignore',
|
|
151
|
-
timeout: TMUX_TIMEOUT_MS,
|
|
152
|
-
}).status === 0, false, 'isolated session killed — no stray');
|
|
153
|
-
}
|
|
154
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/human-new-window-regression.test.ts
|
|
2
|
-
//
|
|
3
|
-
// BUG REGRESSION: `crtr human ask|review|notify` opened NO pane at all.
|
|
4
|
-
//
|
|
5
|
-
// spawnAndDetach's 'new-window' placement passed a tmux PANE id straight to
|
|
6
|
-
// `new-window -t` (`new-window -d -a -t %<pane>`). tmux REJECTS a pane id for
|
|
7
|
-
// new-window — it exits 1 with "can't specify pane here"; only `split-window -t`
|
|
8
|
-
// accepts a pane. So once a node's watched window held >= max_panes_per_window
|
|
9
|
-
// panes, pickPlacement (shared.ts) chose 'new-window', spawnAndDetach FAILED,
|
|
10
|
-
// spawnHumanJob returned {spawned:false}, and the humanloop TUI never appeared.
|
|
11
|
-
// Deterministic for any user whose watched window is already at the pane cap.
|
|
12
|
-
//
|
|
13
|
-
// Regression from 829c1a1 ("land prompts in the watched node's session"), which
|
|
14
|
-
// began routing the TUI to the watched node's pane id but never converted it to
|
|
15
|
-
// the session:window form new-window needs.
|
|
16
|
-
//
|
|
17
|
-
// THE FIX: spawnAndDetach resolves the target pane to its session:window
|
|
18
|
-
// (`paneWindowTarget`) before `new-window -t`. This drives the REAL production
|
|
19
|
-
// spawnAndDetach against a REAL but isolated tmux session — no mocks — mirroring
|
|
20
|
-
// dead-pane-regression.test.ts.
|
|
21
|
-
import { test } from 'node:test';
|
|
22
|
-
import assert from 'node:assert/strict';
|
|
23
|
-
import { spawnSync } from 'node:child_process';
|
|
24
|
-
import { hasTmux, TMUX_TIMEOUT_MS } from '../helpers/harness.js';
|
|
25
|
-
import { spawnAndDetach } from '../../spawn.js';
|
|
26
|
-
const SKIP = !hasTmux();
|
|
27
|
-
function tmux(args) {
|
|
28
|
-
const r = spawnSync('tmux', args, { encoding: 'utf8', timeout: TMUX_TIMEOUT_MS });
|
|
29
|
-
return { code: r.status ?? -1, out: (r.stdout ?? '').trim(), err: (r.stderr ?? '').trim() };
|
|
30
|
-
}
|
|
31
|
-
/** window id of a pane (across the server). '' on error. */
|
|
32
|
-
function windowOfPane(pane) {
|
|
33
|
-
return tmux(['display-message', '-p', '-t', pane, '#{window_id}']).out;
|
|
34
|
-
}
|
|
35
|
-
/** Every pane in the session, across all its windows. */
|
|
36
|
-
function sessionPanes(session) {
|
|
37
|
-
const r = tmux(['list-panes', '-s', '-t', session, '-F', '#{pane_id}']);
|
|
38
|
-
if (r.code !== 0)
|
|
39
|
-
return [];
|
|
40
|
-
return r.out.split('\n').filter((l) => l.trim() !== '');
|
|
41
|
-
}
|
|
42
|
-
test('human-new-window regression: spawnAndDetach new-window into a watched-pane SPAWNS (no longer "can\'t specify pane here")', { skip: SKIP ? 'tmux unavailable' : false, timeout: 60_000 }, async () => {
|
|
43
|
-
const session = `crtr-newwin-${process.pid}-${Date.now().toString(36)}`;
|
|
44
|
-
const origTmux = process.env['TMUX'];
|
|
45
|
-
let tmuxOverridden = false;
|
|
46
|
-
try {
|
|
47
|
-
// --- isolated session on whatever server the current env targets -------
|
|
48
|
-
const created = tmux(['new-session', '-d', '-s', session, '-c', process.cwd(), 'sleep 100000']);
|
|
49
|
-
assert.equal(created.code, 0, `create isolated session failed: ${created.err}`);
|
|
50
|
-
const info = tmux([
|
|
51
|
-
'display-message', '-p', '-t', `${session}:`,
|
|
52
|
-
'#{pane_id}\t#{window_id}\t#{socket_path}',
|
|
53
|
-
]);
|
|
54
|
-
assert.equal(info.code, 0, `display-message failed: ${info.err}`);
|
|
55
|
-
const [basePane, baseWindow, socketPath] = info.out.split('\t');
|
|
56
|
-
assert.ok(basePane && baseWindow && socketPath, `parsed session info: ${info.out}`);
|
|
57
|
-
// ====================================================================
|
|
58
|
-
// CONTROL — prove the bug is REAL and the guard NON-VACUOUS: the pre-fix
|
|
59
|
-
// call shape (a PANE id straight into `new-window -t`) is REJECTED by tmux.
|
|
60
|
-
// ====================================================================
|
|
61
|
-
const ctl = tmux(['new-window', '-d', '-a', '-t', basePane, '-c', process.cwd(), 'sleep 100000']);
|
|
62
|
-
assert.notEqual(ctl.code, 0, 'CONTROL: new-window with a PANE id must FAIL (pre-fix call shape)');
|
|
63
|
-
assert.match(ctl.err, /can't specify pane here/i, `CONTROL: tmux rejects a pane id for new-window — got: ${ctl.err}`);
|
|
64
|
-
// spawnAndDetach gates on isInTmux() (Boolean(process.env.TMUX)) and shells
|
|
65
|
-
// `tmux` with no -L, so point TMUX at THIS session's socket — both our
|
|
66
|
-
// helpers and spawnAndDetach then drive the same server holding `session`.
|
|
67
|
-
process.env['TMUX'] = `${socketPath},0,0`;
|
|
68
|
-
tmuxOverridden = true;
|
|
69
|
-
// ====================================================================
|
|
70
|
-
// MAIN — the REAL production spawnAndDetach, 'new-window' placement,
|
|
71
|
-
// pinned to the watched PANE (exactly what detachHumanTui passes when
|
|
72
|
-
// pickPlacement returns 'new-window').
|
|
73
|
-
// ====================================================================
|
|
74
|
-
const res = spawnAndDetach({
|
|
75
|
-
command: 'sleep 100000', // long-lived stand-in for `crtr human _run`
|
|
76
|
-
cwd: process.cwd(),
|
|
77
|
-
placement: 'new-window',
|
|
78
|
-
detached: true, // don't switch the client (matches detachHumanTui)
|
|
79
|
-
killAfterSeconds: 0, // no self-kill of the originating pane
|
|
80
|
-
targetPane: basePane, // a PANE id — the exact value that used to break new-window
|
|
81
|
-
});
|
|
82
|
-
// (a) THE FIX: it SPAWNS (pre-fix this was 'spawn-failed' / no pane).
|
|
83
|
-
assert.equal(res.status, 'spawned', `spawnAndDetach should spawn a pane: ${res.message}`);
|
|
84
|
-
const pane = res.paneId;
|
|
85
|
-
assert.ok(pane && pane.startsWith('%'), `spawnAndDetach returned a %pane id: ${pane}`);
|
|
86
|
-
// (b) it landed in the SAME session as the watched pane (not stranded /
|
|
87
|
-
// leaked into the global current session), in a NEW window.
|
|
88
|
-
assert.ok(sessionPanes(session).includes(pane), 'spawned pane lives in the watched pane\'s session');
|
|
89
|
-
assert.notEqual(windowOfPane(pane), baseWindow, 'spawned pane is in a NEW window (new-window placement), not the watched pane\'s window');
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
tmux(['kill-session', '-t', session]);
|
|
93
|
-
if (tmuxOverridden) {
|
|
94
|
-
if (origTmux === undefined)
|
|
95
|
-
delete process.env['TMUX'];
|
|
96
|
-
else
|
|
97
|
-
process.env['TMUX'] = origTmux;
|
|
98
|
-
}
|
|
99
|
-
assert.equal(spawnSync('tmux', ['has-session', '-t', session], {
|
|
100
|
-
stdio: 'ignore',
|
|
101
|
-
timeout: TMUX_TIMEOUT_MS,
|
|
102
|
-
}).status === 0, false, 'isolated session killed — no stray');
|
|
103
|
-
}
|
|
104
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/review-render-pane-regression.test.ts
|
|
2
|
-
//
|
|
3
|
-
// BUG REGRESSION: `crtr human review` showed the human RAW markdown — literal
|
|
4
|
-
// `:::callout{...}` / `#` / `**bold**` source — instead of the termrender-
|
|
5
|
-
// rendered document the leaf help promises ("directive-flavored markdown
|
|
6
|
-
// rendered by termrender (panels, columns, trees, callouts, mermaid)").
|
|
7
|
-
//
|
|
8
|
-
// Root cause: the review surface was ONLY the read-only nvim buffer. nvim must
|
|
9
|
-
// show the raw source (anchored comments hang off source line/col numbers), and
|
|
10
|
-
// `-u NONE` treesitter/render-markdown styling never interprets ::: directives —
|
|
11
|
-
// so nothing anywhere rendered the doc. There was no termrender invocation in
|
|
12
|
-
// the entire review path (crouter `_run` review branch + humanloop
|
|
13
|
-
// launchReview): the managed venv binary was healthy and never asked.
|
|
14
|
-
//
|
|
15
|
-
// THE FIX (src/commands/human/queue.ts, `_run` review branch): open a live
|
|
16
|
-
// termrender watch pane via humanloop `display()` BESIDE the editor, record its
|
|
17
|
-
// pane id as `render_pane_id` on run.json (so `human cancel` can clear it), and
|
|
18
|
-
// kill it in a `finally` when the editor exits. The editor keeps the raw
|
|
19
|
-
// source; the human reads the rendered pane.
|
|
20
|
-
//
|
|
21
|
-
// This test drives the REAL `crtr human _run` worker (review mode) in a REAL
|
|
22
|
-
// but isolated tmux session — no mocks — and asserts:
|
|
23
|
-
// (a) a second pane appears beside the editor running termrender on the file,
|
|
24
|
-
// (b) that pane's content is RENDERED ANSI (box-drawing panels), not raw `:::`,
|
|
25
|
-
// (c) run.json carries render_pane_id for the cancel path,
|
|
26
|
-
// (d) the render pane is torn down when the editor exits.
|
|
27
|
-
import { test } from 'node:test';
|
|
28
|
-
import assert from 'node:assert/strict';
|
|
29
|
-
import { spawnSync } from 'node:child_process';
|
|
30
|
-
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
31
|
-
import { tmpdir } from 'node:os';
|
|
32
|
-
import { join, dirname } from 'node:path';
|
|
33
|
-
import { fileURLToPath } from 'node:url';
|
|
34
|
-
import { createRequire } from 'node:module';
|
|
35
|
-
import { hasTmux, TMUX_TIMEOUT_MS } from '../helpers/harness.js';
|
|
36
|
-
const CROUTER = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
|
|
37
|
-
const CLI_SRC = join(CROUTER, 'src', 'cli.ts');
|
|
38
|
-
const TSX_ESM = createRequire(import.meta.url).resolve('tsx/esm');
|
|
39
|
-
function tmux(args) {
|
|
40
|
-
const r = spawnSync('tmux', args, { encoding: 'utf8', timeout: TMUX_TIMEOUT_MS });
|
|
41
|
-
return { code: r.status ?? -1, out: (r.stdout ?? '').trim(), err: (r.stderr ?? '').trim() };
|
|
42
|
-
}
|
|
43
|
-
function sessionPanes(session) {
|
|
44
|
-
const r = tmux(['list-panes', '-s', '-t', session, '-F', '#{pane_id}\t#{pane_current_command}']);
|
|
45
|
-
if (r.code !== 0)
|
|
46
|
-
return [];
|
|
47
|
-
return r.out
|
|
48
|
-
.split('\n')
|
|
49
|
-
.filter((l) => l.trim() !== '')
|
|
50
|
-
.map((l) => {
|
|
51
|
-
const [id, cmd] = l.split('\t');
|
|
52
|
-
return { id: id ?? '', cmd: cmd ?? '' };
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function paneContent(paneId) {
|
|
56
|
-
return tmux(['capture-pane', '-p', '-t', paneId]).out;
|
|
57
|
-
}
|
|
58
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
59
|
-
async function waitFor(fn, timeoutMs, label) {
|
|
60
|
-
const deadline = Date.now() + timeoutMs;
|
|
61
|
-
for (;;) {
|
|
62
|
-
const v = fn();
|
|
63
|
-
if (v !== undefined)
|
|
64
|
-
return v;
|
|
65
|
-
if (Date.now() > deadline)
|
|
66
|
-
throw new Error(`timeout waiting for ${label}`);
|
|
67
|
-
await sleep(300);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const SKIP = !hasTmux();
|
|
71
|
-
test('review-render-pane regression: `human _run` (review) opens a live termrender pane showing RENDERED panels, not raw ::: source', { skip: SKIP ? 'tmux unavailable' : false, timeout: 90_000 }, async () => {
|
|
72
|
-
const dir = mkdtempSync(join(tmpdir(), 'crtr-review-render-'));
|
|
73
|
-
const session = `crtr-revrender-${process.pid}-${Date.now().toString(36)}`;
|
|
74
|
-
const mdPath = join(dir, 'doc.md');
|
|
75
|
-
// A directive doc: rendered output has box-drawing panels; raw output has `:::`.
|
|
76
|
-
writeFileSync(mdPath, '# Render check\n\n:::callout{type="info"}\nRENDER-MARKER body\n:::\n');
|
|
77
|
-
writeFileSync(join(dir, 'run.json'), JSON.stringify({ mode: 'review', job_id: '', file: mdPath, output: join(dir, 'feedback.json') }));
|
|
78
|
-
try {
|
|
79
|
-
// The worker runs the real `crtr human _run` from src via tsx, exactly the
|
|
80
|
-
// command shape detachHumanTui bakes into the spawned pane. job_id is ''
|
|
81
|
-
// so pushFinal is skipped (no canvas home needed) — the surface under
|
|
82
|
-
// test is the panes, not the report fan-out.
|
|
83
|
-
const workerCmd = `CRTR_HUMAN_DIR='${dir}' '${process.execPath}' --import '${TSX_ESM}' '${CLI_SRC}' human _run; sleep 120`;
|
|
84
|
-
const created = tmux(['new-session', '-d', '-s', session, '-x', '200', '-y', '50', workerCmd]);
|
|
85
|
-
assert.equal(created.code, 0, `create isolated session failed: ${created.err}`);
|
|
86
|
-
// (a) a SECOND pane appears: the termrender watch pane beside the editor.
|
|
87
|
-
const renderPane = await waitFor(() => {
|
|
88
|
-
const panes = sessionPanes(session);
|
|
89
|
-
if (panes.length < 2)
|
|
90
|
-
return undefined;
|
|
91
|
-
// The render pane is the one NOT hosting the editor/worker shell.
|
|
92
|
-
const candidates = panes.filter((p) => !/zsh|bash|sh|node|nvim|vim/i.test(p.cmd));
|
|
93
|
-
return candidates[0]?.id;
|
|
94
|
-
}, 30_000, 'termrender render pane to open');
|
|
95
|
-
// (b) its content is RENDERED: panel box-drawing present, raw ::: absent.
|
|
96
|
-
const rendered = await waitFor(() => {
|
|
97
|
-
const c = paneContent(renderPane);
|
|
98
|
-
return c.includes('RENDER-MARKER') ? c : undefined;
|
|
99
|
-
}, 30_000, 'rendered body to paint');
|
|
100
|
-
assert.ok(/[┌│└]/.test(rendered), `render pane draws panels (box chars): ${rendered.slice(0, 200)}`);
|
|
101
|
-
assert.ok(!rendered.includes(':::'), 'render pane does NOT show raw ::: directive source');
|
|
102
|
-
// (c) run.json carries render_pane_id so `human cancel` can clear the pane.
|
|
103
|
-
const rc = JSON.parse(readFileSync(join(dir, 'run.json'), 'utf8'));
|
|
104
|
-
assert.equal(rc.render_pane_id, renderPane, 'run.json records the render pane id');
|
|
105
|
-
// (d) editor exit (any quit submits) tears the render pane down. The
|
|
106
|
-
// editor pane is the OTHER pane (nvim runs as a child of the worker, so
|
|
107
|
-
// #{pane_current_command} may report node/zsh — match on its content:
|
|
108
|
-
// the read-only buffer shows the RAW ::: source).
|
|
109
|
-
const editorPane = await waitFor(() => {
|
|
110
|
-
const other = sessionPanes(session).find((p) => p.id !== renderPane);
|
|
111
|
-
if (other === undefined)
|
|
112
|
-
return undefined;
|
|
113
|
-
return paneContent(other.id).includes(':::') ? other.id : undefined;
|
|
114
|
-
}, 30_000, 'editor pane showing the raw source buffer');
|
|
115
|
-
tmux(['send-keys', '-t', editorPane, 'Space', 's']);
|
|
116
|
-
await waitFor(() => (existsSync(join(dir, 'feedback.json')) ? true : undefined), 30_000, 'feedback.json after submit');
|
|
117
|
-
await waitFor(() => (sessionPanes(session).some((p) => p.id === renderPane) ? undefined : true), 15_000, 'render pane to be killed after editor exit');
|
|
118
|
-
}
|
|
119
|
-
finally {
|
|
120
|
-
tmux(['kill-session', '-t', session]);
|
|
121
|
-
// The worker may still be flushing job files as it exits; retry the rm
|
|
122
|
-
// briefly so a write racing the first attempt can't fail the test.
|
|
123
|
-
for (let i = 0; i < 10; i++) {
|
|
124
|
-
try {
|
|
125
|
-
rmSync(dir, { recursive: true, force: true });
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
await sleep(300);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|