palmier 0.7.4 → 0.7.6
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/pwa/assets/index-uSwkmHBs.js +118 -0
- package/dist/pwa/assets/{web-_b3Dvcvz.js → web-7raT3zOZ.js} +1 -1
- package/dist/pwa/assets/{web-Dc9-IiRD.js → web-DnuoxUd4.js} +1 -1
- package/dist/pwa/index.html +1 -1
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.js +6 -2
- package/package.json +1 -1
- package/palmier-server/pwa/src/components/HostMenu.tsx +42 -160
- package/palmier-server/pwa/src/components/TaskListView.tsx +4 -4
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/pwa/src/pages/Dashboard.tsx +4 -4
- package/src/rpc-handler.ts +6 -2
- package/dist/pwa/assets/index-BirmfPUC.js +0 -118
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as p}from"./index-
|
|
1
|
+
import{W as p}from"./index-uSwkmHBs.js";class f extends p{constructor(){super(...arguments),this.group="CapacitorStorage"}async configure({group:e}){typeof e=="string"&&(this.group=e)}async get(e){return{value:this.impl.getItem(this.applyPrefix(e.key))}}async set(e){this.impl.setItem(this.applyPrefix(e.key),e.value)}async remove(e){this.impl.removeItem(this.applyPrefix(e.key))}async keys(){return{keys:this.rawKeys().map(t=>t.substring(this.prefix.length))}}async clear(){for(const e of this.rawKeys())this.impl.removeItem(e)}async migrate(){var e;const t=[],s=[],n="_cap_",o=Object.keys(this.impl).filter(i=>i.indexOf(n)===0);for(const i of o){const r=i.substring(n.length),a=(e=this.impl.getItem(i))!==null&&e!==void 0?e:"",{value:l}=await this.get({key:r});typeof l=="string"?s.push(r):(await this.set({key:r,value:a}),t.push(r))}return{migrated:t,existing:s}}async removeOld(){const e="_cap_",t=Object.keys(this.impl).filter(s=>s.indexOf(e)===0);for(const s of t)this.impl.removeItem(s)}get impl(){return window.localStorage}get prefix(){return this.group==="NativeStorage"?"":`${this.group}.`}rawKeys(){return Object.keys(this.impl).filter(e=>e.indexOf(this.prefix)===0)}applyPrefix(e){return this.prefix+e}}export{f as PreferencesWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as t}from"./index-
|
|
1
|
+
import{W as t}from"./index-uSwkmHBs.js";class s extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{s as AppWeb};
|
package/dist/pwa/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
9
9
|
<title>Palmier</title>
|
|
10
10
|
<meta name="description" content="Remote control for AI agents running on your own machine. Schedule tasks, approve permissions, and get push notifications." />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-uSwkmHBs.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-B-ByUHPS.css">
|
|
13
13
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
14
14
|
<body>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
try{self["workbox:core:7.3.0"]&&_()}catch{}const N=(n,...e)=>{let t=n;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},E=N;class h extends Error{constructor(e,t){const s=E(e,t);super(s),this.name=e,this.details=t}}const f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},U=n=>[f.prefix,n,f.suffix].filter(e=>e&&e.length>0).join("-"),O=n=>{for(const e of Object.keys(f))n(e)},L={updateDetails:n=>{O(e=>{typeof n[e]=="string"&&(f[e]=n[e])})},getGoogleAnalyticsName:n=>n||U(f.googleAnalytics),getPrecacheName:n=>n||U(f.precache),getPrefix:()=>f.prefix,getRuntimeName:n=>n||U(f.runtime),getSuffix:()=>f.suffix};function v(n,e){const t=e();return n.waitUntil(t),t}try{self["workbox:precaching:7.3.0"]&&_()}catch{}const A="__WB_REVISION__";function M(n){if(!n)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(typeof n=="string"){const i=new URL(n,location.href);return{cacheKey:i.href,url:i.href}}const{revision:e,url:t}=n;if(!t)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(!e){const i=new URL(t,location.href);return{cacheKey:i.href,url:i.href}}const s=new URL(t,location.href),a=new URL(t,location.href);return s.searchParams.set(A,e),{cacheKey:s.href,url:a.href}}class W{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const a=t.originalRequest.url;s?this.notUpdatedURLs.push(a):this.updatedURLs.push(a)}return s}}}class q{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const a=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return a?new Request(a,{headers:t.headers}):t},this._precacheController=e}}let w;function S(){if(w===void 0){const n=new Response("");if("body"in n)try{new Response(n.body),w=!0}catch{w=!1}w=!1}return w}async function j(n,e){let t=null;if(n.url&&(t=new URL(n.url).origin),t!==self.location.origin)throw new h("cross-origin-copy-response",{origin:t});const s=n.clone(),i={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},r=S()?s.body:await s.blob();return new Response(r,i)}const D=n=>new URL(String(n),location.href).href.replace(new RegExp(`^${location.origin}`),"");function T(n,e){const t=new URL(n);for(const s of e)t.searchParams.delete(s);return t.href}async function H(n,e,t,s){const a=T(e.url,t);if(e.url===a)return n.match(e,s);const i=Object.assign(Object.assign({},s),{ignoreSearch:!0}),r=await n.keys(e,i);for(const c of r){const o=T(c.url,t);if(a===o)return n.match(c,s)}}class F{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}const B=new Set;async function $(){for(const n of B)await n()}function V(n){return new Promise(e=>setTimeout(e,n))}try{self["workbox:strategies:7.3.0"]&&_()}catch{}function C(n){return typeof n=="string"?new Request(n):n}class G{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new F,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=C(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const r=await t.preloadResponse;if(r)return r}const a=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const r of this.iterateCallbacks("requestWillFetch"))s=await r({request:s.clone(),event:t})}catch(r){if(r instanceof Error)throw new h("plugin-error-request-will-fetch",{thrownErrorMessage:r.message})}const i=s.clone();try{let r;r=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const c of this.iterateCallbacks("fetchDidSucceed"))r=await c({event:t,request:i,response:r});return r}catch(r){throw a&&await this.runCallbacks("fetchDidFail",{error:r,event:t,originalRequest:a.clone(),request:i.clone()}),r}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=C(e);let s;const{cacheName:a,matchOptions:i}=this._strategy,r=await this.getCacheKey(t,"read"),c=Object.assign(Object.assign({},i),{cacheName:a});s=await caches.match(r,c);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:a,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(e,t){const s=C(e);await V(0);const a=await this.getCacheKey(s,"write");if(!t)throw new h("cache-put-with-no-response",{url:D(a.url)});const i=await this._ensureResponseSafeToCache(t);if(!i)return!1;const{cacheName:r,matchOptions:c}=this._strategy,o=await self.caches.open(r),l=this.hasCallback("cacheDidUpdate"),d=l?await H(o,a.clone(),["__WB_REVISION__"],c):null;try{await o.put(a,l?i.clone():i)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await $(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:r,oldResponse:d,newResponse:i.clone(),request:a,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let a=e;for(const i of this.iterateCallbacks("cacheKeyWillBeUsed"))a=C(await i({mode:t,request:a,event:this.event,params:this.params}));this._cacheKeys[s]=a}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield i=>{const r=Object.assign(Object.assign({},i),{state:s});return t[e](r)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(a=>a.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const a of this.iterateCallbacks("cacheWillUpdate"))if(t=await a({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class J{constructor(e={}){this.cacheName=L.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,a="params"in e?e.params:void 0,i=new G(this,{event:t,request:s,params:a}),r=this._getResponse(i,s,t),c=this._awaitComplete(r,i,s,t);return[r,c]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let a;try{if(a=await this._handle(t,e),!a||a.type==="error")throw new h("no-response",{url:t.url})}catch(i){if(i instanceof Error){for(const r of e.iterateCallbacks("handlerDidError"))if(a=await r({error:i,event:s,request:t}),a)break}if(!a)throw i}for(const i of e.iterateCallbacks("handlerWillRespond"))a=await i({event:s,request:t,response:a});return a}async _awaitComplete(e,t,s,a){let i,r;try{i=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:a,request:s,response:i}),await t.doneWaiting()}catch(c){c instanceof Error&&(r=c)}if(await t.runCallbacks("handlerDidComplete",{event:a,request:s,response:i,error:r}),t.destroy(),r)throw r}}class p extends J{constructor(e={}){e.cacheName=L.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(p.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const a=t.params||{};if(this._fallbackToNetwork){const i=a.integrity,r=e.integrity,c=!r||r===i;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?r||i:void 0})),i&&c&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new h("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new h("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,a]of this.plugins.entries())a!==p.copyRedirectedCacheableResponsesPlugin&&(a===p.defaultPrecacheCacheabilityPlugin&&(e=s),a.cacheWillUpdate&&t++);t===0?this.plugins.push(p.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}p.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:n}){return!n||n.status>=400?null:n}};p.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:n}){return n.redirected?await j(n):n}};class Q{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new p({cacheName:L.getPrecacheName(e),plugins:[...t,new q({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:a,url:i}=M(s),r=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==a)throw new h("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:a});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(a)&&this._cacheKeysToIntegrities.get(a)!==s.integrity)throw new h("add-to-cache-list-conflicting-integrities",{url:i});this._cacheKeysToIntegrities.set(a,s.integrity)}if(this._urlsToCacheKeys.set(i,a),this._urlsToCacheModes.set(i,r),t.length>0){const c=`Workbox is precaching URLs without revision info: ${t.join(", ")}
|
|
2
|
-
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(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 h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"
|
|
2
|
+
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(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 h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"478ef5ebdaf80fed88fcc601cd2ae175","url":"index.html"},{"revision":null,"url":"assets/web-DnuoxUd4.js"},{"revision":null,"url":"assets/web-7raT3zOZ.js"},{"revision":null,"url":"assets/index-uSwkmHBs.js"},{"revision":null,"url":"assets/index-B-ByUHPS.css"},{"revision":"fcc457fce855ad0df7178e0786c0d4ef","url":"apple-touch-icon.png"},{"revision":"276650c30bc4effc7d649ec66519aab6","url":"favicon.ico"},{"revision":"2e46512b835c05e17787059909305f22","url":"pwa-192x192.png"},{"revision":"ec5652b5834b4711337743e80e506a41","url":"pwa-512x512.png"},{"revision":"9f51698004b9cc4d787c75695b74de9d","url":"manifest.webmanifest"}]);const re="/api/push/respond";self.addEventListener("message",n=>{});self.addEventListener("push",n=>{var r;if(!n.data)return;let e;try{e=n.data.json()}catch{e={title:"Palmier",body:n.data.text()}}const t=e.type??((r=e.data)==null?void 0:r.type);if(t==="confirm-dismiss"||t==="permission-dismiss"||t==="input-dismiss"){const c=e.data??e,o=c.host_id,l=c.session_id,d=c.task_id;n.waitUntil(self.registration.getNotifications().then(u=>{var g,P,K;for(const y of u)if(((g=y.data)==null?void 0:g.host_id)===o){if(l&&((P=y.data)==null?void 0:P.session_id)===l){y.close();continue}d&&((K=y.data)==null?void 0:K.task_id)===d&&y.close()}}));return}const s=e.title??"Palmier";let a=e.body??"";!a&&t==="confirm"&&(a="A task requires confirmation to run."),!a&&t==="permission"&&(a="A task needs additional permissions to continue."),!a&&t==="input"&&(a="A task needs your input to continue.");const i={body:a,icon:"/pwa-192x192.png",badge:"/pwa-192x192.png",data:e.data??e,vibrate:[100,50,100]};t==="confirm"&&(i.actions=[{action:"confirm",title:"Confirm"},{action:"abort",title:"Abort"}]),n.waitUntil(self.registration.showNotification(s,i))});self.addEventListener("notificationclick",n=>{const e=n.notification;e.close();const t=e.data??{},s=n.action;if(s&&t.type==="confirm"&&t.session_id&&t.host_id){const a=s==="confirm"?"confirmed":"aborted";n.waitUntil(fetch(re,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:t.session_id,host_id:t.host_id,response:a})}).catch(i=>{console.error("Failed to send push response:",i)}))}else{const a=t.task_id,i=t.run_id,r=a&&i?`/runs/${encodeURIComponent(a)}/${encodeURIComponent(i)}`:a?`/runs/${encodeURIComponent(a)}/latest`:"/";n.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(c=>{for(const o of c)if(o.url.includes(self.location.origin)&&"focus"in o)return o.navigate(r),o.focus();return self.clients.openWindow(r)}))}});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",n=>{n.waitUntil(self.clients.claim())});
|
package/dist/rpc-handler.js
CHANGED
|
@@ -143,13 +143,17 @@ export function createRpcHandler(config, nc) {
|
|
|
143
143
|
switch (request.method) {
|
|
144
144
|
case "task.list": {
|
|
145
145
|
const tasks = listTasks(config.projectRoot);
|
|
146
|
-
const
|
|
146
|
+
const capabilities = {};
|
|
147
|
+
for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd"]) {
|
|
148
|
+
capabilities[cap] = getCapabilityDevice(cap)?.clientToken ?? null;
|
|
149
|
+
}
|
|
147
150
|
return {
|
|
148
151
|
tasks: tasks.map((task) => flattenTask(task)),
|
|
149
152
|
agents: config.agents ?? [],
|
|
150
153
|
version: currentVersion,
|
|
151
154
|
host_platform: process.platform,
|
|
152
|
-
location_client_token:
|
|
155
|
+
location_client_token: capabilities.location,
|
|
156
|
+
capability_tokens: capabilities,
|
|
153
157
|
};
|
|
154
158
|
}
|
|
155
159
|
case "task.get": {
|
package/package.json
CHANGED
|
@@ -92,13 +92,13 @@ const isNative = Capacitor.isNativePlatform();
|
|
|
92
92
|
|
|
93
93
|
interface HostMenuProps {
|
|
94
94
|
daemonVersion?: string | null;
|
|
95
|
-
|
|
95
|
+
capabilityTokens?: Record<string, string | null>;
|
|
96
96
|
activeClientToken?: string | null;
|
|
97
97
|
request?<T = unknown>(method: string, params?: unknown): Promise<T>;
|
|
98
|
-
|
|
98
|
+
onCapabilityTokensChange?(tokens: Record<string, string | null>): void;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
export default function HostMenu({ daemonVersion,
|
|
101
|
+
export default function HostMenu({ daemonVersion, capabilityTokens, activeClientToken, request, onCapabilityTokensChange }: HostMenuProps) {
|
|
102
102
|
const { pairedHosts, activeHostId, setActiveHostId, removePairedHost, renamePairedHost } = useHostStore();
|
|
103
103
|
const navigate = useNavigate();
|
|
104
104
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
@@ -109,22 +109,34 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
109
109
|
const [renameValue, setRenameValue] = useState("");
|
|
110
110
|
const [confirmingDeleteId, setConfirmingDeleteId] = useState<string | null>(null);
|
|
111
111
|
const [togglingLocation, setTogglingLocation] = useState(false);
|
|
112
|
-
const [notificationListenerEnabled, setNotificationListenerEnabled] = useState(false);
|
|
113
112
|
const [togglingNotificationListener, setTogglingNotificationListener] = useState(false);
|
|
114
|
-
const [smsEnabled, setSmsEnabled] = useState(false);
|
|
115
113
|
const [togglingSms, setTogglingSms] = useState(false);
|
|
116
|
-
const [contactsEnabled, setContactsEnabled] = useState(false);
|
|
117
114
|
const [togglingContacts, setTogglingContacts] = useState(false);
|
|
118
|
-
const [calendarEnabled, setCalendarEnabled] = useState(false);
|
|
119
115
|
const [togglingCalendar, setTogglingCalendar] = useState(false);
|
|
120
|
-
const [dndEnabled, setDndEnabled] = useState(false);
|
|
121
116
|
const [togglingDnd, setTogglingDnd] = useState(false);
|
|
122
|
-
const [alarmEnabled, setAlarmEnabled] = useState(false);
|
|
123
117
|
const [togglingAlarm, setTogglingAlarm] = useState(false);
|
|
124
|
-
const [batteryEnabled, setBatteryEnabled] = useState(false);
|
|
125
118
|
const [togglingBattery, setTogglingBattery] = useState(false);
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
// Capability enabled = this device's client token matches the registered device for that capability
|
|
121
|
+
function isCapEnabled(cap: string): boolean {
|
|
122
|
+
return !!(activeClientToken && capabilityTokens?.[cap] === activeClientToken);
|
|
123
|
+
}
|
|
124
|
+
const locationEnabled = isCapEnabled("location");
|
|
125
|
+
const notificationListenerEnabled = isCapEnabled("notifications");
|
|
126
|
+
const smsEnabled = isCapEnabled("sms");
|
|
127
|
+
const contactsEnabled = isCapEnabled("contacts");
|
|
128
|
+
const calendarEnabled = isCapEnabled("calendar");
|
|
129
|
+
const dndEnabled = isCapEnabled("dnd");
|
|
130
|
+
const alarmEnabled = isCapEnabled("alert");
|
|
131
|
+
const batteryEnabled = isCapEnabled("battery");
|
|
132
|
+
|
|
133
|
+
/** Update local capability tokens state after a toggle change */
|
|
134
|
+
function setCapEnabled(cap: string, enabled: boolean) {
|
|
135
|
+
const updated: Record<string, string | null> = {};
|
|
136
|
+
for (const [k, v] of Object.entries(capabilityTokens ?? {})) updated[k] = v ?? null;
|
|
137
|
+
updated[cap] = enabled ? (activeClientToken ?? null) : null;
|
|
138
|
+
onCapabilityTokensChange?.(updated);
|
|
139
|
+
}
|
|
128
140
|
|
|
129
141
|
// Sync location toggle with permission state — on mount and when app resumes from background
|
|
130
142
|
useEffect(() => {
|
|
@@ -136,7 +148,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
136
148
|
if (!fine) {
|
|
137
149
|
// Permission revoked — disable on host
|
|
138
150
|
request!("device.location.disable").then(() => {
|
|
139
|
-
|
|
151
|
+
setCapEnabled("location", false);
|
|
140
152
|
}).catch(() => {});
|
|
141
153
|
}
|
|
142
154
|
});
|
|
@@ -151,29 +163,6 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
151
163
|
return () => { listener.then((h) => h.remove()); };
|
|
152
164
|
}, [locationEnabled, activeClientToken]);
|
|
153
165
|
|
|
154
|
-
// Sync notification listener toggle with system state — on mount and when app resumes
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
if (!isNative || !NotificationListener) return;
|
|
157
|
-
|
|
158
|
-
function syncNotificationListenerState() {
|
|
159
|
-
Promise.all([
|
|
160
|
-
NotificationListener!.check(),
|
|
161
|
-
Preferences.get({ key: "notificationListenerEnabled" }),
|
|
162
|
-
]).then(([{ enabled: systemEnabled }, { value: prefValue }]) => {
|
|
163
|
-
// Enabled only if both system permission is granted AND user toggled on
|
|
164
|
-
setNotificationListenerEnabled(systemEnabled && prefValue !== "false");
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
syncNotificationListenerState();
|
|
169
|
-
|
|
170
|
-
const listener = CapApp.addListener("resume", () => {
|
|
171
|
-
syncNotificationListenerState();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
return () => { listener.then((h) => h.remove()); };
|
|
175
|
-
}, []);
|
|
176
|
-
|
|
177
166
|
async function handleNotificationListenerToggle() {
|
|
178
167
|
if (!NotificationListener || !request) return;
|
|
179
168
|
setTogglingNotificationListener(true);
|
|
@@ -181,7 +170,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
181
170
|
if (notificationListenerEnabled) {
|
|
182
171
|
await Preferences.set({ key: "notificationListenerEnabled", value: "false" });
|
|
183
172
|
await request("device.capability.disable", { capability: "notifications" });
|
|
184
|
-
|
|
173
|
+
setCapEnabled("notifications", false);
|
|
185
174
|
} else {
|
|
186
175
|
const { enabled: systemEnabled } = await NotificationListener.check();
|
|
187
176
|
if (!systemEnabled) {
|
|
@@ -192,7 +181,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
192
181
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
193
182
|
await Preferences.set({ key: "notificationListenerEnabled", value: "true" });
|
|
194
183
|
await request("device.capability.enable", { capability: "notifications", fcmToken });
|
|
195
|
-
|
|
184
|
+
setCapEnabled("notifications", true);
|
|
196
185
|
}
|
|
197
186
|
} catch (err) {
|
|
198
187
|
console.error("Failed to toggle notification listener:", err);
|
|
@@ -201,28 +190,6 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
201
190
|
}
|
|
202
191
|
}
|
|
203
192
|
|
|
204
|
-
// Sync SMS toggle with permission state — on mount and when app resumes
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
if (!isNative || !SmsPermission) return;
|
|
207
|
-
|
|
208
|
-
function syncSmsState() {
|
|
209
|
-
Promise.all([
|
|
210
|
-
SmsPermission!.check(),
|
|
211
|
-
Preferences.get({ key: "smsListenerEnabled" }),
|
|
212
|
-
]).then(([{ granted }, { value: prefValue }]) => {
|
|
213
|
-
setSmsEnabled(granted && prefValue !== "false");
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
syncSmsState();
|
|
218
|
-
|
|
219
|
-
const listener = CapApp.addListener("resume", () => {
|
|
220
|
-
syncSmsState();
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
return () => { listener.then((h) => h.remove()); };
|
|
224
|
-
}, []);
|
|
225
|
-
|
|
226
193
|
async function handleSmsToggle() {
|
|
227
194
|
if (!SmsPermission || !request) return;
|
|
228
195
|
setTogglingSms(true);
|
|
@@ -230,7 +197,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
230
197
|
if (smsEnabled) {
|
|
231
198
|
await Preferences.set({ key: "smsListenerEnabled", value: "false" });
|
|
232
199
|
await request("device.capability.disable", { capability: "sms" });
|
|
233
|
-
|
|
200
|
+
setCapEnabled("sms", false);
|
|
234
201
|
} else {
|
|
235
202
|
const { granted } = await SmsPermission.check();
|
|
236
203
|
if (!granted) {
|
|
@@ -241,7 +208,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
241
208
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
242
209
|
await Preferences.set({ key: "smsListenerEnabled", value: "true" });
|
|
243
210
|
await request("device.capability.enable", { capability: "sms", fcmToken });
|
|
244
|
-
|
|
211
|
+
setCapEnabled("sms", true);
|
|
245
212
|
}
|
|
246
213
|
} catch (err) {
|
|
247
214
|
console.error("Failed to toggle SMS access:", err);
|
|
@@ -250,28 +217,6 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
250
217
|
}
|
|
251
218
|
}
|
|
252
219
|
|
|
253
|
-
// Sync contacts toggle with permission state — on mount and when app resumes
|
|
254
|
-
useEffect(() => {
|
|
255
|
-
if (!isNative || !ContactsPermission) return;
|
|
256
|
-
|
|
257
|
-
function syncContactsState() {
|
|
258
|
-
Promise.all([
|
|
259
|
-
ContactsPermission!.check(),
|
|
260
|
-
Preferences.get({ key: "contactsAccessEnabled" }),
|
|
261
|
-
]).then(([{ granted }, { value: prefValue }]) => {
|
|
262
|
-
setContactsEnabled(granted && prefValue !== "false");
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
syncContactsState();
|
|
267
|
-
|
|
268
|
-
const listener = CapApp.addListener("resume", () => {
|
|
269
|
-
syncContactsState();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
return () => { listener.then((h) => h.remove()); };
|
|
273
|
-
}, []);
|
|
274
|
-
|
|
275
220
|
async function handleContactsToggle() {
|
|
276
221
|
if (!ContactsPermission || !request) return;
|
|
277
222
|
setTogglingContacts(true);
|
|
@@ -279,7 +224,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
279
224
|
if (contactsEnabled) {
|
|
280
225
|
await Preferences.set({ key: "contactsAccessEnabled", value: "false" });
|
|
281
226
|
await request("device.capability.disable", { capability: "contacts" });
|
|
282
|
-
|
|
227
|
+
setCapEnabled("contacts", false);
|
|
283
228
|
} else {
|
|
284
229
|
const { granted } = await ContactsPermission.check();
|
|
285
230
|
if (!granted) {
|
|
@@ -290,7 +235,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
290
235
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
291
236
|
await Preferences.set({ key: "contactsAccessEnabled", value: "true" });
|
|
292
237
|
await request("device.capability.enable", { capability: "contacts", fcmToken });
|
|
293
|
-
|
|
238
|
+
setCapEnabled("contacts", true);
|
|
294
239
|
}
|
|
295
240
|
} catch (err) {
|
|
296
241
|
console.error("Failed to toggle contacts access:", err);
|
|
@@ -299,28 +244,6 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
299
244
|
}
|
|
300
245
|
}
|
|
301
246
|
|
|
302
|
-
// Sync calendar toggle with permission state — on mount and when app resumes
|
|
303
|
-
useEffect(() => {
|
|
304
|
-
if (!isNative || !CalendarPermission) return;
|
|
305
|
-
|
|
306
|
-
function syncCalendarState() {
|
|
307
|
-
Promise.all([
|
|
308
|
-
CalendarPermission!.check(),
|
|
309
|
-
Preferences.get({ key: "calendarAccessEnabled" }),
|
|
310
|
-
]).then(([{ granted }, { value: prefValue }]) => {
|
|
311
|
-
setCalendarEnabled(granted && prefValue !== "false");
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
syncCalendarState();
|
|
316
|
-
|
|
317
|
-
const listener = CapApp.addListener("resume", () => {
|
|
318
|
-
syncCalendarState();
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
return () => { listener.then((h) => h.remove()); };
|
|
322
|
-
}, []);
|
|
323
|
-
|
|
324
247
|
async function handleCalendarToggle() {
|
|
325
248
|
if (!CalendarPermission || !request) return;
|
|
326
249
|
setTogglingCalendar(true);
|
|
@@ -328,7 +251,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
328
251
|
if (calendarEnabled) {
|
|
329
252
|
await Preferences.set({ key: "calendarAccessEnabled", value: "false" });
|
|
330
253
|
await request("device.capability.disable", { capability: "calendar" });
|
|
331
|
-
|
|
254
|
+
setCapEnabled("calendar", false);
|
|
332
255
|
} else {
|
|
333
256
|
const { granted } = await CalendarPermission.check();
|
|
334
257
|
if (!granted) {
|
|
@@ -339,7 +262,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
339
262
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
340
263
|
await Preferences.set({ key: "calendarAccessEnabled", value: "true" });
|
|
341
264
|
await request("device.capability.enable", { capability: "calendar", fcmToken });
|
|
342
|
-
|
|
265
|
+
setCapEnabled("calendar", true);
|
|
343
266
|
}
|
|
344
267
|
} catch (err) {
|
|
345
268
|
console.error("Failed to toggle calendar access:", err);
|
|
@@ -348,33 +271,13 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
348
271
|
}
|
|
349
272
|
}
|
|
350
273
|
|
|
351
|
-
// Sync DND access toggle with system state — on mount and when app resumes
|
|
352
|
-
useEffect(() => {
|
|
353
|
-
if (!isNative || !DndAccess) return;
|
|
354
|
-
|
|
355
|
-
function syncDndState() {
|
|
356
|
-
DndAccess!.check().then(({ enabled }) => {
|
|
357
|
-
setDndEnabled(enabled);
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
syncDndState();
|
|
362
|
-
|
|
363
|
-
const listener = CapApp.addListener("resume", () => {
|
|
364
|
-
syncDndState();
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
return () => { listener.then((h) => h.remove()); };
|
|
368
|
-
}, []);
|
|
369
|
-
|
|
370
274
|
async function handleDndToggle() {
|
|
371
275
|
if (!DndAccess || !request) return;
|
|
372
276
|
setTogglingDnd(true);
|
|
373
277
|
try {
|
|
374
278
|
if (dndEnabled) {
|
|
375
|
-
// DND access can only be revoked in system settings, but we unregister from host
|
|
376
279
|
await request("device.capability.disable", { capability: "dnd" });
|
|
377
|
-
|
|
280
|
+
setCapEnabled("dnd", false);
|
|
378
281
|
} else {
|
|
379
282
|
const { enabled: systemEnabled } = await DndAccess.check();
|
|
380
283
|
if (!systemEnabled) {
|
|
@@ -384,7 +287,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
384
287
|
const { value: fcmToken } = await Preferences.get({ key: "fcmToken" });
|
|
385
288
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
386
289
|
await request("device.capability.enable", { capability: "dnd", fcmToken });
|
|
387
|
-
|
|
290
|
+
setCapEnabled("dnd", true);
|
|
388
291
|
}
|
|
389
292
|
} catch (err) {
|
|
390
293
|
console.error("Failed to toggle DND access:", err);
|
|
@@ -393,58 +296,38 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
393
296
|
}
|
|
394
297
|
}
|
|
395
298
|
|
|
396
|
-
// Sync alarm toggle — no permission needed, just device registration
|
|
397
|
-
useEffect(() => {
|
|
398
|
-
if (!isNative) return;
|
|
399
|
-
Preferences.get({ key: "alertAccessEnabled" }).then(({ value }) => {
|
|
400
|
-
setAlarmEnabled(value === "true");
|
|
401
|
-
});
|
|
402
|
-
}, []);
|
|
403
|
-
|
|
404
299
|
async function handleAlarmToggle() {
|
|
405
300
|
if (!request) return;
|
|
406
301
|
setTogglingAlarm(true);
|
|
407
302
|
try {
|
|
408
303
|
if (alarmEnabled) {
|
|
409
|
-
await Preferences.set({ key: "alertAccessEnabled", value: "false" });
|
|
410
304
|
await request("device.capability.disable", { capability: "alert" });
|
|
411
|
-
|
|
305
|
+
setCapEnabled("alert", false);
|
|
412
306
|
} else {
|
|
413
307
|
const { value: fcmToken } = await Preferences.get({ key: "fcmToken" });
|
|
414
308
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
415
|
-
await Preferences.set({ key: "alertAccessEnabled", value: "true" });
|
|
416
309
|
await request("device.capability.enable", { capability: "alert", fcmToken });
|
|
417
|
-
|
|
310
|
+
setCapEnabled("alert", true);
|
|
418
311
|
}
|
|
419
312
|
} catch (err) {
|
|
420
|
-
console.error("Failed to toggle
|
|
313
|
+
console.error("Failed to toggle alert access:", err);
|
|
421
314
|
} finally {
|
|
422
315
|
setTogglingAlarm(false);
|
|
423
316
|
}
|
|
424
317
|
}
|
|
425
318
|
|
|
426
|
-
// Sync battery toggle — no permission needed, just device registration
|
|
427
|
-
useEffect(() => {
|
|
428
|
-
if (!isNative) return;
|
|
429
|
-
Preferences.get({ key: "batteryAccessEnabled" }).then(({ value }) => {
|
|
430
|
-
setBatteryEnabled(value === "true");
|
|
431
|
-
});
|
|
432
|
-
}, []);
|
|
433
|
-
|
|
434
319
|
async function handleBatteryToggle() {
|
|
435
320
|
if (!request) return;
|
|
436
321
|
setTogglingBattery(true);
|
|
437
322
|
try {
|
|
438
323
|
if (batteryEnabled) {
|
|
439
|
-
await Preferences.set({ key: "batteryAccessEnabled", value: "false" });
|
|
440
324
|
await request("device.capability.disable", { capability: "battery" });
|
|
441
|
-
|
|
325
|
+
setCapEnabled("battery", false);
|
|
442
326
|
} else {
|
|
443
327
|
const { value: fcmToken } = await Preferences.get({ key: "fcmToken" });
|
|
444
328
|
if (!fcmToken) { console.warn("No FCM token available"); return; }
|
|
445
|
-
await Preferences.set({ key: "batteryAccessEnabled", value: "true" });
|
|
446
329
|
await request("device.capability.enable", { capability: "battery", fcmToken });
|
|
447
|
-
|
|
330
|
+
setCapEnabled("battery", true);
|
|
448
331
|
}
|
|
449
332
|
} catch (err) {
|
|
450
333
|
console.error("Failed to toggle battery access:", err);
|
|
@@ -459,13 +342,12 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
459
342
|
try {
|
|
460
343
|
if (locationEnabled) {
|
|
461
344
|
await request("device.location.disable");
|
|
462
|
-
|
|
345
|
+
setCapEnabled("location", false);
|
|
463
346
|
} else {
|
|
464
|
-
// Request location permissions before enabling
|
|
465
347
|
if (LocationPermission) {
|
|
466
348
|
const result = await LocationPermission.request();
|
|
467
349
|
if (!result.fine) {
|
|
468
|
-
return;
|
|
350
|
+
return;
|
|
469
351
|
}
|
|
470
352
|
}
|
|
471
353
|
|
|
@@ -475,7 +357,7 @@ export default function HostMenu({ daemonVersion, locationClientToken, activeCli
|
|
|
475
357
|
return;
|
|
476
358
|
}
|
|
477
359
|
await request("device.location.enable", { fcmToken });
|
|
478
|
-
|
|
360
|
+
setCapEnabled("location", true);
|
|
479
361
|
}
|
|
480
362
|
} catch (err) {
|
|
481
363
|
console.error("Failed to toggle location:", err);
|
|
@@ -26,10 +26,10 @@ interface TaskListViewProps {
|
|
|
26
26
|
onViewRun(taskId: string, runId?: string): void;
|
|
27
27
|
onUpdateRequired?(required: boolean): void;
|
|
28
28
|
onVersion?(version: string | null): void;
|
|
29
|
-
|
|
29
|
+
onCapabilityTokens?(tokens: Record<string, string | null>): void;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export default function TaskListView({ connected, hostId, request, subscribeEvents, onViewRun, onUpdateRequired, onVersion,
|
|
32
|
+
export default function TaskListView({ connected, hostId, request, subscribeEvents, onViewRun, onUpdateRequired, onVersion, onCapabilityTokens }: TaskListViewProps) {
|
|
33
33
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
34
34
|
const [loadingTasks, setLoadingTasks] = useState(false);
|
|
35
35
|
const [taskError, setTaskError] = useState<string | null>(null);
|
|
@@ -53,7 +53,7 @@ export default function TaskListView({ connected, hostId, request, subscribeEven
|
|
|
53
53
|
setLoadingTasks(true);
|
|
54
54
|
setTaskError(null);
|
|
55
55
|
try {
|
|
56
|
-
const result = await request<{ tasks?: (Task & { status?: TaskStatus })[]; agents?: AgentInfo[]; version?: string | null; host_platform?: string; location_client_token?: string | null }>("task.list");
|
|
56
|
+
const result = await request<{ tasks?: (Task & { status?: TaskStatus })[]; agents?: AgentInfo[]; version?: string | null; host_platform?: string; location_client_token?: string | null; capability_tokens?: Record<string, string | null> }>("task.list");
|
|
57
57
|
const taskList = result.tasks ?? [];
|
|
58
58
|
const initialEvents = new Map<string, TaskStatus>();
|
|
59
59
|
const initialConfirms = new Map<string, { description: string; agentName?: string }>();
|
|
@@ -82,7 +82,7 @@ export default function TaskListView({ connected, hostId, request, subscribeEven
|
|
|
82
82
|
setAgentLabels(result.agents ?? []);
|
|
83
83
|
const version = result.version ?? null;
|
|
84
84
|
onVersion?.(version);
|
|
85
|
-
|
|
85
|
+
onCapabilityTokens?.(result.capability_tokens ?? {});
|
|
86
86
|
onUpdateRequired?.(!!version && isOlderThan(version, MIN_HOST_VERSION));
|
|
87
87
|
} catch (err) {
|
|
88
88
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Bump when a breaking host change is made. */
|
|
2
|
-
export const MIN_HOST_VERSION = "0.7.
|
|
2
|
+
export const MIN_HOST_VERSION = "0.7.4";
|
|
@@ -47,7 +47,7 @@ export default function Dashboard() {
|
|
|
47
47
|
const [updating, setUpdating] = useState(false);
|
|
48
48
|
const [updateError, setUpdateError] = useState<string | null>(null);
|
|
49
49
|
const [daemonVersion, setDaemonVersion] = useState<string | null>(null);
|
|
50
|
-
const [
|
|
50
|
+
const [capabilityTokens, setCapabilityTokens] = useState<Record<string, string | null>>({});
|
|
51
51
|
|
|
52
52
|
// Register push subscription for the active host
|
|
53
53
|
usePushSubscription();
|
|
@@ -88,11 +88,11 @@ export default function Dashboard() {
|
|
|
88
88
|
|
|
89
89
|
return (
|
|
90
90
|
<div className="dashboard">
|
|
91
|
-
{isDesktop && <HostMenu daemonVersion={daemonVersion}
|
|
91
|
+
{isDesktop && <HostMenu daemonVersion={daemonVersion} capabilityTokens={capabilityTokens} activeClientToken={activeClientToken} request={request} onCapabilityTokensChange={setCapabilityTokens} />}
|
|
92
92
|
|
|
93
93
|
<div className="dashboard-content">
|
|
94
94
|
<div className="tab-bar">
|
|
95
|
-
{!isDesktop && <HostMenu daemonVersion={daemonVersion}
|
|
95
|
+
{!isDesktop && <HostMenu daemonVersion={daemonVersion} capabilityTokens={capabilityTokens} activeClientToken={activeClientToken} request={request} onCapabilityTokensChange={setCapabilityTokens} />}
|
|
96
96
|
<TabBar />
|
|
97
97
|
</div>
|
|
98
98
|
|
|
@@ -141,7 +141,7 @@ export default function Dashboard() {
|
|
|
141
141
|
onViewRun={handleViewRun}
|
|
142
142
|
onUpdateRequired={setUpdateRequired}
|
|
143
143
|
onVersion={setDaemonVersion}
|
|
144
|
-
|
|
144
|
+
onCapabilityTokens={setCapabilityTokens}
|
|
145
145
|
/>
|
|
146
146
|
</div>
|
|
147
147
|
{isRunDetail ? (
|
package/src/rpc-handler.ts
CHANGED
|
@@ -163,13 +163,17 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
163
163
|
switch (request.method) {
|
|
164
164
|
case "task.list": {
|
|
165
165
|
const tasks = listTasks(config.projectRoot);
|
|
166
|
-
const
|
|
166
|
+
const capabilities: Record<string, string | null> = {};
|
|
167
|
+
for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd"] as const) {
|
|
168
|
+
capabilities[cap] = getCapabilityDevice(cap)?.clientToken ?? null;
|
|
169
|
+
}
|
|
167
170
|
return {
|
|
168
171
|
tasks: tasks.map((task) => flattenTask(task)),
|
|
169
172
|
agents: config.agents ?? [],
|
|
170
173
|
version: currentVersion,
|
|
171
174
|
host_platform: process.platform,
|
|
172
|
-
location_client_token:
|
|
175
|
+
location_client_token: capabilities.location,
|
|
176
|
+
capability_tokens: capabilities,
|
|
173
177
|
};
|
|
174
178
|
}
|
|
175
179
|
|