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.
- package/README.md +14 -0
- package/dist/agents/agent-instructions.md +7 -37
- package/dist/agents/agent.d.ts +2 -2
- package/dist/agents/aider.d.ts +1 -1
- package/dist/agents/aider.js +3 -6
- package/dist/agents/claude.d.ts +1 -1
- package/dist/agents/claude.js +3 -6
- package/dist/agents/cline.d.ts +1 -1
- package/dist/agents/cline.js +3 -6
- package/dist/agents/codex.d.ts +1 -1
- package/dist/agents/codex.js +3 -6
- package/dist/agents/copilot.d.ts +1 -1
- package/dist/agents/copilot.js +3 -6
- package/dist/agents/cursor.d.ts +1 -1
- package/dist/agents/cursor.js +3 -6
- package/dist/agents/deepagents.d.ts +1 -1
- package/dist/agents/deepagents.js +3 -6
- package/dist/agents/droid.d.ts +1 -1
- package/dist/agents/droid.js +3 -6
- package/dist/agents/gemini.d.ts +1 -1
- package/dist/agents/gemini.js +3 -6
- package/dist/agents/goose.d.ts +1 -1
- package/dist/agents/goose.js +3 -6
- package/dist/agents/hermes.d.ts +1 -1
- package/dist/agents/hermes.js +3 -6
- package/dist/agents/kimi.d.ts +1 -1
- package/dist/agents/kimi.js +3 -6
- package/dist/agents/kiro.d.ts +1 -1
- package/dist/agents/kiro.js +3 -6
- package/dist/agents/openclaw.d.ts +1 -1
- package/dist/agents/openclaw.js +3 -6
- package/dist/agents/opencode.d.ts +1 -1
- package/dist/agents/opencode.js +3 -6
- package/dist/agents/qoder.d.ts +1 -1
- package/dist/agents/qoder.js +3 -6
- package/dist/agents/qwen.d.ts +1 -1
- package/dist/agents/qwen.js +3 -6
- package/dist/agents/shared-prompt.d.ts +3 -2
- package/dist/agents/shared-prompt.js +6 -4
- package/dist/commands/run.js +3 -7
- package/dist/mcp-handler.js +1 -1
- package/dist/mcp-tools.d.ts +6 -1
- package/dist/mcp-tools.js +72 -6
- package/dist/pwa/assets/{index-DAI3J-jU.css → index-C6Lz09EY.css} +1 -1
- package/dist/pwa/assets/{index-RrJvjqz9.js → index-CZejk2al.js} +42 -42
- package/dist/pwa/assets/{web-EzNEHXEh.js → web-C48txJFl.js} +1 -1
- package/dist/pwa/assets/{web-DQteXlI7.js → web-zj8Blync.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.js +27 -68
- package/dist/spawn-command.js +3 -1
- package/dist/task.js +2 -3
- package/dist/transports/http-transport.js +4 -5
- package/dist/types.d.ts +0 -1
- package/package.json +2 -2
- package/palmier-server/README.md +1 -1
- package/palmier-server/pwa/src/App.css +9 -0
- package/palmier-server/pwa/src/components/TaskCard.tsx +36 -8
- package/palmier-server/pwa/src/components/TaskForm.tsx +63 -53
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/spec.md +1 -1
- package/src/agents/agent-instructions.md +7 -37
- package/src/agents/agent.ts +2 -2
- package/src/agents/aider.ts +3 -6
- package/src/agents/claude.ts +3 -6
- package/src/agents/cline.ts +3 -6
- package/src/agents/codex.ts +3 -6
- package/src/agents/copilot.ts +3 -6
- package/src/agents/cursor.ts +3 -6
- package/src/agents/deepagents.ts +3 -6
- package/src/agents/droid.ts +3 -6
- package/src/agents/gemini.ts +3 -6
- package/src/agents/goose.ts +3 -6
- package/src/agents/hermes.ts +3 -6
- package/src/agents/kimi.ts +3 -6
- package/src/agents/kiro.ts +3 -6
- package/src/agents/openclaw.ts +3 -6
- package/src/agents/opencode.ts +3 -6
- package/src/agents/qoder.ts +3 -6
- package/src/agents/qwen.ts +3 -6
- package/src/agents/shared-prompt.ts +7 -4
- package/src/commands/run.ts +3 -7
- package/src/mcp-handler.ts +1 -1
- package/src/mcp-tools.ts +78 -7
- package/src/rpc-handler.ts +29 -72
- package/src/spawn-command.ts +3 -1
- package/src/task.ts +2 -3
- package/src/transports/http-transport.ts +4 -5
- package/src/types.ts +0 -1
- package/test/agent-instructions.test.ts +137 -9
- package/test/agent-output-parsing.test.ts +1 -0
- package/test/task-parsing.test.ts +3 -3
- package/dist/commands/plan-generation.md +0 -22
- package/src/commands/plan-generation.md +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as p}from"./index-
|
|
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-
|
|
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};
|
package/dist/pwa/index.html
CHANGED
|
@@ -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-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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":"
|
|
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())});
|
package/dist/rpc-handler.js
CHANGED
|
@@ -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
|
-
*
|
|
102
|
-
*
|
|
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
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const { command, args, stdin, env: agentEnv } =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
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
|
|
256
|
-
if (
|
|
257
|
-
existing.frontmatter.name = existing.frontmatter.user_prompt
|
|
258
|
-
|
|
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
|
package/dist/spawn-command.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palmier",
|
|
3
|
-
"version": "0.6.
|
|
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/
|
|
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"
|
package/palmier-server/README.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 "
|
|
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
|
|
116
|
-
if (dom !== "*") return
|
|
117
|
-
return
|
|
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
|
|
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(
|
|
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
|
-
|
|
381
|
+
Enable schedule
|
|
374
382
|
</label>
|
|
375
383
|
<div className={`triggers-section-body${triggersEnabled ? "" : " disabled"}`}>
|
|
376
|
-
{triggerRows.
|
|
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
|
-
|
|
380
|
-
<
|
|
381
|
-
className="form-
|
|
382
|
-
|
|
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, {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
<
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
{
|
|
445
|
-
<div className="trigger-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
+
{triggerRows.length > 1 && (
|
|
476
|
+
<button
|
|
477
|
+
className="trigger-remove-btn"
|
|
478
|
+
onClick={() => removeRow(i)}
|
|
479
|
+
disabled={!triggersEnabled}
|
|
480
|
+
title="Remove trigger"
|
|
481
|
+
>
|
|
482
|
+
×
|
|
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
|
|
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.
|
|
2
|
+
export const MIN_HOST_VERSION = "0.6.8";
|