palmier 0.8.0 → 0.8.3

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 (132) hide show
  1. package/CLAUDE.md +13 -0
  2. package/README.md +11 -11
  3. package/dist/agents/agent.d.ts +0 -4
  4. package/dist/agents/claude.js +1 -1
  5. package/dist/agents/codex.js +2 -2
  6. package/dist/agents/cursor.js +1 -1
  7. package/dist/agents/deepagents.js +1 -1
  8. package/dist/agents/gemini.js +3 -2
  9. package/dist/agents/goose.js +1 -1
  10. package/dist/agents/hermes.js +1 -1
  11. package/dist/agents/kiro.js +1 -1
  12. package/dist/agents/opencode.js +1 -1
  13. package/dist/agents/qoder.js +1 -1
  14. package/dist/agents/shared-prompt.d.ts +0 -3
  15. package/dist/agents/shared-prompt.js +0 -3
  16. package/dist/app-registry.d.ts +10 -0
  17. package/dist/app-registry.js +44 -0
  18. package/dist/commands/info.d.ts +0 -3
  19. package/dist/commands/info.js +0 -5
  20. package/dist/commands/init.d.ts +0 -3
  21. package/dist/commands/init.js +2 -11
  22. package/dist/commands/pair.d.ts +1 -4
  23. package/dist/commands/pair.js +1 -12
  24. package/dist/commands/restart.d.ts +0 -3
  25. package/dist/commands/restart.js +0 -3
  26. package/dist/commands/run.d.ts +1 -14
  27. package/dist/commands/run.js +18 -61
  28. package/dist/commands/serve.d.ts +0 -3
  29. package/dist/commands/serve.js +33 -27
  30. package/dist/config.d.ts +0 -8
  31. package/dist/config.js +0 -8
  32. package/dist/device-capabilities.d.ts +1 -1
  33. package/dist/event-queues.d.ts +6 -21
  34. package/dist/event-queues.js +6 -21
  35. package/dist/events.d.ts +0 -6
  36. package/dist/events.js +1 -9
  37. package/dist/index.js +0 -1
  38. package/dist/mcp-handler.js +1 -2
  39. package/dist/mcp-tools.d.ts +0 -3
  40. package/dist/mcp-tools.js +14 -18
  41. package/dist/nats-client.d.ts +0 -3
  42. package/dist/nats-client.js +1 -4
  43. package/dist/pending-requests.d.ts +4 -18
  44. package/dist/pending-requests.js +4 -18
  45. package/dist/platform/index.d.ts +1 -4
  46. package/dist/platform/index.js +1 -4
  47. package/dist/platform/linux.d.ts +3 -9
  48. package/dist/platform/linux.js +9 -20
  49. package/dist/platform/platform.d.ts +1 -4
  50. package/dist/platform/windows.d.ts +2 -5
  51. package/dist/platform/windows.js +19 -39
  52. package/dist/pwa/assets/index-B0F9mtid.css +1 -0
  53. package/dist/pwa/assets/index-SYs3mcdJ.js +120 -0
  54. package/dist/pwa/assets/{web-CF-N8Di6.js → web-C6lkQj9J.js} +1 -1
  55. package/dist/pwa/assets/{web-BpM3fNCn.js → web-Z1623me-.js} +1 -1
  56. package/dist/pwa/index.html +2 -2
  57. package/dist/pwa/service-worker.js +1 -1
  58. package/dist/rpc-handler.d.ts +0 -6
  59. package/dist/rpc-handler.js +19 -48
  60. package/dist/spawn-command.d.ts +10 -25
  61. package/dist/spawn-command.js +7 -15
  62. package/dist/task.d.ts +6 -64
  63. package/dist/task.js +7 -70
  64. package/dist/transports/http-transport.d.ts +0 -4
  65. package/dist/transports/http-transport.js +6 -28
  66. package/dist/transports/nats-transport.d.ts +0 -4
  67. package/dist/transports/nats-transport.js +3 -9
  68. package/dist/types.d.ts +3 -7
  69. package/dist/update-checker.d.ts +1 -4
  70. package/dist/update-checker.js +2 -5
  71. package/package.json +1 -1
  72. package/palmier-server/README.md +1 -1
  73. package/palmier-server/pwa/src/App.css +170 -20
  74. package/palmier-server/pwa/src/App.tsx +15 -1
  75. package/palmier-server/pwa/src/components/HostMenu.tsx +282 -473
  76. package/palmier-server/pwa/src/components/RunDetailView.tsx +3 -3
  77. package/palmier-server/pwa/src/components/SessionsView.tsx +57 -25
  78. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +160 -0
  79. package/palmier-server/pwa/src/components/TaskCard.tsx +12 -4
  80. package/palmier-server/pwa/src/components/TaskForm.tsx +230 -33
  81. package/palmier-server/pwa/src/components/TasksView.tsx +5 -0
  82. package/palmier-server/pwa/src/constants.ts +1 -1
  83. package/palmier-server/pwa/src/native/Device.ts +66 -0
  84. package/palmier-server/pwa/src/pages/Dashboard.tsx +11 -6
  85. package/palmier-server/pwa/src/pages/PairHost.tsx +18 -2
  86. package/palmier-server/pwa/src/types.ts +1 -1
  87. package/palmier-server/server/src/index.ts +7 -7
  88. package/palmier-server/server/src/routes/device.ts +4 -4
  89. package/palmier-server/spec.md +47 -6
  90. package/src/agents/agent.ts +0 -4
  91. package/src/agents/claude.ts +1 -1
  92. package/src/agents/codex.ts +2 -2
  93. package/src/agents/cursor.ts +1 -1
  94. package/src/agents/deepagents.ts +1 -1
  95. package/src/agents/gemini.ts +3 -2
  96. package/src/agents/goose.ts +1 -1
  97. package/src/agents/hermes.ts +1 -1
  98. package/src/agents/kiro.ts +1 -1
  99. package/src/agents/opencode.ts +1 -1
  100. package/src/agents/qoder.ts +1 -1
  101. package/src/agents/shared-prompt.ts +0 -3
  102. package/src/app-registry.ts +52 -0
  103. package/src/commands/info.ts +0 -5
  104. package/src/commands/init.ts +2 -11
  105. package/src/commands/pair.ts +1 -12
  106. package/src/commands/restart.ts +0 -3
  107. package/src/commands/run.ts +18 -65
  108. package/src/commands/serve.ts +31 -27
  109. package/src/config.ts +0 -8
  110. package/src/device-capabilities.ts +4 -3
  111. package/src/event-queues.ts +6 -21
  112. package/src/events.ts +1 -9
  113. package/src/index.ts +0 -1
  114. package/src/mcp-handler.ts +1 -2
  115. package/src/mcp-tools.ts +14 -20
  116. package/src/nats-client.ts +1 -4
  117. package/src/pending-requests.ts +4 -18
  118. package/src/platform/index.ts +1 -4
  119. package/src/platform/linux.ts +9 -20
  120. package/src/platform/platform.ts +1 -4
  121. package/src/platform/windows.ts +19 -40
  122. package/src/rpc-handler.ts +20 -48
  123. package/src/spawn-command.ts +11 -27
  124. package/src/task.ts +7 -70
  125. package/src/transports/http-transport.ts +6 -39
  126. package/src/transports/nats-transport.ts +3 -9
  127. package/src/types.ts +3 -10
  128. package/src/update-checker.ts +2 -5
  129. package/test/task-parsing.test.ts +2 -3
  130. package/test/windows-xml.test.ts +11 -12
  131. package/dist/pwa/assets/index-FP1Mipr6.js +0 -120
  132. package/dist/pwa/assets/index-bLTn8zBj.css +0 -1
