@north-light/crouter 0.3.157 → 0.3.159

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/api/client.d.ts +4 -1
  2. package/dist/api/client.js +5 -0
  3. package/dist/api/dto/broker.d.ts +1 -1
  4. package/dist/api/dto/nodes.d.ts +15 -0
  5. package/dist/api/routes.d.ts +1 -0
  6. package/dist/api/routes.js +1 -0
  7. package/dist/builtin-memory/internal/storage-tiers.md +2 -0
  8. package/dist/builtin-views/chat/core.mjs +6 -51
  9. package/dist/builtin-views/chat/tui.mjs +6 -14
  10. package/dist/builtin-views/chat/web.jsx +2 -7
  11. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +1 -1
  12. package/dist/clients/attach/chrome/bash-jobs.d.ts +0 -5
  13. package/dist/clients/attach/chrome/bash-jobs.js +5 -24
  14. package/dist/clients/attach/chrome/roster.d.ts +4 -3
  15. package/dist/clients/attach/chrome/roster.js +20 -36
  16. package/dist/clients/attach/command.js +5 -5
  17. package/dist/clients/attach/input/controller.d.ts +23 -8
  18. package/dist/clients/attach/input/controller.js +59 -29
  19. package/dist/clients/attach/overlays/dialogs.d.ts +1 -2
  20. package/dist/clients/attach/overlays/graph.d.ts +4 -1
  21. package/dist/clients/attach/overlays/graph.js +25 -7
  22. package/dist/clients/attach/session/context.d.ts +7 -0
  23. package/dist/clients/attach/session/frames.js +1 -8
  24. package/dist/clients/attach/session/input-wiring.d.ts +1 -1
  25. package/dist/clients/attach/session/input-wiring.js +21 -11
  26. package/dist/clients/attach/session/mode.d.ts +4 -3
  27. package/dist/clients/attach/session/mode.js +6 -1
  28. package/dist/clients/attach/session/reconnect.d.ts +3 -3
  29. package/dist/clients/attach/session/reconnect.js +6 -7
  30. package/dist/clients/attach/session/state-sync.d.ts +1 -1
  31. package/dist/clients/attach/session/state-sync.js +2 -2
  32. package/dist/clients/attach/slash/dispatch.d.ts +13 -1
  33. package/dist/clients/attach/slash/dispatch.js +65 -17
  34. package/dist/clients/attach/viewer.js +497 -499
  35. package/dist/clients/web/web-client/shared/protocol.d.ts +5 -3
  36. package/dist/commands/node.js +142 -4
  37. package/dist/commands/surface-inspect.js +4 -3
  38. package/dist/core/__tests__/broker-sdk-wiring.test.js +18 -18
  39. package/dist/core/__tests__/canvas-inbox-watcher.test.js +5 -3
  40. package/dist/core/__tests__/chat-view-reconnect.test.js +44 -23
  41. package/dist/core/__tests__/full/broker-attach-limits.test.js +60 -36
  42. package/dist/core/__tests__/full/broker-attach-stream.test.js +4 -4
  43. package/dist/core/__tests__/full/broker-dialogs.test.js +121 -62
  44. package/dist/core/__tests__/helpers/broker-clients.js +2 -2
  45. package/dist/core/__tests__/session-model.test.js +15 -26
  46. package/dist/core/bash-jobs.d.ts +11 -0
  47. package/dist/core/bash-jobs.js +41 -1
  48. package/dist/core/feed/inbox.d.ts +3 -3
  49. package/dist/core/feed/inbox.js +10 -6
  50. package/dist/core/inspector/core.d.ts +17 -2
  51. package/dist/core/inspector/core.js +172 -23
  52. package/dist/core/inspector/model.d.ts +30 -1
  53. package/dist/core/inspector/model.js +39 -0
  54. package/dist/core/inspector/text.js +14 -1
  55. package/dist/core/inspector/tui.js +71 -3
  56. package/dist/core/keybindings/__tests__/resolve.test.js +2 -2
  57. package/dist/core/keybindings/catalog.d.ts +2 -2
  58. package/dist/core/keybindings/catalog.js +3 -4
  59. package/dist/core/runtime/auth-reload.d.ts +4 -4
  60. package/dist/core/runtime/auth-reload.js +9 -13
  61. package/dist/core/runtime/boot-root.d.ts +2 -2
  62. package/dist/core/runtime/boot-root.js +7 -7
  63. package/dist/core/runtime/broker-protocol.d.ts +23 -27
  64. package/dist/core/runtime/broker-protocol.js +1 -1
  65. package/dist/core/runtime/broker-request.js +5 -11
  66. package/dist/core/runtime/broker.d.ts +21 -13
  67. package/dist/core/runtime/broker.js +151 -186
  68. package/dist/core/runtime/interactive-deliver.js +4 -5
  69. package/dist/core/runtime/model-swap.d.ts +2 -3
  70. package/dist/core/runtime/model-swap.js +3 -4
  71. package/dist/core/runtime/node-read.d.ts +20 -0
  72. package/dist/core/runtime/node-read.js +34 -1
  73. package/dist/core/runtime/resume-root.d.ts +1 -1
  74. package/dist/core/runtime/resume-root.js +6 -6
  75. package/dist/core/runtime/spawn.js +3 -3
  76. package/dist/core/session-model/session-state.d.ts +8 -6
  77. package/dist/core/session-model/session-state.js +6 -16
  78. package/dist/core/tui/host.js +21 -6
  79. package/dist/daemon/api/handlers/nodes.js +18 -1
  80. package/dist/daemon/manage.js +2 -2
  81. package/dist/index.d.ts +1 -1
  82. package/dist/pi-extensions/canvas-bash-valve.js +9 -3
  83. package/dist/web-client/assets/{index-DJhQZoAj.css → index-CpEl9LTS.css} +1 -1
  84. package/dist/web-client/assets/{index--SsQYcKu.js → index-CsuwzlcQ.js} +19 -19
  85. package/dist/web-client/index.html +2 -2
  86. package/dist/web-client/sw.js +1 -1
  87. package/docs/compat/hearth-crtr-v5.md +175 -0
  88. package/docs/public-api.md +2 -2
  89. package/package.json +2 -2
  90. package/runtime.lock.json +6 -6
  91. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +0 -1
  92. package/dist/core/__tests__/full/broker-control-preempt.test.js +0 -61
