palmier 0.6.8 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +9 -2
  2. package/dist/agents/agent.d.ts +2 -2
  3. package/dist/agents/aider.d.ts +1 -1
  4. package/dist/agents/aider.js +2 -5
  5. package/dist/agents/claude.d.ts +1 -1
  6. package/dist/agents/claude.js +2 -5
  7. package/dist/agents/cline.d.ts +1 -1
  8. package/dist/agents/cline.js +2 -5
  9. package/dist/agents/codex.d.ts +1 -1
  10. package/dist/agents/codex.js +2 -5
  11. package/dist/agents/copilot.d.ts +1 -1
  12. package/dist/agents/copilot.js +2 -5
  13. package/dist/agents/cursor.d.ts +1 -1
  14. package/dist/agents/cursor.js +2 -5
  15. package/dist/agents/deepagents.d.ts +1 -1
  16. package/dist/agents/deepagents.js +2 -5
  17. package/dist/agents/droid.d.ts +1 -1
  18. package/dist/agents/droid.js +2 -5
  19. package/dist/agents/gemini.d.ts +1 -1
  20. package/dist/agents/gemini.js +2 -5
  21. package/dist/agents/goose.d.ts +1 -1
  22. package/dist/agents/goose.js +2 -5
  23. package/dist/agents/hermes.d.ts +1 -1
  24. package/dist/agents/hermes.js +2 -5
  25. package/dist/agents/kimi.d.ts +1 -1
  26. package/dist/agents/kimi.js +2 -5
  27. package/dist/agents/kiro.d.ts +1 -1
  28. package/dist/agents/kiro.js +2 -5
  29. package/dist/agents/openclaw.d.ts +1 -1
  30. package/dist/agents/openclaw.js +2 -5
  31. package/dist/agents/opencode.d.ts +1 -1
  32. package/dist/agents/opencode.js +2 -5
  33. package/dist/agents/qoder.d.ts +1 -1
  34. package/dist/agents/qoder.js +2 -5
  35. package/dist/agents/qwen.d.ts +1 -1
  36. package/dist/agents/qwen.js +2 -5
  37. package/dist/agents/shared-prompt.js +1 -1
  38. package/dist/commands/run.js +1 -2
  39. package/dist/commands/serve.js +16 -0
  40. package/dist/mcp-handler.d.ts +3 -0
  41. package/dist/mcp-handler.js +59 -3
  42. package/dist/mcp-tools.d.ts +16 -1
  43. package/dist/mcp-tools.js +24 -2
  44. package/dist/notification-store.d.ts +13 -0
  45. package/dist/notification-store.js +19 -0
  46. package/dist/pwa/assets/{index-C8vJwUNi.js → index-DLxrL0hR.js} +42 -42
  47. package/dist/pwa/assets/{web-NxTETXZK.js → web-CBI458eN.js} +1 -1
  48. package/dist/pwa/assets/{web-6UChJFov.js → web-HDs03L2B.js} +1 -1
  49. package/dist/pwa/index.html +1 -1
  50. package/dist/pwa/service-worker.js +1 -1
  51. package/dist/rpc-handler.js +27 -67
  52. package/dist/task.js +2 -3
  53. package/dist/transports/http-transport.js +51 -3
  54. package/dist/types.d.ts +0 -1
  55. package/package.json +2 -2
  56. package/palmier-server/README.md +1 -1
  57. package/palmier-server/pwa/src/components/PlanDialog.tsx +5 -12
  58. package/palmier-server/pwa/src/components/TaskForm.tsx +6 -15
  59. package/palmier-server/pwa/src/constants.ts +1 -1
  60. package/palmier-server/pwa/src/types.ts +0 -1
  61. package/palmier-server/server/src/index.ts +2 -0
  62. package/palmier-server/server/src/routes/device.ts +32 -0
  63. package/palmier-server/spec.md +13 -12
  64. package/src/agents/agent.ts +2 -2
  65. package/src/agents/aider.ts +2 -5
  66. package/src/agents/claude.ts +2 -5
  67. package/src/agents/cline.ts +2 -5
  68. package/src/agents/codex.ts +2 -5
  69. package/src/agents/copilot.ts +2 -5
  70. package/src/agents/cursor.ts +2 -5
  71. package/src/agents/deepagents.ts +2 -5
  72. package/src/agents/droid.ts +2 -5
  73. package/src/agents/gemini.ts +2 -5
  74. package/src/agents/goose.ts +2 -5
  75. package/src/agents/hermes.ts +2 -5
  76. package/src/agents/kimi.ts +2 -5
  77. package/src/agents/kiro.ts +2 -5
  78. package/src/agents/openclaw.ts +2 -5
  79. package/src/agents/opencode.ts +2 -5
  80. package/src/agents/qoder.ts +2 -5
  81. package/src/agents/qwen.ts +2 -5
  82. package/src/agents/shared-prompt.ts +1 -1
  83. package/src/commands/run.ts +1 -2
  84. package/src/commands/serve.ts +16 -1
  85. package/src/mcp-handler.ts +68 -3
  86. package/src/mcp-tools.ts +48 -2
  87. package/src/notification-store.ts +30 -0
  88. package/src/rpc-handler.ts +29 -71
  89. package/src/task.ts +2 -3
  90. package/src/transports/http-transport.ts +49 -3
  91. package/src/types.ts +0 -1
  92. package/test/agent-instructions.test.ts +117 -19
  93. package/test/agent-output-parsing.test.ts +1 -0
  94. package/test/notification-store.test.ts +57 -0
  95. package/test/task-parsing.test.ts +3 -3
  96. package/dist/commands/plan-generation.md +0 -22
  97. package/src/commands/plan-generation.md +0 -22
  98. package/test/fixtures/agent-instructions-snapshot.md +0 -58
