palmier 0.5.1 → 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 (42) 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 +12 -3
  21. package/dist/rpc-handler.js +7 -3
  22. package/dist/task.d.ts +13 -3
  23. package/dist/task.js +39 -7
  24. package/dist/transports/http-transport.js +23 -6
  25. package/dist/types.d.ts +1 -0
  26. package/package.json +1 -1
  27. package/src/agents/agent-instructions.md +7 -11
  28. package/src/agents/agent.ts +16 -4
  29. package/src/agents/claude.ts +11 -6
  30. package/src/agents/codex.ts +11 -7
  31. package/src/agents/copilot.ts +10 -4
  32. package/src/agents/gemini.ts +12 -8
  33. package/src/agents/kimi.ts +37 -0
  34. package/src/agents/openclaw.ts +4 -2
  35. package/src/agents/qwen.ts +34 -0
  36. package/src/agents/shared-prompt.ts +6 -2
  37. package/src/commands/run.ts +14 -3
  38. package/src/rpc-handler.ts +9 -3
  39. package/src/task.ts +43 -8
  40. package/src/transports/http-transport.ts +25 -6
  41. package/src/types.ts +1 -0
  42. 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) },
@@ -442,9 +445,15 @@ export function parsePermissions(output) {
442
445
  */
443
446
  export function parseTaskOutcome(output) {
444
447
  const lastChunk = output.slice(-500);
445
- 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)
446
455
  return "failed";
447
- if (lastChunk.includes(TASK_SUCCESS_MARKER))
456
+ if (last === TASK_SUCCESS_MARKER)
448
457
  return "finished";
449
458
  return "finished";
450
459
  }
@@ -183,6 +183,7 @@ export function createRpcHandler(config, nc) {
183
183
  triggers: params.triggers ?? [],
184
184
  triggers_enabled: params.triggers_enabled ?? true,
185
185
  requires_confirmation: params.requires_confirmation ?? true,
186
+ ...(params.yolo_mode ? { yolo_mode: true } : {}),
186
187
  ...(params.command ? { command: params.command } : {}),
187
188
  },
188
189
  body,
