palmier 0.5.0 → 0.5.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/dist/agents/agent-instructions.md +7 -11
  2. package/dist/agents/agent.d.ts +8 -3
  3. package/dist/agents/agent.js +7 -1
  4. package/dist/agents/claude.d.ts +2 -1
  5. package/dist/agents/claude.js +10 -5
  6. package/dist/agents/codex.d.ts +2 -1
  7. package/dist/agents/codex.js +10 -6
  8. package/dist/agents/copilot.d.ts +2 -1
  9. package/dist/agents/copilot.js +10 -3
  10. package/dist/agents/gemini.d.ts +2 -1
  11. package/dist/agents/gemini.js +11 -7
  12. package/dist/agents/kimi.d.ts +9 -0
  13. package/dist/agents/kimi.js +35 -0
  14. package/dist/agents/openclaw.d.ts +2 -1
  15. package/dist/agents/openclaw.js +3 -1
  16. package/dist/agents/qwen.d.ts +9 -0
  17. package/dist/agents/qwen.js +32 -0
  18. package/dist/agents/shared-prompt.d.ts +1 -1
  19. package/dist/agents/shared-prompt.js +6 -2
  20. package/dist/commands/run.js +22 -5
  21. package/dist/platform/windows.js +17 -1
  22. package/dist/rpc-handler.js +15 -4
  23. package/dist/task.d.ts +13 -3
  24. package/dist/task.js +39 -7
  25. package/dist/transports/http-transport.js +29 -9
  26. package/dist/types.d.ts +1 -0
  27. package/package.json +1 -1
  28. package/src/agents/agent-instructions.md +7 -11
  29. package/src/agents/agent.ts +16 -4
  30. package/src/agents/claude.ts +11 -6
  31. package/src/agents/codex.ts +11 -7
  32. package/src/agents/copilot.ts +10 -4
  33. package/src/agents/gemini.ts +12 -8
  34. package/src/agents/kimi.ts +37 -0
  35. package/src/agents/openclaw.ts +4 -2
  36. package/src/agents/qwen.ts +34 -0
  37. package/src/agents/shared-prompt.ts +6 -2
  38. package/src/commands/run.ts +24 -5
  39. package/src/platform/windows.ts +14 -1
  40. package/src/rpc-handler.ts +17 -4
  41. package/src/task.ts +43 -8
  42. package/src/transports/http-transport.ts +34 -9
  43. package/src/types.ts +1 -0
  44. package/test/agent-instructions.test.ts +31 -0
@@ -2,23 +2,19 @@ You are an AI agent executing a task on behalf of the user via the Palmier platf
2
2
 
3
3
  ## Reporting Output
4
4
 
5
- If you generate report or output files, print each file path on its own line prefixed with [PALMIER_REPORT]:
6
- [PALMIER_REPORT] report.md
7
- [PALMIER_REPORT] summary.md
5
+ If you generate report or output files, print each file path on its own line using this exact format (no code block):
6
+ `[PALMIER_REPORT] <filename>`
8
7
 
9
8
  ## Completion
10
9
 
11
- When you are done, output exactly one of these markers as the very last line:
12
- - Success: [PALMIER_TASK_SUCCESS]
13
- - Failure: [PALMIER_TASK_FAILURE]
14
- Do not wrap them in code blocks or add text on the same line.
10
+ When you are done, output exactly one of these markers as the very last line (no code block, no other text on the same line):
11
+ - Success: `[PALMIER_TASK_SUCCESS]`
12
+ - Failure: `[PALMIER_TASK_FAILURE]`
15
13
 
16
14
  ## Permissions
17
15
 
18
- If the task fails because a tool was denied or you lack the required permissions, print each required permission on its own line prefixed with [PALMIER_PERMISSION]:
19
- [PALMIER_PERMISSION] Read | Read file contents from the repository
20
- [PALMIER_PERMISSION] Bash(npm test) | Run the test suite via npm
21
- [PALMIER_PERMISSION] Write | Write generated output files
16
+ If the task fails because a tool was denied or you lack the required permissions, print each required permission on its own line using this exact format (no code block):
17
+ `[PALMIER_PERMISSION] <tool_name> | <description>`
22
18
 
