palmier 0.6.1 → 0.6.2

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 (44) hide show
  1. package/DISCLAIMER.md +2 -2
  2. package/dist/agents/agent-instructions.md +1 -1
  3. package/dist/agents/agent.d.ts +2 -0
  4. package/dist/agents/agent.js +21 -0
  5. package/dist/agents/aider.d.ts +9 -0
  6. package/dist/agents/aider.js +32 -0
  7. package/dist/agents/cursor.d.ts +9 -0
  8. package/dist/agents/cursor.js +35 -0
  9. package/dist/agents/deepagents.d.ts +9 -0
  10. package/dist/agents/deepagents.js +35 -0
  11. package/dist/agents/droid.d.ts +9 -0
  12. package/dist/agents/droid.js +32 -0
  13. package/dist/agents/goose.d.ts +9 -0
  14. package/dist/agents/goose.js +32 -0
  15. package/dist/agents/opencode.d.ts +9 -0
  16. package/dist/agents/opencode.js +35 -0
  17. package/dist/agents/openhands.d.ts +9 -0
  18. package/dist/agents/openhands.js +35 -0
  19. package/dist/commands/run.js +2 -2
  20. package/dist/pwa/assets/{index-C7Ib48wG.js → index-ByhOhTz1.js} +1 -1
  21. package/dist/pwa/index.html +2 -2
  22. package/dist/pwa/manifest.webmanifest +1 -1
  23. package/dist/pwa/service-worker.js +1 -1
  24. package/dist/rpc-handler.js +4 -3
  25. package/dist/transports/http-transport.js +3 -1
  26. package/package.json +1 -1
  27. package/palmier-server/pwa/index.html +1 -1
  28. package/palmier-server/pwa/src/constants.ts +1 -1
  29. package/palmier-server/pwa/src/service-worker.ts +3 -3
  30. package/palmier-server/pwa/vite.config.ts +1 -1
  31. package/palmier-server/server/src/index.ts +2 -0
  32. package/palmier-server/spec.md +1 -1
  33. package/src/agents/agent-instructions.md +1 -1
  34. package/src/agents/agent.ts +23 -0
  35. package/src/agents/aider.ts +37 -0
  36. package/src/agents/cursor.ts +38 -0
  37. package/src/agents/deepagents.ts +38 -0
  38. package/src/agents/droid.ts +37 -0
  39. package/src/agents/goose.ts +35 -0
  40. package/src/agents/opencode.ts +38 -0
  41. package/src/agents/openhands.ts +38 -0
  42. package/src/commands/run.ts +2 -2
  43. package/src/rpc-handler.ts +4 -3
  44. package/src/transports/http-transport.ts +3 -2
@@ -2,16 +2,25 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
2
2
  import { ClaudeAgent } from "./claude.js";
3
3
  import { GeminiAgent } from "./gemini.js";
4
4
  import { CodexAgent } from "./codex.js";
5
+ import { DroidAgent } from "./droid.js";
5
6
  import { OpenClawAgent } from "./openclaw.js";
6
7
  import { CopilotAgent } from "./copilot.js";
7
8
  import { QwenAgent } from "./qwen.js";
8
9
  import { KimiAgent } from "./kimi.js";
10
+ import { GooseAgent } from "./goose.js";
11
+ import { OpenCodeAgent } from "./opencode.js";
12
+ import { DeepAgents } from "./deepagents.js";
13
+ import { Aider } from "./aider.js";
14
+ import { OpenHands } from "./openhands.js";
15
+ import { Cursor } from "./cursor.js";
9
16
 
10
17
  export interface CommandLine {
11
18
  command: string;
12
19
  args: string[];
13
20
  /** If provided, the string is written to the process's stdin and then the pipe is closed. */
14
21
  stdin?: string;
22
+ /** Additional environment variables to set for the spawned process. */
23
+ env?: Record<string, string>;
15
24
  }
16
25
 