package/README.md CHANGED
@@ -34,11 +34,11 @@ It runs on your machine as a background daemon and connects to a mobile-friendly
34
34
 
35
35
  ## How It Works
36
36
 
37
- Palmier runs as a background daemon (systemd on Linux, Task Scheduler on Windows). It invokes your agent CLIs directly, schedules tasks via native OS timers, and exposes an API that the PWA connects to — either directly over HTTP or remotely through a relay server. Agents can interact with the user's mobile device during execution — requesting input, sending push notifications, and fetching GPS location.
37
+ Palmier runs as a background daemon (systemd on Linux, Task Scheduler on Windows). It invokes your agent CLIs directly, schedules tasks via native OS timers, and exposes an API that the PWA connects to — either directly over HTTP or remotely through a relay server. Agents can interact with the user's mobile device during execution — requesting input, sending push notifications, fetching GPS location, and reading device notifications.
38
38
 
39
39
  ### MCP Server
40
40
 
41
- Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://localhost:<port>/mcp` (streamable HTTP transport). MCP-capable agents can register it to get tool definitions automatically. The same tools are also available as REST endpoints for curl-based agents.
41
+ Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://localhost:<port>/mcp` (streamable HTTP transport). MCP-capable agents can register it to get tool and resource definitions automatically. The same tools and resources are also available as REST endpoints for curl-based agents.
42
42
 
43
43
  **MCP server URL:** `http://localhost:<port>/mcp`
44
44
 
@@ -50,6 +50,13 @@ Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://loca
50
50
  | `request-confirmation` | Request confirmation from the user (blocks until response) |
51
51
  | `device-geolocation` | Get GPS location of the user's mobile device |
52
52
 