@@ -1 +1 @@
1
- import{W as p}from"./index-FP1Mipr6.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
+ import{W as p}from"./index-SYs3mcdJ.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-FP1Mipr6.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};
1
+ import{W as t}from"./index-SYs3mcdJ.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};
@@ -8,8 +8,8 @@
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-FP1Mipr6.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-bLTn8zBj.css">
11
+ <script type="module" crossorigin src="/assets/index-SYs3mcdJ.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-B0F9mtid.css">
13
13
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -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":"194ab684f5fe99891f1bd66fa0d50447","url":"index.html"},{"revision":null,"url":"assets/web-CF-N8Di6.js"},{"revision":null,"url":"assets/web-BpM3fNCn.js"},{"revision":null,"url":"assets/index-bLTn8zBj.css"},{"revision":null,"url":"assets/index-FP1Mipr6.js"},{"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())});
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":"c78d7ea0a3891a032082aa56b9c2e8d7","url":"index.html"},{"revision":null,"url":"assets/web-Z1623me-.js"},{"revision":null,"url":"assets/web-C6lkQj9J.js"},{"revision":null,"url":"assets/index-SYs3mcdJ.js"},{"revision":null,"url":"assets/index-B0F9mtid.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())});
@@ -1,11 +1,5 @@
1
1
  import { type NatsConnection } from "nats";
