palmier 0.6.7 → 0.6.9

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 (94) hide show
  1. package/README.md +14 -0
  2. package/dist/agents/agent-instructions.md +7 -37
  3. package/dist/agents/agent.d.ts +2 -2
  4. package/dist/agents/aider.d.ts +1 -1
  5. package/dist/agents/aider.js +3 -6
  6. package/dist/agents/claude.d.ts +1 -1
  7. package/dist/agents/claude.js +3 -6
  8. package/dist/agents/cline.d.ts +1 -1
  9. package/dist/agents/cline.js +3 -6
  10. package/dist/agents/codex.d.ts +1 -1
  11. package/dist/agents/codex.js +3 -6
  12. package/dist/agents/copilot.d.ts +1 -1
  13. package/dist/agents/copilot.js +3 -6
  14. package/dist/agents/cursor.d.ts +1 -1
  15. package/dist/agents/cursor.js +3 -6
  16. package/dist/agents/deepagents.d.ts +1 -1
  17. package/dist/agents/deepagents.js +3 -6
  18. package/dist/agents/droid.d.ts +1 -1
  19. package/dist/agents/droid.js +3 -6
  20. package/dist/agents/gemini.d.ts +1 -1
  21. package/dist/agents/gemini.js +3 -6
  22. package/dist/agents/goose.d.ts +1 -1
  23. package/dist/agents/goose.js +3 -6
  24. package/dist/agents/hermes.d.ts +1 -1
  25. package/dist/agents/hermes.js +3 -6
  26. package/dist/agents/kimi.d.ts +1 -1
  27. package/dist/agents/kimi.js +3 -6
  28. package/dist/agents/kiro.d.ts +1 -1
  29. package/dist/agents/kiro.js +3 -6
  30. package/dist/agents/openclaw.d.ts +1 -1
  31. package/dist/agents/openclaw.js +3 -6
  32. package/dist/agents/opencode.d.ts +1 -1
  33. package/dist/agents/opencode.js +3 -6
  34. package/dist/agents/qoder.d.ts +1 -1
  35. package/dist/agents/qoder.js +3 -6
  36. package/dist/agents/qwen.d.ts +1 -1
  37. package/dist/agents/qwen.js +3 -6
  38. package/dist/agents/shared-prompt.d.ts +3 -2
  39. package/dist/agents/shared-prompt.js +6 -4
  40. package/dist/commands/run.js +3 -7
  41. package/dist/mcp-handler.js +1 -1
  42. package/dist/mcp-tools.d.ts +6 -1
  43. package/dist/mcp-tools.js +72 -6
  44. package/dist/pwa/assets/{index-DAI3J-jU.css → index-C6Lz09EY.css} +1 -1
  45. package/dist/pwa/assets/{index-RrJvjqz9.js → index-CZejk2al.js} +42 -42
  46. package/dist/pwa/assets/{web-EzNEHXEh.js → web-C48txJFl.js} +1 -1
  47. package/dist/pwa/assets/{web-DQteXlI7.js → web-zj8Blync.js} +1 -1
  48. package/dist/pwa/index.html +2 -2
  49. package/dist/pwa/service-worker.js +1 -1
  50. package/dist/rpc-handler.js +27 -68
  51. package/dist/spawn-command.js +3 -1
  52. package/dist/task.js +2 -3
  53. package/dist/transports/http-transport.js +4 -5
  54. package/dist/types.d.ts +0 -1
  55. package/package.json +2 -2
  56. package/palmier-server/README.md +1 -1
  57. package/palmier-server/pwa/src/App.css +9 -0
  58. package/palmier-server/pwa/src/components/TaskCard.tsx +36 -8
  59. package/palmier-server/pwa/src/components/TaskForm.tsx +63 -53
  60. package/palmier-server/pwa/src/constants.ts +1 -1
  61. package/palmier-server/spec.md +1 -1
  62. package/src/agents/agent-instructions.md +7 -37
  63. package/src/agents/agent.ts +2 -2
  64. package/src/agents/aider.ts +3 -6
  65. package/src/agents/claude.ts +3 -6
  66. package/src/agents/cline.ts +3 -6
  67. package/src/agents/codex.ts +3 -6
  68. package/src/agents/copilot.ts +3 -6
  69. package/src/agents/cursor.ts +3 -6
  70. package/src/agents/deepagents.ts +3 -6
  71. package/src/agents/droid.ts +3 -6
  72. package/src/agents/gemini.ts +3 -6
  73. package/src/agents/goose.ts +3 -6
  74. package/src/agents/hermes.ts +3 -6
  75. package/src/agents/kimi.ts +3 -6
  76. package/src/agents/kiro.ts +3 -6
  77. package/src/agents/openclaw.ts +3 -6
  78. package/src/agents/opencode.ts +3 -6
  79. package/src/agents/qoder.ts +3 -6
  80. package/src/agents/qwen.ts +3 -6
  81. package/src/agents/shared-prompt.ts +7 -4
  82. package/src/commands/run.ts +3 -7
  83. package/src/mcp-handler.ts +1 -1
  84. package/src/mcp-tools.ts +78 -7
  85. package/src/rpc-handler.ts +29 -72
  86. package/src/spawn-command.ts +3 -1
  87. package/src/task.ts +2 -3
  88. package/src/transports/http-transport.ts +4 -5
  89. package/src/types.ts +0 -1
  90. package/test/agent-instructions.test.ts +137 -9
  91. package/test/agent-output-parsing.test.ts +1 -0
  92. package/test/task-parsing.test.ts +3 -3
  93. package/dist/commands/plan-generation.md +0 -22
  94. package/src/commands/plan-generation.md +0 -22