@@ -211,6 +212,8 @@ export function createRpcHandler(config, nc) {
211
212
  existing.frontmatter.triggers_enabled = params.triggers_enabled;
212
213
  if (params.requires_confirmation !== undefined)
213
214
  existing.frontmatter.requires_confirmation = params.requires_confirmation;
215
+ if (params.yolo_mode !== undefined)
216
+ existing.frontmatter.yolo_mode = params.yolo_mode || undefined;
214
217
  if (params.command !== undefined) {
215
218
  if (params.command) {
216
219
  existing.frontmatter.command = params.command;
@@ -261,6 +264,7 @@ export function createRpcHandler(config, nc) {
261
264
  triggers: [],
262
265
  triggers_enabled: false,
263
266
  requires_confirmation: params.requires_confirmation ?? false,
267
+ ...(params.yolo_mode ? { yolo_mode: true } : {}),
264
268
  ...(params.command ? { command: params.command } : {}),
265
269
  },
266
270
  body: "",
@@ -324,7 +328,7 @@ export function createRpcHandler(config, nc) {
324
328
  await publishHostEvent(nc, config.hostId, params.id, { event_type: "result-updated", run_id: params.run_id });
325
329
  // Fire-and-forget: invoke agent inline as a child of the serve process
326
330
  const followupAgent = getAgent(followupTask.frontmatter.agent);
327
- 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);
328
332
  // Spawn directly via crossSpawn so we can track and kill the child
329
333
  const child = crossSpawn(cmd, cmdArgs, {
330
334
  cwd: followupRunDir,
@@ -488,8 +492,8 @@ export function createRpcHandler(config, nc) {
488
492
  const reports = [];
489
493
  const runDir = path.join(config.projectRoot, "tasks", params.id, params.run_id);
490
494
  for (const file of params.report_files) {
491
- if (!file.endsWith(".md")) {
492
- 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" });
493
497
  continue;
494
498
  }
495
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
  */
package/dist/task.js CHANGED
@@ -169,29 +169,61 @@ export function beginStreamingMessage(taskDir, runId, time) {
169
169
  const filePath = path.join(taskDir, runId, "TASKRUN.md");
170
170
  const delimiter = `<!-- palmier:message role="assistant" time="${time}" -->`;
171
171
  fs.appendFileSync(filePath, `${delimiter}\n\n`, "utf-8");
172
- return new StreamingMessageWriter(filePath, delimiter);
172
+ return new StreamingMessageWriter(filePath);
173
173
  }
174
174
  export class StreamingMessageWriter {
175
175
  filePath;
176
- delimiter;
177
- constructor(filePath, delimiter) {
176
+ constructor(filePath) {
178
177
  this.filePath = filePath;
179
- this.delimiter = delimiter;
180
178
  }
181
179
  /** Append a chunk of content to the current message. */
182
180
  write(chunk) {
183
181
  fs.appendFileSync(this.filePath, chunk, "utf-8");
184
182
  }
185
- /** Finalize the message. If attachments are provided, rewrites the delimiter to include them. */
183
+ /** Finalize the message. If attachments are provided, rewrites the last assistant delimiter to include them. */
186
184
  end(attachments) {
187
185
  fs.appendFileSync(this.filePath, "\n\n", "utf-8");
188
186
  if (attachments?.length) {
189
187
  const raw = fs.readFileSync(this.filePath, "utf-8");
190
- const updated = raw.replace(this.delimiter, `${this.delimiter.slice(0, -4)} attachments="${attachments.join(",")}" -->`);
191
- fs.writeFileSync(this.filePath, updated, "utf-8");
188
+ // Find the last assistant delimiter (may differ from the original if spliceUserMessage created a new one)
189
+ const pattern = /<!-- palmier:message role="assistant" time="\d+" -->/g;
190
+ let lastMatch = null;
191
+ let m;
192
+ while ((m = pattern.exec(raw)) !== null)
193
+ lastMatch = m;
194
+ if (lastMatch) {
195
+ const before = raw.slice(0, lastMatch.index);
196
+ const after = raw.slice(lastMatch.index + lastMatch[0].length);
197
+ const updated = before + `${lastMatch[0].slice(0, -4)} attachments="${attachments.join(",")}" -->` + after;
198
+ fs.writeFileSync(this.filePath, updated, "utf-8");
199
+ }
192
200
  }
193
201
  }
194
202
  }
203
+ /**
204
+ * Splice a user message into a running assistant stream.
205
+ * Ends the current assistant block, writes the user message,
206
+ * then opens a new assistant block — all as direct file appends.
207
+ * The existing StreamingMessageWriter keeps working because its
208
+ * write() is just appendFileSync, so subsequent chunks land in
209
+ * the new assistant block.
210
+ */
211
+ export function spliceUserMessage(taskDir, runId, userMsg,
212
+ /** Optional text to append to the current assistant block before ending it. */
213
+ assistantAppend) {
214
+ const filePath = path.join(taskDir, runId, "TASKRUN.md");
215
+ // 1. Optionally append to the current assistant block (e.g. the input questions)
216
+ if (assistantAppend) {
217
+ fs.appendFileSync(filePath, assistantAppend, "utf-8");
218
+ }
219
+ // 2. End the current assistant block
220
+ fs.appendFileSync(filePath, "\n\n", "utf-8");
221
+ // 3. Write the user message
222
+ appendRunMessage(taskDir, runId, userMsg);
223
+ // 4. Open a new assistant block for subsequent agent output
224
+ const delimiter = `<!-- palmier:message role="assistant" time="${Date.now()}" -->`;
225
+ fs.appendFileSync(filePath, `${delimiter}\n\n`, "utf-8");
226
+ }
195
227
  /**
196
228
  * Read conversation messages from a run's TASKRUN.md file.
197
229
  */