@@ -8,8 +8,8 @@
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--SsQYcKu.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-DJhQZoAj.css">
11
+ <script type="module" crossorigin src="/assets/index-CsuwzlcQ.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-CpEl9LTS.css">
13
13
  <link rel="manifest" href="/manifest.webmanifest"></head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -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":"bce04fc51b9a99df13267c3204979345","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-DJhQZoAj.css"},{"revision":null,"url":"assets/index--SsQYcKu.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":"ff26019ba3f9acb250cfcb1ebbbe8247","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-CsuwzlcQ.js"},{"revision":null,"url":"assets/index-CpEl9LTS.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))}
@@ -0,0 +1,175 @@
1
+ # Hearth ↔ crtr runtime compatibility — v5
2
+
3
+ A versioned contract for any external product (originally: `@crouton-kit/hearth`) that runs alongside a crtr install as a separate process/repo and needs a stable surface to pin a crtr package version against. "v5" names the shape described here; a future breaking change to any part of this contract gets a new compatibility document rather than silently rewriting this one.
4
+
5
+ ## Status and migration from v4
6
+
7
+ v5 is a breaking hard cut of broker singleton control. A client's role is fixed by its `hello`: `observer` is read-only and `controller` is writable. Multiple controller-role clients are independently writable; no client owns a singleton controller slot, and opening, focusing, backgrounding, or disconnecting another client does not change an existing client's role. `request_control`, `release_control`, and `control_changed` do not exist in the current broker protocol.
8
+
9
+ The observer `hello` remains valid for the `reload_auth` subset. Its `welcome` frame omits `controller_id`; `role` is the complete per-client authority statement. Sections 1–4 and 6 are unchanged from v4 and are reproduced below so this document remains a complete compatibility snapshot. Section 5 records the current broker handshake and `reload_auth` contract. The historical v4 document remains unchanged.
10
+
11
+ ## 1. Import contract
12
+
13
+ See `docs/public-api.md` for the full picture. The subset this note assumes:
14
+ `general`, `nowIso`, and `BROKER_READ_CAPS`, all imported from the package
15
+ root (`@crouton-kit/crouter`). No subpath beyond `.`, `./cli`, `./web` exists
16
+ or is supported.
17
+
18
+ ## 2. `crtr --json sys version`
19
+
20
+ Read-only. Returns an object with at minimum:
21
+
22
+ ```json
23
+ { "version": "0.3.40" }
24
+ ```
25
+
26
+ `version` is a required string, a semver matching `package.json#version` of
27
+ the installed crtr. A consumer asserting a running crtr's version (e.g. after
28
+ a guest image roll) should exec this command in-guest and compare the
29
+ returned `version` against the expected target — do not parse `--help` output
30
+ or read `package.json` directly, since the installed layout is not a public
31
+ contract.
32
+
33
+ ## 3. `crtr --json node message send` — immediate delivery
34
+
35
+ This leaf delivers an inbox message immediately. Future delivery, deadline waits (`node wait deadline`), and fresh revivals (`node lifecycle revive` for the immediate case) all have separate command paths and are outside this compatibility contract; deferred future delivery and deferred revival are arranged through `crtr cron add` rather than a dedicated leaf (see "Status and migration from v3" above). Typed-output requests (`node message request`) are likewise outside this contract.
36
+
37
+ **Invocation:**
38
+
39
+ ```
40
+ <body on stdin> | crtr --json node message send --to <node-id> [--tier critical|urgent|normal|deferred]
41
+ ```
42
+
43
+ - `body` — the message text, passed on stdin (or positional).
44
+ - `--to <node-id>` — required target (a real node id; `--self` is not
45
+ applicable to an external caller).
46
+ - `--tier` — optional, one of `critical | urgent | normal | deferred`.
47
+ Defaults to `normal` when omitted. Governs how the message is presented at
48
+ the target's next turn, not delivery timing.
49
+
50
+ **Output** (JSON object, `outputKind: object`):
51
+
52
+ | field | type | when present |
53
+ |---|---|---|
54
+ | `target` | string | always — the target node id |
55
+ | `delivered` | boolean | always — `true` once the inbox entry is appended |
56
+ | `revived` | boolean | always — whether delivery revived a dormant target |
57
+ | `guidance` | string | always — human/agent-readable confirmation text |
58
+
59
+ **Delivery semantics — read carefully:** this appends an entry to the
60
+ target's `inbox.jsonl` and, if the target is dormant, makes a **best-effort**
61
+ attempt to revive it so the message is acted on. Revival is asynchronous —
62
+ the command returning `delivered: true` means the inbox write succeeded, not
63
+ that the target has processed the message or is now running. A caller must
64
+ not block synchronously waiting for a revive to complete as part of this
65
+ call; if a caller needs to know the target acted on the message, that is a
66
+ separate, out-of-band observation (e.g. polling node status or waiting for
67
+ the target to message back), not a guarantee `node message send` makes.
68
+
69
+ ## 4. In-guest web port
70
+
71
+ `crtr surface web serve --host 127.0.0.1 --port <PRIVATE>` starts crtr's
72
+ unified web server (shell SPA, source/command bridge, SSE change lane, and
73
+ the browser⇄broker relay) bound to loopback on a private port chosen by the
74
+ guest. This is a **private, unauthenticated-by-default loopback surface** —
75
+ binding a non-loopback host requires `--token`; a consumer that fronts this
76
+ port (reverse-proxying non-model-auth or non-product traffic to it) is
77
+ responsible for whatever auth gate sits in front of the public listener. crtr
78
+ itself makes no claim about what proxies to this port or how; it only
79
+ guarantees the port serves the crtr web UI when bound.
80
+
81
+ ## 5. Broker `view.sock` — the `reload_auth` subset
82
+
83
+ A minimal, dependency-free client can nudge a live broker to re-read credentials after an external auth flow completes (e.g. a login page that just wrote fresh OAuth tokens to disk) without importing crtr's `ViewSocketClient` or any frame type. This is a v5-stable subset of the full broker wire protocol (`src/core/runtime/broker-protocol.ts`).
84
+
85
+ ### 5.1 Socket path resolution
86
+
87
+ - Always `<CRTR_HOME>/nodes/<nodeId>/view.sock` (`CRTR_HOME` defaults to
88
+ `~/.crouter/canvas`). `CRTR_SOCK_DIR` is not read.
89
+
90
+ ### 5.2 Framing
91
+
92
+ Newline-delimited JSON. Each frame is one JSON object serialized on a single
93
+ line, terminated by `\n`. Write frames to the socket in this form; read
94
+ replies the same way (buffer until a `\n`, then `JSON.parse` the line).
95
+
96
+ ### 5.3 Handshake — `hello`
97
+
98
+ On connect, send:
99
+
100
+ ```json
101
+ {"type":"hello","role":"observer","client_id":"<any string>"}
102
+ ```
103
+
104
+ `role: "observer"` remains sufficient for this subset — `reload_auth` is open to an observer because it is an idempotent local credential re-read that does not steer the conversation. The broker replies with a `welcome` frame:
105
+
106
+ ```json
107
+ {"type":"welcome","snapshot":{...},"role":"observer",...}
108
+ ```
109
+
110
+ The `welcome` frame omits `controller_id`. The role is fixed by the role sent in `hello`: an observer is read-only and a controller is writable. Multiple controller-role clients may write independently; there is no singleton controller owner or handoff.
111
+
112
+ **The `welcome.snapshot` can carry the full message/session history and may
113
+ be many megabytes** — the read side is capped generously (`CLIENT_READ_CAPS`:
114
+ 256 MiB per frame and per total buffer), not tightly. A `reload_auth`-only
115
+ client must tolerate this frame arriving and either parse-and-discard it or
116
+ skip it structurally (it is still one JSON line — read and drop it) rather
117
+ than assuming the first frame is small.
118
+
119
+ ### 5.4 The nudge — `reload_auth`
120
+
121
+ Send:
122
+
123
+ ```json
124
+ {"type":"reload_auth"}
125
+ ```
126
+
127
+ No other fields. The broker re-reads on-disk auth storage and refreshes its
128
+ model registry in response.
129
+
130
+ ### 5.5 The reply — `ack`
131
+
132
+ ```json
133
+ {"type":"ack","for":"reload_auth","ok":true}
134
+ ```
135
+
136
+ - `for` echoes the frame type that was acted on.
137
+ - `ok: true` on success. On failure the broker may instead send an `error`
138
+ frame (`{"type":"error","code":"...","message":"..."}`). A minimal client
139
+ must read frames until it receives either `{"type":"ack","for":"reload_auth",...}` or `{"type":"error",...}` and IGNORE any non-terminal frames sent in between (notably: `model_changed` broadcasts after the ack). Only the explicit ack or error frame determines the result.
140
+
141
+ ### 5.6 What this subset intentionally omits
142
+
143
+ No other frame type in this protocol (prompt/steer, session control, model selection, read-op pickers, bash execution, etc.) is part of this v5 note. In particular, `request_control`, `release_control`, and `control_changed` do not exist. A consumer that needs more than the `reload_auth` nudge should import `ViewSocketClient` from the package root rather than hand-rolling a larger client against this doc.
144
+
145
+ ## 6. `canvas.db` byte-shape — version 1
146
+
147
+ **Read/compat constraint only — never a write contract.** crtr's CLI and
148
+ runtime primitives (`spawnChild`, `reviveNode`, `node message send`, etc.) are the only
149
+ sanctioned way to mutate canvas state. Documenting the byte-shape below is
150
+ for a consumer that needs to *read* or *reason about* the database (e.g.
151
+ recovering a home's identity after a guest recreate), not for direct SQL
152
+ writes.
153
+
154
+ - Opened with Node's built-in `node:sqlite` `DatabaseSync`.
155
+ - WAL mode enabled by `openDb()`.
156
+ - Forward-only migrations gated by `PRAGMA user_version`, currently through
157
+ **v12**. A consumer pinning this contract should treat `user_version` as
158
+ the schema version to check, not assume a fixed column set never changes
159
+ going forward — this note describes the shape AS OF v12 and will need a later compatibility document if the schema changes in a way external readers depend on.
160
+ - **`nodes` table** — identity columns (`id`, and the fields mirrored from
161
+ each node's `meta.json`) are derived from `nodes/<id>/meta.json` on disk;
162
+ runtime columns (status, lifecycle, etc.) are row-authoritative *while the
163
+ local canvas.db exists*, but are **not recoverable from a wiped local DB**
164
+ — a rebuild re-derives identity from meta.json but cannot resurrect
165
+ transient runtime state that only ever lived in the row.
166
+ - **`subscribes_to`** — DB-authoritative edge table (the push/subscription
167
+ spine). Not derived from any on-disk file; a wiped DB loses these edges.
168
+ - **`spawned_by`** — provenance/audit edges, **re-derived from each node's
169
+ `meta.json` during a rebuild** (unlike `subscribes_to`, this one survives a
170
+ DB wipe as long as the node directories/meta.json files survive).
171
+ - **Auxiliary tables**: `focuses`, `crons`, `cron_runs`, `canvas_meta`.
172
+
173
+ A consumer pinning this contract should re-verify §6 against a live crtr
174
+ install (`PRAGMA user_version`, `.schema`) whenever bumping its pinned crtr
175
+ version, rather than assuming this note tracks every schema change.
@@ -35,7 +35,7 @@ comment-organized); this table is the map, not the inventory:
35
35
  `asksAcrossCanvas`, `readTelemetry`, `readContextTokens` (+ their types).
36
36
  Read-only queries against the canvas.db-backed node graph.
37
37
  - **Broker client** — `ViewSocketClient`, `BrokerUnavailableError`. A typed
38
- client for a node's `view.sock` (see `docs/compat/hearth-crtr-v2.md` for
38
+ client for a node's `view.sock` (see `docs/compat/hearth-crtr-v5.md` for
39
39
  the socket path resolution and a minimal-subset alternative that avoids
40
40
  depending on this class).