17
26
  /**
@@ -45,16 +54,30 @@ const agentRegistry: Record<string, AgentTool> = {
45
54
  copilot: new CopilotAgent(),
46
55
  qwen: new QwenAgent(),
47
56
  kimi: new KimiAgent(),
57
+ droid: new DroidAgent(),
58
+ goose: new GooseAgent(),
59
+ opencode: new OpenCodeAgent(),
60
+ deepagents: new DeepAgents(),
61
+ aider: new Aider(),
62
+ openhands: new OpenHands(),
63
+ cursor: new Cursor(),
48
64
  };
49
65
 
50
66
  const agentLabels: Record<string, string> = {
51
67
  claude: "Claude Code",
52
68
  gemini: "Gemini CLI",
53
69
  codex: "Codex CLI",
70
+ droid: "Droid CLI",
54
71
  openclaw: "OpenClaw",
55
72
  copilot: "Copilot CLI",
56
73
  qwen: "Qwen Code",
57
74
  kimi: "Kimi Code",
75
+ goose: "Goose CLI",
76
+ opencode: "OpenCode",
77
+ deepagents: "Deep Agents CLI",
78
+ aider: "Aider",
79
+ openhands: "OpenHands",
80
+ cursor: "Cursor CLI",
58
81
  };
59
82
 
60
83
  export interface DetectedAgent {
@@ -0,0 +1,37 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class Aider implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "aider",
12
+ args: ["--message", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = [];
20
+
21
+ if (yolo) {
22
+ args.push("--yes-always");
23
+ }
24
+ args.push("--message", prompt);
25
+
26
+ return { command: "aider", args};
27
+ }
28
+
29
+ async init(): Promise<boolean> {
30
+ try {
31
+ execSync("aider --version", { stdio: "ignore", shell: SHELL });
32
+ } catch {
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+ }
@@ -0,0 +1,38 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class Cursor implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "cursor",
12
+ args: ["-p", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = [];
20
+
21
+ if (yolo) {
22
+ args.push("--force");
23
+ }
24
+ if (followupPrompt) {args.push("--continue");} // continue mode for followups
25
+ args.push("-p", prompt);
26
+
27
+ return { command: "cursor", args};
28
+ }
29
+
30
+ async init(): Promise<boolean> {
31
+ try {
32
+ execSync("cursor --version", { stdio: "ignore", shell: SHELL });
33
+ } catch {
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+ }
@@ -0,0 +1,38 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class DeepAgents implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "deepagents",
12
+ args: ["--non-interactive", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = [];
20
+
21
+ if (yolo) {
22
+ args.push("--auto-approve");
23
+ }
24
+ if (followupPrompt) {args.push("--resume");} // continue mode for followups
25
+ args.push("--non-interactive", prompt);
26
+
27
+ return { command: "deepagents", args};
28
+ }
29
+
30
+ async init(): Promise<boolean> {
31
+ try {
32
+ execSync("deepagents --version", { stdio: "ignore", shell: SHELL });
33
+ } catch {
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+ }
@@ -0,0 +1,37 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class DroidAgent implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "droid",
12
+ args: ["exec", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = ["exec", "--session-id", task.frontmatter.id];
20
+
21
+ if (yolo) {
22
+ args.push("--skip-permissions-unsafe");
23
+ }
24
+ args.push(prompt);
25
+
26
+ return { command: "droid", args};
27
+ }
28
+
29
+ async init(): Promise<boolean> {
30
+ try {
31
+ execSync("droid --version", { stdio: "ignore", shell: SHELL });
32
+ } catch {
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+ }
@@ -0,0 +1,35 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class GooseAgent implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "goose",
12
+ args: ["run", "--text", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = ["run"];
20
+
21
+ if (followupPrompt) {args.push("--resume");} // continue mode for followups
22
+ args.push("--text", prompt);
23
+
24
+ return { command: "goose", args, ...(yolo ? { env: { GOOSE_MODE: "auto" } } : {}) };
25
+ }
26
+
27
+ async init(): Promise<boolean> {
28
+ try {
29
+ execSync("goose --version", { stdio: "ignore", shell: SHELL });
30
+ } catch {
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+ }
@@ -0,0 +1,38 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class OpenCodeAgent implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "opencode",
12
+ args: ["run", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = ["run"];
20
+
21
+ if (yolo) {
22
+ args.push("--dangerously-skip-permissions");
23
+ }
24
+ if (followupPrompt) {args.push("--continue");} // continue mode for followups
25
+ args.push(prompt);
26
+
27
+ return { command: "opencode", args};
28
+ }
29
+
30
+ async init(): Promise<boolean> {
31
+ try {
32
+ execSync("opencode --version", { stdio: "ignore", shell: SHELL });
33
+ } catch {
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+ }
@@ -0,0 +1,38 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import { execSync } from "child_process";
3
+ import type { AgentTool, CommandLine } from "./agent.js";
4
+ import { getAgentInstructions } from "./shared-prompt.js";
5
+ import { SHELL } from "../platform/index.js";
6
+
7
+ export class OpenHands implements AgentTool {
8
+ supportsPermissions = false;
9
+ getPlanGenerationCommandLine(prompt: string): CommandLine {
10
+ return {
11
+ command: "openhands",
12
+ args: ["--headless", "-t", prompt],
13
+ };
14
+ }
15
+
16
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
17
+ const yolo = extraPermissions === "yolo";
18
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
19
+ const args = ["--headless"];
20
+
21
+ if (yolo) {
22
+ args.push("--always-approve");
23
+ }
24
+ if (followupPrompt) {args.push("--resume", "--last");} // continue mode for followups
25
+ args.push("-t", prompt);
26
+
27
+ return { command: "openhands", args};
28
+ }
29
+
30
+ async init(): Promise<boolean> {
31
+ try {
32
+ execSync("openhands --version", { stdio: "ignore", shell: SHELL });
33
+ } catch {
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+ }
@@ -62,7 +62,7 @@ async function invokeAgentWithRetries(
62
62
  }, 500);
63
63
  }
64
64
 
65
- const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(
65
+ const { command, args, stdin, env: agentEnv } = ctx.agent.getTaskRunCommandLine(
66
66
  invokeTask, undefined, ctx.task.frontmatter.yolo_mode ? "yolo" : ctx.transientPermissions,
67
67
  );
68
68
  const truncate = (s: string, max = 100) => s.length > max ? s.slice(0, max) + "…" : s;
@@ -70,7 +70,7 @@ async function invokeAgentWithRetries(
70
70
  console.log(`[invoke] ${command} ${displayArgs.join(" ")}${stdin ? ` (stdin: ${truncate(stdin, 100)})` : ""}`);
71
71
  const result = await spawnCommand(command, args, {
72
72
  cwd: getRunDir(ctx.taskDir, ctx.runId),
73
- env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 9966) },
73
+ env: { ...ctx.guiEnv, ...agentEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 9966) },
74
74
  echoStdout: true,
75
75
  resolveOnFailure: true,
76
76
  stdin,
@@ -124,13 +124,14 @@ async function generatePlan(
124
124
  ): Promise<{ name: string; body: string }> {
125
125
  const fullPrompt = PLAN_GENERATION_PROMPT + userPrompt;
126
126
  const planAgent = getAgent(agentName);
127
- const { command, args, stdin } = planAgent.getPlanGenerationCommandLine(fullPrompt);
127
+ const { command, args, stdin, env: agentEnv } = planAgent.getPlanGenerationCommandLine(fullPrompt);
128
128
  console.log(`[generatePlan] Running: ${command} ${args.join(" ")}`);
129
129
 
130
130
  const { output } = await spawnCommand(command, args, {
131
131
  cwd: projectRoot,
132
132
  timeout: 120_000,
133
133
  stdin,
134
+ ...(agentEnv ? { env: agentEnv } : {}),
134
135
  });
135
136
 
136
137
  let name = "";
@@ -423,7 +424,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
423
424
 
424
425
  // Fire-and-forget: invoke agent inline as a child of the serve process
425
426
  const followupAgent = getAgent(followupTask.frontmatter.agent);
426
- const { command: cmd, args: cmdArgs, stdin } = followupAgent.getTaskRunCommandLine(
427
+ const { command: cmd, args: cmdArgs, stdin, env: followupAgentEnv } = followupAgent.getTaskRunCommandLine(
427
428
  followupTask, params.message, followupTask.frontmatter.yolo_mode ? "yolo" : followupTask.frontmatter.permissions,
428
429
  );
429
430
 
@@ -431,7 +432,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
431
432
  const child = crossSpawn(cmd, cmdArgs, {
432
433
  cwd: followupRunDir,
433
434
  stdio: [stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
434
- env: { ...process.env, PALMIER_TASK_ID: params.id },
435
+ env: { ...process.env, ...followupAgentEnv, PALMIER_TASK_ID: params.id },
435
436
  windowsHide: true,
436
437
  });
437
438
  if (stdin != null) child.stdin!.end(stdin);
@@ -225,11 +225,12 @@ export async function startHttpTransport(
225
225
 
226
226
  try {
227
227
  const body = await readBody(req);
228
- const { title, body: notifBody } = JSON.parse(body) as { title: string; body: string };
228
+ const { taskId: notifTaskId, title, body: notifBody } = JSON.parse(body) as { taskId?: string; title: string; body: string };
229
229
  if (!title || !notifBody) { sendJson(res, 400, { error: "title and body are required" }); return; }
230
230
 
231
231
  const sc = StringCodec();
232
- const payload = { hostId: config.hostId, title, body: notifBody };
232
+ const payload: Record<string, string> = { hostId: config.hostId, title, body: notifBody };
233
+ if (notifTaskId) payload.task_id = notifTaskId;
233
234
  const subject = `host.${config.hostId}.push.send`;
234
235
  const reply = await nc.request(subject, sc.encode(JSON.stringify(payload)), { timeout: 15_000 });
235
236
  const result = JSON.parse(sc.decode(reply.data)) as { ok?: boolean; error?: string };