53
+ **Available resources:**
54
+ | Resource | URI | REST | Description |
55
+ |----------|-----|------|-------------|
56
+ | Device Notifications | `notifications://device` | `GET /notifications` | Recent notifications from the user's Android device |
57
+
58
+ Resources support MCP subscriptions — clients can subscribe via `resources/subscribe` and receive real-time `notifications/resources/updated` events via the streamable HTTP transport when the resource changes. The Android app requires notification listener access to be enabled in system settings.
59
+
53
60
  ```
54
61
  ┌──────────────┐ HTTP ┌──────────────────┐
55
62
  │ │◄──────────────────────│ │
@@ -12,8 +12,8 @@ export interface CommandLine {
12
12
  * Abstracts how plans are generated and tasks are executed across different AI agents.
13
13
  */
14
14
  export interface AgentTool {
15
- /** Return the command and args used to generate a plan from a prompt. */
16
- getPlanGenerationCommandLine(prompt: string): CommandLine;
15
+ /** Return the command and args for a short, non-interactive prompt (e.g. generating a task name). */
16
+ getPromptCommandLine(prompt: string): CommandLine;
17
17
  /** Return the command and args used to run a task. If followupPrompt is provided, use it instead of the task's prompt,
18
18
  * and treat it as a continuation of the original run (reuse the same session, etc).
19
19
  * extraPermissions: pass an array of RequiredPermission for transient permissions granted for this run only,
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class Aider implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class Aider {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "aider",
9
- args: ["--message", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "aider", args: ["--message", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class ClaudeAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class ClaudeAgent {
5
5
  supportsPermissions = true;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "claude",
9
- args: ["-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "claude", args: ["-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class Cline implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class Cline {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "cline ",
9
- args: ["--yolo", "-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "cline ", args: ["--yolo", "-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class CodexAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class CodexAgent {
5
5
  supportsPermissions = true;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "codex",
9
- args: ["exec", "--skip-git-repo-check", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "codex", args: ["exec", "--skip-git-repo-check", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class CopilotAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class CopilotAgent {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "copilot",
9
- args: ["-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "copilot", args: ["-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class Cursor implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class Cursor {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "cursor",
9
- args: ["-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "cursor", args: ["-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class DeepAgents implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class DeepAgents {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "deepagents",
9
- args: ["--non-interactive", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "deepagents", args: ["--non-interactive", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class DroidAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class DroidAgent {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "droid",
9
- args: ["exec", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "droid", args: ["exec", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class GeminiAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class GeminiAgent {
5
5
  supportsPermissions = true;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "gemini",
9
- args: ["--prompt", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "gemini", args: ["--prompt", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class GooseAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class GooseAgent {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "goose",
9
- args: ["run", "--text", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "goose", args: ["run", "--text", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class Hermes implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class Hermes {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "hermes",
9
- args: ["chat", "-q", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "hermes", args: ["chat", "-q", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class KimiAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class KimiAgent {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "kimi",
9
- args: ["-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "kimi", args: ["-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class Kiro implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class Kiro {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "kiro-cli",
9
- args: ["--no-interactive", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "kiro-cli", args: ["--no-interactive", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class OpenClawAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -2,11 +2,8 @@ import { execSync } from "child_process";
2
2
  import { getAgentInstructions } from "./shared-prompt.js";
3
3
  export class OpenClawAgent {
4
4
  supportsPermissions = false;
5
- getPlanGenerationCommandLine(prompt) {
6
- return {
7
- command: "openclaw",
8
- args: ["agent", "--local", "--agent", "main", "--message", prompt],
9
- };
5
+ getPromptCommandLine(prompt) {
6
+ return { command: "openclaw", args: ["agent", "--local", "--agent", "main", "--message", prompt] };
10
7
  }
11
8
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
12
9
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class OpenCodeAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class OpenCodeAgent {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "opencode",
9
- args: ["run", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "opencode", args: ["run", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class Qoder implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class Qoder {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "qodercli",
9
- args: ["-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "qodercli", args: ["-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import type { AgentTool, CommandLine } from "./agent.js";
3
3
  export declare class QwenAgent implements AgentTool {
4
4
  supportsPermissions: boolean;
5
- getPlanGenerationCommandLine(prompt: string): CommandLine;
5
+ getPromptCommandLine(prompt: string): CommandLine;
6
6
  getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
7
  init(): Promise<boolean>;
8
8
  }
@@ -3,11 +3,8 @@ import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class QwenAgent {
5
5
  supportsPermissions = false;
6
- getPlanGenerationCommandLine(prompt) {
7
- return {
8
- command: "qwen",
9
- args: ["-p", prompt],
10
- };
6
+ getPromptCommandLine(prompt) {
7
+ return { command: "qwen", args: ["-p", prompt] };
11
8
  }
12
9
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
10
  const yolo = extraPermissions === "yolo";
@@ -10,7 +10,7 @@ const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(path.join(__dirname, "agent-
10
10
  */
11
11
  export function getAgentInstructions(task, skipPermissions) {
12
12
  const port = loadConfig().httpPort ?? 9966;
13
- const taskDescription = task.body || task.frontmatter.user_prompt;
13
+ const taskDescription = task.frontmatter.user_prompt;
14
14
  let instructions = AGENT_INSTRUCTIONS_TEMPLATE
15
15
  .replace(/\{\{ENDPOINT_DOCS\}\}/g, generateEndpointDocs(port, task.frontmatter.id))
16
16
  .replace(/\{\{TASK_DESCRIPTION\}\}/g, taskDescription);
@@ -214,7 +214,7 @@ export async function runCommand(taskId) {
214
214
  await appendAndNotify(ctx, {
215
215
  role: "user",
216
216
  time: Date.now(),
217
- content: task.body || task.frontmatter.user_prompt,
217
+ content: task.frontmatter.user_prompt,
218
218
  });
219
219
  const result = await invokeAgentWithRetries(ctx, task);
220
220
  const outcome = resolveOutcome(taskDir, result.outcome);
@@ -294,7 +294,6 @@ async function runCommandTriggeredMode(ctx) {
294
294
  const perLinePrompt = `${ctx.task.frontmatter.user_prompt}\n\nProcess this input:\n${line}`;
295
295
  const perLineTask = {
296
296
  frontmatter: { ...ctx.task.frontmatter, user_prompt: perLinePrompt },
297
- body: "",
298
297
  };
299
298
  const result = await invokeAgentWithRetries(ctx, perLineTask);
300
299
  if (result.outcome === "finished") {
@@ -11,6 +11,8 @@ import { getPlatform } from "../platform/index.js";
11
11
  import { detectAgents } from "../agents/agent.js";
12
12
  import { saveConfig } from "../config.js";
13
13
  import { CONFIG_DIR } from "../config.js";
14
+ import { StringCodec } from "nats";
15
+ import { addNotification } from "../notification-store.js";
14
16
  const POLL_INTERVAL_MS = 30_000;
15
17
  const DAEMON_PID_FILE = path.join(CONFIG_DIR, "daemon.pid");
16
18
  /**
@@ -114,6 +116,20 @@ export async function serveCommand() {
114
116
  // Start NATS transport (loops forever, fire-and-forget)
115
117
  if (nc) {
116
118
  startNatsTransport(config, handleRpc, nc);
119
+ // Subscribe to device notifications from Android
120
+ const sc = StringCodec();
121
+ const notifSub = nc.subscribe(`host.${config.hostId}.device.notifications`);
122
+ (async () => {
123
+ for await (const msg of notifSub) {
124
+ try {
125
+ const data = JSON.parse(sc.decode(msg.data));
126
+ addNotification({ ...data, receivedAt: Date.now() });
127
+ }
128
+ catch (err) {
129
+ console.error("[nats] Failed to parse device notification:", err);
130
+ }
131
+ }
132
+ })();
117
133
  }
118
134
  // Start HTTP transport (loops forever)
119
135
  await startHttpTransport(config, handleRpc, httpPort, nc);
@@ -2,7 +2,10 @@ import { type ToolContext } from "./mcp-tools.js";
2
2
  export interface McpResponse {
3
3
  body: object;
4
4
  sessionId?: string;
5
+ /** If true, the HTTP transport should keep the response open as an SSE stream for server-initiated notifications. */
6
+ stream?: boolean;
5
7
  }
8
+ export declare function getResourceSubscriptions(): Map<string, Set<string>>;
6
9
  export declare function getAgentName(sessionId: string): string | undefined;
7
10
  export declare function handleMcpRequest(body: string, sessionId: string | undefined, ctx: ToolContext): Promise<McpResponse>;
8
11
  //# sourceMappingURL=mcp-handler.d.ts.map
@@ -1,5 +1,10 @@
1
1
  import { randomUUID } from "crypto";
2
- import { agentTools, agentToolMap, ToolError } from "./mcp-tools.js";
2
+ import { agentTools, agentToolMap, agentResources, agentResourceMap, ToolError } from "./mcp-tools.js";
3
+ // Resource subscriptions: sessionId → Set of resource URIs
4
+ const resourceSubscriptions = new Map();
5
+ export function getResourceSubscriptions() {
6
+ return resourceSubscriptions;
7
+ }
3
8
  // Session-to-agent name map with 24h TTL
4
9
  const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
5
10
  const sessionAgents = new Map();
@@ -16,8 +21,10 @@ export function getAgentName(sessionId) {
16
21
  function pruneExpiredSessions() {
17
22
  const now = Date.now();
18
23
  for (const [id, entry] of sessionAgents) {
19
- if (now > entry.expiresAt)
24
+ if (now > entry.expiresAt) {
20
25
  sessionAgents.delete(id);
26
+ resourceSubscriptions.delete(id);
27
+ }
21
28
  }
22
29
  }
23
30
  function rpcError(id, code, message) {
@@ -57,7 +64,7 @@ export async function handleMcpRequest(body, sessionId, ctx) {
57
64
  return {
58
65
  body: rpcResult(id, {
59
66
  protocolVersion: "2025-03-26",
60
- capabilities: { tools: {} },
67
+ capabilities: { tools: {}, resources: { subscribe: true } },
61
68
  serverInfo: { name: "palmier", version: "1.0.0" },
62
69
  }),
63
70
  sessionId: newSessionId,
@@ -102,6 +109,55 @@ export async function handleMcpRequest(body, sessionId, ctx) {
102
109
  };
103
110
  }
104
111
  }
112
+ case "resources/list": {
113
+ return {
114
+ body: rpcResult(id, {
115
+ resources: agentResources.map((r) => ({
116
+ uri: r.uri,
117
+ name: r.name,
118
+ description: r.description[0],
119
+ mimeType: r.mimeType,
120
+ })),
121
+ }),
122
+ };
123
+ }
124
+ case "resources/read": {
125
+ const uri = req.params?.uri;
126
+ const resource = agentResourceMap.get(uri);
127
+ if (!resource) {
128
+ return { body: rpcError(id, -32602, `Unknown resource: ${uri}`) };
129
+ }
130
+ return {
131
+ body: rpcResult(id, {
132
+ contents: [{
133
+ uri: resource.uri,
134
+ mimeType: resource.mimeType,
135
+ text: JSON.stringify(resource.read()),
136
+ }],
137
+ }),
138
+ };
139
+ }
140
+ case "resources/subscribe": {
141
+ const uri = req.params?.uri;
142
+ if (!agentResourceMap.has(uri)) {
143
+ return { body: rpcError(id, -32602, `Unknown resource: ${uri}`) };
144
+ }
145
+ if (!sessionId) {
146
+ return { body: rpcError(id, -32600, "Session required for subscriptions") };
147
+ }
148
+ if (!resourceSubscriptions.has(sessionId)) {
149
+ resourceSubscriptions.set(sessionId, new Set());
150
+ }
151
+ resourceSubscriptions.get(sessionId).add(uri);
152
+ return { body: rpcResult(id, {}), stream: true };
153
+ }
154
+ case "resources/unsubscribe": {
155
+ const uri = req.params?.uri;
156
+ if (sessionId) {
157
+ resourceSubscriptions.get(sessionId)?.delete(uri);
158
+ }
159
+ return { body: rpcResult(id, {}) };
160
+ }
105
161
  default:
106
162
  console.warn(`${logPrefix} Unknown method: ${req.method}`);
107
163
  return { body: rpcError(id, -32601, `Method not found: ${req.method}`) };