41
41
  - **Broker wire protocol** — `encodeFrame`, `FrameDecoder`,
@@ -69,7 +69,7 @@ A consumer that runs alongside crtr (a separate process, a separate repo)
69
69
  also has a CLI/socket/schema surface to depend on instead of an import.
70
70
  That contract — `crtr sys version`, `crtr node message send`, the in-guest web port,
71
71
  the broker `view.sock` `reload_auth` subset, and the canvas.db byte-shape —
72
- is documented separately and versioned: see `docs/compat/hearth-crtr-v2.md`.
72
+ is documented separately and versioned: see `docs/compat/hearth-crtr-v5.md`.
73
73
 
74
74
  ## Packaging
75
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.157",
3
+ "version": "0.3.159",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -107,7 +107,7 @@
107
107
  },
108
108
  "license": "MIT",
109
109
  "dependencies": {
110
- "@crouton-kit/humanloop": "0.4.19",
110
+ "@crouton-kit/humanloop": "0.4.20",
111
111
  "@earendil-works/pi-agent-core": "0.82.1",
112
112
  "@earendil-works/pi-ai": "0.82.1",
113
113
  "@earendil-works/pi-coding-agent": "0.82.1",
package/runtime.lock.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.157",
3
+ "version": "0.3.159",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.157",
9
+ "version": "0.3.159",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
13
- "@crouton-kit/humanloop": "0.4.19",
13
+ "@crouton-kit/humanloop": "0.4.20",
14
14
  "@earendil-works/pi-agent-core": "0.82.1",
15
15
  "@earendil-works/pi-ai": "0.82.1",
16
16
  "@earendil-works/pi-coding-agent": "0.82.1",
@@ -2073,9 +2073,9 @@
2073
2073
  }