2
2
  import type { HostConfig, RpcMessage } from "./types.js";
3
- /**
4
- * Parse RESULT frontmatter and conversation messages.
5
- */
6
3
  export declare function parseResultFrontmatter(raw: string): Record<string, unknown>;
7
- /**
8
- * Create a transport-agnostic RPC handler bound to the given config.
9
- */
10
4
  export declare function createRpcHandler(config: HostConfig, nc?: NatsConnection): (request: RpcMessage) => Promise<unknown>;
11
5
  //# sourceMappingURL=rpc-handler.d.ts.map
@@ -11,12 +11,10 @@ import { getAgent } from "./agents/agent.js";
11
11
  import { validateClient } from "./client-store.js";
12
12
  import { publishHostEvent } from "./events.js";
13
13
  import { getCapabilityDevice, setCapabilityDevice, clearCapabilityDevice } from "./device-capabilities.js";
14
+ import { listApps } from "./app-registry.js";
14
15
  import { currentVersion, performUpdate } from "./update-checker.js";
15
16
  import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
16
17
  import { clearTaskQueue } from "./event-queues.js";
17
- /**
18
- * Parse RESULT frontmatter and conversation messages.
19
- */
20
18
  export function parseResultFrontmatter(raw) {
21
19
  const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
22
20
  if (!fmMatch)
@@ -29,18 +27,15 @@ export function parseResultFrontmatter(raw) {
29
27
  meta[line.slice(0, sep).trim()] = line.slice(sep + 2).trim();
30
28
  }
31
29
  const messages = parseConversationMessages(fmMatch[2]);
32
- // Derive state from status messages — just look at the last one
33
30
  const statusMessages = messages.filter((m) => m.role === "status");
34
31
  const lastStatus = statusMessages[statusMessages.length - 1];
35
32
  const startedMsg = statusMessages.find((m) => m.type === "started");
36
33
  const terminalStates = ["finished", "failed", "aborted"];
37
34
  const terminalMsg = [...statusMessages].reverse().find((m) => terminalStates.includes(m.type ?? ""));
38
- // If last status is "started" (or continuation like "confirmation"/"monitoring"),
39
- // determine if it's a task run or follow-up
40
35
  const activeStates = ["started", "monitoring", "confirmation"];
41
36
  let runningState;
42
37
  if (lastStatus?.type === "monitoring") {
43
- // Only show monitoring if no assistant/user message came after it
38
+ // Show "monitoring" only if no assistant/user message followed it.
44
39
  const lastStatusIdx = messages.lastIndexOf(lastStatus);
45
40
  const hasMessageAfter = messages.slice(lastStatusIdx + 1).some((m) => m.role === "assistant" || m.role === "user");
46
41
  runningState = hasMessageAfter ? "started" : "monitoring";
@@ -60,15 +55,12 @@ export function parseResultFrontmatter(raw) {
60
55
  end_time: terminalMsg?.time || undefined,
61
56
  };
62
57
  }
63
- /**
64
- * Parse conversation messages from the body of a RESULT file.
65
- */
66
58
  function parseConversationMessages(body) {
67
59
  const delimiterRegex = /<!-- palmier:message\s+(.*?)\s*-->/g;
68
60
  const messages = [];
69
61
  const matches = [...body.matchAll(delimiterRegex)];
70
62
  if (matches.length === 0) {
71
- // No delimiters — treat entire body as single assistant message if non-empty
63
+ // No delimiters — treat entire body as a single assistant message.
72
64
  const content = body.trim();
73
65
  if (content) {
74
66
  messages.push({ role: "assistant", time: 0, content });
@@ -94,10 +86,6 @@ function parseAttr(attrs, name) {
94
86
  const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
95
87
  return match ? match[1] : undefined;
96
88
  }
97
- /**
98
- * Generate a concise task name from a user prompt using the given agent.
99
- * Falls back to the raw prompt on failure.
100
- */
101
89
  async function generateName(projectRoot, userPrompt, agentName) {
102
90
  const prompt = `Generate a concise 3-6 word name for this task. Reply with ONLY the name, nothing else.\n\nTask: ${userPrompt}`;
103
91
  const agent = getAgent(agentName);
@@ -118,9 +106,6 @@ async function generateName(projectRoot, userPrompt, agentName) {
118
106
  }
119
107
  /** Active follow-up child processes, keyed by "taskId:runId". */
120
108
  const activeFollowups = new Map();
121
- /**
122
- * Create a transport-agnostic RPC handler bound to the given config.
123
- */
124
109
  export function createRpcHandler(config, nc) {
125
110
  function flattenTask(task) {
126
111
  const taskDir = getTaskDir(config.projectRoot, task.frontmatter.id);
@@ -131,20 +116,17 @@ export function createRpcHandler(config, nc) {
131
116
  };
132
117
  }
133
118
  async function handleRpc(request) {
134
- // Client token validation: skip for trusted localhost requests and
135
- // task.user_input (server-originated push responses; gated by getPending instead)
119
+ // task.user_input comes from server-originated push responses; it's gated
120
+ // by getPending() rather than a client token.
136
121
  const skipAuth = request.method === "task.user_input";
137
122
  if (!skipAuth && !request.localhost && (!request.clientToken || !validateClient(request.clientToken))) {
138
123
  return { error: "Unauthorized" };
139
124
  }
140
125
  switch (request.method) {
141
126
  case "host.info": {
142
- // Bootstrap metadata the PWA needs on connect, independent of which tab
143
- // is active. Includes any prompts already waiting so a reconnecting
144
- // PWA can render their modals without replaying events.
145
127
  const capabilities = {};
146
- for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd", "email"]) {
147
- capabilities[cap] = getCapabilityDevice(cap)?.clientToken ?? null;
128
+ for (const capability of ["location", "notifications", "sms-read", "sms-send", "contacts", "calendar", "alarm", "battery", "dnd", "send-email"]) {
129
+ capabilities[capability] = getCapabilityDevice(capability)?.clientToken ?? null;
148
130
  }
149
131
  return {
150
132
  agents: config.agents ?? [],
@@ -200,10 +182,8 @@ export function createRpcHandler(config, nc) {
200
182
  const params = request.params;
201
183
  const taskDir = getTaskDir(config.projectRoot, params.id);
202
184
  const existing = parseTaskFile(taskDir);
203
- // Detect whether name needs regeneration
204
185
  const promptChanged = params.user_prompt !== undefined && params.user_prompt !== existing.frontmatter.user_prompt;
205
186
  const agentChanged = params.agent !== undefined && params.agent !== existing.frontmatter.agent;
206
- // Merge updates
207
187
  if (params.user_prompt !== undefined)
208
188
  existing.frontmatter.user_prompt = params.user_prompt;
209
189
  if (params.agent !== undefined)
@@ -243,15 +223,14 @@ export function createRpcHandler(config, nc) {
243
223
  delete existing.frontmatter.command;
244
224
  }
245
225
  }
246
- // Regenerate name when prompt or agent changes
247
226
  if (promptChanged || agentChanged) {
248
227
  existing.frontmatter.name = existing.frontmatter.user_prompt.length <= 50
249
228
  ? existing.frontmatter.user_prompt
250
229
  : await generateName(config.projectRoot, existing.frontmatter.user_prompt, existing.frontmatter.agent);
251
230
  }
252
231
  writeTaskFile(taskDir, existing);
253
- // Update timers — installTaskTimer overwrites in-place (schtasks /f,
254
- // systemd unit rewrite) without killing a running task process.
232
+ // installTaskTimer overwrites in-place (schtasks /f, systemd unit rewrite)
233
+ // without killing a running task process.
255
234
  getPlatform().installTaskTimer(config, existing);
256
235
  return flattenTask(existing);
257
236
  }
@@ -281,11 +260,9 @@ export function createRpcHandler(config, nc) {
281
260
  },
282
261
  };
283
262
  writeTaskFile(taskDir, task);
284
- // Do NOT append to tasks.jsonl — this is a one-off run
285
- // Create initial result file so it appears in runs list immediately
263
+ // One-off run: do NOT append to tasks.jsonl.
286
264
  const runId = createRunDir(taskDir, name, Date.now(), params.agent);
287
265
  appendHistory(config.projectRoot, { task_id: id, run_id: runId });
288
- // Spawn `palmier run <id>` directly as a detached process
289
266
  const script = process.argv[1] || "palmier";
290
267
  const child = spawn(process.execPath, [script, "run", id], {
291
268
  detached: true,
@@ -300,12 +277,10 @@ export function createRpcHandler(config, nc) {
300
277
  try {
301
278
  const runTaskDir = getTaskDir(config.projectRoot, params.id);
302
279
  const platform = getPlatform();
303
- // If the task is already running, kill the stale process and start fresh
304
280
  if (platform.isTaskRunning(params.id)) {
305
281
  console.log(`[task.run] Task ${params.id} is already running, killing stale process`);
306
282
  await platform.stopTask(params.id);
307
283
  }
308
- // Create initial result file so it appears in runs list immediately
309
284
  const runTask = parseTaskFile(runTaskDir);
310
285
  const taskRunId = createRunDir(runTaskDir, runTask.frontmatter.name, Date.now(), runTask.frontmatter.agent);
311
286
  appendHistory(config.projectRoot, { task_id: params.id, run_id: taskRunId });
@@ -330,7 +305,6 @@ export function createRpcHandler(config, nc) {
330
305
  const followupTaskDir = getTaskDir(config.projectRoot, params.id);
331
306
  const followupTask = parseTaskFile(followupTaskDir);
332
307
  const followupRunDir = getRunDir(followupTaskDir, params.run_id);
333
- // Append user message + started status
334
308
  appendRunMessage(followupTaskDir, params.run_id, {
335
309
  role: "user",
336
310
  time: Date.now(),
@@ -343,10 +317,8 @@ export function createRpcHandler(config, nc) {
343
317
  type: "started",
344
318
  });
345
319
  await publishHostEvent(nc, config.hostId, params.id, { event_type: "result-updated", run_id: params.run_id });
346
- // Fire-and-forget: invoke agent inline as a child of the serve process
347
320
  const followupAgent = getAgent(followupTask.frontmatter.agent);
348
321
  const { command: cmd, args: cmdArgs, stdin, env: followupAgentEnv } = followupAgent.getTaskRunCommandLine(followupTask, params.message, followupTask.frontmatter.yolo_mode ? "yolo" : followupTask.frontmatter.permissions);
349
- // Spawn directly via crossSpawn so we can track and kill the child
350
322
  const child = crossSpawn(cmd, cmdArgs, {
351
323
  cwd: followupRunDir,
352
324
  stdio: [stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
@@ -356,13 +328,12 @@ export function createRpcHandler(config, nc) {
356
328
  if (stdin != null)
357
329
  child.stdin.end(stdin);
358
330
  activeFollowups.set(followupKey, child);
359
- // Collect output
360
331
  const chunks = [];
361
332
  child.stdout?.on("data", (d) => chunks.push(d));
362
333
  child.stderr?.on("data", (d) => process.stderr.write(d));
363
334
  child.on("close", async (code) => {
364
335
  activeFollowups.delete(followupKey);
365
- // If killed by stop_followup, the stopped status is already written
336
+ // stop_followup already wrote the stopped status.
366
337
  if (child.killed)
367
338
  return;
368
339
  const output = Buffer.concat(chunks).toString("utf-8");
@@ -405,7 +376,6 @@ export function createRpcHandler(config, nc) {
405
376
  if (!child) {
406
377
  return { error: "No active follow-up for this run" };
407
378
  }
408
- // Kill the child process tree
409
379
  if (process.platform === "win32" && child.pid) {
410
380
  try {
411
381
  const { execFileSync } = await import("child_process");
@@ -416,7 +386,7 @@ export function createRpcHandler(config, nc) {
416
386
  else {
417
387
  child.kill();
418
388
  }
419
- // Append stopped status (child.killed prevents the close handler from writing)
389
+ // child.killed stops the close handler from double-writing the status.
420
390
  const stopTaskDir = getTaskDir(config.projectRoot, params.id);
421
391
  appendRunMessage(stopTaskDir, params.run_id, {
422
392
  role: "status",
@@ -431,17 +401,16 @@ export function createRpcHandler(config, nc) {
431
401
  case "task.abort": {
432
402
  const params = request.params;
433
403
  const abortTaskDir = getTaskDir(config.projectRoot, params.id);
434
- // Read the PID before overwriting status — stopTask needs it to
435
- // kill the entire process tree on Windows.
404
+ // Read PID before overwriting — stopTask needs it to kill the
405
+ // process tree on Windows.
436
406
  const abortPrevStatus = readTaskStatus(abortTaskDir);
437
- // Write abort status BEFORE killing so the dying process's signal
438
- // handler can detect this was RPC-initiated and skip publishing.
407
+ // Write abort status before killing so the dying process's signal
408
+ // handler sees this was RPC-initiated and skips publishing.
439
409
  writeTaskStatus(abortTaskDir, {
440
410
  running_state: "aborted",
441
411
  time_stamp: Date.now(),
442
412
  ...(abortPrevStatus?.pid ? { pid: abortPrevStatus.pid } : {}),
443
413
  });
444
- // Append aborted status to the latest run
445
414
  try {
446
415
  const runDirs = fs.readdirSync(abortTaskDir)
447
416
  .filter((f) => /^\d+$/.test(f) && fs.existsSync(path.join(abortTaskDir, f, "TASKRUN.md")))
@@ -465,7 +434,6 @@ export function createRpcHandler(config, nc) {
465
434
  console.error(`task.abort failed for ${params.id}: ${e.stderr || e.message}`);
466
435
  return { error: `Failed to abort task: ${e.stderr || e.message}` };
467
436
  }
468
- // Notify connected clients (NATS + HTTP SSE if LAN server is running)
469
437
  const abortPayload = { event_type: "running-state", running_state: "aborted" };
470
438
  await publishHostEvent(nc, config.hostId, params.id, abortPayload);
471
439
  return { ok: true, task_id: params.id };
@@ -607,6 +575,9 @@ export function createRpcHandler(config, nc) {
607
575
  clearCapabilityDevice(params.capability);
608
576
  return { ok: true };
609
577
  }
578
+ case "device.notifications.apps": {
579
+ return { apps: listApps() };
580
+ }
610
581
  default:
611
582
  return { error: `Unknown method: ${request.method}` };
612
583
  }
@@ -4,19 +4,12 @@ export interface SpawnStreamingOptions {
4
4
  env?: Record<string, string>;
5
5
  }
6
6
  /**
7
- * Spawn a command with shell interpretation, returning the ChildProcess
8
- * with stdout piped for line-by-line reading.
9
- *
10
- * Unlike spawnCommand(), this does NOT collect output into a buffer
11
- * the caller reads from child.stdout directly (e.g. via readline).
12
- *
13
- * shell: true is required so users can write piped commands like
14
- * "tail -f log | grep ERROR".
15
- *
16
- * stdin is "pipe" (kept open, never written to) rather than "ignore"
17
- * (/dev/null). Some long-running commands exit when stdin is closed/EOF.
18
- * This differs from spawnCommand() which uses "ignore" because agent
19
- * CLIs like `claude -p` hang on an open stdin pipe.
7
+ * Spawn with shell interpretation for piped commands like "tail -f log | grep".
8
+ * Returns the ChildProcess with stdout piped so the caller can read it directly
9
+ * (contrast with spawnCommand which buffers). stdin is "pipe" (held open, not
10
+ * written to): some long-running commands exit on stdin EOF. Agent CLIs like
11
+ * `claude -p` conversely hang on an open stdin, which is why spawnCommand
12
+ * defaults to "ignore".
20
13
  */
21
14
  export declare function spawnStreamingCommand(command: string, opts: SpawnStreamingOptions): ChildProcess;
22
15
  export interface SpawnCommandOptions {
@@ -33,18 +26,10 @@ export interface SpawnCommandOptions {
33
26
  onData?: (chunk: string) => void;
34
27
  }
35
28
  /**
36
- * Spawn a command with additional arguments.
37
- *
38
- * Uses cross-spawn to correctly resolve .cmd shims and escape arguments
39
- * on Windows without shell: true (which mishandles special characters).
40
- *
41
- * On other platforms the command is executed directly (no shell), so no
42
- * escaping is needed.
43
- *
44
- * stdin is set to "ignore" by default (equivalent to < /dev/null) because
45
- * tools like `claude -p` hang indefinitely on an open stdin pipe.
46
- * When opts.stdin is provided, stdin is set to "pipe" and the string is
47
- * written to the process before closing the pipe.
29
+ * cross-spawn resolves .cmd shims and escapes args on Windows without shell:true
30
+ * (which mishandles special characters). stdin defaults to "ignore" because
31
+ * tools like `claude -p` hang on an open stdin pipe; pass opts.stdin to write
32
+ * a string and then close the pipe.
48
33
  */
49
34
  export interface SpawnCommandResult {
50
35
  output: string;
@@ -12,19 +12,12 @@ function treeKill(child) {
12
12
  child.kill();
13
13
  }
14
14
  /**
15
- * Spawn a command with shell interpretation, returning the ChildProcess
16
- * with stdout piped for line-by-line reading.
17
- *
18
- * Unlike spawnCommand(), this does NOT collect output into a buffer
19
- * the caller reads from child.stdout directly (e.g. via readline).
20
- *
21
- * shell: true is required so users can write piped commands like
22
- * "tail -f log | grep ERROR".
23
- *
24
- * stdin is "pipe" (kept open, never written to) rather than "ignore"
25
- * (/dev/null). Some long-running commands exit when stdin is closed/EOF.
26
- * This differs from spawnCommand() which uses "ignore" because agent
27
- * CLIs like `claude -p` hang on an open stdin pipe.
15
+ * Spawn with shell interpretation for piped commands like "tail -f log | grep".
16
+ * Returns the ChildProcess with stdout piped so the caller can read it directly
17
+ * (contrast with spawnCommand which buffers). stdin is "pipe" (held open, not
18
+ * written to): some long-running commands exit on stdin EOF. Agent CLIs like
19
+ * `claude -p` conversely hang on an open stdin, which is why spawnCommand
20
+ * defaults to "ignore".
28
21
  */
29
22
  export function spawnStreamingCommand(command, opts) {
30
23
  return crossSpawn(command, [], {
@@ -37,8 +30,7 @@ export function spawnStreamingCommand(command, opts) {
37
30
  }
38
31
  export function spawnCommand(command, args, opts) {
39
32
  return new Promise((resolve, reject) => {
40
- // Collapse newlines to spaces — cmd.exe can't handle literal newlines
41
- // in arguments, and CLI prompts don't need them.
33
+ // cmd.exe can't handle literal newlines in arguments.
42
34
  const finalArgs = process.platform === "win32"
43
35
  ? args.map((a) => a.replace(/[\r\n]+/g, " "))
44
36
  : args;
package/dist/task.d.ts CHANGED
@@ -1,60 +1,17 @@
1
1
  import type { ParsedTask, TaskStatus, HistoryEntry, ConversationMessage } from "./types.js";
2
- /**
3
- * Parse a TASK.md file from the given task directory.
4
- */
5
2
  export declare function parseTaskFile(taskDir: string): ParsedTask;
6
- /**
7
- * Parse TASK.md content string into frontmatter + body.
8
- */
9
3
  export declare function parseTaskContent(content: string): ParsedTask;
10
- /**
11
- * Write a TASK.md file to the given task directory.
12
- * Creates the directory if it doesn't exist.
13
- */
14
4
  export declare function writeTaskFile(taskDir: string, task: ParsedTask): void;
15
- /**
16
- * Append a task ID to the project-level tasks.jsonl file.
17
- */
18
5
  export declare function appendTaskList(projectRoot: string, taskId: string): void;
19
- /**
20
- * Remove a task ID from the project-level tasks.jsonl file.
21
- * Returns true if the entry was found and removed.
22
- */
23
6
  export declare function removeFromTaskList(projectRoot: string, taskId: string): boolean;
24
- /**
25
- * List all tasks referenced in tasks.jsonl.
26
- */
27
7
  export declare function listTasks(projectRoot: string): ParsedTask[];
28
- /**
29
- * Get the directory path for a task by its ID.
30
- */
31
8
  export declare function getTaskDir(projectRoot: string, taskId: string): string;
32
- /**
33
- * Write task status to status.json in the task directory.
34
- */
35
9
  export declare function writeTaskStatus(taskDir: string, status: TaskStatus): void;
36
- /**
37
- * Read task status from status.json in the task directory.
38
- * Returns undefined if the file doesn't exist.
39
- */
40
10
  export declare function readTaskStatus(taskDir: string): TaskStatus | undefined;
41
- /**
42
- * Create a run directory with an initial TASKRUN.md file.
43
- * Returns the run ID (timestamp string used as directory name).
44
- */
11
+ /** Returns the run ID (timestamp string used as directory name). */
45
12
  export declare function createRunDir(taskDir: string, taskName: string, startTime: number, agent?: string): string;
46
- /**
47
- * Get the path to a run directory.
48
- */
49
13
  export declare function getRunDir(taskDir: string, runId: string): string;
50
- /**
51
- * Append a conversation message to a run's TASKRUN.md file.
52
- */
53
14
  export declare function appendRunMessage(taskDir: string, runId: string, msg: ConversationMessage): void;
54
- /**
55
- * Begin a streaming assistant message — writes the delimiter only.
56
- * Returns a writer that appends content chunks and finalizes the message.
57
- */
58
15
  export declare function beginStreamingMessage(taskDir: string, runId: string, time: number): StreamingMessageWriter;
59
16
  export declare class StreamingMessageWriter {
60
17
  private filePath;
@@ -65,33 +22,18 @@ export declare class StreamingMessageWriter {
65
22
  end(attachments?: string[]): void;
66
23
  }
67
24
  /**
68
- * Splice a user message into a running assistant stream.
69
- * Ends the current assistant block, writes the user message,
70
- * then opens a new assistant block all as direct file appends.
71
- * The existing StreamingMessageWriter keeps working because its
72
- * write() is just appendFileSync, so subsequent chunks land in
73
- * the new assistant block.
25
+ * Splice a user message into a running assistant stream: close the current
26
+ * assistant block, write the user message, open a new assistant block. Direct
27
+ * appends only, so an existing StreamingMessageWriter keeps working its
28
+ * subsequent chunks land in the new block.
74
29
  */
75
30
  export declare function spliceUserMessage(taskDir: string, runId: string, userMsg: ConversationMessage,
76
31
  /** Optional text to append to the current assistant block before ending it. */
77
32
  assistantAppend?: string): void;
78
- /**
79
- * Read conversation messages from a run's TASKRUN.md file.
80
- */
81
33
  export declare function readRunMessages(taskDir: string, runId: string): ConversationMessage[];
82
- /**
83
- * Append a history entry to the project-level history.jsonl file.
84
- */
85
34
  export declare function appendHistory(projectRoot: string, entry: HistoryEntry): void;
86
- /**
87
- * Delete a history entry and its associated run directory.
88
- * Returns true if the entry was found and removed.
89
- */
90
35
  export declare function deleteHistoryEntry(projectRoot: string, taskId: string, runId: string): boolean;
91
- /**
92
- * Read history entries from history.jsonl with pagination.
93
- * Returns entries sorted most-recent-first.
94
- */
36
+ /** Returns entries most-recent-first. */
95
37
  export declare function readHistory(projectRoot: string, opts: {
96
38
  offset?: number;
97
39
  limit?: number;