23
19
  ## HTTP Endpoints
24
20
 
@@ -13,9 +13,13 @@ export interface AgentTool {
13
13
  /** Return the command and args used to generate a plan from a prompt. */
14
14
  getPlanGenerationCommandLine(prompt: string): CommandLine;
15
15
  /** Return the command and args used to run a task. If followupPrompt is provided, use it instead of the task's prompt,
16
- * and treat it as a continuation of the original run (reuse the same session, etc). extraPermissions are transient
17
- * permissions granted for this run only (not persisted in frontmatter). */
18
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
16
+ * and treat it as a continuation of the original run (reuse the same session, etc).
17
+ * extraPermissions: pass an array of RequiredPermission for transient permissions granted for this run only,
18
+ * or pass `"yolo"` to enable yolo mode (auto-approve all tools, skip permission instructions). */
19
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
20
+ /** Whether this agent supports permission overrides (e.g. --allowedTools).
21
+ * If false, the permissions section is omitted from agent instructions. */
22
+ supportsPermissions: boolean;
19
23
  /** Detect whether the agent CLI is available and perform any agent-specific
20
24
  * initialization. Returns true if the agent was detected and initialized successfully. */
21
25
  init(): Promise<boolean>;
@@ -23,6 +27,7 @@ export interface AgentTool {
23
27
  export interface DetectedAgent {
24
28
  key: string;
25
29
  label: string;
30
+ supportsPermissions: boolean;
26
31
  }
27
32
  export declare function detectAgents(): Promise<DetectedAgent[]>;
28
33
  export declare function getAgent(name: string): AgentTool;
@@ -3,12 +3,16 @@ import { GeminiAgent } from "./gemini.js";
3
3
  import { CodexAgent } from "./codex.js";
4
4
  import { OpenClawAgent } from "./openclaw.js";
5
5
  import { CopilotAgent } from "./copilot.js";
6
+ import { QwenAgent } from "./qwen.js";
7
+ import { KimiAgent } from "./kimi.js";
6
8
  const agentRegistry = {
7
9
  claude: new ClaudeAgent(),
8
10
  gemini: new GeminiAgent(),
9
11
  codex: new CodexAgent(),
10
12
  openclaw: new OpenClawAgent(),
11
13
  copilot: new CopilotAgent(),
14
+ qwen: new QwenAgent(),
15
+ kimi: new KimiAgent(),
12
16
  };
13
17
  const agentLabels = {
14
18
  claude: "Claude Code",
@@ -16,6 +20,8 @@ const agentLabels = {
16
20
  codex: "Codex CLI",
17
21
  openclaw: "OpenClaw",
18
22
  copilot: "Copilot CLI",
23
+ qwen: "Qwen Code",
24
+ kimi: "Kimi Code",
19
25
  };
20
26
  export async function detectAgents() {
21
27
  const detected = [];
@@ -23,7 +29,7 @@ export async function detectAgents() {
23
29
  const label = agentLabels[key] ?? key;
24
30
  const ok = await agent.init();
25
31
  if (ok)
26
- detected.push({ key, label });
32
+ detected.push({ key, label, supportsPermissions: agent.supportsPermissions });
27
33
  }
28
34
  return detected;
29
35
  }
@@ -1,8 +1,9 @@
1
1
  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
+ supportsPermissions: boolean;
4
5
  getPlanGenerationCommandLine(prompt: string): CommandLine;
5
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
6
7
  init(): Promise<boolean>;
7
8
  }
8
9
  //# sourceMappingURL=claude.d.ts.map
@@ -2,6 +2,7 @@ import { execSync } from "child_process";
2
2
  import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class ClaudeAgent {
5
+ supportsPermissions = true;
5
6
  getPlanGenerationCommandLine(prompt) {
6
7
  return {
7
8
  command: "claude",
@@ -9,11 +10,15 @@ export class ClaudeAgent {
9
10
  };
10
11
  }
11
12
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
12
- const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id) + "\n\n" + (task.body || task.frontmatter.user_prompt));
13
- const args = ["--permission-mode", "acceptEdits", "-p", "--allowedTools", "WebFetch"];
14
- const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
15
- for (const p of allPerms) {
16
- args.push("--allowedTools", p.name);
13
+ const yolo = extraPermissions === "yolo";
14
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
15
+ const args = ["--permission-mode", yolo ? "bypassPermissions" : "acceptEdits", "-p"];
16
+ if (!yolo) {
17
+ args.push("--allowedTools", "WebFetch");
18
+ const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
19
+ for (const p of allPerms) {
20
+ args.push("--allowedTools", p.name);
21
+ }
17
22
  }
18
23
  if (followupPrompt) {
19
24
  args.push("-c");
@@ -1,8 +1,9 @@
1
1
  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
+ supportsPermissions: boolean;
4
5
  getPlanGenerationCommandLine(prompt: string): CommandLine;
5
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
6
7
  init(): Promise<boolean>;
7
8
  }
8
9
  //# sourceMappingURL=codex.d.ts.map
@@ -2,6 +2,7 @@ import { execSync } from "child_process";
2
2
  import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class CodexAgent {
5
+ supportsPermissions = true;
5
6
  getPlanGenerationCommandLine(prompt) {
6
7
  return {
7
8
  command: "codex",
@@ -9,13 +10,16 @@ export class CodexAgent {
9
10
  };
10
11
  }
11
12
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
12
- const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id) + "\n\n" + (task.body || task.frontmatter.user_prompt));
13
+ const yolo = extraPermissions === "yolo";
14
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
13
15
  // Using danger-full-access until workspace-write is fixed: https://github.com/openai/codex/issues/12572
14
- const args = ["exec", "--full-auto", "--skip-git-repo-check", "--sandbox", "danger-full-access"];
15
- const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
16
- for (const p of allPerms) {
17
- args.push("--config");
18
- args.push(`apps.${p.name}.default_tools_approval_mode="approve"`);
16
+ const args = ["exec", "--skip-git-repo-check", "--sandbox", "danger-full-access"];
17
+ if (!yolo) {
18
+ const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
19
+ for (const p of allPerms) {
20
+ args.push("--config");
21
+ args.push(`apps.${p.name}.default_tools_approval_mode="approve"`);
22
+ }
19
23
  }
20
24
  if (followupPrompt) {
21
25
  args.push("resume", "--last");
@@ -1,8 +1,9 @@
1
1
  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
+ supportsPermissions: boolean;
4
5
  getPlanGenerationCommandLine(prompt: string): CommandLine;
5
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
6
7
  init(): Promise<boolean>;
7
8
  }
8
9
  //# sourceMappingURL=copilot.d.ts.map
@@ -2,6 +2,7 @@ import { execSync } from "child_process";
2
2
  import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class CopilotAgent {
5
+ supportsPermissions = false;
5
6
  getPlanGenerationCommandLine(prompt) {
6
7
  return {
7
8
  command: "copilot",
@@ -9,10 +10,16 @@ export class CopilotAgent {
9
10
  };
10
11
  }
11
12
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
12
- const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id) + "\n\n" + (task.body || task.frontmatter.user_prompt));
13
+ const yolo = extraPermissions === "yolo";
14
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
13
15
  const args = ["-p", prompt];
14
- const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
15
- args.push(`--allow-tool=${["web_fetch", ...allPerms.map((p) => p.name)].join(",")}`);
16
+ if (yolo) {
17
+ args.push("--yolo");
18
+ }
19
+ else {
20
+ const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
21
+ args.push(`--allow-tool=${["web_fetch", ...allPerms.map((p) => p.name)].join(",")}`);
22
+ }
16
23
  if (followupPrompt) {
17
24
  args.push("--continue");
18
25
  }
@@ -1,8 +1,9 @@
1
1
  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
+ supportsPermissions: boolean;
4
5
  getPlanGenerationCommandLine(prompt: string): CommandLine;
5
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
6
7
  init(): Promise<boolean>;
7
8
  }
8
9
  //# sourceMappingURL=gemini.d.ts.map
@@ -2,6 +2,7 @@ import { execSync } from "child_process";
2
2
  import { getAgentInstructions } from "./shared-prompt.js";
3
3
  import { SHELL } from "../platform/index.js";
4
4
  export class GeminiAgent {
5
+ supportsPermissions = true;
5
6
  getPlanGenerationCommandLine(prompt) {
6
7
  return {
7
8
  command: "gemini",
@@ -9,19 +10,22 @@ export class GeminiAgent {
9
10
  };
10
11
  }
11
12
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
12
- const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id) + "\n\n" + (task.body || task.frontmatter.user_prompt));
13
- const args = ["--approval-mode", "auto_edit", "--allowed-tools", "web_fetch"];
14
- const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
15
- if (allPerms.length > 0) {
13
+ const yolo = extraPermissions === "yolo";
14
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
15
+ const args = ["--approval-mode", yolo ? "yolo" : "auto_edit"];
16
+ if (!yolo) {
17
+ const tools = ["run_shell_command", "web_fetch"];
18
+ const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
16
19
  for (const p of allPerms) {
17
- args.push(p.name);
20
+ tools.push(p.name);
18
21
  }
22
+ args.push("--allowed-tools", tools.join(","));
19
23
  }
20
24
  if (followupPrompt) {
21
25
  args.push("--resume");
22
26
  } // continue mode for followups
23
- args.push("--prompt", prompt);
24
- return { command: "gemini", args };
27
+ args.push("--prompt", "-"); // read prompt from stdin to avoid command line length limits
28
+ return { command: "gemini", args, stdin: prompt };
25
29
  }
26
30
  async init() {
27
31
  try {
@@ -0,0 +1,9 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import type { AgentTool, CommandLine } from "./agent.js";
3
+ export declare class KimiAgent implements AgentTool {
4
+ supportsPermissions: boolean;
5
+ getPlanGenerationCommandLine(prompt: string): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
+ init(): Promise<boolean>;
8
+ }
9
+ //# sourceMappingURL=kimi.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { execSync } from "child_process";
2
+ import { getAgentInstructions } from "./shared-prompt.js";
3
+ import { SHELL } from "../platform/index.js";
4
+ export class KimiAgent {
5
+ supportsPermissions = false;
6
+ getPlanGenerationCommandLine(prompt) {
7
+ return {
8
+ command: "kimi",
9
+ args: ["-p", prompt],
10
+ };
11
+ }
12
+ getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
+ const yolo = extraPermissions === "yolo";
14
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
15
+ const args = [];
16
+ if (yolo) {
17
+ args.push("--yolo");
18
+ }
19
+ if (followupPrompt) {
20
+ args.push("--continue");
21
+ }
22
+ args.push("-p", prompt);
23
+ return { command: "kimi", args };
24
+ }
25
+ async init() {
26
+ try {
27
+ execSync("kimi --version", { stdio: "ignore", shell: SHELL });
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ }
35
+ //# sourceMappingURL=kimi.js.map
@@ -1,8 +1,9 @@
1
1
  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
+ supportsPermissions: boolean;
4
5
  getPlanGenerationCommandLine(prompt: string): CommandLine;
5
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
6
7
  init(): Promise<boolean>;
7
8
  }
8
9
  //# sourceMappingURL=openclaw.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { execSync } from "child_process";
2
2
  import { getAgentInstructions } from "./shared-prompt.js";
3
3
  export class OpenClawAgent {
4
+ supportsPermissions = false;
4
5
  getPlanGenerationCommandLine(prompt) {
5
6
  return {
6
7
  command: "openclaw",
@@ -8,7 +9,8 @@ export class OpenClawAgent {
8
9
  };
9
10
  }
10
11
  getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
11
- const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id) + "\n\n" + (task.body || task.frontmatter.user_prompt));
12
+ const yolo = extraPermissions === "yolo";
13
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
12
14
  // OpenClaw does not support stdin as prompt.
13
15
  const args = ["agent", "--local", "--session-id", task.frontmatter.id, "--message", prompt];
14
16
  return { command: "openclaw", args };
@@ -0,0 +1,9 @@
1
+ import type { ParsedTask, RequiredPermission } from "../types.js";
2
+ import type { AgentTool, CommandLine } from "./agent.js";
3
+ export declare class QwenAgent implements AgentTool {
4
+ supportsPermissions: boolean;
5
+ getPlanGenerationCommandLine(prompt: string): CommandLine;
6
+ getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine;
7
+ init(): Promise<boolean>;
8
+ }
9
+ //# sourceMappingURL=qwen.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { execSync } from "child_process";
2
+ import { getAgentInstructions } from "./shared-prompt.js";
3
+ import { SHELL } from "../platform/index.js";
4
+ export class QwenAgent {
5
+ supportsPermissions = false;
6
+ getPlanGenerationCommandLine(prompt) {
7
+ return {
8
+ command: "qwen",
9
+ args: ["-p", prompt],
10
+ };
11
+ }
12
+ getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
13
+ const yolo = extraPermissions === "yolo";
14
+ const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
15
+ const args = ["--approval-mode", yolo ? "yolo" : "auto-edit"];
16
+ if (followupPrompt) {
17
+ args.push("-c");
18
+ }
19
+ args.push("-p", prompt);
20
+ return { command: "qwen", args };
21
+ }
22
+ async init() {
23
+ try {
24
+ execSync("qwen --version", { stdio: "ignore", shell: SHELL });
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+ }
32
+ //# sourceMappingURL=qwen.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Agent instructions with the serve daemon's HTTP port and task ID baked in.
3
3
  */
4
- export declare function getAgentInstructions(taskId: string): string;
4
+ export declare function getAgentInstructions(taskId: string, skipPermissions?: boolean): string;
5
5
  export declare const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
6
6
  export declare const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
7
7
  export declare const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
@@ -7,11 +7,15 @@ const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(path.join(__dirname, "agent-
7
7
  /**
8
8
  * Agent instructions with the serve daemon's HTTP port and task ID baked in.
9
9
  */
10
- export function getAgentInstructions(taskId) {
10
+ export function getAgentInstructions(taskId, skipPermissions) {
11
11
  const port = loadConfig().httpPort ?? 7400;
12
- return AGENT_INSTRUCTIONS_TEMPLATE
12
+ let instructions = AGENT_INSTRUCTIONS_TEMPLATE
13
13
  .replace(/\{\{PORT\}\}/g, String(port))
14
14
  .replace(/\{\{TASK_ID\}\}/g, taskId);
15
+ if (skipPermissions) {
16
+ instructions = instructions.replace(/## Permissions\r?\n[\s\S]*?(?=## |\r?\n---)/m, "");
17
+ }
18
+ return instructions;
15
19
  }
16
20
  export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
17
21
  export const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
@@ -33,7 +33,10 @@ async function invokeAgentWithRetries(ctx, invokeTask) {
33
33
  publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
34
34
  }, 500);
35
35
  }
36
- const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask, undefined, ctx.transientPermissions);
36
+ const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask, undefined, ctx.task.frontmatter.yolo_mode ? "yolo" : ctx.transientPermissions);
37
+ const truncate = (s, max = 100) => s.length > max ? s.slice(0, max) + "…" : s;
38
+ const displayArgs = args.map((a) => truncate(a));
39
+ console.log(`[invoke] ${command} ${displayArgs.join(" ")}${stdin ? ` (stdin: ${truncate(stdin, 100)})` : ""}`);
37
40
  const result = await spawnCommand(command, args, {
38
41
  cwd: getRunDir(ctx.taskDir, ctx.runId),
39
42
  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 ?? 7400) },
@@ -372,7 +375,11 @@ async function requestPermission(config, task, taskDir, requiredPermissions) {
372
375
  permissions: requiredPermissions,
373
376
  }),
374
377
  });
375
- const { response } = await res.json();
378
+ const body = await res.json();
379
+ const response = body.response;
380
+ if (!response || !["granted", "granted_all", "aborted"].includes(response)) {
381
+ throw new Error(`Permission request failed: ${body.error ?? `unexpected response: ${JSON.stringify(body)}`}`);
382
+ }
376
383
  writeTaskStatus(taskDir, {
377
384
  running_state: response === "aborted" ? "aborted" : "started",
378
385
  time_stamp: Date.now(),
@@ -386,7 +393,11 @@ async function requestConfirmation(config, task, taskDir) {
386
393
  headers: { "Content-Type": "application/json" },
387
394
  body: JSON.stringify({ taskId: task.frontmatter.id, taskName: task.frontmatter.name }),
388
395
  });
389
- const { confirmed } = await res.json();
396
+ const body = await res.json();
397
+ if (typeof body.confirmed !== "boolean") {
398
+ throw new Error(`Confirmation request failed: ${body.error ?? `unexpected response: ${JSON.stringify(body)}`}`);
399
+ }
400
+ const { confirmed } = body;
390
401
  writeTaskStatus(taskDir, {
391
402
  running_state: confirmed ? "started" : "aborted",
392
403
  time_stamp: Date.now(),
@@ -434,9 +445,15 @@ export function parsePermissions(output) {
434
445
  */
435
446
  export function parseTaskOutcome(output) {
436
447
  const lastChunk = output.slice(-500);
437
- if (lastChunk.includes(TASK_FAILURE_MARKER))
448
+ const regex = new RegExp(`^\\${TASK_FAILURE_MARKER}$|^\\${TASK_SUCCESS_MARKER}$`, "gm");
449
+ let last = null;
450
+ let match;
451
+ while ((match = regex.exec(lastChunk)) !== null) {
452
+ last = match[0];
453
+ }
454
+ if (last === TASK_FAILURE_MARKER)
438
455
  return "failed";
439
- if (lastChunk.includes(TASK_SUCCESS_MARKER))
456
+ if (last === TASK_SUCCESS_MARKER)
440
457
  return "finished";
441
458
  return "finished";
442
459
  }
@@ -106,7 +106,7 @@ export class WindowsPlatform {
106
106
  this.startDaemonTask();
107
107
  process.exit(0);
108
108
  }
109
- // Kill old daemon first, then spawn new one.
109
+ // Kill old daemon by PID
110
110
  if (oldPid) {
111
111
  try {
112
112
  execFileSync("taskkill", ["/pid", oldPid, "/f", "/t"], { windowsHide: true, stdio: "pipe" });
@@ -115,6 +115,22 @@ export class WindowsPlatform {
115
115
  // Process may have already exited
116
116
  }
117
117
  }
118
+ // Also kill any stale palmier serve processes (e.g. leftover from a previous daemon)
119
+ try {
120
+ const out = execFileSync("wmic", ["process", "where", `CommandLine like '%palmier%serve%' and ProcessId != '${process.pid}'`, "get", "ProcessId"], { encoding: "utf-8", windowsHide: true, stdio: "pipe" });
121
+ for (const line of out.split("\n")) {
122
+ const pid = line.trim();
123
+ if (pid && /^\d+$/.test(pid)) {
124
+ try {
125
+ execFileSync("taskkill", ["/pid", pid, "/f", "/t"], { windowsHide: true, stdio: "pipe" });
126
+ }
127
+ catch { }
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ // wmic may not be available on all Windows versions
133
+ }
118
134
  this.startDaemonTask();
119
135
  }
120
136
  /** Create or update the Task Scheduler entry for the daemon. */
@@ -126,10 +126,17 @@ const activeFollowups = new Map();
126
126
  export function createRpcHandler(config, nc) {
127
127
  function flattenTask(task) {
128
128
  const taskDir = getTaskDir(config.projectRoot, task.frontmatter.id);
129
+ const status = readTaskStatus(taskDir);
130
+ const pending = getPending(task.frontmatter.id);
129
131
  return {
130
132
  ...task.frontmatter,
131
133
  body: task.body,
132
- status: readTaskStatus(taskDir),
134
+ status: status ? {
135
+ ...status,
136
+ ...(pending?.type === "confirmation" ? { pending_confirmation: true } : {}),
137
+ ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
138
+ ...(pending?.type === "input" ? { pending_input: pending.params } : {}),
139
+ } : undefined,
133
140
  };
134
141
  }
135
142
  async function handleRpc(request) {
@@ -176,6 +183,7 @@ export function createRpcHandler(config, nc) {
176
183
  triggers: params.triggers ?? [],
177
184
  triggers_enabled: params.triggers_enabled ?? true,
178
185
  requires_confirmation: params.requires_confirmation ?? true,
186
+ ...(params.yolo_mode ? { yolo_mode: true } : {}),
179
187
  ...(params.command ? { command: params.command } : {}),
180
188
  },
181
189
  body,
@@ -204,6 +212,8 @@ export function createRpcHandler(config, nc) {
204
212
  existing.frontmatter.triggers_enabled = params.triggers_enabled;
205
213
  if (params.requires_confirmation !== undefined)
206
214
  existing.frontmatter.requires_confirmation = params.requires_confirmation;
215
+ if (params.yolo_mode !== undefined)
216
+ existing.frontmatter.yolo_mode = params.yolo_mode || undefined;
207
217
  if (params.command !== undefined) {
208
218
  if (params.command) {
209
219
  existing.frontmatter.command = params.command;
@@ -254,6 +264,7 @@ export function createRpcHandler(config, nc) {
254
264
  triggers: [],
255
265
  triggers_enabled: false,
256
266
  requires_confirmation: params.requires_confirmation ?? false,
267
+ ...(params.yolo_mode ? { yolo_mode: true } : {}),
257
268
  ...(params.command ? { command: params.command } : {}),
258
269
  },
259
270
  body: "",
@@ -317,7 +328,7 @@ export function createRpcHandler(config, nc) {
317
328
  await publishHostEvent(nc, config.hostId, params.id, { event_type: "result-updated", run_id: params.run_id });
318
329
  // Fire-and-forget: invoke agent inline as a child of the serve process
319
330
  const followupAgent = getAgent(followupTask.frontmatter.agent);
320
- const { command: cmd, args: cmdArgs, stdin } = followupAgent.getTaskRunCommandLine(followupTask, params.message, followupTask.frontmatter.permissions);
331
+ const { command: cmd, args: cmdArgs, stdin } = followupAgent.getTaskRunCommandLine(followupTask, params.message, followupTask.frontmatter.yolo_mode ? "yolo" : followupTask.frontmatter.permissions);
321
332
  // Spawn directly via crossSpawn so we can track and kill the child
322
333
  const child = crossSpawn(cmd, cmdArgs, {
323
334
  cwd: followupRunDir,
@@ -481,8 +492,8 @@ export function createRpcHandler(config, nc) {
481
492
  const reports = [];
482
493
  const runDir = path.join(config.projectRoot, "tasks", params.id, params.run_id);
483
494
  for (const file of params.report_files) {
484
- if (!file.endsWith(".md")) {
485
- reports.push({ file, error: "must end with .md" });
495
+ if (!file.endsWith(".md") && !file.endsWith(".txt")) {
496
+ reports.push({ file, error: "must end with .md or .txt" });
486
497
  continue;
487
498
  }
488
499
  const basename = path.basename(file);
package/dist/task.d.ts CHANGED
@@ -58,13 +58,23 @@ export declare function appendRunMessage(taskDir: string, runId: string, msg: Co
58
58
  export declare function beginStreamingMessage(taskDir: string, runId: string, time: number): StreamingMessageWriter;
59
59
  export declare class StreamingMessageWriter {
60
60
  private filePath;
61
- private delimiter;
62
- constructor(filePath: string, delimiter: string);
61
+ constructor(filePath: string);
63
62
  /** Append a chunk of content to the current message. */
64
63
  write(chunk: string): void;
65
- /** Finalize the message. If attachments are provided, rewrites the delimiter to include them. */
64
+ /** Finalize the message. If attachments are provided, rewrites the last assistant delimiter to include them. */
66
65
  end(attachments?: string[]): void;
67
66
  }
67
+ /**
68
+ * Splice a user message into a running assistant stream.
69
+ * Ends the current assistant block, writes the user message,
70
+ * then opens a new assistant block — all as direct file appends.
71
+ * The existing StreamingMessageWriter keeps working because its
72
+ * write() is just appendFileSync, so subsequent chunks land in
73
+ * the new assistant block.
74
+ */
75
+ export declare function spliceUserMessage(taskDir: string, runId: string, userMsg: ConversationMessage,
76
+ /** Optional text to append to the current assistant block before ending it. */
77
+ assistantAppend?: string): void;
68
78
  /**
69
79
  * Read conversation messages from a run's TASKRUN.md file.
70
80
  */