2074
2074
  },
2075
2075
  "node_modules/@crouton-kit/humanloop": {
2076
- "version": "0.4.19",
2077
- "resolved": "https://registry.npmjs.org/@crouton-kit/humanloop/-/humanloop-0.4.19.tgz",
2078
- "integrity": "sha512-ombxlPmoNQMacEO5hhf2GtCAUkPjPa7l27xIPL34utFGLx11/heDLArihkbhXVtZR9uxBf6P2/uZQfLsh9fvCA==",
2076
+ "version": "0.4.20",
2077
+ "resolved": "https://registry.npmjs.org/@crouton-kit/humanloop/-/humanloop-0.4.20.tgz",
2078
+ "integrity": "sha512-4MBEVzQegNQQVCUktgsCyk4civYvhpCYJfY6nPTuOjNMpz+prT0sXfjNntcZS14cG0Hj/Igzzc010VpgVnN5WQ==",
2079
2079
  "hasInstallScript": true,
2080
2080
  "workspaces": [
2081
2081
  "web"
@@ -1,61 +0,0 @@
1
- // Run with: node --import tsx/esm --test src/core/__tests__/full/broker-control-preempt.test.ts
2
- // (FULL tier — boots a real isolated tmux session + a real headless broker.)
3
- //
4
- // BUG-REGRESSION (broker-universal-host cut, design §D preemptive handoff; U1
5
- // report mq8ftenr-5d55313d). Before the cut, `request_control` granted control
6
- // ONLY when controllerId===null and otherwise returned a `control_held` error —
7
- // so a second client could NEVER preempt a live (and usually idle/abandoned)
8
- // controller. With every node now broker-hosted, a tmux pane and a web tab are
9
- // true peers: either must be able to take control of a node the other currently
10
- // drives. The fix makes request_control preemptive last-requester-wins — it
11
- // ALWAYS succeeds, reassigning control to the requester and broadcasting a
12
- // control_changed that demotes the prior controller. There is no `control_held`
13
- // error frame anymore.
14
- //
15
- // Drive: controller A attaches and holds control; observer B then requests
16
- // control. Assert control reassigns to B, a control_changed broadcasts demoting
17
- // A, and B got NO error frame back (the old `control_held` reject is gone).
18
- // Regression check: restore the controllerId!==null reject and B's request is
19
- // refused (controller_id stays A / B receives an error) → these asserts go RED.
20
- import { test, before, after, afterEach } from 'node:test';
21
- import assert from 'node:assert/strict';
22
- import { createHeadlessHarness } from '../helpers/harness.js';
23
- import { createAttachKit } from '../helpers/broker-clients.js';
24
- let h;
25
- let id;
26
- const kit = createAttachKit(() => h);
27
- const { attach, attachUntil } = kit;
28
- before(async () => {
29
- h = await createHeadlessHarness({ sessionPrefix: 'crtr-ctrlpreempt' });
30
- const root = h.spawnRoot('control-preempt suite root');
31
- id = await h.spawnHeadlessChild(root, 'headless worker — control preemption');
32
- });
33
- after(async () => {
34
- if (h !== undefined)
35
- await h.dispose();
36
- });
37
- afterEach(() => {
38
- kit.closeAll();
39
- });
40
- test('D3 — a second client preempts the live controller (last-requester-wins); the prior controller is demoted, no control_held reject', async () => {
41
- // A attaches and is granted control (controllerId was null). attachUntil
42
- // suffixes the client_id per retry, so the broker-assigned id is the welcome's.
43
- const bId = 'd3-B';
44
- const a = await attachUntil(id, 'controller', 'd3-A', (x) => x.welcome.role === 'controller', 'A admitted controller');
45
- const aId = a.welcome.controller_id;
46
- assert.ok(aId !== null, 'A holds control after attach');
47
- // B attaches as an observer while A holds control (its welcome reflects A).
48
- const b = await attach(id, 'observer', bId);
49
- assert.equal(b.welcome.role, 'observer', 'B starts as an observer (A holds control)');
50
- assert.equal(b.welcome.controller_id, aId, 'B sees A as the current controller');
51
- // B preempts. Under the OLD model this returned a control_held error and B
52
- // never became controller; under §D it ALWAYS succeeds.
53
- b.send({ type: 'request_control' });
54
- // A is demoted via a broadcast control_changed naming B as the new controller.
55
- const changed = await a.waitFrame((f) => f.type === 'control_changed' && f.controller_id === bId, 'A receives control_changed demoting it to B');
56
- assert.equal(changed.controller_id, bId, 'control reassigned to B (last-requester-wins)');
57
- // B sees itself as the controller too (same broadcast).
58
- await b.waitFrame((f) => f.type === 'control_changed' && f.controller_id === bId, 'B observes itself become controller');
59
- // The old `control_held` reject is gone: B's request drew NO error frame.
60
- assert.ok(!b.frames.some((f) => f.type === 'error'), 'B received NO error frame — there is no control_held reject in the preemptive model');
61
- });