palmier 1.1.23 → 1.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DISCLAIMER.md CHANGED
@@ -10,7 +10,7 @@ Palmier spawns third-party AI agent CLIs (such as Claude Code, Codex CLI, GitHub
10
10
  - **Execute arbitrary shell commands** with your user permissions
11
11
  - **Make network requests** and interact with external services
12
12
 
13
- AI agents may produce unexpected, incorrect, or harmful outputs. **You are solely responsible for reviewing and approving all actions taken by AI agents on your system.** The authors of Palmier have no control over the behavior of third-party AI agents and accept no liability for their actions.
13
+ AI agents may produce unexpected, incorrect, or harmful outputs. **You are solely responsible for all actions taken by AI agents on your system, including actions taken without per-action approval.** The authors of Palmier have no control over the behavior of third-party AI agents and accept no liability for their actions.
14
14
 
15
15
  ## Agent CLI Installation
16
16
 
@@ -20,10 +20,12 @@ Agent CLIs that Palmier installed (those carrying a Palmier-managed version stam
20
20
 
21
21
  ## Unattended and Scheduled Execution
22
22
 
23
+ Palmier runs agents with tool approvals bypassed whenever the agent CLI supports it. Agents can act without asking first.
24
+
23
25
  Tasks can be configured to run on schedules (cron) or in response to events without active supervision. You should:
24
26
 
25
27
  - Use the **confirmation** feature for sensitive tasks
26
- - Restrict **permissions** granted to agents to the minimum necessary
28
+ - Limit what the **host's user account** can access, for example by using a dedicated account, container or VM
27
29
  - Regularly review **task history and results**
28
30
  - Maintain **backups** of any important data in directories where agents operate
29
31
 
package/README.md CHANGED
@@ -53,7 +53,6 @@ Every task Palmier runs gets these capabilities automatically. Their usage is wr
53
53
  | Capability | Description |
54
54
  |------------|-------------|
55
55
  | `notify` | Send a push notification to the user's device |
56
- | `request-input` | Request input from the user (blocks until response; aborting the prompt aborts the task) |
57
56
  | `request-choice` | Ask the user a question with a fixed set of options and return the one picked; a single option serves as a confirmation (blocks until response; aborting aborts the task) |
58
57
  | `fill-password` | Fill a saved password or PIN into the active browser session and submit the form, prompting the user if unknown (the secret is never revealed to the agent) |
59
58
  | `device-geolocation` | Get GPS location of the user's mobile device |
@@ -75,6 +74,12 @@ Every task Palmier runs gets these capabilities automatically. Their usage is wr
75
74
 
76
75
  All device tools work while the Palmier Android app is in the background — they communicate via FCM data messages which wake the app's service even when it's not in the foreground. Each host has one **linked device**: the phone the host uses for SMS, contacts, location, and other device capabilities. Choose it at pair time (the "Link the host to this device" checkbox) or later from the drawer. Each capability must be enabled and its Android permission granted via toggles in the linked device's drawer.
77
76
 
77
+ ### Asking the User
78
+
79
+ When an agent needs an answer it can't get from a fixed list, it writes its question as its final message and ends with the `[PALMIER_TASK_NEED_INPUT]` marker. The agent CLI then exits and the run moves to a "needs input" state, with a push notification that opens the run. The user answers in the run view's message box, or aborts from there.
80
+
81
+ `palmier run` stays alive during the wait, so anything the agent left running, such as its browser, stays open. Palmier polls for the reply and resumes the same agent session with it, repeating until the agent reports success or failure. An unanswered question fails the run after 24 hours. The reply is handed over through a file in the run directory, so a daemon restart doesn't lose the wait.
82
+
78
83
  ### Files in Agent Output
79
84
 
80
85
  Agents run with the task's run directory (`~/palmier/tasks/<task-id>/<run-id>/`) as their working directory and are instructed to link any file they produce with a relative markdown link, e.g. `[View report](report.md)` or `![Chart](chart.png)`. The app opens these links over whichever transport is in use (local, relay, or auto-LAN). It fetches the file through the `task.file` RPC in 512 KiB chunks, so large files fit within the relay's message size. Markdown files render in-app, with their own relative links and images resolved from the file's location. Text and images are previewed, and other file types are offered as a browser download. The host only serves files whose real path (after resolving symlinks) is inside that task's directory, up to 25 MB. A run whose output links an existing file triggers a "report ready" push notification.
@@ -41,63 +41,6 @@ const notifyTool = {
41
41
  throw new ToolError(result.error ?? "Push notification failed", 502);
42
42
  },
43
43
  };
44
- const requestInputTool = {
45
- name: "request-input",
46
- description: [
47
- "Request input from the user.",
48
- "The request blocks until the user responds.",
49
- 'Response: `{"values": ["answer1", "answer2"]}` on success, or `{"aborted": true}` if the user declines.',
50
- "When you need information from the user (credentials, answers to questions, preferences, clarifications, etc.), do not guess, fail, or prompt via stdout, even in a non-interactive environment — use this instead.",
51
- ],
52
- inputSchema: {
53
- type: "object",
54
- properties: {
55
- description: { type: "string", description: "Context or heading for the input request" },
56
- questions: {
57
- type: "array",
58
- items: { type: "string" },
59
- description: "Questions to present to the user",
60
- minItems: 1,
61
- },
62
- },
63
- required: ["questions"],
64
- },
65
- async handler(args, ctx) {
66
- const { description, questions } = args;
67
- if (!questions?.length)
68
- throw new ToolError("questions is required", 400);
69
- const pendingPromise = registerPending(ctx.taskId, "input", questions, {
70
- session_id: ctx.taskId,
71
- description,
72
- input_questions: questions,
73
- });
74
- await ctx.publishEvent("_input", {
75
- event_type: "input-request",
76
- host_id: ctx.config.hostId,
77
- session_id: ctx.taskId,
78
- description,
79
- input_questions: questions,
80
- });
81
- const response = await pendingPromise;
82
- const aborted = response.length === 1 && response[0] === "aborted";
83
- recordUserResponseToRun(ctx, aborted
84
- ? "Aborted"
85
- : questions.map((q, i) => `**${q}**\n\n${response[i] ?? ""}`).join("\n\n"));
86
- if (aborted) {
87
- await ctx.publishEvent("_input", {
88
- event_type: "input-resolved", host_id: ctx.config.hostId,
89
- session_id: ctx.taskId, status: "aborted",
90
- });
91
- await ctx.abortTask();
92
- return { aborted: true };
93
- }
94
- await ctx.publishEvent("_input", {
95
- event_type: "input-resolved", host_id: ctx.config.hostId,
96
- session_id: ctx.taskId, status: "provided",
97
- });
98
- return { values: response };
99
- },
100
- };
101
44
  /** Splice the user's answer into the run file so history shows it even if the agent never echoes it. */
102
45
  function recordUserResponseToRun(ctx, content) {
103
46
  const taskId = ctx.taskId;
@@ -166,7 +109,7 @@ const requestChoiceTool = {
166
109
  "Ask the user a question with a fixed set of answers and let them pick one.",
167
110
  "The request blocks until the user picks an option or aborts the task.",
168
111
  'Response: `{"choice": "<option>"}` with the selected option, or `{"aborted": true}` if the user aborts.',
169
- "Use this instead of request-input whenever the possible answers are known in advance.",
112
+ "Use this whenever the possible answers are known in advance.",
170
113
  "An abort option that ends the task is always shown, so to ask for confirmation before continuing, pass a single option naming the action (e.g. [\"Send email\"]). Add a decline option only if declining should let the task continue differently.",
171
114
  ],
172
115
  inputSchema: {
@@ -269,7 +212,7 @@ const fillPasswordTool = {
269
212
  event_type: "input-resolved", host_id: ctx.config.hostId,
270
213
  session_id: ctx.taskId, status: aborted ? "aborted" : "provided",
271
214
  });
272
- // Never record the secret to the task run file (unlike request-input).
215
+ // Never record the secret to the task run file.
273
216
  if (aborted) {
274
217
  await ctx.abortTask();
275
218
  return { aborted: true };
@@ -779,7 +722,7 @@ const sendEmailTool = {
779
722
  return result;
780
723
  },
781
724
  };
782
- export const agentTools = [notifyTool, requestInputTool, requestChoiceTool, fillPasswordTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
725
+ export const agentTools = [notifyTool, requestChoiceTool, fillPasswordTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
783
726
  export const agentToolMap = new Map(agentTools.map((t) => [t.name, t]));
784
727
  const deviceNotificationsResource = {
785
728
  name: "Device Notifications",
@@ -13,19 +13,21 @@ The user reads your output on a different device and can only open files you lin
13
13
 
14
14
  ## Completion
15
15
 
16
- When you are done, output exactly one of these markers as the very last line on stdout (no other text on the same line):
16
+ When you stop, output exactly one of these markers as the very last line on stdout (no other text on the same line):
17
17
  [PALMIER_TASK_SUCCESS]
18
18
  [PALMIER_TASK_FAILURE]
19
+ [PALMIER_TASK_NEED_INPUT]
19
20
 
20
21
  On the line right before that marker, in the same final message, print the model you are running as, using the exact model name or ID from your system prompt (skip this line if you do not know it rather than guessing):
21
22
  [PALMIER_AGENT_MODEL] <model>
22
23
 
23
24
  Do this at the end of every response, including responses to follow-up messages.
24
25
 
25
- ## Permissions
26
+ ## Asking the User
26
27
 
27
- Whenever a tool you are trying to use is denied or you lack the required permissions, print each required permission on its own line to stdout using this exact format:
28
- [PALMIER_PERMISSION] <tool_name> | <description>
28
+ When you need information from the user (answers to questions, preferences, clarifications, etc.), do not guess or fail. Write your questions in full as your final message, then end it with `[PALMIER_TASK_NEED_INPUT]`. Your process will exit, and the user's reply will arrive as the next message in this same session. Anything you left running, such as a browser, stays open, so leave it as it is.
29
+
30
+ If the possible answers are known in advance, use the `request-choice` endpoint instead. For passwords and PINs, use `fill-password`.
29
31
 
30
32
  ## Browsers
31
33
 
@@ -33,7 +35,7 @@ Whenever the task needs a browser, launch it with the Playwright CLI (`playwrigh
33
35
 
34
36
  When launching, always pass `--headed` (never run headless) and `--persistent` (reuse a persistent profile so logins and cookies survive across runs).
35
37
 
36
- When the task is finished, close any browser you opened with the Playwright CLI `close` command (pass the same `-s=<session>` if you used one) before printing the completion marker.
38
+ When the task has succeeded or failed, close any browser you opened with the Playwright CLI `close` command (pass the same `-s=<session>` if you used one) before printing the completion marker. Do not close it when you are stopping to ask the user for input.
37
39
 
38
40
  ## HTTP Endpoints
39
41
 
@@ -25,9 +25,6 @@ export interface AgentTool {
25
25
  * can sign in before configuration continues. Leave undefined for agents that auth on
26
26
  * first run with no separate command. */
27
27
  authArgs?: string[];
28
- /** Whether this agent supports permission overrides (e.g. --allowedTools).
29
- * When falsy, the permissions section is omitted from agent instructions. */
30
- supportsPermissions?: boolean;
31
28
  /** When true, the run loop persists the agent's stderr output only if the invocation fails. */
32
29
  suppressStdErr?: boolean;
33
30
  /** Matches the model name (capture group 1) in the banner the CLI prints on stderr.
@@ -51,7 +48,6 @@ export declare function getNpmInstalledVersion(npmPackage: string): string | nul
51
48
  export interface DetectedAgent {
52
49
  key: string;
53
50
  label: string;
54
- supportsPermissions?: boolean;
55
51
  npmPackage?: string;
56
52
  /** Runtime marker for "managed by Palmier" — present iff Palmier installed/manages this agent. */
57
53
  version?: string;
@@ -10,10 +10,8 @@ import { kimiAgent } from "./kimi.js";
10
10
  import { gooseAgent } from "./goose.js";
11
11
  import { openCodeAgent } from "./opencode.js";
12
12
  import { deepAgentsAgent } from "./deepagents.js";
13
- import { aiderAgent } from "./aider.js";
14
13
  import { cursorAgent } from "./cursor.js";
15
14
  import { kiroAgent } from "./kiro.js";
16
- import { clineAgent } from "./cline.js";
17
15
  import { qoderAgent } from "./qoder.js";
18
16
  import { hermesAgent } from "./hermes.js";
19
17
  import { grokAgent } from "./grok.js";
@@ -66,10 +64,8 @@ const agentRegistry = {
66
64
  goose: gooseAgent,
67
65
  opencode: openCodeAgent,
68
66
  deepagents: deepAgentsAgent,
69
- aider: aiderAgent,
70
67
  cursor: cursorAgent,
71
68
  kiro: kiroAgent,
72
- cline: clineAgent,
73
69
  openclaw: openClawAgent,
74
70
  hermes: hermesAgent,
75
71
  };
@@ -120,7 +116,6 @@ export async function detectAgents(previous, newlyInstalled) {
120
116
  detected.push({
121
117
  key,
122
118
  label: agent.label,
123
- supportsPermissions: agent.supportsPermissions,
124
119
  ...(agent.npmPackage ? { npmPackage: agent.npmPackage } : {}),
125
120
  ...(version ? { version } : {}),
126
121
  });
@@ -5,7 +5,6 @@ export const claudeAgent = {
5
5
  promptArgs: ["-p"],
6
6
  probeArg: "--version",
7
7
  authArgs: ["auth", "login"],
8
- supportsPermissions: true,
9
8
  npmPackage: "@anthropic-ai/claude-code",
10
9
  freeUsage: "Free with Claude Pro",
11
10
  getTaskRunCommandLine(task, followupPrompt) {
@@ -2,5 +2,7 @@ import type { ParsedTask } from "../types.js";
2
2
  export declare function getAgentInstructions(task: ParsedTask): string;
3
3
  export declare const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
4
4
  export declare const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
5
- export declare const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
5
+ export declare const TASK_NEED_INPUT_MARKER = "[PALMIER_TASK_NEED_INPUT]";
6
6
  export declare const AGENT_MODEL_PREFIX = "[PALMIER_AGENT_MODEL]";
7
+ /** A resumed session may have lost the original instructions to compaction, and a missing marker silently ends the run. */
8
+ export declare function getResumePrompt(userMessage: string): string;
@@ -10,10 +10,13 @@ export function getAgentInstructions(task) {
10
10
  const taskDescription = task.frontmatter.user_prompt;
11
11
  return AGENT_INSTRUCTIONS_TEMPLATE
12
12
  .replace(/\{\{ENDPOINT_DOCS\}\}/g, generateEndpointDocs(port, task.frontmatter.id))
13
- .replace(/\{\{TASK_DESCRIPTION\}\}/g, taskDescription)
14
- .replace(/## Permissions\r?\n[\s\S]*?(?=## |\r?\n---)/m, "");
13
+ .replace(/\{\{TASK_DESCRIPTION\}\}/g, taskDescription);
15
14
  }
16
15
  export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
17
16
  export const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
18
- export const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
17
+ export const TASK_NEED_INPUT_MARKER = "[PALMIER_TASK_NEED_INPUT]";
19
18
  export const AGENT_MODEL_PREFIX = "[PALMIER_AGENT_MODEL]";
19
+ /** A resumed session may have lost the original instructions to compaction, and a missing marker silently ends the run. */
20
+ export function getResumePrompt(userMessage) {
21
+ return `${userMessage}\n\n---\nEnd your response with exactly one of these as the very last line on stdout: ${TASK_SUCCESS_MARKER}, ${TASK_FAILURE_MARKER}, or ${TASK_NEED_INPUT_MARKER} if you need more from the user.`;
22
+ }
@@ -60,7 +60,6 @@ export async function pickAndInstallAgent(current, options = {}) {
60
60
  const record = {
61
61
  key: choice.key,
62
62
  label: choice.label,
63
- ...(tool.supportsPermissions ? { supportsPermissions: true } : {}),
64
63
  npmPackage: choice.npmPackage,
65
64
  ...(version ? { version } : {}),
66
65
  };
@@ -1,12 +1,11 @@
1
1
  import type { AgentTool } from "../agents/agent.js";
2
- import type { TaskRunningState, RequiredPermission } from "../types.js";
2
+ import type { TaskRunningState } from "../types.js";
3
3
  export declare function stripPalmierMarkers(output: string): string;
4
4
  export declare function runCommand(taskId: string): Promise<void>;
5
5
  /** Only links to files that exist count, so example links echoed from the prompt are ignored. */
6
6
  export declare function listLinkedTaskFiles(taskDir: string, runId: string, output: string): string[];
7
- export declare function parsePermissions(output: string): RequiredPermission[];
8
7
  export declare function parseAgentModel(output: string): string | undefined;
9
8
  /** Returns a function that accumulates stderr chunks and yields the model once the CLI banner reveals it. */
10
9
  export declare function createCliModelCapture(agent: AgentTool): (stderrChunk: string) => string | undefined;
11
- /** Falls back to "finished" if no success/failure marker is found. */
10
+ /** Falls back to "finished" if no outcome marker is found. */
12
11
  export declare function parseTaskOutcome(output: string): TaskRunningState;