@@ -1 +1 @@
1
- import{W as p}from"./index-RrJvjqz9.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-CZejk2al.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-RrJvjqz9.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-CZejk2al.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-RrJvjqz9.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-DAI3J-jU.css">
11
+ <script type="module" crossorigin src="/assets/index-CZejk2al.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-C6Lz09EY.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":"f31d164733f432f4807c770bdfc626b2","url":"index.html"},{"revision":null,"url":"assets/web-EzNEHXEh.js"},{"revision":null,"url":"assets/web-DQteXlI7.js"},{"revision":null,"url":"assets/index-RrJvjqz9.js"},{"revision":null,"url":"assets/index-DAI3J-jU.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())});
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":"7932c200bac35280617b0b0b6fdce381","url":"index.html"},{"revision":null,"url":"assets/web-zj8Blync.js"},{"revision":null,"url":"assets/web-C48txJFl.js"},{"revision":null,"url":"assets/index-CZejk2al.js"},{"revision":null,"url":"assets/index-C6Lz09EY.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,9 +1,7 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
- import { fileURLToPath } from "url";
5
4
  import { spawn } from "child_process";
6
- import { parse as parseYaml } from "yaml";
7
5
  import { listTasks, parseTaskFile, writeTaskFile, getTaskDir, readTaskStatus, writeTaskStatus, readHistory, deleteHistoryEntry, appendTaskList, removeFromTaskList, appendHistory, createRunDir, appendRunMessage, getRunDir } from "./task.js";
8
6
  import { resolvePending, getPending } from "./pending-requests.js";
9
7
  import { getPlatform } from "./platform/index.js";
@@ -15,8 +13,6 @@ import { publishHostEvent } from "./events.js";
15
13
  import { getLocationDevice, setLocationDevice, clearLocationDevice } from "./location-device.js";
16
14
  import { currentVersion, performUpdate } from "./update-checker.js";
17
15
  import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
- const PLAN_GENERATION_PROMPT = fs.readFileSync(path.join(__dirname, "commands", "plan-generation.md"), "utf-8");
20
16
  /**
21
17
  * Parse RESULT frontmatter and conversation messages.
22
18
  */
@@ -98,35 +94,26 @@ function parseAttr(attrs, name) {
98
94
  return match ? match[1] : undefined;
99
95
  }
100
96
  /**
101
- * Run plan generation for a task prompt using the given agent.
102
- * Returns the generated plan body and task name.
97
+ * Generate a concise task name from a user prompt using the given agent.
98
+ * Falls back to the raw prompt on failure.
103
99
  */
104
- async function generatePlan(projectRoot, userPrompt, agentName) {
105
- const fullPrompt = PLAN_GENERATION_PROMPT + userPrompt;
106
- const planAgent = getAgent(agentName);
107
- const { command, args, stdin, env: agentEnv } = planAgent.getPlanGenerationCommandLine(fullPrompt);
108
- console.log(`[generatePlan] Running: ${command} ${args.join(" ")}`);
109
- const { output } = await spawnCommand(command, args, {
110
- cwd: projectRoot,
111
- timeout: 120_000,
112
- stdin,
113
- ...(agentEnv ? { env: agentEnv } : {}),
114
- });
115
- let name = "";
116
- const trimmed = output.trim();
117
- let body = trimmed;
118
- const fmMatch = trimmed.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
119
- if (fmMatch) {
120
- try {
121
- const fm = parseYaml(fmMatch[1]);
122
- name = fm.task_name ?? "";
123
- }
124
- catch {
125
- // If frontmatter parsing fails, treat entire output as body
126
- }
127
- body = fmMatch[2].trimStart();
100
+ async function generateName(projectRoot, userPrompt, agentName) {
101
+ const prompt = `Generate a concise 3-6 word name for this task. Reply with ONLY the name, nothing else.\n\nTask: ${userPrompt}`;
102
+ const agent = getAgent(agentName);
103
+ const { command, args, stdin, env: agentEnv } = agent.getPromptCommandLine(prompt);
104
+ try {
105
+ const { output } = await spawnCommand(command, args, {
106
+ cwd: projectRoot,
107
+ timeout: 30_000,
108
+ stdin,
109
+ ...(agentEnv ? { env: agentEnv } : {}),
110
+ });
111
+ const name = output.trim().replace(/^["']|["']$/g, "").slice(0, 80);
112
+ return name || userPrompt;
113
+ }
114
+ catch {
115
+ return userPrompt;
128
116
  }
129
- return { name, body };
130
117
  }
131
118
  /** Active follow-up child processes, keyed by "taskId:runId". */
132
119
  const activeFollowups = new Map();
@@ -140,7 +127,6 @@ export function createRpcHandler(config, nc) {
140
127
  const pending = getPending(task.frontmatter.id);
141
128
  return {
142
129
  ...task.frontmatter,
143
- body: task.body,
144
130
  status: status ? {
145
131
  ...status,
146
132
  ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
@@ -179,23 +165,9 @@ export function createRpcHandler(config, nc) {
179
165
  }
180
166
  case "task.create": {
181
167
  const params = request.params;
182
- // Only generate a plan for longer prompts that benefit from it
183
- let name = "";
184
- let body = "";
185
- if (params.user_prompt.length <= 50) {
186
- name = params.user_prompt;
187
- }
188
- else {
189
- try {
190
- const plan = await generatePlan(config.projectRoot, params.user_prompt, params.agent);
191
- name = plan.name;
192
- body = plan.body;
193
- }
194
- catch (err) {
195
- const error = err;
196
- return { error: "plan generation failed", stdout: error.stdout, stderr: error.stderr };
197
- }
198
- }
168
+ const name = params.user_prompt.length <= 50
169
+ ? params.user_prompt
170
+ : await generateName(config.projectRoot, params.user_prompt, params.agent);
199
171
  const id = randomUUID();
200
172
  const taskDir = getTaskDir(config.projectRoot, id);
201
173
  const task = {
@@ -211,7 +183,6 @@ export function createRpcHandler(config, nc) {
211
183
  ...(params.foreground_mode ? { foreground_mode: true } : {}),
212
184
  ...(params.command ? { command: params.command } : {}),
213
185
  },
214
- body,
215
186
  };
216
187
  writeTaskFile(taskDir, task);
217
188
  appendTaskList(config.projectRoot, id);
@@ -222,10 +193,9 @@ export function createRpcHandler(config, nc) {
222
193
  const params = request.params;
223
194
  const taskDir = getTaskDir(config.projectRoot, params.id);
224
195
  const existing = parseTaskFile(taskDir);
225
- // Detect whether plan needs regeneration
196
+ // Detect whether name needs regeneration
226
197
  const promptChanged = params.user_prompt !== undefined && params.user_prompt !== existing.frontmatter.user_prompt;
227
198
  const agentChanged = params.agent !== undefined && params.agent !== existing.frontmatter.agent;
228
- const needsRegeneration = promptChanged || agentChanged || !existing.body;
229
199
  // Merge updates
230
200
  if (params.user_prompt !== undefined)
231
201
  existing.frontmatter.user_prompt = params.user_prompt;
@@ -252,21 +222,11 @@ export function createRpcHandler(config, nc) {
252
222
  delete existing.frontmatter.command;
253
223
  }
254
224
  }
255
- // Regenerate plan if needed (only for longer prompts)
256
- if (existing.frontmatter.user_prompt.length <= 50) {
257
- existing.frontmatter.name = existing.frontmatter.user_prompt;
258
- existing.body = "";
259
- }
260
- else if (needsRegeneration) {
261
- try {
262
- const plan = await generatePlan(config.projectRoot, existing.frontmatter.user_prompt, existing.frontmatter.agent);
263
- existing.frontmatter.name = plan.name;
264
- existing.body = plan.body;
265
- }
266
- catch (err) {
267
- const error = err;
268
- return { error: "plan generation failed", stdout: error.stdout, stderr: error.stderr };
269
- }
225
+ // Regenerate name when prompt or agent changes
226
+ if (promptChanged || agentChanged) {
227
+ existing.frontmatter.name = existing.frontmatter.user_prompt.length <= 50
228
+ ? existing.frontmatter.user_prompt
229
+ : await generateName(config.projectRoot, existing.frontmatter.user_prompt, existing.frontmatter.agent);
270
230
  }
271
231
  writeTaskFile(taskDir, existing);
272
232
  // Update timers — installTaskTimer overwrites in-place (schtasks /f,
@@ -298,7 +258,6 @@ export function createRpcHandler(config, nc) {
298
258
  ...(params.foreground_mode ? { foreground_mode: true } : {}),
299
259
  ...(params.command ? { command: params.command } : {}),
300
260
  },
301
- body: "",
302
261
  };
303
262
  writeTaskFile(taskDir, task);
304
263
  // Do NOT append to tasks.jsonl — this is a one-off run
@@ -42,7 +42,9 @@ export function spawnCommand(command, args, opts) {
42
42
  const finalArgs = process.platform === "win32"
43
43
  ? args.map((a) => a.replace(/[\r\n]+/g, " "))
44
44
  : args;
45
- // console.log(`[spawn] ${command} ${finalArgs.join(" ")}`);
45
+ const truncate = (s, max = 100) => s.length > max ? s.slice(0, max) + "..." : s;
46
+ const displayArgs = finalArgs.map((arg) => truncate(arg));
47
+ console.log(`[spawn] ${command} ${displayArgs.join(" ")}`);
46
48
  const child = crossSpawn(command, finalArgs, {
47
49
  cwd: opts.cwd,
48
50
  stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
package/dist/task.js CHANGED
@@ -22,14 +22,13 @@ export function parseTaskContent(content) {
22
22
  throw new Error("TASK.md is missing valid YAML frontmatter delimiters (---)");
23
23
  }
24
24
  const frontmatter = parseYaml(match[1]);
25
- const body = (match[2] || "").trim();
26
25
  if (!frontmatter.id) {
27
26
  throw new Error("TASK.md frontmatter must include at least: id");
28
27
  }
29
28
  frontmatter.name ??= frontmatter.user_prompt?.slice(0, 60) ?? "";
30
29
  frontmatter.agent ??= "claude";
31
30
  frontmatter.triggers_enabled ??= true;
32
- return { frontmatter, body };
31
+ return { frontmatter };
33
32
  }
34
33
  /**
35
34
  * Write a TASK.md file to the given task directory.
@@ -38,7 +37,7 @@ export function parseTaskContent(content) {
38
37
  export function writeTaskFile(taskDir, task) {
39
38
  fs.mkdirSync(taskDir, { recursive: true });
40
39
  const yamlStr = stringifyYaml(task.frontmatter).trim();
41
- const content = `---\n${yamlStr}\n---\n${task.body}\n`;
40
+ const content = `---\n${yamlStr}\n---\n`;
42
41
  const filePath = path.join(taskDir, "TASK.md");
43
42
  fs.writeFileSync(filePath, content, "utf-8");
44
43
  }
@@ -161,11 +161,9 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
161
161
  }
162
162
  const tool = agentToolMap.get(pathname.slice(1));
163
163
  try {
164
- const body = await readBody(req);
165
- const args = body.trim() ? JSON.parse(body) : {};
166
- const { taskId } = args;
164
+ const taskId = url.searchParams.get("taskId");
167
165
  if (!taskId) {
168
- sendJson(res, 400, { error: "taskId is required" });
166
+ sendJson(res, 400, { error: "taskId query parameter is required" });
169
167
  return;
170
168
  }
171
169
  const taskDir = getTaskDir(config.projectRoot, taskId);
@@ -173,7 +171,8 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
173
171
  sendJson(res, 404, { error: `Task not found: ${taskId}` });
174
172
  return;
175
173
  }
176
- delete args.taskId;
174
+ const body = await readBody(req);
175
+ const args = body.trim() ? JSON.parse(body) : {};
177
176
  const ctx = makeToolContext(taskId);
178
177
  console.log(`[mcp] REST [${taskId.slice(0, 8)}] ${tool.name}`);
179
178
  const result = await tool.handler(args, ctx);
package/dist/types.d.ts CHANGED
@@ -30,7 +30,6 @@ export interface Trigger {
30
30
  }
31
31
  export interface ParsedTask {
32
32
  frontmatter: TaskFrontmatter;
33
- body: string;
34
33
  }
35
34
  /**
36
35
  * - `started`: task is actively running
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "scripts": {
22
22
  "dev": "tsx src/index.ts",
23
- "build": "tsc && node -e \"const fs=require('fs');fs.cpSync('src/commands/plan-generation.md','dist/commands/plan-generation.md');fs.cpSync('src/agents/agent-instructions.md','dist/agents/agent-instructions.md');const p=process.env.PALMIER_PWA_DIST||'../palmier-server/pwa/dist';if(fs.existsSync(p))fs.cpSync(p,'dist/pwa',{recursive:true});else console.warn('PWA dist not found at '+p+', skipping')\"",
23
+ "build": "tsc && node -e \"const fs=require('fs');fs.cpSync('src/agents/agent-instructions.md','dist/agents/agent-instructions.md');const p=process.env.PALMIER_PWA_DIST||'../palmier-server/pwa/dist';if(fs.existsSync(p))fs.cpSync(p,'dist/pwa',{recursive:true});else console.warn('PWA dist not found at '+p+', skipping')\"",
24
24
  "test": "tsx --test test/**/*.test.ts",
25
25
  "prepare": "npm run build",
26
26
  "start": "node dist/index.js"
@@ -28,7 +28,7 @@ Palmier is a platform for remotely scheduling, managing, and executing autonomou
28
28
  - **PWA** -- React 19 + Vite progressive web app. Connects to NATS over WebSocket for real-time task updates and to the web server for host registration and push notifications. No user accounts — paired hosts are stored in localStorage.
29
29
  - **Web Server** -- Express + TypeScript API server. Handles host registration, push notifications (subscribes to `host-event.>` pub/sub for confirmation and completion events), and push notification relay (for host CLI requests via NATS). In production, also serves the built PWA static files.
30
30
  - **NATS Server** -- Message broker. Provides pub/sub messaging and request-reply for real-time communication between all components.
31
- - **Host** -- Runs on remote Linux/Windows machines to execute tasks via pluggable agent tools (e.g., Claude Code, Codex, Gemini). Each agent implements an `AgentTool` interface that handles command construction. Communicates with the platform over NATS and exposes a local HTTP server with agent-facing endpoints (`/notify`, `/request-input`, `/device-geolocation`) for task execution flows. See the [palmier](https://github.com/caihongxu/palmier) repo.
31
+ - **Host** -- Runs on remote Linux/Windows machines to execute tasks via pluggable agent tools (e.g., Claude Code, Codex, Gemini). Each agent implements an `AgentTool` interface that handles command construction. Communicates with the platform over NATS and exposes a local MCP server (`/mcp`, streamable HTTP) and auto-generated REST endpoints for agent-facing tools (`/notify`, `/request-input`, `/request-confirmation`, `/device-geolocation`). See the [palmier](https://github.com/caihongxu/palmier) repo.
32
32
  - **Android App** -- Native Android wrapper (Capacitor) for the PWA. Provides FCM push messaging and background GPS access. The server sends FCM data messages to wake the device for geolocation requests. See the [palmier-android](https://github.com/caihongxu/palmier-android) repo.
33
33
 
34
34
  ## Prerequisites
@@ -1152,6 +1152,7 @@ body {
1152
1152
  width: auto;
1153
1153
  }
1154
1154
 
1155
+ .triggers-section-body > .form-select,
1155
1156
  .trigger-row-card .form-select,
1156
1157
  .trigger-row-card .form-input {
1157
1158
  margin-bottom: 0;
@@ -1160,6 +1161,14 @@ body {
1160
1161
  height: 32px;
1161
1162
  box-sizing: border-box;
1162
1163
  min-width: 0;
1164
+ }
1165
+
1166
+ .triggers-section-body > .form-select {
1167
+ width: 100%;
1168
+ }
1169
+
1170
+ .trigger-row-card .form-select,
1171
+ .trigger-row-card .form-input {
1163
1172
  flex: 1;
1164
1173
  }
1165
1174
 
@@ -99,25 +99,53 @@ export default function TaskCard({ task, lastEvent, onEdit, onDelete, onViewRun
99
99
  };
100
100
 
101
101
 
102
- function formatTrigger(t: { type: string; value: string }): string {
102
+ function formatTriggersGrouped(triggers: { type: string; value: string }[]): string {
103
+ if (triggers.length === 0) return "";
104
+ if (triggers.length === 1) return formatSingleTrigger(triggers[0]);
105
+
106
+ // Detect the shared schedule type
107
+ const classified = triggers.map(classifyTrigger);
108
+ const types = new Set(classified.map((c) => c.kind));
109
+
110
+ // If all the same type, group them
111
+ if (types.size === 1) {
112
+ const kind = classified[0].kind;
113
+ if (kind === "hourly") return "Every hour";
114
+ const details = classified.map((c) => c.detail);
115
+ return `${kind.charAt(0).toUpperCase() + kind.slice(1)}: ${details.join(", ")}`;
116
+ }
117
+
118
+ // Mixed types — fall back to listing each
119
+ return triggers.map(formatSingleTrigger).join(", ");
120
+ }
121
+
122
+ function classifyTrigger(t: { type: string; value: string }): { kind: string; detail: string } {
103
123
  if (t.type === "once") {
104
124
  const d = new Date(t.value);
105
- return isNaN(d.getTime()) ? t.value : `Once on ${d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })} at ${d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })}`;
125
+ const label = isNaN(d.getTime()) ? t.value : `${d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })} at ${d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })}`;
126
+ return { kind: "once", detail: label };
106
127
  }
107
128
  const parts = t.value.split(" ");
108
- if (parts.length !== 5) return t.value;
129
+ if (parts.length !== 5) return { kind: "unknown", detail: t.value };
109
130
  const [min, hour, dom, , dow] = parts;
110
131
  const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
111
- if (hour === "*") return "Every hour";
132
+ if (hour === "*") return { kind: "hourly", detail: "" };
112
133
  const d = new Date();
113
134
  d.setHours(Number(hour), Number(min), 0, 0);
114
135
  const time = d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
115
- if (dow !== "*") return `Weekly on ${DAYS[Number(dow)] ?? dow} at ${time}`;
116
- if (dom !== "*") return `Monthly on day ${dom} at ${time}`;
117
- return `Daily at ${time}`;
136
+ if (dow !== "*") return { kind: "weekly", detail: `${DAYS[Number(dow)] ?? dow} at ${time}` };
137
+ if (dom !== "*") return { kind: "monthly", detail: `day ${dom} at ${time}` };
138
+ return { kind: "daily", detail: time };
139
+ }
140
+
141
+ function formatSingleTrigger(t: { type: string; value: string }): string {
142
+ const c = classifyTrigger(t);
143
+ if (c.kind === "hourly") return "Every hour";
144
+ if (c.kind === "once") return `Once on ${c.detail}`;
145
+ return `${c.kind.charAt(0).toUpperCase() + c.kind.slice(1)}: ${c.detail}`;
118
146
  }
119
147
 
120
- const triggersText = task.triggers.map(formatTrigger).join(", ");
148
+ const triggersText = formatTriggersGrouped(task.triggers);
121
149
 
122
150
  const actionItems = (
123
151
  <>
@@ -104,6 +104,9 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
104
104
  const [triggerRows, setTriggerRows] = useState<TriggerRow[]>(
105
105
  () => (initial?.triggers ?? []).map(triggerToRow)
106
106
  );
107
+ const [schedule, setSchedule] = useState<Schedule>(
108
+ () => (initial?.triggers ?? []).map(triggerToRow)[0]?.schedule ?? "daily"
109
+ );
107
110
  const [triggersEnabled, setTriggersEnabled] = useState(
108
111
  initial?.triggers_enabled ?? false
109
112
  );
@@ -152,7 +155,12 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
152
155
  }
153
156
 
154
157
  function addRow() {
155
- setTriggerRows((prev) => [...prev, newRow(prev.length > 0 ? prev[prev.length - 1].schedule : undefined)]);
158
+ setTriggerRows((prev) => [...prev, newRow(schedule)]);
159
+ }
160
+
161
+ function changeSchedule(s: Schedule) {
162
+ setSchedule(s);
163
+ setTriggerRows([newRow(s)]);
156
164
  }
157
165
 
158
166
  function collectTriggers(): Trigger[] {
@@ -370,26 +378,47 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
370
378
  }}
371
379
  disabled={saving}
372
380
  />
373
- Add schedules
381
+ Enable schedule
374
382
  </label>
375
383
  <div className={`triggers-section-body${triggersEnabled ? "" : " disabled"}`}>
376
- {triggerRows.map((row, i) => (
384
+ {triggerRows.length > 0 && (
385
+ <select
386
+ className="form-select"
387
+ value={schedule}
388
+ disabled={!triggersEnabled}
389
+ onChange={(e) => changeSchedule(e.target.value as Schedule)}
390
+ >
391
+ <option value="once">Specific Time</option>
392
+ <option value="hourly">Hourly</option>
393
+ <option value="daily">Daily</option>
394
+ <option value="weekly">Weekly</option>
395
+ <option value="monthly">Monthly</option>
396
+ </select>
397
+ )}
398
+ {schedule !== "hourly" && triggerRows.map((row, i) => (
377
399
  <div key={i} className="trigger-row-card">
378
400
  <div className="trigger-row-content">
379
- <div className="trigger-row-top">
380
- <select
381
- className="form-select"
382
- value={row.schedule}
401
+ {schedule === "daily" && (
402
+ <input
403
+ className="form-input"
404
+ type="time"
405
+ value={row.time}
383
406
  disabled={!triggersEnabled}
384
- onChange={(e) => updateRow(i, { schedule: e.target.value as Schedule })}
385
- >
386
- <option value="once">Specific Time</option>
387
- <option value="hourly">Hourly</option>
388
- <option value="daily">Daily</option>
389
- <option value="weekly">Weekly</option>
390
- <option value="monthly">Monthly</option>
391
- </select>
392
- {row.schedule === "daily" && (
407
+ onChange={(e) => updateRow(i, { time: e.target.value })}
408
+ />
409
+ )}
410
+ {schedule === "weekly" && (
411
+ <div className="trigger-row-top">
412
+ <select
413
+ className="form-select"
414
+ value={row.dayOfWeek}
415
+ disabled={!triggersEnabled}
416
+ onChange={(e) => updateRow(i, { dayOfWeek: e.target.value })}
417
+ >
418
+ {DAYS_OF_WEEK.map((d, di) => (
419
+ <option key={di} value={String(di)}>{d}</option>
420
+ ))}
421
+ </select>
393
422
  <input
394
423
  className="form-input"
395
424
  type="time"
@@ -397,31 +426,10 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
397
426
  disabled={!triggersEnabled}
398
427
  onChange={(e) => updateRow(i, { time: e.target.value })}
399
428
  />
400
- )}
401
- {row.schedule === "weekly" && (
402
- <>
403
- <select
404
- className="form-select"
405
- value={row.dayOfWeek}
406
- disabled={!triggersEnabled}
407
- onChange={(e) => updateRow(i, { dayOfWeek: e.target.value })}
408
- >
409
- {DAYS_OF_WEEK.map((d, di) => (
410
- <option key={di} value={String(di)}>{d}</option>
411
- ))}
412
- </select>
413
- <input
414
- className="form-input"
415
- type="time"
416
- value={row.time}
417
- disabled={!triggersEnabled}
418
- onChange={(e) => updateRow(i, { time: e.target.value })}
419
- />
420
- </>
421
- )}
422
- </div>
423
- {row.schedule === "monthly" && (
424
- <div className="trigger-details">
429
+ </div>
430
+ )}
431
+ {schedule === "monthly" && (
432
+ <div className="trigger-row-top">
425
433
  <select
426
434
  className="form-select"
427
435
  value={row.dayOfMonth}
@@ -441,8 +449,8 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
441
449
  />
442
450
  </div>
443
451
  )}
444
- {row.schedule === "once" && (
445
- <div className="trigger-details">
452
+ {schedule === "once" && (
453
+ <div className="trigger-row-top">
446
454
  <input
447
455
  className="form-input"
448
456
  type="date"
@@ -464,19 +472,21 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
464
472
  </div>
465
473
  )}
466
474
  </div>
467
- <button
468
- className="trigger-remove-btn"
469
- onClick={() => removeRow(i)}
470
- disabled={!triggersEnabled}
471
- title="Remove trigger"
472
- >
473
- &times;
474
- </button>
475
+ {triggerRows.length > 1 && (
476
+ <button
477
+ className="trigger-remove-btn"
478
+ onClick={() => removeRow(i)}
479
+ disabled={!triggersEnabled}
480
+ title="Remove trigger"
481
+ >
482
+ &times;
483
+ </button>
484
+ )}
475
485
  </div>
476
486
  ))}
477
- {triggerRows.length > 0 && (
487
+ {triggerRows.length > 0 && schedule !== "hourly" && (
478
488
  <button className="trigger-add-btn" onClick={addRow} disabled={!triggersEnabled}>
479
- + Add Schedule
489
+ + Add
480
490
  </button>
481
491
  )}
482
492
  </div>
@@ -1,2 +1,2 @@
1
1
  /** Bump when a breaking host change is made. */
2
- export const MIN_HOST_VERSION = "0.6.4";
2
+ export const MIN_HOST_VERSION = "0.6.8";