palmier 0.8.0 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +13 -0
- package/README.md +11 -11
- package/dist/agents/agent.d.ts +0 -4
- package/dist/agents/claude.js +1 -1
- package/dist/agents/codex.js +2 -2
- package/dist/agents/cursor.js +1 -1
- package/dist/agents/deepagents.js +1 -1
- package/dist/agents/gemini.js +3 -2
- package/dist/agents/goose.js +1 -1
- package/dist/agents/hermes.js +1 -1
- package/dist/agents/kiro.js +1 -1
- package/dist/agents/opencode.js +1 -1
- package/dist/agents/qoder.js +1 -1
- package/dist/agents/shared-prompt.d.ts +0 -3
- package/dist/agents/shared-prompt.js +0 -3
- package/dist/app-registry.d.ts +10 -0
- package/dist/app-registry.js +44 -0
- package/dist/commands/info.d.ts +0 -3
- package/dist/commands/info.js +0 -5
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.js +2 -11
- package/dist/commands/pair.d.ts +1 -4
- package/dist/commands/pair.js +1 -12
- package/dist/commands/restart.d.ts +0 -3
- package/dist/commands/restart.js +0 -3
- package/dist/commands/run.d.ts +1 -14
- package/dist/commands/run.js +18 -61
- package/dist/commands/serve.d.ts +0 -3
- package/dist/commands/serve.js +33 -27
- package/dist/config.d.ts +0 -8
- package/dist/config.js +0 -8
- package/dist/device-capabilities.d.ts +1 -1
- package/dist/event-queues.d.ts +6 -21
- package/dist/event-queues.js +6 -21
- package/dist/events.d.ts +0 -6
- package/dist/events.js +1 -9
- package/dist/index.js +0 -1
- package/dist/mcp-handler.js +1 -2
- package/dist/mcp-tools.d.ts +0 -3
- package/dist/mcp-tools.js +14 -18
- package/dist/nats-client.d.ts +0 -3
- package/dist/nats-client.js +1 -4
- package/dist/pending-requests.d.ts +4 -18
- package/dist/pending-requests.js +4 -18
- package/dist/platform/index.d.ts +1 -4
- package/dist/platform/index.js +1 -4
- package/dist/platform/linux.d.ts +3 -9
- package/dist/platform/linux.js +9 -20
- package/dist/platform/platform.d.ts +1 -4
- package/dist/platform/windows.d.ts +2 -5
- package/dist/platform/windows.js +19 -39
- package/dist/pwa/assets/index-B0F9mtid.css +1 -0
- package/dist/pwa/assets/index-SYs3mcdJ.js +120 -0
- package/dist/pwa/assets/{web-CF-N8Di6.js → web-C6lkQj9J.js} +1 -1
- package/dist/pwa/assets/{web-BpM3fNCn.js → web-Z1623me-.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.d.ts +0 -6
- package/dist/rpc-handler.js +19 -48
- package/dist/spawn-command.d.ts +10 -25
- package/dist/spawn-command.js +7 -15
- package/dist/task.d.ts +6 -64
- package/dist/task.js +7 -70
- package/dist/transports/http-transport.d.ts +0 -4
- package/dist/transports/http-transport.js +6 -28
- package/dist/transports/nats-transport.d.ts +0 -4
- package/dist/transports/nats-transport.js +3 -9
- package/dist/types.d.ts +3 -7
- package/dist/update-checker.d.ts +1 -4
- package/dist/update-checker.js +2 -5
- package/package.json +1 -1
- package/palmier-server/README.md +1 -1
- package/palmier-server/pwa/src/App.css +170 -20
- package/palmier-server/pwa/src/App.tsx +15 -1
- package/palmier-server/pwa/src/components/HostMenu.tsx +282 -473
- package/palmier-server/pwa/src/components/RunDetailView.tsx +3 -3
- package/palmier-server/pwa/src/components/SessionsView.tsx +57 -25
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +160 -0
- package/palmier-server/pwa/src/components/TaskCard.tsx +12 -4
- package/palmier-server/pwa/src/components/TaskForm.tsx +230 -33
- package/palmier-server/pwa/src/components/TasksView.tsx +5 -0
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/pwa/src/native/Device.ts +66 -0
- package/palmier-server/pwa/src/pages/Dashboard.tsx +11 -6
- package/palmier-server/pwa/src/pages/PairHost.tsx +18 -2
- package/palmier-server/pwa/src/types.ts +1 -1
- package/palmier-server/server/src/index.ts +7 -7
- package/palmier-server/server/src/routes/device.ts +4 -4
- package/palmier-server/spec.md +47 -6
- package/src/agents/agent.ts +0 -4
- package/src/agents/claude.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/cursor.ts +1 -1
- package/src/agents/deepagents.ts +1 -1
- package/src/agents/gemini.ts +3 -2
- package/src/agents/goose.ts +1 -1
- package/src/agents/hermes.ts +1 -1
- package/src/agents/kiro.ts +1 -1
- package/src/agents/opencode.ts +1 -1
- package/src/agents/qoder.ts +1 -1
- package/src/agents/shared-prompt.ts +0 -3
- package/src/app-registry.ts +52 -0
- package/src/commands/info.ts +0 -5
- package/src/commands/init.ts +2 -11
- package/src/commands/pair.ts +1 -12
- package/src/commands/restart.ts +0 -3
- package/src/commands/run.ts +18 -65
- package/src/commands/serve.ts +31 -27
- package/src/config.ts +0 -8
- package/src/device-capabilities.ts +4 -3
- package/src/event-queues.ts +6 -21
- package/src/events.ts +1 -9
- package/src/index.ts +0 -1
- package/src/mcp-handler.ts +1 -2
- package/src/mcp-tools.ts +14 -20
- package/src/nats-client.ts +1 -4
- package/src/pending-requests.ts +4 -18
- package/src/platform/index.ts +1 -4
- package/src/platform/linux.ts +9 -20
- package/src/platform/platform.ts +1 -4
- package/src/platform/windows.ts +19 -40
- package/src/rpc-handler.ts +20 -48
- package/src/spawn-command.ts +11 -27
- package/src/task.ts +7 -70
- package/src/transports/http-transport.ts +6 -39
- package/src/transports/nats-transport.ts +3 -9
- package/src/types.ts +3 -10
- package/src/update-checker.ts +2 -5
- package/test/task-parsing.test.ts +2 -3
- package/test/windows-xml.test.ts +11 -12
- package/dist/pwa/assets/index-FP1Mipr6.js +0 -120
- package/dist/pwa/assets/index-bLTn8zBj.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as p}from"./index-
|
|
1
|
+
import{W as p}from"./index-SYs3mcdJ.js";class f extends p{constructor(){super(...arguments),this.group="CapacitorStorage"}async configure({group:e}){typeof e=="string"&&(this.group=e)}async get(e){return{value:this.impl.getItem(this.applyPrefix(e.key))}}async set(e){this.impl.setItem(this.applyPrefix(e.key),e.value)}async remove(e){this.impl.removeItem(this.applyPrefix(e.key))}async keys(){return{keys:this.rawKeys().map(t=>t.substring(this.prefix.length))}}async clear(){for(const e of this.rawKeys())this.impl.removeItem(e)}async migrate(){var e;const t=[],s=[],n="_cap_",o=Object.keys(this.impl).filter(i=>i.indexOf(n)===0);for(const i of o){const r=i.substring(n.length),a=(e=this.impl.getItem(i))!==null&&e!==void 0?e:"",{value:l}=await this.get({key:r});typeof l=="string"?s.push(r):(await this.set({key:r,value:a}),t.push(r))}return{migrated:t,existing:s}}async removeOld(){const e="_cap_",t=Object.keys(this.impl).filter(s=>s.indexOf(e)===0);for(const s of t)this.impl.removeItem(s)}get impl(){return window.localStorage}get prefix(){return this.group==="NativeStorage"?"":`${this.group}.`}rawKeys(){return Object.keys(this.impl).filter(e=>e.indexOf(this.prefix)===0)}applyPrefix(e){return this.prefix+e}}export{f as PreferencesWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as t}from"./index-
|
|
1
|
+
import{W as t}from"./index-SYs3mcdJ.js";class s extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{s as AppWeb};
|
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-SYs3mcdJ.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B0F9mtid.css">
|
|
13
13
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
try{self["workbox:core:7.3.0"]&&_()}catch{}const N=(n,...e)=>{let t=n;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},E=N;class h extends Error{constructor(e,t){const s=E(e,t);super(s),this.name=e,this.details=t}}const f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},U=n=>[f.prefix,n,f.suffix].filter(e=>e&&e.length>0).join("-"),O=n=>{for(const e of Object.keys(f))n(e)},L={updateDetails:n=>{O(e=>{typeof n[e]=="string"&&(f[e]=n[e])})},getGoogleAnalyticsName:n=>n||U(f.googleAnalytics),getPrecacheName:n=>n||U(f.precache),getPrefix:()=>f.prefix,getRuntimeName:n=>n||U(f.runtime),getSuffix:()=>f.suffix};function v(n,e){const t=e();return n.waitUntil(t),t}try{self["workbox:precaching:7.3.0"]&&_()}catch{}const A="__WB_REVISION__";function M(n){if(!n)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(typeof n=="string"){const i=new URL(n,location.href);return{cacheKey:i.href,url:i.href}}const{revision:e,url:t}=n;if(!t)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(!e){const i=new URL(t,location.href);return{cacheKey:i.href,url:i.href}}const s=new URL(t,location.href),a=new URL(t,location.href);return s.searchParams.set(A,e),{cacheKey:s.href,url:a.href}}class W{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const a=t.originalRequest.url;s?this.notUpdatedURLs.push(a):this.updatedURLs.push(a)}return s}}}class q{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const a=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return a?new Request(a,{headers:t.headers}):t},this._precacheController=e}}let w;function S(){if(w===void 0){const n=new Response("");if("body"in n)try{new Response(n.body),w=!0}catch{w=!1}w=!1}return w}async function j(n,e){let t=null;if(n.url&&(t=new URL(n.url).origin),t!==self.location.origin)throw new h("cross-origin-copy-response",{origin:t});const s=n.clone(),i={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},r=S()?s.body:await s.blob();return new Response(r,i)}const D=n=>new URL(String(n),location.href).href.replace(new RegExp(`^${location.origin}`),"");function T(n,e){const t=new URL(n);for(const s of e)t.searchParams.delete(s);return t.href}async function H(n,e,t,s){const a=T(e.url,t);if(e.url===a)return n.match(e,s);const i=Object.assign(Object.assign({},s),{ignoreSearch:!0}),r=await n.keys(e,i);for(const c of r){const o=T(c.url,t);if(a===o)return n.match(c,s)}}class F{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}const B=new Set;async function $(){for(const n of B)await n()}function V(n){return new Promise(e=>setTimeout(e,n))}try{self["workbox:strategies:7.3.0"]&&_()}catch{}function C(n){return typeof n=="string"?new Request(n):n}class G{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new F,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=C(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const r=await t.preloadResponse;if(r)return r}const a=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const r of this.iterateCallbacks("requestWillFetch"))s=await r({request:s.clone(),event:t})}catch(r){if(r instanceof Error)throw new h("plugin-error-request-will-fetch",{thrownErrorMessage:r.message})}const i=s.clone();try{let r;r=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const c of this.iterateCallbacks("fetchDidSucceed"))r=await c({event:t,request:i,response:r});return r}catch(r){throw a&&await this.runCallbacks("fetchDidFail",{error:r,event:t,originalRequest:a.clone(),request:i.clone()}),r}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=C(e);let s;const{cacheName:a,matchOptions:i}=this._strategy,r=await this.getCacheKey(t,"read"),c=Object.assign(Object.assign({},i),{cacheName:a});s=await caches.match(r,c);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:a,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(e,t){const s=C(e);await V(0);const a=await this.getCacheKey(s,"write");if(!t)throw new h("cache-put-with-no-response",{url:D(a.url)});const i=await this._ensureResponseSafeToCache(t);if(!i)return!1;const{cacheName:r,matchOptions:c}=this._strategy,o=await self.caches.open(r),l=this.hasCallback("cacheDidUpdate"),d=l?await H(o,a.clone(),["__WB_REVISION__"],c):null;try{await o.put(a,l?i.clone():i)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await $(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:r,oldResponse:d,newResponse:i.clone(),request:a,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let a=e;for(const i of this.iterateCallbacks("cacheKeyWillBeUsed"))a=C(await i({mode:t,request:a,event:this.event,params:this.params}));this._cacheKeys[s]=a}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield i=>{const r=Object.assign(Object.assign({},i),{state:s});return t[e](r)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(a=>a.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const a of this.iterateCallbacks("cacheWillUpdate"))if(t=await a({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class J{constructor(e={}){this.cacheName=L.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,a="params"in e?e.params:void 0,i=new G(this,{event:t,request:s,params:a}),r=this._getResponse(i,s,t),c=this._awaitComplete(r,i,s,t);return[r,c]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let a;try{if(a=await this._handle(t,e),!a||a.type==="error")throw new h("no-response",{url:t.url})}catch(i){if(i instanceof Error){for(const r of e.iterateCallbacks("handlerDidError"))if(a=await r({error:i,event:s,request:t}),a)break}if(!a)throw i}for(const i of e.iterateCallbacks("handlerWillRespond"))a=await i({event:s,request:t,response:a});return a}async _awaitComplete(e,t,s,a){let i,r;try{i=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:a,request:s,response:i}),await t.doneWaiting()}catch(c){c instanceof Error&&(r=c)}if(await t.runCallbacks("handlerDidComplete",{event:a,request:s,response:i,error:r}),t.destroy(),r)throw r}}class p extends J{constructor(e={}){e.cacheName=L.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(p.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const a=t.params||{};if(this._fallbackToNetwork){const i=a.integrity,r=e.integrity,c=!r||r===i;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?r||i:void 0})),i&&c&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new h("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new h("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,a]of this.plugins.entries())a!==p.copyRedirectedCacheableResponsesPlugin&&(a===p.defaultPrecacheCacheabilityPlugin&&(e=s),a.cacheWillUpdate&&t++);t===0?this.plugins.push(p.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}p.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:n}){return!n||n.status>=400?null:n}};p.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:n}){return n.redirected?await j(n):n}};class Q{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new p({cacheName:L.getPrecacheName(e),plugins:[...t,new q({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:a,url:i}=M(s),r=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==a)throw new h("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:a});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(a)&&this._cacheKeysToIntegrities.get(a)!==s.integrity)throw new h("add-to-cache-list-conflicting-integrities",{url:i});this._cacheKeysToIntegrities.set(a,s.integrity)}if(this._urlsToCacheKeys.set(i,a),this._urlsToCacheModes.set(i,r),t.length>0){const c=`Workbox is precaching URLs without revision info: ${t.join(", ")}
|
|
2
|
-
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"
|
|
2
|
+
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"c78d7ea0a3891a032082aa56b9c2e8d7","url":"index.html"},{"revision":null,"url":"assets/web-Z1623me-.js"},{"revision":null,"url":"assets/web-C6lkQj9J.js"},{"revision":null,"url":"assets/index-SYs3mcdJ.js"},{"revision":null,"url":"assets/index-B0F9mtid.css"},{"revision":"fcc457fce855ad0df7178e0786c0d4ef","url":"apple-touch-icon.png"},{"revision":"276650c30bc4effc7d649ec66519aab6","url":"favicon.ico"},{"revision":"2e46512b835c05e17787059909305f22","url":"pwa-192x192.png"},{"revision":"ec5652b5834b4711337743e80e506a41","url":"pwa-512x512.png"},{"revision":"9f51698004b9cc4d787c75695b74de9d","url":"manifest.webmanifest"}]);const re="/api/push/respond";self.addEventListener("message",n=>{});self.addEventListener("push",n=>{var r;if(!n.data)return;let e;try{e=n.data.json()}catch{e={title:"Palmier",body:n.data.text()}}const t=e.type??((r=e.data)==null?void 0:r.type);if(t==="confirm-dismiss"||t==="permission-dismiss"||t==="input-dismiss"){const c=e.data??e,o=c.host_id,l=c.session_id,d=c.task_id;n.waitUntil(self.registration.getNotifications().then(u=>{var g,P,K;for(const y of u)if(((g=y.data)==null?void 0:g.host_id)===o){if(l&&((P=y.data)==null?void 0:P.session_id)===l){y.close();continue}d&&((K=y.data)==null?void 0:K.task_id)===d&&y.close()}}));return}const s=e.title??"Palmier";let a=e.body??"";!a&&t==="confirm"&&(a="A task requires confirmation to run."),!a&&t==="permission"&&(a="A task needs additional permissions to continue."),!a&&t==="input"&&(a="A task needs your input to continue.");const i={body:a,icon:"/pwa-192x192.png",badge:"/pwa-192x192.png",data:e.data??e,vibrate:[100,50,100]};t==="confirm"&&(i.actions=[{action:"confirm",title:"Confirm"},{action:"abort",title:"Abort"}]),n.waitUntil(self.registration.showNotification(s,i))});self.addEventListener("notificationclick",n=>{const e=n.notification;e.close();const t=e.data??{},s=n.action;if(s&&t.type==="confirm"&&t.session_id&&t.host_id){const a=s==="confirm"?"confirmed":"aborted";n.waitUntil(fetch(re,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:t.session_id,host_id:t.host_id,response:a})}).catch(i=>{console.error("Failed to send push response:",i)}))}else{const a=t.task_id,i=t.run_id,r=a&&i?`/runs/${encodeURIComponent(a)}/${encodeURIComponent(i)}`:a?`/runs/${encodeURIComponent(a)}/latest`:"/";n.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(c=>{for(const o of c)if(o.url.includes(self.location.origin)&&"focus"in o)return o.navigate(r),o.focus();return self.clients.openWindow(r)}))}});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",n=>{n.waitUntil(self.clients.claim())});
|
package/dist/rpc-handler.d.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { type NatsConnection } from "nats";
|
|
2
2
|
import type { HostConfig, RpcMessage } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Parse RESULT frontmatter and conversation messages.
|
|
5
|
-
*/
|
|
6
3
|
export declare function parseResultFrontmatter(raw: string): Record<string, unknown>;
|
|
7
|
-
/**
|
|
8
|
-
* Create a transport-agnostic RPC handler bound to the given config.
|
|
9
|
-
*/
|
|
10
4
|
export declare function createRpcHandler(config: HostConfig, nc?: NatsConnection): (request: RpcMessage) => Promise<unknown>;
|
|
11
5
|
//# sourceMappingURL=rpc-handler.d.ts.map
|
package/dist/rpc-handler.js
CHANGED
|
@@ -11,12 +11,10 @@ import { getAgent } from "./agents/agent.js";
|
|
|
11
11
|
import { validateClient } from "./client-store.js";
|
|
12
12
|
import { publishHostEvent } from "./events.js";
|
|
13
13
|
import { getCapabilityDevice, setCapabilityDevice, clearCapabilityDevice } from "./device-capabilities.js";
|
|
14
|
+
import { listApps } from "./app-registry.js";
|
|
14
15
|
import { currentVersion, performUpdate } from "./update-checker.js";
|
|
15
16
|
import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
|
|
16
17
|
import { clearTaskQueue } from "./event-queues.js";
|
|
17
|
-
/**
|
|
18
|
-
* Parse RESULT frontmatter and conversation messages.
|
|
19
|
-
*/
|
|
20
18
|
export function parseResultFrontmatter(raw) {
|
|
21
19
|
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
22
20
|
if (!fmMatch)
|
|
@@ -29,18 +27,15 @@ export function parseResultFrontmatter(raw) {
|
|
|
29
27
|
meta[line.slice(0, sep).trim()] = line.slice(sep + 2).trim();
|
|
30
28
|
}
|
|
31
29
|
const messages = parseConversationMessages(fmMatch[2]);
|
|
32
|
-
// Derive state from status messages — just look at the last one
|
|
33
30
|
const statusMessages = messages.filter((m) => m.role === "status");
|
|
34
31
|
const lastStatus = statusMessages[statusMessages.length - 1];
|
|
35
32
|
const startedMsg = statusMessages.find((m) => m.type === "started");
|
|
36
33
|
const terminalStates = ["finished", "failed", "aborted"];
|
|
37
34
|
const terminalMsg = [...statusMessages].reverse().find((m) => terminalStates.includes(m.type ?? ""));
|
|
38
|
-
// If last status is "started" (or continuation like "confirmation"/"monitoring"),
|
|
39
|
-
// determine if it's a task run or follow-up
|
|
40
35
|
const activeStates = ["started", "monitoring", "confirmation"];
|
|
41
36
|
let runningState;
|
|
42
37
|
if (lastStatus?.type === "monitoring") {
|
|
43
|
-
//
|
|
38
|
+
// Show "monitoring" only if no assistant/user message followed it.
|
|
44
39
|
const lastStatusIdx = messages.lastIndexOf(lastStatus);
|
|
45
40
|
const hasMessageAfter = messages.slice(lastStatusIdx + 1).some((m) => m.role === "assistant" || m.role === "user");
|
|
46
41
|
runningState = hasMessageAfter ? "started" : "monitoring";
|
|
@@ -60,15 +55,12 @@ export function parseResultFrontmatter(raw) {
|
|
|
60
55
|
end_time: terminalMsg?.time || undefined,
|
|
61
56
|
};
|
|
62
57
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Parse conversation messages from the body of a RESULT file.
|
|
65
|
-
*/
|
|
66
58
|
function parseConversationMessages(body) {
|
|
67
59
|
const delimiterRegex = /<!-- palmier:message\s+(.*?)\s*-->/g;
|
|
68
60
|
const messages = [];
|
|
69
61
|
const matches = [...body.matchAll(delimiterRegex)];
|
|
70
62
|
if (matches.length === 0) {
|
|
71
|
-
// No delimiters — treat entire body as single assistant message
|
|
63
|
+
// No delimiters — treat entire body as a single assistant message.
|
|
72
64
|
const content = body.trim();
|
|
73
65
|
if (content) {
|
|
74
66
|
messages.push({ role: "assistant", time: 0, content });
|
|
@@ -94,10 +86,6 @@ function parseAttr(attrs, name) {
|
|
|
94
86
|
const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
|
|
95
87
|
return match ? match[1] : undefined;
|
|
96
88
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Generate a concise task name from a user prompt using the given agent.
|
|
99
|
-
* Falls back to the raw prompt on failure.
|
|
100
|
-
*/
|
|
101
89
|
async function generateName(projectRoot, userPrompt, agentName) {
|
|
102
90
|
const prompt = `Generate a concise 3-6 word name for this task. Reply with ONLY the name, nothing else.\n\nTask: ${userPrompt}`;
|
|
103
91
|
const agent = getAgent(agentName);
|
|
@@ -118,9 +106,6 @@ async function generateName(projectRoot, userPrompt, agentName) {
|
|
|
118
106
|
}
|
|
119
107
|
/** Active follow-up child processes, keyed by "taskId:runId". */
|
|
120
108
|
const activeFollowups = new Map();
|
|
121
|
-
/**
|
|
122
|
-
* Create a transport-agnostic RPC handler bound to the given config.
|
|
123
|
-
*/
|
|
124
109
|
export function createRpcHandler(config, nc) {
|
|
125
110
|
function flattenTask(task) {
|
|
126
111
|
const taskDir = getTaskDir(config.projectRoot, task.frontmatter.id);
|
|
@@ -131,20 +116,17 @@ export function createRpcHandler(config, nc) {
|
|
|
131
116
|
};
|
|
132
117
|
}
|
|
133
118
|
async function handleRpc(request) {
|
|
134
|
-
//
|
|
135
|
-
//
|
|
119
|
+
// task.user_input comes from server-originated push responses; it's gated
|
|
120
|
+
// by getPending() rather than a client token.
|
|
136
121
|
const skipAuth = request.method === "task.user_input";
|
|
137
122
|
if (!skipAuth && !request.localhost && (!request.clientToken || !validateClient(request.clientToken))) {
|
|
138
123
|
return { error: "Unauthorized" };
|
|
139
124
|
}
|
|
140
125
|
switch (request.method) {
|
|
141
126
|
case "host.info": {
|
|
142
|
-
// Bootstrap metadata the PWA needs on connect, independent of which tab
|
|
143
|
-
// is active. Includes any prompts already waiting so a reconnecting
|
|
144
|
-
// PWA can render their modals without replaying events.
|
|
145
127
|
const capabilities = {};
|
|
146
|
-
for (const
|
|
147
|
-
capabilities[
|
|
128
|
+
for (const capability of ["location", "notifications", "sms-read", "sms-send", "contacts", "calendar", "alarm", "battery", "dnd", "send-email"]) {
|
|
129
|
+
capabilities[capability] = getCapabilityDevice(capability)?.clientToken ?? null;
|
|
148
130
|
}
|
|
149
131
|
return {
|
|
150
132
|
agents: config.agents ?? [],
|
|
@@ -200,10 +182,8 @@ export function createRpcHandler(config, nc) {
|
|
|
200
182
|
const params = request.params;
|
|
201
183
|
const taskDir = getTaskDir(config.projectRoot, params.id);
|
|
202
184
|
const existing = parseTaskFile(taskDir);
|
|
203
|
-
// Detect whether name needs regeneration
|
|
204
185
|
const promptChanged = params.user_prompt !== undefined && params.user_prompt !== existing.frontmatter.user_prompt;
|
|
205
186
|
const agentChanged = params.agent !== undefined && params.agent !== existing.frontmatter.agent;
|
|
206
|
-
// Merge updates
|
|
207
187
|
if (params.user_prompt !== undefined)
|
|
208
188
|
existing.frontmatter.user_prompt = params.user_prompt;
|
|
209
189
|
if (params.agent !== undefined)
|
|
@@ -243,15 +223,14 @@ export function createRpcHandler(config, nc) {
|
|
|
243
223
|
delete existing.frontmatter.command;
|
|
244
224
|
}
|
|
245
225
|
}
|
|
246
|
-
// Regenerate name when prompt or agent changes
|
|
247
226
|
if (promptChanged || agentChanged) {
|
|
248
227
|
existing.frontmatter.name = existing.frontmatter.user_prompt.length <= 50
|
|
249
228
|
? existing.frontmatter.user_prompt
|
|
250
229
|
: await generateName(config.projectRoot, existing.frontmatter.user_prompt, existing.frontmatter.agent);
|
|
251
230
|
}
|
|
252
231
|
writeTaskFile(taskDir, existing);
|
|
253
|
-
//
|
|
254
|
-
//
|
|
232
|
+
// installTaskTimer overwrites in-place (schtasks /f, systemd unit rewrite)
|
|
233
|
+
// without killing a running task process.
|
|
255
234
|
getPlatform().installTaskTimer(config, existing);
|
|
256
235
|
return flattenTask(existing);
|
|
257
236
|
}
|
|
@@ -281,11 +260,9 @@ export function createRpcHandler(config, nc) {
|
|
|
281
260
|
},
|
|
282
261
|
};
|
|
283
262
|
writeTaskFile(taskDir, task);
|
|
284
|
-
//
|
|
285
|
-
// Create initial result file so it appears in runs list immediately
|
|
263
|
+
// One-off run: do NOT append to tasks.jsonl.
|
|
286
264
|
const runId = createRunDir(taskDir, name, Date.now(), params.agent);
|
|
287
265
|
appendHistory(config.projectRoot, { task_id: id, run_id: runId });
|
|
288
|
-
// Spawn `palmier run <id>` directly as a detached process
|
|
289
266
|
const script = process.argv[1] || "palmier";
|
|
290
267
|
const child = spawn(process.execPath, [script, "run", id], {
|
|
291
268
|
detached: true,
|
|
@@ -300,12 +277,10 @@ export function createRpcHandler(config, nc) {
|
|
|
300
277
|
try {
|
|
301
278
|
const runTaskDir = getTaskDir(config.projectRoot, params.id);
|
|
302
279
|
const platform = getPlatform();
|
|
303
|
-
// If the task is already running, kill the stale process and start fresh
|
|
304
280
|
if (platform.isTaskRunning(params.id)) {
|
|
305
281
|
console.log(`[task.run] Task ${params.id} is already running, killing stale process`);
|
|
306
282
|
await platform.stopTask(params.id);
|
|
307
283
|
}
|
|
308
|
-
// Create initial result file so it appears in runs list immediately
|
|
309
284
|
const runTask = parseTaskFile(runTaskDir);
|
|
310
285
|
const taskRunId = createRunDir(runTaskDir, runTask.frontmatter.name, Date.now(), runTask.frontmatter.agent);
|
|
311
286
|
appendHistory(config.projectRoot, { task_id: params.id, run_id: taskRunId });
|
|
@@ -330,7 +305,6 @@ export function createRpcHandler(config, nc) {
|
|
|
330
305
|
const followupTaskDir = getTaskDir(config.projectRoot, params.id);
|
|
331
306
|
const followupTask = parseTaskFile(followupTaskDir);
|
|
332
307
|
const followupRunDir = getRunDir(followupTaskDir, params.run_id);
|
|
333
|
-
// Append user message + started status
|
|
334
308
|
appendRunMessage(followupTaskDir, params.run_id, {
|
|
335
309
|
role: "user",
|
|
336
310
|
time: Date.now(),
|
|
@@ -343,10 +317,8 @@ export function createRpcHandler(config, nc) {
|
|
|
343
317
|
type: "started",
|
|
344
318
|
});
|
|
345
319
|
await publishHostEvent(nc, config.hostId, params.id, { event_type: "result-updated", run_id: params.run_id });
|
|
346
|
-
// Fire-and-forget: invoke agent inline as a child of the serve process
|
|
347
320
|
const followupAgent = getAgent(followupTask.frontmatter.agent);
|
|
348
321
|
const { command: cmd, args: cmdArgs, stdin, env: followupAgentEnv } = followupAgent.getTaskRunCommandLine(followupTask, params.message, followupTask.frontmatter.yolo_mode ? "yolo" : followupTask.frontmatter.permissions);
|
|
349
|
-
// Spawn directly via crossSpawn so we can track and kill the child
|
|
350
322
|
const child = crossSpawn(cmd, cmdArgs, {
|
|
351
323
|
cwd: followupRunDir,
|
|
352
324
|
stdio: [stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
|
|
@@ -356,13 +328,12 @@ export function createRpcHandler(config, nc) {
|
|
|
356
328
|
if (stdin != null)
|
|
357
329
|
child.stdin.end(stdin);
|
|
358
330
|
activeFollowups.set(followupKey, child);
|
|
359
|
-
// Collect output
|
|
360
331
|
const chunks = [];
|
|
361
332
|
child.stdout?.on("data", (d) => chunks.push(d));
|
|
362
333
|
child.stderr?.on("data", (d) => process.stderr.write(d));
|
|
363
334
|
child.on("close", async (code) => {
|
|
364
335
|
activeFollowups.delete(followupKey);
|
|
365
|
-
//
|
|
336
|
+
// stop_followup already wrote the stopped status.
|
|
366
337
|
if (child.killed)
|
|
367
338
|
return;
|
|
368
339
|
const output = Buffer.concat(chunks).toString("utf-8");
|
|
@@ -405,7 +376,6 @@ export function createRpcHandler(config, nc) {
|
|
|
405
376
|
if (!child) {
|
|
406
377
|
return { error: "No active follow-up for this run" };
|
|
407
378
|
}
|
|
408
|
-
// Kill the child process tree
|
|
409
379
|
if (process.platform === "win32" && child.pid) {
|
|
410
380
|
try {
|
|
411
381
|
const { execFileSync } = await import("child_process");
|
|
@@ -416,7 +386,7 @@ export function createRpcHandler(config, nc) {
|
|
|
416
386
|
else {
|
|
417
387
|
child.kill();
|
|
418
388
|
}
|
|
419
|
-
//
|
|
389
|
+
// child.killed stops the close handler from double-writing the status.
|
|
420
390
|
const stopTaskDir = getTaskDir(config.projectRoot, params.id);
|
|
421
391
|
appendRunMessage(stopTaskDir, params.run_id, {
|
|
422
392
|
role: "status",
|
|
@@ -431,17 +401,16 @@ export function createRpcHandler(config, nc) {
|
|
|
431
401
|
case "task.abort": {
|
|
432
402
|
const params = request.params;
|
|
433
403
|
const abortTaskDir = getTaskDir(config.projectRoot, params.id);
|
|
434
|
-
// Read
|
|
435
|
-
//
|
|
404
|
+
// Read PID before overwriting — stopTask needs it to kill the
|
|
405
|
+
// process tree on Windows.
|
|
436
406
|
const abortPrevStatus = readTaskStatus(abortTaskDir);
|
|
437
|
-
// Write abort status
|
|
438
|
-
// handler
|
|
407
|
+
// Write abort status before killing so the dying process's signal
|
|
408
|
+
// handler sees this was RPC-initiated and skips publishing.
|
|
439
409
|
writeTaskStatus(abortTaskDir, {
|
|
440
410
|
running_state: "aborted",
|
|
441
411
|
time_stamp: Date.now(),
|
|
442
412
|
...(abortPrevStatus?.pid ? { pid: abortPrevStatus.pid } : {}),
|
|
443
413
|
});
|
|
444
|
-
// Append aborted status to the latest run
|
|
445
414
|
try {
|
|
446
415
|
const runDirs = fs.readdirSync(abortTaskDir)
|
|
447
416
|
.filter((f) => /^\d+$/.test(f) && fs.existsSync(path.join(abortTaskDir, f, "TASKRUN.md")))
|
|
@@ -465,7 +434,6 @@ export function createRpcHandler(config, nc) {
|
|
|
465
434
|
console.error(`task.abort failed for ${params.id}: ${e.stderr || e.message}`);
|
|
466
435
|
return { error: `Failed to abort task: ${e.stderr || e.message}` };
|
|
467
436
|
}
|
|
468
|
-
// Notify connected clients (NATS + HTTP SSE if LAN server is running)
|
|
469
437
|
const abortPayload = { event_type: "running-state", running_state: "aborted" };
|
|
470
438
|
await publishHostEvent(nc, config.hostId, params.id, abortPayload);
|
|
471
439
|
return { ok: true, task_id: params.id };
|
|
@@ -607,6 +575,9 @@ export function createRpcHandler(config, nc) {
|
|
|
607
575
|
clearCapabilityDevice(params.capability);
|
|
608
576
|
return { ok: true };
|
|
609
577
|
}
|
|
578
|
+
case "device.notifications.apps": {
|
|
579
|
+
return { apps: listApps() };
|
|
580
|
+
}
|
|
610
581
|
default:
|
|
611
582
|
return { error: `Unknown method: ${request.method}` };
|
|
612
583
|
}
|
package/dist/spawn-command.d.ts
CHANGED
|
@@ -4,19 +4,12 @@ export interface SpawnStreamingOptions {
|
|
|
4
4
|
env?: Record<string, string>;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Spawn
|
|
8
|
-
* with stdout piped
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* shell: true is required so users can write piped commands like
|
|
14
|
-
* "tail -f log | grep ERROR".
|
|
15
|
-
*
|
|
16
|
-
* stdin is "pipe" (kept open, never written to) rather than "ignore"
|
|
17
|
-
* (/dev/null). Some long-running commands exit when stdin is closed/EOF.
|
|
18
|
-
* This differs from spawnCommand() which uses "ignore" because agent
|
|
19
|
-
* CLIs like `claude -p` hang on an open stdin pipe.
|
|
7
|
+
* Spawn with shell interpretation for piped commands like "tail -f log | grep".
|
|
8
|
+
* Returns the ChildProcess with stdout piped so the caller can read it directly
|
|
9
|
+
* (contrast with spawnCommand which buffers). stdin is "pipe" (held open, not
|
|
10
|
+
* written to): some long-running commands exit on stdin EOF. Agent CLIs like
|
|
11
|
+
* `claude -p` conversely hang on an open stdin, which is why spawnCommand
|
|
12
|
+
* defaults to "ignore".
|
|
20
13
|
*/
|
|
21
14
|
export declare function spawnStreamingCommand(command: string, opts: SpawnStreamingOptions): ChildProcess;
|
|
22
15
|
export interface SpawnCommandOptions {
|
|
@@ -33,18 +26,10 @@ export interface SpawnCommandOptions {
|
|
|
33
26
|
onData?: (chunk: string) => void;
|
|
34
27
|
}
|
|
35
28
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* On other platforms the command is executed directly (no shell), so no
|
|
42
|
-
* escaping is needed.
|
|
43
|
-
*
|
|
44
|
-
* stdin is set to "ignore" by default (equivalent to < /dev/null) because
|
|
45
|
-
* tools like `claude -p` hang indefinitely on an open stdin pipe.
|
|
46
|
-
* When opts.stdin is provided, stdin is set to "pipe" and the string is
|
|
47
|
-
* written to the process before closing the pipe.
|
|
29
|
+
* cross-spawn resolves .cmd shims and escapes args on Windows without shell:true
|
|
30
|
+
* (which mishandles special characters). stdin defaults to "ignore" because
|
|
31
|
+
* tools like `claude -p` hang on an open stdin pipe; pass opts.stdin to write
|
|
32
|
+
* a string and then close the pipe.
|
|
48
33
|
*/
|
|
49
34
|
export interface SpawnCommandResult {
|
|
50
35
|
output: string;
|
package/dist/spawn-command.js
CHANGED
|
@@ -12,19 +12,12 @@ function treeKill(child) {
|
|
|
12
12
|
child.kill();
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Spawn
|
|
16
|
-
* with stdout piped
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* shell: true is required so users can write piped commands like
|
|
22
|
-
* "tail -f log | grep ERROR".
|
|
23
|
-
*
|
|
24
|
-
* stdin is "pipe" (kept open, never written to) rather than "ignore"
|
|
25
|
-
* (/dev/null). Some long-running commands exit when stdin is closed/EOF.
|
|
26
|
-
* This differs from spawnCommand() which uses "ignore" because agent
|
|
27
|
-
* CLIs like `claude -p` hang on an open stdin pipe.
|
|
15
|
+
* Spawn with shell interpretation for piped commands like "tail -f log | grep".
|
|
16
|
+
* Returns the ChildProcess with stdout piped so the caller can read it directly
|
|
17
|
+
* (contrast with spawnCommand which buffers). stdin is "pipe" (held open, not
|
|
18
|
+
* written to): some long-running commands exit on stdin EOF. Agent CLIs like
|
|
19
|
+
* `claude -p` conversely hang on an open stdin, which is why spawnCommand
|
|
20
|
+
* defaults to "ignore".
|
|
28
21
|
*/
|
|
29
22
|
export function spawnStreamingCommand(command, opts) {
|
|
30
23
|
return crossSpawn(command, [], {
|
|
@@ -37,8 +30,7 @@ export function spawnStreamingCommand(command, opts) {
|
|
|
37
30
|
}
|
|
38
31
|
export function spawnCommand(command, args, opts) {
|
|
39
32
|
return new Promise((resolve, reject) => {
|
|
40
|
-
//
|
|
41
|
-
// in arguments, and CLI prompts don't need them.
|
|
33
|
+
// cmd.exe can't handle literal newlines in arguments.
|
|
42
34
|
const finalArgs = process.platform === "win32"
|
|
43
35
|
? args.map((a) => a.replace(/[\r\n]+/g, " "))
|
|
44
36
|
: args;
|
package/dist/task.d.ts
CHANGED
|
@@ -1,60 +1,17 @@
|
|
|
1
1
|
import type { ParsedTask, TaskStatus, HistoryEntry, ConversationMessage } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Parse a TASK.md file from the given task directory.
|
|
4
|
-
*/
|
|
5
2
|
export declare function parseTaskFile(taskDir: string): ParsedTask;
|
|
6
|
-
/**
|
|
7
|
-
* Parse TASK.md content string into frontmatter + body.
|
|
8
|
-
*/
|
|
9
3
|
export declare function parseTaskContent(content: string): ParsedTask;
|
|
10
|
-
/**
|
|
11
|
-
* Write a TASK.md file to the given task directory.
|
|
12
|
-
* Creates the directory if it doesn't exist.
|
|
13
|
-
*/
|
|
14
4
|
export declare function writeTaskFile(taskDir: string, task: ParsedTask): void;
|
|
15
|
-
/**
|
|
16
|
-
* Append a task ID to the project-level tasks.jsonl file.
|
|
17
|
-
*/
|
|
18
5
|
export declare function appendTaskList(projectRoot: string, taskId: string): void;
|
|
19
|
-
/**
|
|
20
|
-
* Remove a task ID from the project-level tasks.jsonl file.
|
|
21
|
-
* Returns true if the entry was found and removed.
|
|
22
|
-
*/
|
|
23
6
|
export declare function removeFromTaskList(projectRoot: string, taskId: string): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* List all tasks referenced in tasks.jsonl.
|
|
26
|
-
*/
|
|
27
7
|
export declare function listTasks(projectRoot: string): ParsedTask[];
|
|
28
|
-
/**
|
|
29
|
-
* Get the directory path for a task by its ID.
|
|
30
|
-
*/
|
|
31
8
|
export declare function getTaskDir(projectRoot: string, taskId: string): string;
|
|
32
|
-
/**
|
|
33
|
-
* Write task status to status.json in the task directory.
|
|
34
|
-
*/
|
|
35
9
|
export declare function writeTaskStatus(taskDir: string, status: TaskStatus): void;
|
|
36
|
-
/**
|
|
37
|
-
* Read task status from status.json in the task directory.
|
|
38
|
-
* Returns undefined if the file doesn't exist.
|
|
39
|
-
*/
|
|
40
10
|
export declare function readTaskStatus(taskDir: string): TaskStatus | undefined;
|
|
41
|
-
/**
|
|
42
|
-
* Create a run directory with an initial TASKRUN.md file.
|
|
43
|
-
* Returns the run ID (timestamp string used as directory name).
|
|
44
|
-
*/
|
|
11
|
+
/** Returns the run ID (timestamp string used as directory name). */
|
|
45
12
|
export declare function createRunDir(taskDir: string, taskName: string, startTime: number, agent?: string): string;
|
|
46
|
-
/**
|
|
47
|
-
* Get the path to a run directory.
|
|
48
|
-
*/
|
|
49
13
|
export declare function getRunDir(taskDir: string, runId: string): string;
|
|
50
|
-
/**
|
|
51
|
-
* Append a conversation message to a run's TASKRUN.md file.
|
|
52
|
-
*/
|
|
53
14
|
export declare function appendRunMessage(taskDir: string, runId: string, msg: ConversationMessage): void;
|
|
54
|
-
/**
|
|
55
|
-
* Begin a streaming assistant message — writes the delimiter only.
|
|
56
|
-
* Returns a writer that appends content chunks and finalizes the message.
|
|
57
|
-
*/
|
|
58
15
|
export declare function beginStreamingMessage(taskDir: string, runId: string, time: number): StreamingMessageWriter;
|
|
59
16
|
export declare class StreamingMessageWriter {
|
|
60
17
|
private filePath;
|
|
@@ -65,33 +22,18 @@ export declare class StreamingMessageWriter {
|
|
|
65
22
|
end(attachments?: string[]): void;
|
|
66
23
|
}
|
|
67
24
|
/**
|
|
68
|
-
* Splice a user message into a running assistant stream
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* write() is just appendFileSync, so subsequent chunks land in
|
|
73
|
-
* the new assistant block.
|
|
25
|
+
* Splice a user message into a running assistant stream: close the current
|
|
26
|
+
* assistant block, write the user message, open a new assistant block. Direct
|
|
27
|
+
* appends only, so an existing StreamingMessageWriter keeps working — its
|
|
28
|
+
* subsequent chunks land in the new block.
|
|
74
29
|
*/
|
|
75
30
|
export declare function spliceUserMessage(taskDir: string, runId: string, userMsg: ConversationMessage,
|
|
76
31
|
/** Optional text to append to the current assistant block before ending it. */
|
|
77
32
|
assistantAppend?: string): void;
|
|
78
|
-
/**
|
|
79
|
-
* Read conversation messages from a run's TASKRUN.md file.
|
|
80
|
-
*/
|
|
81
33
|
export declare function readRunMessages(taskDir: string, runId: string): ConversationMessage[];
|
|
82
|
-
/**
|
|
83
|
-
* Append a history entry to the project-level history.jsonl file.
|
|
84
|
-
*/
|
|
85
34
|
export declare function appendHistory(projectRoot: string, entry: HistoryEntry): void;
|
|
86
|
-
/**
|
|
87
|
-
* Delete a history entry and its associated run directory.
|
|
88
|
-
* Returns true if the entry was found and removed.
|
|
89
|
-
*/
|
|
90
35
|
export declare function deleteHistoryEntry(projectRoot: string, taskId: string, runId: string): boolean;
|
|
91
|
-
/**
|
|
92
|
-
* Read history entries from history.jsonl with pagination.
|
|
93
|
-
* Returns entries sorted most-recent-first.
|
|
94
|
-
*/
|
|
36
|
+
/** Returns entries most-recent-first. */
|
|
95
37
|
export declare function readHistory(projectRoot: string, opts: {
|
|
96
38
|
offset?: number;
|
|
97
39
|
limit?: number;
|