palmier 0.9.6 → 0.9.8
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 +28 -13
- package/dist/agents/agent.d.ts +0 -1
- package/dist/agents/agent.js +0 -1
- package/dist/agents/aider.d.ts +0 -1
- package/dist/agents/aider.js +0 -1
- package/dist/agents/claude.d.ts +0 -1
- package/dist/agents/claude.js +0 -1
- package/dist/agents/cline.d.ts +0 -1
- package/dist/agents/cline.js +0 -1
- package/dist/agents/codex.d.ts +0 -1
- package/dist/agents/codex.js +0 -1
- package/dist/agents/copilot.d.ts +0 -1
- package/dist/agents/copilot.js +0 -1
- package/dist/agents/cursor.d.ts +0 -1
- package/dist/agents/cursor.js +0 -1
- package/dist/agents/deepagents.d.ts +0 -1
- package/dist/agents/deepagents.js +0 -1
- package/dist/agents/droid.d.ts +0 -1
- package/dist/agents/droid.js +0 -1
- package/dist/agents/gemini.d.ts +0 -1
- package/dist/agents/gemini.js +0 -1
- package/dist/agents/goose.d.ts +0 -1
- package/dist/agents/goose.js +0 -1
- package/dist/agents/hermes.d.ts +0 -1
- package/dist/agents/hermes.js +0 -1
- package/dist/agents/kimi.d.ts +0 -1
- package/dist/agents/kimi.js +0 -1
- package/dist/agents/kiro.d.ts +0 -1
- package/dist/agents/kiro.js +0 -1
- package/dist/agents/openclaw.d.ts +0 -1
- package/dist/agents/openclaw.js +0 -1
- package/dist/agents/opencode.d.ts +0 -1
- package/dist/agents/opencode.js +0 -1
- package/dist/agents/qoder.d.ts +0 -1
- package/dist/agents/qoder.js +0 -1
- package/dist/agents/qwen.d.ts +0 -1
- package/dist/agents/qwen.js +0 -1
- package/dist/agents/shared-prompt.d.ts +0 -1
- package/dist/agents/shared-prompt.js +0 -1
- package/dist/client-store.d.ts +0 -1
- package/dist/client-store.js +0 -1
- package/dist/commands/clients.d.ts +0 -1
- package/dist/commands/clients.js +0 -1
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +0 -1
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/pair.d.ts +0 -1
- package/dist/commands/pair.js +0 -1
- package/dist/commands/restart.d.ts +0 -1
- package/dist/commands/restart.js +0 -1
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +19 -3
- package/dist/commands/serve.d.ts +0 -1
- package/dist/commands/serve.js +0 -1
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/event-queues.d.ts +0 -1
- package/dist/event-queues.js +0 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/linked-device.d.ts +0 -1
- package/dist/linked-device.js +0 -1
- package/dist/mcp-handler.d.ts +0 -1
- package/dist/mcp-handler.js +0 -1
- package/dist/mcp-tools.d.ts +0 -1
- package/dist/mcp-tools.js +0 -1
- package/dist/nats-client.d.ts +0 -1
- package/dist/nats-client.js +0 -1
- package/dist/network.d.ts +0 -1
- package/dist/network.js +0 -1
- package/dist/notification-store.d.ts +0 -1
- package/dist/notification-store.js +0 -1
- package/dist/pending-requests.d.ts +0 -1
- package/dist/pending-requests.js +0 -1
- package/dist/platform/index.d.ts +0 -1
- package/dist/platform/index.js +0 -1
- package/dist/platform/linux.d.ts +0 -1
- package/dist/platform/linux.js +0 -1
- package/dist/platform/macos.d.ts +0 -1
- package/dist/platform/macos.js +0 -1
- package/dist/platform/platform.d.ts +0 -1
- package/dist/platform/platform.js +0 -1
- package/dist/platform/windows.d.ts +0 -1
- package/dist/platform/windows.js +0 -1
- package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
- package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
- package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
- package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/rpc-handler.d.ts +0 -1
- package/dist/rpc-handler.js +0 -1
- package/dist/sms-store.d.ts +0 -1
- package/dist/sms-store.js +0 -1
- package/dist/spawn-command.d.ts +0 -1
- package/dist/spawn-command.js +0 -1
- package/dist/task.d.ts +0 -1
- package/dist/task.js +0 -1
- package/dist/transports/http-transport.d.ts +0 -1
- package/dist/transports/http-transport.js +0 -1
- package/dist/transports/nats-transport.d.ts +0 -1
- package/dist/transports/nats-transport.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/update-checker.d.ts +0 -1
- package/dist/update-checker.js +0 -1
- package/package.json +11 -1
- package/.github/workflows/ci.yml +0 -16
- package/.github/workflows/publish.yml +0 -37
- package/CLAUDE.md +0 -22
- package/dist/pwa/apple-touch-icon.png +0 -0
- package/dist/pwa/manifest.webmanifest +0 -1
- package/dist/pwa/pwa-192x192.png +0 -0
- package/dist/pwa/pwa-512x512.png +0 -0
- package/dist/pwa/registerSW.js +0 -1
- package/dist/pwa/service-worker.js +0 -2
- package/palmier-server/.github/workflows/ci.yml +0 -21
- package/palmier-server/.github/workflows/deploy.yml +0 -38
- package/palmier-server/CLAUDE.md +0 -17
- package/palmier-server/PRODUCTION.md +0 -358
- package/palmier-server/README.md +0 -231
- package/palmier-server/nats.conf +0 -19
- package/palmier-server/package.json +0 -15
- package/palmier-server/pnpm-lock.yaml +0 -7639
- package/palmier-server/pnpm-workspace.yaml +0 -3
- package/palmier-server/pwa/index.html +0 -16
- package/palmier-server/pwa/logo/logo_20260421.png +0 -0
- package/palmier-server/pwa/package.json +0 -34
- package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
- package/palmier-server/pwa/public/favicon.ico +0 -0
- package/palmier-server/pwa/public/pwa-192x192.png +0 -0
- package/palmier-server/pwa/public/pwa-512x512.png +0 -0
- package/palmier-server/pwa/src/App.css +0 -3012
- package/palmier-server/pwa/src/App.tsx +0 -59
- package/palmier-server/pwa/src/agentLabels.ts +0 -11
- package/palmier-server/pwa/src/api.ts +0 -67
- package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
- package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
- package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
- package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
- package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
- package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
- package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
- package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
- package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
- package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
- package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
- package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
- package/palmier-server/pwa/src/constants.ts +0 -2
- package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
- package/palmier-server/pwa/src/draftGuard.ts +0 -24
- package/palmier-server/pwa/src/formatTime.ts +0 -44
- package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
- package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
- package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
- package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
- package/palmier-server/pwa/src/main.tsx +0 -14
- package/palmier-server/pwa/src/native/Device.ts +0 -49
- package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
- package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
- package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
- package/palmier-server/pwa/src/service-worker.ts +0 -142
- package/palmier-server/pwa/src/types.ts +0 -75
- package/palmier-server/pwa/src/vite-env.d.ts +0 -11
- package/palmier-server/pwa/tsconfig.json +0 -21
- package/palmier-server/pwa/tsconfig.node.json +0 -19
- package/palmier-server/pwa/vite.config.ts +0 -47
- package/palmier-server/server/.env.example +0 -20
- package/palmier-server/server/package.json +0 -36
- package/palmier-server/server/src/db.ts +0 -44
- package/palmier-server/server/src/fcm.ts +0 -74
- package/palmier-server/server/src/index.ts +0 -688
- package/palmier-server/server/src/nats-jwt.ts +0 -299
- package/palmier-server/server/src/nats-setup.ts +0 -48
- package/palmier-server/server/src/nats.ts +0 -33
- package/palmier-server/server/src/notify.ts +0 -34
- package/palmier-server/server/src/push.ts +0 -68
- package/palmier-server/server/src/routes/device.ts +0 -224
- package/palmier-server/server/src/routes/fcm.ts +0 -64
- package/palmier-server/server/src/routes/hosts.ts +0 -56
- package/palmier-server/server/src/routes/push.ts +0 -101
- package/palmier-server/server/tsconfig.json +0 -20
- package/palmier-server/spec.md +0 -533
- package/src/agents/agent-instructions.md +0 -28
- package/src/agents/agent.ts +0 -114
- package/src/agents/aider.ts +0 -35
- package/src/agents/claude.ts +0 -39
- package/src/agents/cline.ts +0 -35
- package/src/agents/codex.ts +0 -40
- package/src/agents/copilot.ts +0 -37
- package/src/agents/cursor.ts +0 -36
- package/src/agents/deepagents.ts +0 -36
- package/src/agents/droid.ts +0 -35
- package/src/agents/gemini.ts +0 -43
- package/src/agents/goose.ts +0 -33
- package/src/agents/hermes.ts +0 -36
- package/src/agents/kimi.ts +0 -35
- package/src/agents/kiro.ts +0 -36
- package/src/agents/openclaw.ts +0 -29
- package/src/agents/opencode.ts +0 -36
- package/src/agents/qoder.ts +0 -36
- package/src/agents/qwen.ts +0 -32
- package/src/agents/shared-prompt.ts +0 -30
- package/src/client-store.ts +0 -68
- package/src/commands/clients.ts +0 -29
- package/src/commands/info.ts +0 -29
- package/src/commands/init.ts +0 -165
- package/src/commands/pair.ts +0 -137
- package/src/commands/restart.ts +0 -6
- package/src/commands/run.ts +0 -608
- package/src/commands/serve.ts +0 -211
- package/src/commands/uninstall.ts +0 -9
- package/src/config.ts +0 -36
- package/src/cross-spawn.d.ts +0 -5
- package/src/event-queues.ts +0 -41
- package/src/events.ts +0 -29
- package/src/index.ts +0 -111
- package/src/linked-device.ts +0 -52
- package/src/mcp-handler.ts +0 -200
- package/src/mcp-tools.ts +0 -839
- package/src/nats-client.ts +0 -19
- package/src/network.ts +0 -96
- package/src/notification-store.ts +0 -30
- package/src/pending-requests.ts +0 -73
- package/src/platform/index.ts +0 -20
- package/src/platform/linux.ts +0 -296
- package/src/platform/macos.ts +0 -329
- package/src/platform/platform.ts +0 -31
- package/src/platform/windows.ts +0 -299
- package/src/rpc-handler.ts +0 -691
- package/src/sms-store.ts +0 -28
- package/src/spawn-command.ts +0 -123
- package/src/task.ts +0 -343
- package/src/transports/http-transport.ts +0 -478
- package/src/transports/nats-transport.ts +0 -76
- package/src/types.ts +0 -89
- package/src/update-checker.ts +0 -40
- package/test/agent-instructions.test.ts +0 -209
- package/test/agent-output-parsing.test.ts +0 -74
- package/test/linux-cron.test.ts +0 -41
- package/test/macos-plist.test.ts +0 -112
- package/test/notification-store.test.ts +0 -57
- package/test/pairing.test.ts +0 -35
- package/test/result-state.test.ts +0 -110
- package/test/task-parsing.test.ts +0 -82
- package/test/taskrun-messages.test.ts +0 -224
- package/test/tsconfig.json +0 -9
- package/test/windows-xml.test.ts +0 -89
- package/tsconfig.json +0 -19
package/palmier-server/spec.md
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
# Project Palmier: Architecture & Implementation Plan
|
|
2
|
-
|
|
3
|
-
Palmier is a platform enabling end-users to remotely schedule, manage, and execute autonomous tasks on their host machines via a Progressive Web App (PWA). It acts as a secure, distributed bridge between a user's mobile device/browser and a local host daemon running on their hardware.
|
|
4
|
-
|
|
5
|
-
## 1. System Architecture & Components
|
|
6
|
-
|
|
7
|
-
The system relies on a publish-subscribe model utilizing NATS to bypass firewall restrictions and enable real-time, bi-directional communication. All infrastructure runs on a single VPS (DigitalOcean), with automated CI/CD via GitHub Actions.
|
|
8
|
-
|
|
9
|
-
### 1.1 Platform Support
|
|
10
|
-
|
|
11
|
-
The host supports **Linux** (systemd), **macOS** (launchd user LaunchAgent), and **Windows** (Task Scheduler for both daemon and task triggers). OS-specific details in this spec use Linux examples unless noted otherwise; the `PlatformService` abstraction handles cross-platform differences. macOS LaunchAgents run only while the user is logged into the GUI session — there is no user-level equivalent of Linux's `loginctl enable-linger`, so scheduled tasks stay dormant after reboot until the user logs in.
|
|
12
|
-
|
|
13
|
-
### 1.2 Components
|
|
14
|
-
|
|
15
|
-
* **Host Binary (Node.js):** Runs persistently on the user's host machine as a NATS + HTTP RPC handler. Manages file system operations (task CRUD), OS-level scheduling (systemd), and task generation. Provides a CLI with commands: `palmier init` (provisioning), `palmier pair` (generate pairing code for device pairing), `palmier clients` (manage client tokens), `palmier run <task-id>` (executes a task via the configured agent tool), `palmier uninstall` (stop daemon and remove all scheduled tasks), and `palmier serve` (persistent RPC handler, default command). The `serve` process always starts an HTTP server bound to `0.0.0.0` alongside the NATS transport. The web UI, `/pair`, and `/events` are gated to loopback callers; `/rpc/<method>` (bearer-auth) and `/health` (public) are reachable from the LAN to support the Capacitor app's auto-LAN mode. Exposes a localhost-only MCP server at `/mcp` (streamable HTTP transport) with tools: `notify`, `request-input`, `request-confirmation`, `device-geolocation`, `read-contacts`, `create-contact`, `read-calendar`, `create-calendar-event`, `send-sms-message`, `send-alarm`, `read-battery`, `set-ringer-mode`; and resources: `notifications://device` (device notifications), `sms-messages://device` (SMS messages). Tools and resources are auto-generated as REST endpoints from shared registries (`ToolDefinition[]`, `ResourceDefinition[]`) — zero duplication. Tool REST endpoints are POST with `taskId` query param; resource REST endpoints are GET. `/request-permission` remains a separate endpoint (not part of the MCP registries). MCP resources support subscriptions — clients call `resources/subscribe` and the server holds the POST response open as an SSE stream, pushing `notifications/resources/updated` notifications when the resource data changes. MCP sessions track agent names from `initialize` clientInfo for logging and UI display. `palmier run` is a short-lived process invoked by systemd. Task execution is abstracted through an `AgentTool` interface (`src/agents/agent.ts`) so different AI CLI tools can be supported — each agent implements `getPromptCommandLine()`, `getTaskRunCommandLine()`, and `init()`. The task's `agent` field (e.g., `"claude"`) selects which agent is used.
|
|
16
|
-
|
|
17
|
-
* **Web Server (Node.js):** Serves the PWA assets (React) via `app.palmier.me` (Cloudflare proxied), manages Web Push VAPID keys, and provides host registration. Uses **PostgreSQL** for persistent storage (host registrations, push subscriptions, FCM tokens). Connects to NATS via TCP to subscribe to `host-event.>` for sending push notifications (confirmations, dismissals, completion/failure). For `POST /api/push/respond` (confirmation responses via push notification action buttons), the Web Server forwards the response to the host via the `task.user_input` NATS RPC. Subscribes to `host.*.push.send` NATS subjects to relay push notification requests from the host CLI. Subscribes to `host.*.fcm.geolocation` to relay device geolocation requests via FCM. Subscribes to `host.*.fcm.contacts`, `host.*.fcm.calendar`, `host.*.fcm.sms`, `host.*.fcm.alarm`, `host.*.fcm.battery`, and `host.*.fcm.ringer` to relay device capability requests via FCM. Provides HTTP endpoints for Android to post responses back (`/api/device/contacts-response`, `/api/device/calendar-response`, `/api/device/sms-response`, `/api/device/alarm-response`, `/api/device/battery-response`, `/api/device/ringer-response`). Co-located with the NATS server on the same machine.
|
|
18
|
-
|
|
19
|
-
* **Android App (Capacitor):** Native Android wrapper for the PWA. Provides FCM push messaging for receiving data messages in the background, `FusedLocationProviderClient` for GPS access, `NotificationListenerService` for capturing device notifications, `BroadcastReceiver` for incoming SMS, and handlers for contacts, calendar, alarms, battery, and ringer mode. All device tools work while the app is in the background via FCM data messages. When a request arrives via FCM, the appropriate handler executes the action and POSTs the result back to the Web Server. Device capabilities and their permissions:
|
|
20
|
-
- **Notifications**: `NotificationListenerService` — requires notification listener access (system settings toggle)
|
|
21
|
-
- **SMS Read**: `SmsBroadcastReceiver` — requires `RECEIVE_SMS` runtime permission
|
|
22
|
-
- **SMS Send**: `SmsHandler` — requires `SEND_SMS` runtime permission
|
|
23
|
-
- **Contacts**: `ContactsHandler` — requires `READ_CONTACTS` + `WRITE_CONTACTS` runtime permissions
|
|
24
|
-
- **Calendar**: `CalendarHandler` — requires `READ_CALENDAR` + `WRITE_CALENDAR` runtime permissions
|
|
25
|
-
- **Geolocation**: `GeolocationForegroundService` — requires `ACCESS_FINE_LOCATION` runtime permission
|
|
26
|
-
- **Alarm**: `AlarmHandler` + `AlarmActivity` — triggers a full-screen alarm popup with looping ringtone; requires `USE_FULL_SCREEN_INTENT` (Android 14+ requires user grant in settings)
|
|
27
|
-
- **Battery**: `BatteryHandler` — no permission required
|
|
28
|
-
- **Ringer mode**: `RingerHandler` — requires Do Not Disturb access (system settings toggle)
|
|
29
|
-
|
|
30
|
-
The notification listener excludes Palmier's own task notifications (channel `palmier_tasks`) and the default SMS app's notifications (to avoid duplicates with the SMS resource). Each capability can be toggled on/off from the drawer when the device is the host's **linked device** (the one device the host talks to for device capabilities); toggles are backed by SharedPreferences flags that handlers check before executing. See the `palmier-android` repo.
|
|
31
|
-
|
|
32
|
-
* **PWA (React):** The user-facing frontend, primarily targeting mobile devices. Connects to the NATS server via **WebSockets** at `nats.palmier.me` (DNS only, not Cloudflare proxied, to avoid interference with persistent connections). No user accounts — paired hosts are stored in localStorage.
|
|
33
|
-
|
|
34
|
-
* **NATS Server:** The central message broker. Runs in Docker on the same machine as the Web Server.
|
|
35
|
-
|
|
36
|
-
### 1.3 Security & Authentication
|
|
37
|
-
|
|
38
|
-
* **NATS authentication:** The NATS server uses **token-based authentication** with a single shared token. Subject scoping (e.g., `host.<host_id>.>`) is enforced at the application layer.
|
|
39
|
-
|
|
40
|
-
* **Client tokens:** Each PWA device paired with a host receives a unique client token, generated and stored on the host. Client tokens are included in every RPC request and validated by the host before processing. Tokens do not expire and can be revoked via the `palmier clients` CLI.
|
|
41
|
-
|
|
42
|
-
* **Pairing:** Devices pair with hosts using a 6-character alphanumeric pairing code. The code serves as a routing key — the PWA sends the code to NATS subject `pair.<CODE>` or to the host's HTTP `POST /pair` endpoint. The host validates the code and returns a client token. Codes expire after 5 minutes or first successful use.
|
|
43
|
-
|
|
44
|
-
* **Future migration:** Token-based auth can be migrated to full **JWT/NKey Authentication** for finer-grained access control and dynamic credential issuance without restarting the NATS server.
|
|
45
|
-
|
|
46
|
-
### 1.4 Repository Structure
|
|
47
|
-
|
|
48
|
-
The project is split across two repositories:
|
|
49
|
-
|
|
50
|
-
* **`palmier`**: The host binary. A standalone Node.js CLI that runs on the user's machine.
|
|
51
|
-
* **`palmier-server`**: Contains both the Web Server (`server/`) and the PWA (`pwa/`, built with Vite + React). Uses **pnpm** for package management with a pnpm workspace.
|
|
52
|
-
* **`palmier-android`**: Capacitor-based Android wrapper that loads the PWA from `app.palmier.me` in a WebView and exposes native device capabilities via a custom plugin.
|
|
53
|
-
|
|
54
|
-
### 1.5 Android Implementation Details
|
|
55
|
-
|
|
56
|
-
The Android app is a thin native shell over the remotely-hosted PWA. The design decisions below are non-obvious enough that changing them silently would break assumptions the rest of the system makes.
|
|
57
|
-
|
|
58
|
-
**Remote-first WebView.** `capacitor.config.json` sets `server.url` to `https://app.palmier.me`; the WebView fetches the PWA from the cloud on every launch. This means PWA updates ship instantly with no APK rebuild, and release builds run `npx cap sync` only (no PWA build step). Consequences:
|
|
59
|
-
|
|
60
|
-
- **Server mode only.** LAN and Local modes are browser-only. The WebView blocks cleartext `http://<host-ip>:<port>` requests as mixed content, so LAN users must open the PWA from Chrome/Safari directly.
|
|
61
|
-
- **Offline fallback.** When `app.palmier.me` is unreachable, the WebView loads `www/offline.html` (configured via `server.errorPath`), which auto-reloads when connectivity returns.
|
|
62
|
-
- **PWA ships ahead of the APK.** The PWA may reference capabilities or native methods that the installed APK doesn't implement yet. `Device.getCapabilityStatus()` only returns capabilities the APK knows about, so the PWA naturally hides toggles it can't fulfill. Calls to `setCapabilityEnabled` for unknown capabilities resolve with `{ enabled: false, reason: "unsupported" }` rather than throwing.
|
|
63
|
-
|
|
64
|
-
**Unified `Device` Capacitor plugin.** A single plugin (`DevicePlugin.kt`) exposes the entire native surface — FCM token, capability gating (with internal permission orchestration), installed-app enumeration, deep-link events. Methods: `getFcmToken`, `getCapabilityStatus`, `setCapabilityEnabled({capability, enabled})`, `getInstalledApps`, `addListener("deepLink", ...)`. Capabilities: `sms-read`, `sms-send`, `send-email`, `notifications`, `contacts`, `calendar`, `location`, `dnd`, `alarm`.
|
|
65
|
-
|
|
66
|
-
**Capability kill-switch (`CapabilityState`).** Local whitelist persisted as a JSON-array string under `enabledCapabilities` in `CapacitorStorage` SharedPreferences. Native receivers (`SmsBroadcastReceiver`, `DeviceNotificationListenerService`) and all FCM handlers consult `CapabilityState.isEnabled` before acting — a second line of defense beyond the server-side linked-device check. The plugin owns reads and writes through `setCapabilityEnabled` (writes only after permission grant) and prunes the set on every app resume so any permission revoked in system Settings auto-flips the corresponding toggle off. Battery is the one capability without a kill-switch — it's always allowed.
|
|
67
|
-
|
|
68
|
-
**FCM token flow.** The PWA reads the current token on demand via `Device.getFcmToken()`; no cached copy in SharedPreferences. `PalmierFirebaseMessagingService.onNewToken` still re-registers with the relay server itself (using the stored `hostId`) because background token refreshes can fire while the PWA isn't running.
|
|
69
|
-
|
|
70
|
-
**Deep links.** FCM notification taps pass a host-scoped relative path (e.g. `/hosts/:hostId/runs/:taskId/:runId`) via an `Intent` extra named `deepLink`. Including the host in the path ensures the PWA switches to the originating host even if the user was viewing a different one when the notification arrived. `MainActivity.handleDeepLink` forwards the path to `DevicePlugin.emitDeepLink`, which emits a `deepLink` event the PWA's router handles client-side. If the plugin isn't ready yet (intent arrives before `onPostCreate`), `MainActivity` buffers the path and flushes in `onPostCreate`. No external `intent-filter` is registered — Android 11+ `<queries>` entries are declared so `hasEmailClient` and installed-app enumeration work without `QUERY_ALL_PACKAGES`.
|
|
71
|
-
|
|
72
|
-
**Notification listener filtering and debounce.** `DeviceNotificationListenerService` drops notifications from (a) Palmier's own `palmier_tasks` channel to avoid feedback loops and (b) the default SMS app (SMS is captured separately via `SmsBroadcastReceiver`, which arrives before the SMS app's notification). Empty-title+body notifications are skipped. A 2-second debounce per `packageName:title` key dedupes rapid updates; the debounce map is LRU-capped at 200 entries.
|
|
73
|
-
|
|
74
|
-
**SMS capture.** Multi-part SMS arrives as multiple PDUs in a single `SMS_RECEIVED` broadcast. `SmsBroadcastReceiver` groups parts by `displayOriginatingAddress` and concatenates bodies before relaying, otherwise long messages would arrive as fragments.
|
|
75
|
-
|
|
76
|
-
**Alarm capability.** `AlarmHandler` posts a `CATEGORY_ALARM` notification on the DND-bypassing `palmier_alarms` channel with a full-screen intent targeting `AlarmActivity`. `AlarmActivity` extends `AppCompatActivity`, shows over the lock screen (`setShowWhenLocked`/`setTurnScreenOn` on O_MR1+, legacy window flags below), and plays the default alarm ringtone on the alarm audio stream via `RingtoneManager`. Requires `USE_FULL_SCREEN_INTENT`; Android 14+ requires the user to grant it in per-app settings.
|
|
77
|
-
|
|
78
|
-
**Email capability.** FCM `send-email` messages post a "Pending email" notification whose tap launches `EmailActivity` — a translucent `Activity` (not `AppCompatActivity`, so `Theme.Translucent` applies) that builds a `mailto:` URI and starts the email app with `ACTION_SENDTO`. The activity auto-finishes on result, returning the user to the previous screen. `setCapabilityEnabled("send-email", true)` checks for an installed `mailto:` resolver before granting; if none exists it returns `{ enabled: false, reason: "no-email-client" }` so the PWA can prompt the user to install one.
|
|
79
|
-
|
|
80
|
-
**Location capability.** `GeolocationForegroundService` briefly starts as a foreground service (`FOREGROUND_SERVICE_TYPE_LOCATION` on U+) and uses `FusedLocationProviderClient.getCurrentLocation(PRIORITY_HIGH_ACCURACY)` for a single fix before stopping itself. Requires both `ACCESS_FINE_LOCATION` and `ACCESS_BACKGROUND_LOCATION` on Q+; the plugin requests them sequentially.
|
|
81
|
-
|
|
82
|
-
**Releases.** GitHub Actions builds a signed APK and creates a release when a `v*` tag is pushed. The workflow runs `npm ci && npx cap sync` — no PWA build step since the WebView loads remotely.
|
|
83
|
-
|
|
84
|
-
## 2. Host Provisioning & Device Pairing
|
|
85
|
-
|
|
86
|
-
### 2.1 Host Provisioning
|
|
87
|
-
|
|
88
|
-
Each host machine is provisioned via `palmier init`, an interactive wizard that registers the host with the Palmier server.
|
|
89
|
-
|
|
90
|
-
`palmier init` is an interactive wizard that:
|
|
91
|
-
|
|
92
|
-
1. Detects installed agent CLIs.
|
|
93
|
-
2. Asks which HTTP port to use (default 7256).
|
|
94
|
-
3. Detects the OS default network interface (used as the source for the host's LAN URL in `host.info` responses).
|
|
95
|
-
4. Shows a summary of task storage directory, local access URL, detected agents, and any existing tasks to recover. Asks for confirmation before proceeding.
|
|
96
|
-
5. Registers with the Palmier server via `POST <url>/api/hosts/register` — server returns `{ hostId, natsUrl, natsWsUrl, natsJwt, natsNkeySeed }`.
|
|
97
|
-
6. Saves config to `~/.config/palmier/host.json` (includes `httpPort`, `defaultInterface`, NATS credentials).
|
|
98
|
-
7. Installs a systemd user service (Linux), user LaunchAgent (macOS), or Task Scheduler entry (Windows) and auto-enters pair mode.
|
|
99
|
-
|
|
100
|
-
The daemon automatically recovers existing tasks by reinstalling their system timers on startup.
|
|
101
|
-
|
|
102
|
-
`defaultInterface` is captured once at `init` time. On each `host.info` RPC, the host re-reads the current IPv4 of that interface (`getInterfaceIpv4(config.defaultInterface)`) so DHCP-assigned IP changes on the same adapter propagate to clients without a re-pair. Users should re-run `palmier init` if they physically switch adapters (e.g., Ethernet ↔ WiFi, adding a new tethered interface).
|
|
103
|
-
|
|
104
|
-
The `serve` daemon always starts an HTTP server bound to `0.0.0.0:<port>`. Two access modes are available:
|
|
105
|
-
|
|
106
|
-
**Local mode** (always available, loopback only):
|
|
107
|
-
- The PWA is accessible at `http://localhost:<port>` without pairing or internet. The PWA is bundled with the host package. The serve daemon injects `window.__PALMIER_SERVE__=true` into the HTML; the PWA detects this and auto-connects. Web UI assets, `/pair`, and `/events` are gated to loopback callers (`127.0.0.1`/`::1`); non-loopback requests get 404.
|
|
108
|
-
|
|
109
|
-
**Server mode** (NATS cloud relay):
|
|
110
|
-
- Communication is relayed through the Palmier cloud server via NATS. PWA is accessed at `https://app.palmier.me`. Enables push notifications and remote access.
|
|
111
|
-
|
|
112
|
-
**Auto-LAN** (transparent perf optimization on top of Server mode):
|
|
113
|
-
- The host's HTTP server exposes `/rpc/<method>` (bearer-auth) and `/health` (public) on all interfaces, so other devices on the LAN can reach them. The native Capacitor app probes `${lanUrl}/health` (URL captured at pair time) and routes RPC over direct HTTP when reachable, falling back to NATS otherwise. Events stay on NATS regardless. Browser PWAs cannot use this path due to Private Network Access / mixed-content rules.
|
|
114
|
-
|
|
115
|
-
### 2.2 Device Pairing
|
|
116
|
-
|
|
117
|
-
Local access (`http://localhost:<port>`) requires no pairing — the PWA auto-connects with a placeholder host ID.
|
|
118
|
-
|
|
119
|
-
For server-mode pairing, `palmier pair` generates a 6-character pairing code from the charset `ABCDEFGHJKMNPQRSTUVWXYZ23456789` (excludes ambiguous O/0/I/1/L) and listens on both NATS (relay) and HTTP (loopback only) in parallel:
|
|
120
|
-
|
|
121
|
-
**Server pairing (NATS, primary path):**
|
|
122
|
-
1. Host subscribes to `pair.<CODE>` on NATS with a 5-minute timeout.
|
|
123
|
-
2. User enters the code in the PWA at `https://app.palmier.me`.
|
|
124
|
-
3. Host validates the code, generates a client token via `addClient()`, and responds with `{ hostId, clientToken, directUrl, hostName }`. `directUrl` is the host's LAN URL (`http://<lan-ip>:<port>`) — stored on the device as `lanUrl` and probed by the native app for auto-LAN.
|
|
125
|
-
|
|
126
|
-
**Local pairing (HTTP, loopback only):**
|
|
127
|
-
1. Host registers the code with the serve daemon via `POST /pair-register` (loopback-gated).
|
|
128
|
-
2. User opens `http://localhost:<port>` on the host machine and enters the code.
|
|
129
|
-
3. PWA posts `POST /pair` (loopback-gated) with `{ code }` and gets the same payload as above. The PWA treats `directUrl = window.location.origin` (loopback), and stores nothing as `lanUrl`.
|
|
130
|
-
|
|
131
|
-
The PWA stores the paired host in localStorage and navigates to the dashboard. Codes expire after 5 minutes or first successful use.
|
|
132
|
-
|
|
133
|
-
### 2.3 Client Management
|
|
134
|
-
|
|
135
|
-
Client tokens are stored on the host in `~/.config/palmier/clients.json`. Each token is a 32-byte hex string.
|
|
136
|
-
|
|
137
|
-
* `palmier clients list` — shows tokens (truncated), labels, creation dates
|
|
138
|
-
* `palmier clients revoke <token>` — removes a client token
|
|
139
|
-
* `palmier clients revoke-all` — clears all clients
|
|
140
|
-
|
|
141
|
-
If no clients exist, the host skips client validation (backward compatibility for unpaired hosts).
|
|
142
|
-
|
|
143
|
-
### 2.3.1 Linked Device
|
|
144
|
-
|
|
145
|
-
Each host tracks a single **linked device** — the one paired device responsible for answering device capability requests (SMS, contacts, calendar, location, alarm, ringer, email, battery). The host stores only `{ clientToken, fcmToken }` in `~/.config/palmier/linked-device.json`; it has no knowledge of which capabilities are actually enabled on the device. That set lives in Android SharedPreferences on the linked device itself and is consulted by the FCM handlers as a local kill-switch.
|
|
146
|
-
|
|
147
|
-
- **Opt-in at pair time.** The PWA shows a "Link the host to this device" checkbox during pairing (native only, default on). If checked, the pair flow continues to a setup step that calls `device.link` with the FCM token. On the very first host pair (when the device has no other paired hosts), that step also shows a one-time "Device Capabilities" screen with all toggles default OFF; the user opts into each one, and Finish writes the set to SharedPreferences. On subsequent host pairs the capability screen is skipped — the device-wide enabled set is host-agnostic and set once.
|
|
148
|
-
- **Reassignment.** Any paired device can take over as the linked device from the drawer's "Link the host to this device" button. This displaces the previous linked device (its drawer toggles go dark).
|
|
149
|
-
- **Loss.** If the linked device is unpaired (via `clients.revoke_self` or CLI `palmier clients revoke`), `linked-device.json` is cleared and capability tools return "No linked device configured" until the user picks a new one.
|
|
150
|
-
- **Routing.** MCP capability tools look up the linked device once per invocation and publish FCM to its token (via `host.<host_id>.fcm.<capability>` relayed by the server). Non-linked devices aren't woken and don't receive capability FCMs.
|
|
151
|
-
|
|
152
|
-
Battery reads don't have a Settings toggle — capability is always on — but still route to the linked device (which is where the Android handler runs).
|
|
153
|
-
|
|
154
|
-
### 2.4 NATS Communication
|
|
155
|
-
|
|
156
|
-
All communication is scoped per host. **Request-reply** is used for RPC-style calls (task CRUD, status queries) — the PWA publishes a request and receives a response on an auto-generated inbox, eliminating the need for separate response subjects.
|
|
157
|
-
|
|
158
|
-
The **RPC method is derived from the NATS subject**, not the message body. The host subscribes to `host.<host_id>.rpc.>` and extracts the method by splitting the subject at `rpc.` (e.g., `...rpc.task.create` → `task.create`). The message body contains the request parameters as JSON, including the `clientToken` field for authentication.
|
|
159
|
-
|
|
160
|
-
**Auto-LAN (native Capacitor app only).** When the device probes the host's LAN URL successfully, it routes the same RPC methods over direct HTTP (`POST <lanUrl>/rpc/<method>` with `Authorization: Bearer <clientToken>`) instead of through NATS. Identical request/response payloads, lower latency. Browser PWA always uses NATS. Events (`host-event.*`) always flow through NATS regardless of mode.
|
|
161
|
-
|
|
162
|
-
**Host RPC endpoints** (request-reply, subject: `host.<host_id>.rpc.<method>`):
|
|
163
|
-
|
|
164
|
-
| Method | Params | Description |
|
|
165
|
-
|---|---|---|
|
|
166
|
-
| `host.info` | *(none)* | Bootstrap metadata fetched once per connection. Returns `{ agents, version, host_platform, linked_client_token, pending_prompts, lan_url }`. `linked_client_token` is the clientToken of the device currently linked to the host (or `null`); device capability requests route to that device's FCM token. `pending_prompts` is an array of prompts already waiting when the PWA reconnects (each `{ key, type, params?, meta? }`), so modals can render without replaying events. |
|
|
167
|
-
| `device.link` | `fcmToken` | Mark the calling client as the host's linked device. Stores `{ clientToken, fcmToken }` in `~/.config/palmier/linked-device.json` and replaces any existing linked device. Device capability tools (`device-geolocation`, `read-contacts`, `send-sms-message`, etc.) route FCM to this device. |
|
|
168
|
-
| `device.unlink` | *(none)* | Clear the linked device if the caller is currently linked. No-op otherwise. |
|
|
169
|
-
| `clients.revoke_self` | *(none)* | Revoke the calling client's token. Also clears the linked device when the caller was the linked one. Called by the PWA when the user unpairs the currently-active host. |
|
|
170
|
-
| `task.list` | *(none)* | List all tasks with frontmatter, created_at, and current status. |
|
|
171
|
-
| `task.get` | `id` | Get a single task with frontmatter and current status. |
|
|
172
|
-
| `task.create` | `user_prompt`, `agent`, `schedule_type?`, `schedule_values?`, `schedule_enabled?`, `requires_confirmation?`, `yolo_mode?`, `foreground_mode?`, `command?` | Create a new task with auto-generated name (30s timeout for prompts > 50 chars), install system timers if a schedule is present. |
|
|
173
|
-
| `task.update` | `id`, `user_prompt?`, `agent?`, `schedule_type?`, `schedule_values?`, `schedule_enabled?`, `requires_confirmation?`, `yolo_mode?`, `foreground_mode?`, `command?` | Update an existing task. Regenerates name if `user_prompt` or `agent` changed. Reinstall timers as needed. Pass `null` for `schedule_type` or `schedule_values` to clear them. |
|
|
174
|
-
| `task.delete` | `id` | Delete a task and its systemd timers |
|
|
175
|
-
| `task.run` | `id` | Start a task via system scheduler (`systemctl --user start` / `schtasks /run`) |
|
|
176
|
-
| `task.abort` | `id` | Stop a running task via system scheduler (`systemctl --user stop` / `schtasks /end`) |
|
|
177
|
-
| `task.user_input` | `id`, `value` | Respond to a pending request (confirmation, permission, or input). Resolves an in-memory pending request held by the serve daemon's HTTP endpoint. |
|
|
178
|
-
| `task.status` | `id` | Read current status from `status.json`, enriched with pending request state from in-memory registry |
|
|
179
|
-
| `task.result` | `id`, `run_id` | Read a run's TASKRUN.md conversational messages and metadata. Returns `{ messages: ConversationMessage[], task_name, agent, running_state, start_time, end_time }`. |
|
|
180
|
-
| `task.followup` | `id`, `run_id`, `message` | Send a follow-up message to an existing run. Appends user message + started status, invokes agent inline, appends result. |
|
|
181
|
-
| `task.stop_followup` | `id`, `run_id` | Stop an active follow-up. Kills the agent child process and appends a stopped status. |
|
|
182
|
-
| `task.reports` | `id`, `run_id`, `report_files` | Read one or more report files from the run directory. Supports `.md`, `.txt`, and image files (`.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`). Text files return `{ file, content }`, images return `{ file, data_url }` (base64). |
|
|
183
|
-
| `task.logs` | `id` | Read recent journalctl logs for the task's systemd service |
|
|
184
|
-
| `taskrun.list` | `offset?`, `limit?`, `task_id?` | Read paginated run history from `history.jsonl` (default limit: 10). Optional `task_id` filter. Returns `{ entries, total }` where each entry is enriched with TASKRUN.md metadata. |
|
|
185
|
-
| `taskrun.delete` | `task_id`, `run_id` | Delete a run and its directory. |
|
|
186
|
-
|
|
187
|
-
All RPC requests include a `clientToken` field in the JSON payload. The host validates the token before processing the request.
|
|
188
|
-
|
|
189
|
-
**Host CLI → Web Server** (request-reply):
|
|
190
|
-
|
|
191
|
-
| Subject | Payload | Description |
|
|
192
|
-
|---|---|---|
|
|
193
|
-
| `host.<host_id>.push.send` | `{ hostId, title, body }` | Send push notification to all paired devices (15s timeout) |
|
|
194
|
-
|
|
195
|
-
**Pub/Sub** (fire-and-forget, published by `palmier run`):
|
|
196
|
-
|
|
197
|
-
| Subject | Payload | Subscriber | Description |
|
|
198
|
-
|---|---|---|---|
|
|
199
|
-
| `host-event.<host_id>.<task_id>` | `{ event_type, ... }` | PWA, Web Server | Unified event subject. `event_type` is one of `"running-state"`, `"confirm-request"`, `"confirm-resolved"`, `"permission-request"`, `"permission-resolved"`, or `"report-generated"`. Payloads: running-state includes `{ running_state, name? }`, confirm-request includes `{ host_id }`, confirm-resolved includes `{ host_id, status }`, report-generated includes `{ name?, run_id, report_files }`. Same payload shape is used for both NATS and HTTP SSE. |
|
|
200
|
-
| `host.<host_id>.device.notifications` | `{ id, packageName, appName, title, text, timestamp }` | Host | Device notification from Android. Published by Web Server when it receives `POST /api/device/notifications` from the Android app. Host stores in bounded in-memory collection and exposes via MCP resource. |
|
|
201
|
-
| `host.<host_id>.device.sms` | `{ id, sender, body, timestamp }` | Host | Incoming SMS from Android. Published by Web Server when it receives `POST /api/device/sms` from the Android app. Host stores in bounded in-memory collection and exposes via MCP resource. |
|
|
202
|
-
|
|
203
|
-
### 2.5 Push Subscription Management
|
|
204
|
-
|
|
205
|
-
Push notification subscriptions are stored in PostgreSQL, keyed by host ID and device endpoint. A host may have multiple paired devices (e.g., phone + tablet). All push notifications for a host are delivered to **all registered devices** for that host.
|
|
206
|
-
|
|
207
|
-
## 3. Data Model: The Task Directory
|
|
208
|
-
|
|
209
|
-
All tasks are stored locally on the Host machine under a `tasks/` directory relative to the project root (the directory where `palmier init` was run).
|
|
210
|
-
|
|
211
|
-
### Structure
|
|
212
|
-
|
|
213
|
-
```text
|
|
214
|
-
history.jsonl # Project-level run history index (append-only JSONL: { task_id, run_id })
|
|
215
|
-
tasks/
|
|
216
|
-
└── <task-id>/
|
|
217
|
-
├── TASK.md # Current task definition (YAML frontmatter)
|
|
218
|
-
├── status.json # Latest execution status (running_state, time_stamp, pid)
|
|
219
|
-
└── <timestamp>/ # Run directory (one per run, isolated per agent session)
|
|
220
|
-
├── TASKRUN.md # Conversational thread (frontmatter + message entries)
|
|
221
|
-
└── ... # Agent session files, reports, artifacts
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### `TASKRUN.md` Format
|
|
225
|
-
|
|
226
|
-
TASKRUN files use a conversational format with YAML frontmatter and HTML comment delimiters separating messages:
|
|
227
|
-
|
|
228
|
-
```markdown
|
|
229
|
-
---
|
|
230
|
-
task_name: My Task
|
|
231
|
-
agent: claude
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
<!-- palmier:message role="status" time="1712282400000" type="started" -->
|
|
235
|
-
|
|
236
|
-
<!-- palmier:message role="user" time="1712282400100" -->
|
|
237
|
-
|
|
238
|
-
Run the audit and generate a report.
|
|
239
|
-
|
|
240
|
-
<!-- palmier:message role="assistant" time="1712282430000" attachments="report.md" -->
|
|
241
|
-
|
|
242
|
-
Audit complete. Generated report.
|
|
243
|
-
|
|
244
|
-
<!-- palmier:message role="status" time="1712282450000" type="finished" -->
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**Frontmatter** contains `task_name` and `agent` (snapshotted at run creation time). Timing and state are derived from status messages:
|
|
248
|
-
- **running_state**: derived from the last status message. `"started"` with no prior terminal = task running. `"started"` with prior terminal = follow-up running (`"followup"`). Terminal types: `"finished"`, `"failed"`, `"aborted"`, `"stopped"`.
|
|
249
|
-
- **start_time**: `time` of the first `type="started"` status message
|
|
250
|
-
- **end_time**: `time` of the last terminal status message
|
|
251
|
-
|
|
252
|
-
**Message delimiter:** `<!-- palmier:message role="{role}" time="{ms}" [type="{type}"] [attachments="{files}"] -->`
|
|
253
|
-
|
|
254
|
-
- **role**: `"assistant"` (agent output), `"user"` (user input/permissions/confirmations), or `"status"` (lifecycle events)
|
|
255
|
-
- **time**: Unix timestamp in milliseconds
|
|
256
|
-
- **type** (optional): `"input"`, `"permission"`, `"confirmation"`, `"started"`, `"finished"`, `"failed"`, `"aborted"`, `"stopped"`
|
|
257
|
-
- **attachments** (optional): comma-separated report filenames
|
|
258
|
-
|
|
259
|
-
Messages are appended incrementally during execution.
|
|
260
|
-
|
|
261
|
-
### `TASK.md` Schema
|
|
262
|
-
|
|
263
|
-
```yaml
|
|
264
|
-
---
|
|
265
|
-
id: "uuid-v4"
|
|
266
|
-
user_prompt: "Run a system audit and summarize large files..."
|
|
267
|
-
agent: "claude"
|
|
268
|
-
schedule_type: "crons"
|
|
269
|
-
schedule_values:
|
|
270
|
-
- "0 9 * * 1"
|
|
271
|
-
schedule_enabled: true
|
|
272
|
-
requires_confirmation: true
|
|
273
|
-
---
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
`schedule_type` is one of:
|
|
277
|
-
|
|
278
|
-
* `"crons"` — `schedule_values` holds cron expressions.
|
|
279
|
-
* `"specific_times"` — `schedule_values` holds local datetime strings (e.g. `"2026-04-20T09:00"`).
|
|
280
|
-
* `"on_new_notification"` — fires once per new Android notification relayed over NATS. Optional `schedule_values` holds a single-entry packageName filter; empty/unset matches any app.
|
|
281
|
-
* `"on_new_sms"` — fires once per new SMS relayed over NATS. Optional `schedule_values` holds a single-entry sender filter (phone number or alphanumeric shortcode); compared after normalizing away spaces, dashes, parens, plus sign, and case. Empty/unset matches any sender.
|
|
282
|
-
|
|
283
|
-
For `crons` / `specific_times` the schedule is installed as an OS timer (systemd / Task Scheduler). For the `on_new_*` types the `palmier run` process subscribes directly to the corresponding NATS subject (`host.<hostId>.device.notifications` or `...device.sms`), drains events through a bounded FIFO queue, and invokes the agent once per event with the event payload spliced into the user prompt (mirroring command-triggered mode). These event-driven tasks are not started on save — they launch via `task.run` (the PWA auto-runs them on create, matching command-triggered UX).
|
|
284
|
-
|
|
285
|
-
The `name` field is auto-generated by spawning the configured agent CLI with a short prompt (for prompts > 50 chars). For shorter prompts, the `user_prompt` is used directly as the name.
|
|
286
|
-
|
|
287
|
-
The `agent` field stores the agent name (e.g., `"claude"`, `"codex"`). The corresponding `AgentTool` implementation is responsible for constructing the full command and arguments at execution time.
|
|
288
|
-
|
|
289
|
-
The optional `command` field stores a shell command for command-triggered tasks. When set, the task runs in command-triggered mode: the command is spawned with `shell: true`, and each line of its stdout triggers a separate agent invocation with `user_prompt + "\n\nProcess this input:\n" + line`.
|
|
290
|
-
|
|
291
|
-
#### Schedule Lifecycle
|
|
292
|
-
|
|
293
|
-
* **`schedule_enabled`:** Controls whether systemd timers are installed for the task's schedule. When `false`, all timers are removed; when toggled back to `true`, timers are reinstalled. Defaults to `true`. The task can still be run manually via "Run Now" regardless of this setting. The "Enable Schedule" checkbox only appears in the UI when the task has a schedule.
|
|
294
|
-
* **`crons` schedules:** Persist indefinitely. The systemd timer remains active until the task is deleted or the schedule is disabled.
|
|
295
|
-
* **`specific_times` schedules:** After a value fires, it is removed from `schedule_values` and its corresponding systemd timer/service files are cleaned up. Once all values have fired the schedule is cleared, and the task remains in the `tasks/` directory as a manual task (can still be executed on-demand via the PWA or CLI, but will not fire automatically again).
|
|
296
|
-
* **`on_new_notification` / `on_new_sms` schedules:** No OS timers are installed. Instead, the serve daemon subscribes to the matching NATS subject (`host.<host_id>.device.notifications` / `.sms`), maintains a per-task in-memory FIFO queue (max 100 entries; overflow drops the oldest), and spawns `palmier run <id>` via the OS scheduler when the task transitions from idle to active (tracked by an `active_run` flag flipped atomically on the empty-pop). The run process drains the queue by calling `POST /task-event/pop?taskId=<id>` on the localhost daemon: each non-empty response is spliced into the user prompt (`user_prompt + "\n\nProcess this new notification:\n" + <raw JSON payload>` or `...new SMS...`) and invoked through the standard agent retry loop. When the endpoint returns `{ empty: true }` the active flag is cleared and the run exits; the next incoming NATS event will start a fresh run. These tasks are never auto-run on save — they launch only in response to NATS events. Requires server mode (NATS).
|
|
297
|
-
|
|
298
|
-
### Task Events
|
|
299
|
-
|
|
300
|
-
Task lifecycle status is persisted to a `status.json` file in the task directory on the host. The file contains `{ running_state, time_stamp, pid }` and is used primarily for crash detection. Interactive request flows (confirmation, permission, input) are handled via held HTTP connections on the serve daemon's in-memory pending request registry. The `running_state` is one of:
|
|
301
|
-
|
|
302
|
-
* **`started`** — task execution has begun (set immediately, before confirmation if applicable).
|
|
303
|
-
* **`finished`** — task completed successfully.
|
|
304
|
-
* **`aborted`** — task was aborted by the user (confirmation denied or manual abort via RPC).
|
|
305
|
-
* **`failed`** — task execution failed (the command exited with a non-zero code).
|
|
306
|
-
|
|
307
|
-
`palmier run` writes `status.json` and publishes a notification on `host-event.<host_id>.<task_id>` (payload: `{ event_type: "running-state", running_state }`) via NATS and HTTP SSE. The `time_stamp` field is UTC time in milliseconds since epoch (`Date.now()`).
|
|
308
|
-
|
|
309
|
-
The `task.list` RPC includes each task's current status (read from `status.json`). The `task.status` RPC returns the status for a single task.
|
|
310
|
-
|
|
311
|
-
The PWA receives initial statuses from `task.list` on load. It subscribes to `host-event.<hostId>.>` for live updates; on each notification it parses the `event_type` field and calls the host's `task.status` RPC to fetch the current status. Task cards display the `user_prompt` as the title (truncated to 2 lines) and a status indicator: a marching dots animation when running (`started`), a red dot for errors (`aborted` or `failed`), a gray dot when the schedule is disabled or absent, and a green dot when idle (no entry or `finished`). When the last run was successful (`finished`), a "View Result" button loads the task's result file in a popup dialog. The `specific_times` date/time picker only allows selecting future dates and times.
|
|
312
|
-
|
|
313
|
-
The Web Server subscribes to `host-event.>` and sends push notifications based on `event_type`: confirmation pushes for `confirm-request`, dismiss pushes for `confirm-resolved`, permission pushes for `permission-request`, dismiss pushes for `permission-resolved`, and report-ready/failure pushes for `report-generated` events.
|
|
314
|
-
|
|
315
|
-
### Logs
|
|
316
|
-
|
|
317
|
-
Task execution logs are managed by systemd's journal. Each task's systemd service unit is tagged with the task ID, allowing logs to be queried with:
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
journalctl --user -u palmier-task-<task-id>.service
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
The host exposes a `task.logs` RPC handler that runs this query and returns recent log lines to the PWA.
|
|
324
|
-
|
|
325
|
-
## 4. UI & Task Management Flow
|
|
326
|
-
|
|
327
|
-
The PWA (React) provides a responsive CRUD interface.
|
|
328
|
-
|
|
329
|
-
The PWA connects to **one host at a time**. A host menu (hamburger drawer) lets the user switch between paired hosts. All hosts are selectable regardless of online status — the PWA does not probe or display host connectivity in the picker.
|
|
330
|
-
|
|
331
|
-
### 4.1 Initialization
|
|
332
|
-
|
|
333
|
-
1. PWA loads. If no hosts are paired, it shows an empty state with a "Pair Host" button.
|
|
334
|
-
|
|
335
|
-
2. If hosts are paired, PWA fetches host-scoped NATS credentials from `GET /api/nats-credentials/<hostId>` (returns `{ natsWsUrl, natsJwt, natsNkeySeed }`) and connects to NATS via WebSocket using JWT auth. The credentials are scoped to the paired host's subjects only.
|
|
336
|
-
|
|
337
|
-
3. PWA sends a `host.info` request using NATS request-reply, including the `clientToken` in the payload. The response carries bootstrap metadata (`agents`, `host_platform`, `version`, `linked_client_token`) and `pending_prompts` — any prompts already open on the host when the PWA connected. The Dashboard consumes this once per connection; both tabs read from it.
|
|
338
|
-
|
|
339
|
-
4. PWA lands on the Sessions tab and fetches run history via `taskrun.list`. `task.list` is not called at startup — it fires lazily the first time the user opens the Tasks tab. If either RPC fails with NATS 503 ("no responders"), the PWA shows an empty state — this is not treated as an error.
|
|
340
|
-
|
|
341
|
-
5. PWA registers the service worker and subscribes the browser for Web Push notifications (via `pushManager.subscribe` with the server's VAPID public key). The push subscription is sent to `POST /api/push/subscribe` with the `hostId` so the server can relay notifications to the device.
|
|
342
|
-
|
|
343
|
-
6. Initial pending-prompt discovery uses `host.info.pending_prompts`: each entry is `{ key, type, params?, meta? }` where `meta` carries the display context (`session_id`, `session_name`, `description`, `input_questions`) needed to render the modal cold. The PWA seeds its modal state maps from this array. After connection, live arrivals come through the NATS event subscription. The PWA responds by calling the `task.user_input` RPC on the host, which resolves the in-memory pending request held by the serve daemon. The `run` process (blocked on an HTTP call to the serve daemon) receives the response and proceeds or exits accordingly.
|
|
344
|
-
|
|
345
|
-
### 4.2 UI Layout: Sessions & Tasks Tabs
|
|
346
|
-
|
|
347
|
-
All authenticated views are scoped under `/hosts/:hostId/` so the URL is the source of truth for "which host am I looking at":
|
|
348
|
-
|
|
349
|
-
- `/hosts/:hostId` — Sessions tab (default)
|
|
350
|
-
- `/hosts/:hostId/tasks` — Tasks tab
|
|
351
|
-
- `/hosts/:hostId/runs/:taskId` — latest run for a task
|
|
352
|
-
- `/hosts/:hostId/runs/:taskId/:runId` — specific run
|
|
353
|
-
- `/hosts/:hostId/pair/setup` — capability setup right after pairing (native only)
|
|
354
|
-
- `/pair` — enter a pairing code
|
|
355
|
-
- `/` — redirects to `/hosts/<firstPairedHostId>`, or to `/pair` if no hosts are paired. No "last visited" state is persisted; the URL is the source of truth for the active host.
|
|
356
|
-
|
|
357
|
-
Unknown or stale `:hostId` values redirect back to `/`. This lets notification deep links (`/hosts/:hostId/runs/...`) switch the active host automatically instead of opening a run against whatever host happens to be selected.
|
|
358
|
-
|
|
359
|
-
The PWA has two tabs: **Sessions** (default) and **Tasks** (secondary). Sessions is the primary workflow — it lists all run history across tasks (a "session" is a single run) and includes a session composer at the top of the list.
|
|
360
|
-
|
|
361
|
-
* **Session composer:** An inline textarea with an agent picker, a yolo-mode toggle, and a round play button. Entering text and clicking play dispatches `task.run_oneoff`, starting an immediate unsaved session. The composer never opens a dialog; typing is direct. When the textarea has content, navigating away (tab switch, host switch, browser reload, clicking a session row) triggers a confirmation dialog so the draft isn't lost silently.
|
|
362
|
-
* **Tasks tab:** Lists saved tasks (scheduled or reusable). A floating round `+` button in the bottom-right of the screen opens the task form, which is used only to create/edit saved or scheduled tasks — it has no Run button (run one-offs via the session composer instead). The form's primary action is "Save" (no schedule) or "Schedule" (when a schedule is configured).
|
|
363
|
-
|
|
364
|
-
Bootstrap data (agents, host version, host platform, linked device) is fetched once per connection at the Dashboard level via `host.info` — independent of which tab is active. `task.list` is called lazily on the Tasks tab mount; `taskrun.list` is called lazily on the Sessions tab mount. Neither list RPC carries bootstrap metadata.
|
|
365
|
-
|
|
366
|
-
Dashboard owns the always-on NATS event subscription and renders pending `confirm-request` / `permission-request` / `input-request` modals via React portal, so prompts surface regardless of which tab is active. Initial pending prompts (those already open when the PWA connects) are seeded from `host.info`'s `pending_prompts` field — each entry carries the display context (`session_name`, `description`, `input_questions`) needed to render the modal cold, since the task list is no longer available at bootstrap. `session_name` is a unified label: agent name for confirm/input, task name for permission.
|
|
367
|
-
|
|
368
|
-
### 4.3 Task Creation & Update
|
|
369
|
-
|
|
370
|
-
1. User taps the floating `+` button on the Tasks tab, which opens the task form.
|
|
371
|
-
|
|
372
|
-
2. User enters a prompt, selects an agent, configures the schedule (UI translates human-readable times to cron expressions or ISO datetime strings) and confirmation settings, and clicks "Save" (no schedule) or "Schedule" (with a schedule).
|
|
373
|
-
|
|
374
|
-
3. PWA sends `task.create` (or `task.update`) via NATS request-reply to Host (45s timeout). For prompts > 50 chars, the host generates a concise task name by running the configured agent CLI in non-interactive mode (e.g., `claude -p "Generate a concise 3-6 word name for this task..."`). For shorter prompts, the prompt is used directly as the name.
|
|
375
|
-
|
|
376
|
-
4. For updates: if the user changes the `user_prompt` or `agent`, the name is regenerated. If neither changed, the existing name is preserved. Existing tasks with granted permissions show a clickable "Granted Permissions" link to view them.
|
|
377
|
-
|
|
378
|
-
5. PWA sends `task.create` (or `task.update` with `id`) with the task fields as the message body. The `id` field is **not sent on create** — the host generates a UUID. `schedule_type` and `schedule_values` are omitted when the task has no schedule. For `on_new_notification` / `on_new_sms` types, `schedule_values` is sent only when a filter is configured (single packageName for notifications, single sender for SMS).
|
|
379
|
-
|
|
380
|
-
6. Host creates/updates the `tasks/<task-id>/TASK.md` file and returns the **full flat task object** (all frontmatter fields at the top level). The PWA uses this response directly to update the UI.
|
|
381
|
-
|
|
382
|
-
7. **OS Integration:** Host translates the schedule into a systemd user timer (`~/.config/systemd/user/palmier-task-<task-id>.timer` and `.service`). The `.service` runs `palmier run <task-id>`, which executes the task as a background process. Host runs `systemctl --user daemon-reload` and enables the timer.
|
|
383
|
-
|
|
384
|
-
### 4.4 On-Demand Execution
|
|
385
|
-
|
|
386
|
-
Any task that is not currently running can be executed immediately:
|
|
387
|
-
|
|
388
|
-
* **PWA (saved task):** A "Run Now" button is shown on each task card when the task is not already running. Clicking it sends a `task.run` request via NATS request-reply to the Host, which starts execution via the system scheduler (`systemctl --user start` on Linux, `schtasks /run` on Windows).
|
|
389
|
-
* **PWA (one-off session):** The session composer on the Sessions tab sends `task.run_oneoff` with `{ user_prompt, agent, yolo_mode }`. The host creates an ephemeral task, runs it, and returns `{ task_id, run_id }`; the PWA navigates to the run detail view.
|
|
390
|
-
* **CLI:** `palmier run <task-id>` executes the task directly (outside the system scheduler).
|
|
391
|
-
|
|
392
|
-
Both paths follow the same execution loop described in §5.2 (including confirmation checks if configured). The system scheduler prevents concurrent runs of the same task — if the service/task is already active, the start command is a no-op.
|
|
393
|
-
|
|
394
|
-
### 4.5 Task Deletion
|
|
395
|
-
|
|
396
|
-
1. PWA sends `task.delete` via NATS request-reply.
|
|
397
|
-
|
|
398
|
-
2. Host runs `systemctl --user stop palmier-task-<task-id>.timer`, disables it, deletes the systemd files, removes the `tasks/<task-id>` directory, and runs `daemon-reload`.
|
|
399
|
-
|
|
400
|
-
## 5. Task Execution & Host Interaction
|
|
401
|
-
|
|
402
|
-
### 5.1 Execution Architecture
|
|
403
|
-
|
|
404
|
-
Task execution is handled by `palmier run <task-id>`, a short-lived process that resolves the task's `agent` field to an `AgentTool` implementation, which constructs the full command line. The `agent` field defaults to `"claude"`. Each execution is its own process — systemd manages its lifecycle via the `.service` unit.
|
|
405
|
-
|
|
406
|
-
The persistent host process monitors running tasks via a **crash detection polling loop**: every 30 seconds, it scans all tasks with `running_state: "started"` and queries the system scheduler (`systemctl --user is-active` on Linux, `schtasks /query` on Windows) to check if the task process is still alive. If the scheduler reports the task is no longer running but `status.json` still says `"started"`, the daemon marks it as failed, writes a RESULT file, appends to history, and broadcasts the failure event. This also runs once at daemon startup to reconcile any tasks that crashed while the daemon was offline. Real-time task events are broadcast via a shared events module (`events.ts`) that publishes to NATS pub/sub and the serve daemon's HTTP SSE endpoint.
|
|
407
|
-
|
|
408
|
-
### 5.2 The Execution Loop
|
|
409
|
-
|
|
410
|
-
When `palmier run <task-id>` executes (triggered by a systemd timer, `systemctl start` from the host's `task.run` RPC handler, or direct CLI invocation):
|
|
411
|
-
|
|
412
|
-
1. **Confirmation Check:**
|
|
413
|
-
|
|
414
|
-
* Reads `TASK.md`. If `requires_confirmation: true`:
|
|
415
|
-
|
|
416
|
-
* `palmier run` publishes a `started` event on `host-event.<host_id>.<task_id>`, then POSTs to the serve daemon's `/request-confirmation` HTTP endpoint. This registers an in-memory pending request and publishes a `confirm-request` event via NATS and SSE. The Web Server subscribes to `host-event.>` and sends a push notification.
|
|
417
|
-
|
|
418
|
-
* The user responds either via the PWA (which calls the `task.user_input` RPC on the host) or via the push notification action buttons (Service Worker calls `POST /api/push/respond`, Web Server forwards to the `task.user_input` RPC). Both paths resolve the in-memory pending request on the serve daemon.
|
|
419
|
-
|
|
420
|
-
* The `/request-confirmation` HTTP response returns to `palmier run` with `{ confirmed: true/false }`. If confirmed, it proceeds. If aborted, it publishes an `aborted` event and exits.
|
|
421
|
-
|
|
422
|
-
2. **Launching the Task Process:**
|
|
423
|
-
|
|
424
|
-
* `palmier run` resolves the task's `agent` field to an `AgentTool` implementation and calls `getTaskRunCommandLine(task)` to obtain the command and arguments. The process is spawned directly (without a shell). stdin is closed (equivalent to `< /dev/null`) to prevent tools from hanging on an open pipe. The working directory is the project root (from `host.json`). The environment variable `PALMIER_TASK_ID=<task-id>` is set for identification.
|
|
425
|
-
|
|
426
|
-
* The spawned process inherits the default physical GUI session environment (`DISPLAY=:0`, `XDG_RUNTIME_DIR=/run/user/<uid>`) so that commands requiring a graphical display (e.g., headed browsers) run within the user's desktop session. `PALMIER_HTTP_PORT` is also set so agents can call the serve daemon's HTTP endpoints.
|
|
427
|
-
|
|
428
|
-
* The agent implementation is responsible for constructing the appropriate arguments (e.g., `--allowedTools` flags for Claude based on the task's permissions). The task's `user_prompt` is included in the arguments by the agent.
|
|
429
|
-
|
|
430
|
-
3. **Completion:**
|
|
431
|
-
|
|
432
|
-
* When the child process exits successfully, `palmier run` publishes a `finished` event on `host-event` and persists it to `status.json`. If the process exits with a non-zero code, it publishes a `failed` event instead. If report files were generated, `palmier run` also publishes a `report-generated` event; the Web Server sends a push notification when it receives this event.
|
|
433
|
-
|
|
434
|
-
### 5.3 Command-Triggered Execution
|
|
435
|
-
|
|
436
|
-
When a task has a `command` field set, `palmier run` enters command-triggered mode after the confirmation check:
|
|
437
|
-
|
|
438
|
-
1. **Spawn the command** using `shell: true` (allowing pipes, redirects, etc.) with stdout piped. stdin is closed. stderr is forwarded to the palmier process's stderr.
|
|
439
|
-
|
|
440
|
-
2. **Read stdout line by line** using Node's `readline` interface. Empty lines are skipped.
|
|
441
|
-
|
|
442
|
-
3. **For each line**, invoke the agent CLI:
|
|
443
|
-
* Build a per-line prompt: `user_prompt + "\n\nProcess this input:\n" + <line>` + the standard task outcome suffix.
|
|
444
|
-
* Call `agent.getTaskRunCommandLine()` with the augmented prompt.
|
|
445
|
-
* Spawn the agent via `spawnCommand()` and collect output.
|
|
446
|
-
* The standard permission/input retry loop applies: if the agent requests permissions or user input, line processing pauses, the user is prompted, and the invocation retries once resolved. Granted permissions accumulate across lines within the same run.
|
|
447
|
-
|
|
448
|
-
4. **Sequential processing with bounded queue**: lines are processed one at a time. If lines arrive faster than agent invocations complete, they queue up to a max of 100 entries. Overflow drops the oldest unprocessed line.
|
|
449
|
-
|
|
450
|
-
5. **On command exit or signal**: each agent invocation is written as a conversation entry in the RESULT file. Per-line agent outputs are also logged to `command-output.log` in the task directory.
|
|
451
|
-
|
|
452
|
-
6. **Composable with schedules**: the configured schedule starts `palmier run` at the scheduled time, which spawns the command. The command runs until it exits or the task is aborted.
|
|
453
|
-
|
|
454
|
-
### 5.4 Failsafes & Constraints
|
|
455
|
-
|
|
456
|
-
* **Crash Detection:** The `palmier serve` daemon polls every 30 seconds, querying the system scheduler to detect tasks whose process exited without updating `status.json`. Detected crashes append a failed status entry to the existing RESULT file and broadcast the failure. This also runs at daemon startup to catch crashes that occurred while the daemon was offline.
|
|
457
|
-
|
|
458
|
-
* **Process Tracking:** Each `palmier run` process writes its PID to `status.json`. On abort, `taskkill /pid <pid> /f /t` (Windows), `systemctl --user stop` (Linux), or `launchctl kill SIGTERM gui/$UID/<label>` (macOS, with a PID-based SIGTERM fallback) kills the process tree. On Windows, tasks use S4U LogonType in Task Scheduler to run without visible console windows.
|
|
459
|
-
|
|
460
|
-
* **No Remote Timeout:** If a confirmation request is sent to the user's devices and the user does not respond, the task continues to wait indefinitely. The user can always respond via the PWA. There is no automatic deny-on-timeout.
|
|
461
|
-
|
|
462
|
-
* **Confirmation Cleanup:** If `palmier run` is killed during a pending confirmation, the in-memory pending request on the serve daemon is orphaned. This is harmless — the `task.user_input` RPC will return `"not pending"` since the pending entry is removed when the HTTP connection closes.
|
|
463
|
-
|
|
464
|
-
* **No Execution Time Limit:** Tasks may be long-running by design. There is no global execution timeout.
|
|
465
|
-
|
|
466
|
-
## 6. Agent HTTP Endpoints
|
|
467
|
-
|
|
468
|
-
The serve daemon exposes localhost-only HTTP endpoints that agents call during task execution. The port and task ID are baked into the agent's system prompt via template variables (`{{PORT}}`, `{{TASK_ID}}`).
|
|
469
|
-
|
|
470
|
-
### 6.1 Endpoints
|
|
471
|
-
|
|
472
|
-
* **`POST /notify`** — Sends a push notification to all paired devices. Body: `{ title, body }`. The serve daemon forwards to NATS `host.<host_id>.push.send`; the Web Server delivers via Web Push. Requires server mode.
|
|
473
|
-
|
|
474
|
-
* **`POST /request-input`** — Requests input from the user during task execution. Body: `{ taskId, descriptions }`. The connection is held open until the user responds via the PWA (`task.user_input` RPC). Returns `{ values: [...] }` on success or `{ aborted: true }` if declined.
|
|
475
|
-
|
|
476
|
-
* **`POST /request-confirmation`** — Requests task confirmation. Body: `{ taskId, taskName }`. Called by `palmier run` (not agents). Returns `{ confirmed: boolean }`.
|
|
477
|
-
|
|
478
|
-
* **`POST /request-permission`** — Requests permission grants. Body: `{ taskId, taskName, permissions }`. Called by `palmier run` (not agents). Returns `{ response: "granted" | "granted_all" | "aborted" }`.
|
|
479
|
-
|
|
480
|
-
* **`POST /task-event/pop?taskId=<id>`** — Drains one queued event for an event-triggered task. Called by `palmier run` (not agents). Atomically: if the task's in-memory FIFO queue is non-empty, returns `{ event: "<raw JSON payload>" }` and keeps `active_run = true`; if empty, clears `active_run` and returns `{ empty: true }`. Used only for `schedule_type: "on_new_notification" | "on_new_sms"` tasks.
|
|
481
|
-
|
|
482
|
-
### 6.2 Resource Endpoints
|
|
483
|
-
|
|
484
|
-
Resource REST endpoints are auto-generated from the `ResourceDefinition[]` registry in `mcp-tools.ts`. Each resource exposes a GET endpoint at its `restPath`. These are also available via the MCP protocol (`resources/list`, `resources/read`).
|
|
485
|
-
|
|
486
|
-
* **`GET /notifications`** — Returns recent notifications from the user's Android device as a JSON array. Each notification contains `{ id, packageName, appName, title, text, timestamp, receivedAt }`. The host maintains a bounded in-memory collection (last 50 notifications) fed by NATS subscription to `host.<host_id>.device.notifications`.
|
|
487
|
-
|
|
488
|
-
* **`GET /sms-messages`** — Returns recent SMS messages from the user's Android device as a JSON array. Each message contains `{ id, sender, body, timestamp, receivedAt }`. The host maintains a bounded in-memory collection (last 50 messages) fed by NATS subscription to `host.<host_id>.device.sms`.
|
|
489
|
-
|
|
490
|
-
## 7. Database Schema (PostgreSQL)
|
|
491
|
-
|
|
492
|
-
```sql
|
|
493
|
-
-- Host registrations
|
|
494
|
-
CREATE TABLE hosts (
|
|
495
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
496
|
-
name VARCHAR(255),
|
|
497
|
-
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
498
|
-
);
|
|
499
|
-
|
|
500
|
-
-- Push notification subscriptions (Web Push)
|
|
501
|
-
CREATE TABLE push_subscriptions (
|
|
502
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
503
|
-
host_id UUID NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
|
504
|
-
endpoint TEXT NOT NULL,
|
|
505
|
-
p256dh TEXT NOT NULL,
|
|
506
|
-
auth TEXT NOT NULL,
|
|
507
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
508
|
-
UNIQUE(host_id, endpoint)
|
|
509
|
-
);
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
## 8. Web Server API Endpoints
|
|
513
|
-
|
|
514
|
-
All endpoints are served over HTTPS. No user authentication is required — the server is stateless with respect to user identity.
|
|
515
|
-
|
|
516
|
-
| Method | Path | Description |
|
|
517
|
-
|--------|------|-------------|
|
|
518
|
-
| `POST` | `/api/hosts/register` | Register a new host. Returns `{ hostId, natsUrl, natsWsUrl, natsJwt, natsNkeySeed }` with host-scoped NATS credentials. |
|
|
519
|
-
| `GET` | `/api/config` | Returns pairing-only NATS credentials: `{ natsWsUrl, natsJwt, natsNkeySeed }`. JWT can only publish to `pair.*`. |
|
|
520
|
-
| `GET` | `/api/nats-credentials/:hostId` | Returns host-scoped NATS credentials for PWA: `{ natsWsUrl, natsJwt, natsNkeySeed }`. JWT scoped to one host's RPC + events. |
|
|
521
|
-
| `POST` | `/api/push/subscribe` | Register a push subscription. Body: `{ hostId, endpoint, keys: { p256dh, auth } }`. |
|
|
522
|
-
| `DELETE` | `/api/push/subscribe` | Unregister a push subscription. Body: `{ hostId, endpoint }`. |
|
|
523
|
-
| `GET` | `/api/push/vapid-key` | Returns the server's VAPID public key for push subscription. |
|
|
524
|
-
| `POST` | `/api/push/respond` | Called by Service Worker to relay user responses to task confirmations. Body: `{ type, task_id, host_id, response }`. Web Server forwards the response to the host via the `host.<host_id>.rpc.task.user_input` NATS RPC. |
|
|
525
|
-
| `POST` | `/api/device/notifications` | Relay a device notification from Android to the host. Body: `{ hostId, notification: { id, packageName, appName, title, text, timestamp } }`. Publishes to NATS `host.<hostId>.device.notifications`. |
|
|
526
|
-
| `POST` | `/api/device/sms` | Relay an incoming SMS from Android to the host. Body: `{ hostId, sms: { id, sender, body, timestamp } }`. Publishes to NATS `host.<hostId>.device.sms`. |
|
|
527
|
-
| `POST` | `/api/device/contacts-response` | Relay contacts response from Android to the host. Body: `{ requestId, hostId, result }`. Publishes to NATS `host.<hostId>.contacts.<requestId>`. |
|
|
528
|
-
| `POST` | `/api/device/calendar-response` | Relay calendar response from Android to the host. Body: `{ requestId, hostId, result }`. Publishes to NATS `host.<hostId>.calendar.<requestId>`. |
|
|
529
|
-
| `POST` | `/api/device/sms-response` | Relay SMS send response from Android to the host. Body: `{ requestId, hostId, result }`. Publishes to NATS `host.<hostId>.sms.<requestId>`. |
|
|
530
|
-
| `POST` | `/api/device/alarm-response` | Relay alarm response from Android to the host. Body: `{ requestId, hostId, result }`. Publishes to NATS `host.<hostId>.alarm.<requestId>`. |
|
|
531
|
-
| `POST` | `/api/device/battery-response` | Relay battery response from Android to the host. Body: `{ requestId, hostId, result }`. Publishes to NATS `host.<hostId>.battery.<requestId>`. |
|
|
532
|
-
| `POST` | `/api/device/ringer-response` | Relay ringer mode response from Android to the host. Body: `{ requestId, hostId, result }`. Publishes to NATS `host.<hostId>.ringer.<requestId>`. |
|
|
533
|
-
| `GET` | `/health` | Health check. |
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
You are an AI agent executing a task on behalf of the user. Follow these instructions carefully.
|
|
2
|
-
|
|
3
|
-
## Reporting Output
|
|
4
|
-
|
|
5
|
-
If you generate report or output files, print each file path on its own line using this exact format:
|
|
6
|
-
[PALMIER_REPORT] <filename>
|
|
7
|
-
|
|
8
|
-
## Completion
|
|
9
|
-
|
|
10
|
-
When you are done, output exactly one of these markers as the very last line (no other text on the same line):
|
|
11
|
-
[PALMIER_TASK_SUCCESS]
|
|
12
|
-
[PALMIER_TASK_FAILURE]
|
|
13
|
-
|
|
14
|
-
## Permissions
|
|
15
|
-
|
|
16
|
-
Whenever a tool you are trying to use is denied or you lack the required permissions, print each required permission on its own line using this exact format:
|
|
17
|
-
[PALMIER_PERMISSION] <tool_name> | <description>
|
|
18
|
-
|
|
19
|
-
## HTTP Endpoints
|
|
20
|
-
|
|
21
|
-
{{ENDPOINT_DOCS}}
|
|
22
|
-
|
|
23
|
-
The task to execute follows below:
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
{{TASK_DESCRIPTION}}
|
|
28
|
-
|