palmier 0.4.4 → 0.4.5
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/README.md +4 -3
- package/dist/agents/agent.d.ts +2 -2
- package/dist/agents/claude.d.ts +1 -1
- package/dist/agents/claude.js +4 -4
- package/dist/agents/codex.d.ts +1 -1
- package/dist/agents/codex.js +4 -4
- package/dist/agents/copilot.d.ts +1 -1
- package/dist/agents/copilot.js +3 -3
- package/dist/agents/gemini.d.ts +1 -1
- package/dist/agents/gemini.js +4 -4
- package/dist/agents/openclaw.d.ts +1 -1
- package/dist/agents/openclaw.js +2 -2
- package/dist/commands/run.js +14 -11
- package/package.json +1 -1
- package/src/agents/agent.ts +2 -2
- package/src/agents/claude.ts +3 -3
- package/src/agents/codex.ts +3 -3
- package/src/agents/copilot.ts +3 -3
- package/src/agents/gemini.ts +3 -3
- package/src/agents/openclaw.ts +2 -2
- package/src/commands/run.ts +14 -11
- package/test/agent-output-parsing.test.ts +1 -14
package/README.md
CHANGED
|
@@ -130,8 +130,9 @@ palmier restart
|
|
|
130
130
|
- **Task execution** uses the system scheduler on both platforms — `systemctl --user start` on Linux, `schtasks /run` on Windows. On Windows, tasks run via a VBS wrapper (`wscript.exe`) to avoid visible console windows. The daemon polls every 30 seconds to detect crashed tasks (processes that exited without updating status) and marks them as failed, broadcasting the failure to connected clients.
|
|
131
131
|
- **Command-triggered tasks** — optionally specify a shell command (e.g., `tail -f /var/log/app.log`). Palmier runs the command continuously and invokes the agent for each line of stdout, passing it alongside your prompt. Useful for log monitoring, event-driven automation, and reactive workflows.
|
|
132
132
|
- **Task confirmation** — tasks can optionally require your approval before running. You'll get a push notification (server mode) or a prompt in the PWA to confirm or abort.
|
|
133
|
-
- **Conversational run history** — each run
|
|
134
|
-
- **
|
|
133
|
+
- **Conversational run history** — each run gets its own directory (`tasks/<id>/<timestamp>/`) with a `TASKRUN.md` file containing a conversational thread: assistant messages (agent output), user messages (input responses, permission grants, confirmations), and status entries (started, finished, failed, aborted, stopped). The agent runs inside the run directory, so each run's session files and artifacts are isolated. The PWA displays runs as a chat-like thread with follow-up support.
|
|
134
|
+
- **Follow-up messages** — after a task run completes, users can send follow-up messages from the run detail view. The agent is invoked inline by the serve daemon (no new process spawning), and the response is appended to the same conversation thread.
|
|
135
|
+
- **Real-time updates** — task status changes and result updates are pushed to connected PWA clients via NATS pub/sub (server mode) and/or SSE (LAN mode). The run detail view live-updates as the agent produces output. Events are scoped to specific runs.
|
|
135
136
|
- **Agent CLI commands** — `palmier notify` and `palmier request-input` allow agents to send push notifications and request user input during task execution without requiring MCP support.
|
|
136
137
|
|
|
137
138
|
## NATS Subjects
|
|
@@ -139,7 +140,7 @@ palmier restart
|
|
|
139
140
|
| Subject | Direction | Description |
|
|
140
141
|
|---|---|---|
|
|
141
142
|
| `host.<hostId>.rpc.<method>` | Client → Host | RPC request/reply (e.g., `task.list`, `task.create`) |
|
|
142
|
-
| `host-event.<hostId>.<taskId>` | Host → Client | Real-time task events (`running-state`, `confirm-request`, `permission-request`, `input-request`) |
|
|
143
|
+
| `host-event.<hostId>.<taskId>` | Host → Client | Real-time task events (`running-state`, `result-updated`, `confirm-request`, `permission-request`, `input-request`) |
|
|
143
144
|
| `host.<hostId>.push.send` | Host → Server | Request server to deliver a push notification |
|
|
144
145
|
| `pair.<code>` | Client → Host | OTP pairing request/reply |
|
|
145
146
|
|
package/dist/agents/agent.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ export interface CommandLine {
|
|
|
12
12
|
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
|
-
/** Return the command and args used to run a task. If
|
|
15
|
+
/** Return the command and args used to run a task. If followupPrompt is provided, use it instead of the task's prompt,
|
|
16
16
|
* and treat it as a continuation of the original run (reuse the same session, etc). extraPermissions are transient
|
|
17
17
|
* permissions granted for this run only (not persisted in frontmatter). */
|
|
18
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
18
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
19
19
|
/** Detect whether the agent CLI is available and perform any agent-specific
|
|
20
20
|
* initialization. Returns true if the agent was detected and initialized successfully. */
|
|
21
21
|
init(): Promise<boolean>;
|
package/dist/agents/claude.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class ClaudeAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=claude.d.ts.map
|
package/dist/agents/claude.js
CHANGED
|
@@ -8,16 +8,16 @@ export class ClaudeAgent {
|
|
|
8
8
|
args: ["-p", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
13
13
|
const args = ["--permission-mode", "acceptEdits", "-p"];
|
|
14
14
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
15
15
|
for (const p of allPerms) {
|
|
16
16
|
args.push("--allowedTools", p.name);
|
|
17
17
|
}
|
|
18
|
-
if (
|
|
18
|
+
if (followupPrompt) {
|
|
19
19
|
args.push("-c");
|
|
20
|
-
} // continue mode for
|
|
20
|
+
} // continue mode for followups
|
|
21
21
|
return { command: "claude", args, stdin: prompt };
|
|
22
22
|
}
|
|
23
23
|
async init() {
|
package/dist/agents/codex.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class CodexAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=codex.d.ts.map
|
package/dist/agents/codex.js
CHANGED
|
@@ -8,8 +8,8 @@ export class CodexAgent {
|
|
|
8
8
|
args: ["exec", "--skip-git-repo-check", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
13
13
|
// Using danger-full-access until workspace-write is fixed: https://github.com/openai/codex/issues/12572
|
|
14
14
|
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--sandbox", "danger-full-access"];
|
|
15
15
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
@@ -18,9 +18,9 @@ export class CodexAgent {
|
|
|
18
18
|
args.push(`apps.${p.name}.default_tools_approval_mode="approve"`);
|
|
19
19
|
}
|
|
20
20
|
args.push("-"); // read prompt from stdin
|
|
21
|
-
if (
|
|
21
|
+
if (followupPrompt) {
|
|
22
22
|
args.push("resume", "--last");
|
|
23
|
-
} // continue mode for
|
|
23
|
+
} // continue mode for followups
|
|
24
24
|
return { command: "codex", args, stdin: prompt };
|
|
25
25
|
}
|
|
26
26
|
async init() {
|
package/dist/agents/copilot.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class CopilotAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=copilot.d.ts.map
|
package/dist/agents/copilot.js
CHANGED
|
@@ -8,15 +8,15 @@ export class CopilotAgent {
|
|
|
8
8
|
args: ["-p", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
13
13
|
const args = ["-p", prompt];
|
|
14
14
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
15
15
|
if (allPerms.length > 0) {
|
|
16
16
|
args.push(`--allow-tool='${allPerms.map((p) => p.name).join(",")}'`);
|
|
17
17
|
;
|
|
18
18
|
}
|
|
19
|
-
if (
|
|
19
|
+
if (followupPrompt) {
|
|
20
20
|
args.push("--continue");
|
|
21
21
|
}
|
|
22
22
|
return { command: "copilot", args };
|
package/dist/agents/gemini.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class GeminiAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=gemini.d.ts.map
|
package/dist/agents/gemini.js
CHANGED
|
@@ -8,8 +8,8 @@ export class GeminiAgent {
|
|
|
8
8
|
args: ["--approval-mode", "auto_edit", "--prompt", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt =
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = followupPrompt ?? (task.body || task.frontmatter.user_prompt);
|
|
13
13
|
const fullPrompt = AGENT_INSTRUCTIONS + "\n\n" + prompt;
|
|
14
14
|
const args = ["--prompt", "-"];
|
|
15
15
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
@@ -19,9 +19,9 @@ export class GeminiAgent {
|
|
|
19
19
|
args.push(p.name);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
22
|
+
if (followupPrompt) {
|
|
23
23
|
args.push("--resume");
|
|
24
|
-
} // continue mode for
|
|
24
|
+
} // continue mode for followups
|
|
25
25
|
return { command: "gemini", args, stdin: fullPrompt };
|
|
26
26
|
}
|
|
27
27
|
async init() {
|
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class OpenClawAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=openclaw.d.ts.map
|
package/dist/agents/openclaw.js
CHANGED
|
@@ -7,8 +7,8 @@ export class OpenClawAgent {
|
|
|
7
7
|
args: ["agent", "--local", "--agent", "main", "--message", prompt],
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
getTaskRunCommandLine(task,
|
|
11
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
10
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
11
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
12
12
|
// OpenClaw does not support stdin as prompt.
|
|
13
13
|
const args = ["agent", "--local", "--session-id", task.frontmatter.id, "--message", prompt];
|
|
14
14
|
return { command: "openclaw", args };
|
package/dist/commands/run.js
CHANGED
|
@@ -11,17 +11,17 @@ import { TASK_SUCCESS_MARKER, TASK_FAILURE_MARKER, TASK_REPORT_PREFIX, TASK_PERM
|
|
|
11
11
|
import { publishHostEvent } from "../events.js";
|
|
12
12
|
import { waitForUserInput } from "../user-input.js";
|
|
13
13
|
/**
|
|
14
|
-
* Invoke the agent CLI with a
|
|
14
|
+
* Invoke the agent CLI with a continuation loop for permissions and user input.
|
|
15
15
|
*
|
|
16
16
|
* Both standard and command-triggered execution use this.
|
|
17
17
|
* The `invokeTask` is the ParsedTask whose prompt is passed to the agent
|
|
18
18
|
* (for command-triggered mode this is the per-line augmented task).
|
|
19
19
|
*/
|
|
20
|
-
async function
|
|
21
|
-
let
|
|
20
|
+
async function invokeAgentWithContinuation(ctx, invokeTask) {
|
|
21
|
+
let followupPrompt;
|
|
22
22
|
// eslint-disable-next-line no-constant-condition
|
|
23
23
|
while (true) {
|
|
24
|
-
const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask,
|
|
24
|
+
const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask, followupPrompt, ctx.transientPermissions);
|
|
25
25
|
const result = await spawnCommand(command, args, {
|
|
26
26
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
27
27
|
env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId) },
|
|
@@ -39,8 +39,8 @@ async function invokeAgentWithRetry(ctx, invokeTask) {
|
|
|
39
39
|
content: stripPalmierMarkers(result.output),
|
|
40
40
|
attachments: reportFiles.length > 0 ? reportFiles : undefined,
|
|
41
41
|
});
|
|
42
|
-
// Permission
|
|
43
|
-
if (
|
|
42
|
+
// Permission handling — agent requested permissions
|
|
43
|
+
if (requiredPermissions.length > 0) {
|
|
44
44
|
const response = await requestPermission(ctx.nc, ctx.config, ctx.task, ctx.taskDir, requiredPermissions);
|
|
45
45
|
await publishPermissionResolved(ctx.nc, ctx.config, ctx.taskId, response);
|
|
46
46
|
if (response === "aborted") {
|
|
@@ -69,10 +69,13 @@ async function invokeAgentWithRetry(ctx, invokeTask) {
|
|
|
69
69
|
else {
|
|
70
70
|
ctx.transientPermissions = [...ctx.transientPermissions, ...newPerms];
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// If the agent actually failed, retry with the new permissions
|
|
73
|
+
if (outcome === "failed") {
|
|
74
|
+
followupPrompt = "Permissions granted, please continue.";
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
|
-
// Normal completion (success or
|
|
78
|
+
// Normal completion (success or terminal failure)
|
|
76
79
|
return { outcome };
|
|
77
80
|
}
|
|
78
81
|
}
|
|
@@ -177,7 +180,7 @@ export async function runCommand(taskId) {
|
|
|
177
180
|
time: Date.now(),
|
|
178
181
|
content: task.body || task.frontmatter.user_prompt,
|
|
179
182
|
});
|
|
180
|
-
const result = await
|
|
183
|
+
const result = await invokeAgentWithContinuation(ctx, task);
|
|
181
184
|
const outcome = resolveOutcome(taskDir, result.outcome);
|
|
182
185
|
appendRunMessage(taskDir, runId, { role: "status", time: Date.now(), content: "", type: outcome });
|
|
183
186
|
await publishTaskEvent(nc, config, taskDir, taskId, outcome, taskName, runId);
|
|
@@ -255,7 +258,7 @@ async function runCommandTriggeredMode(ctx) {
|
|
|
255
258
|
frontmatter: { ...ctx.task.frontmatter, user_prompt: perLinePrompt },
|
|
256
259
|
body: "",
|
|
257
260
|
};
|
|
258
|
-
const result = await
|
|
261
|
+
const result = await invokeAgentWithContinuation(ctx, perLineTask);
|
|
259
262
|
if (result.outcome === "finished") {
|
|
260
263
|
invocationsSucceeded++;
|
|
261
264
|
}
|
package/package.json
CHANGED
package/src/agents/agent.ts
CHANGED
|
@@ -20,10 +20,10 @@ export interface AgentTool {
|
|
|
20
20
|
/** Return the command and args used to generate a plan from a prompt. */
|
|
21
21
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
22
22
|
|
|
23
|
-
/** Return the command and args used to run a task. If
|
|
23
|
+
/** Return the command and args used to run a task. If followupPrompt is provided, use it instead of the task's prompt,
|
|
24
24
|
* and treat it as a continuation of the original run (reuse the same session, etc). extraPermissions are transient
|
|
25
25
|
* permissions granted for this run only (not persisted in frontmatter). */
|
|
26
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
26
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
27
27
|
|
|
28
28
|
/** Detect whether the agent CLI is available and perform any agent-specific
|
|
29
29
|
* initialization. Returns true if the agent was detected and initialized successfully. */
|
package/src/agents/claude.ts
CHANGED
|
@@ -12,8 +12,8 @@ export class ClaudeAgent implements AgentTool {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
16
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
15
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
16
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
17
17
|
const args = ["--permission-mode", "acceptEdits", "-p"];
|
|
18
18
|
|
|
19
19
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
@@ -21,7 +21,7 @@ export class ClaudeAgent implements AgentTool {
|
|
|
21
21
|
args.push("--allowedTools", p.name);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (
|
|
24
|
+
if (followupPrompt) {args.push("-c");} // continue mode for followups
|
|
25
25
|
return { command: "claude", args, stdin: prompt };
|
|
26
26
|
}
|
|
27
27
|
|
package/src/agents/codex.ts
CHANGED
|
@@ -12,8 +12,8 @@ export class CodexAgent implements AgentTool {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
16
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
15
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
16
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
17
17
|
// Using danger-full-access until workspace-write is fixed: https://github.com/openai/codex/issues/12572
|
|
18
18
|
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--sandbox", "danger-full-access"];
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ export class CodexAgent implements AgentTool {
|
|
|
24
24
|
}
|
|
25
25
|
args.push("-"); // read prompt from stdin
|
|
26
26
|
|
|
27
|
-
if (
|
|
27
|
+
if (followupPrompt) {args.push("resume", "--last");} // continue mode for followups
|
|
28
28
|
return { command: "codex", args, stdin: prompt };
|
|
29
29
|
}
|
|
30
30
|
|
package/src/agents/copilot.ts
CHANGED
|
@@ -12,8 +12,8 @@ export class CopilotAgent implements AgentTool {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
16
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
15
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
16
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
17
17
|
const args = ["-p", prompt];
|
|
18
18
|
|
|
19
19
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
@@ -21,7 +21,7 @@ export class CopilotAgent implements AgentTool {
|
|
|
21
21
|
args.push(`--allow-tool='${allPerms.map((p) => p.name).join(",")}'`);;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (
|
|
24
|
+
if (followupPrompt) { args.push("--continue"); }
|
|
25
25
|
return { command: "copilot", args};
|
|
26
26
|
}
|
|
27
27
|
|
package/src/agents/gemini.ts
CHANGED
|
@@ -12,8 +12,8 @@ export class GeminiAgent implements AgentTool {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
16
|
-
const prompt =
|
|
15
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
16
|
+
const prompt = followupPrompt ?? (task.body || task.frontmatter.user_prompt);
|
|
17
17
|
const fullPrompt = AGENT_INSTRUCTIONS + "\n\n" + prompt;
|
|
18
18
|
const args = ["--prompt", "-"];
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ export class GeminiAgent implements AgentTool {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
if (
|
|
28
|
+
if (followupPrompt) {args.push("--resume");} // continue mode for followups
|
|
29
29
|
return { command: "gemini", args, stdin: fullPrompt };
|
|
30
30
|
}
|
|
31
31
|
|
package/src/agents/openclaw.ts
CHANGED
|
@@ -11,8 +11,8 @@ export class OpenClawAgent implements AgentTool {
|
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
15
|
-
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (
|
|
14
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
15
|
+
const prompt = AGENT_INSTRUCTIONS + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
16
16
|
// OpenClaw does not support stdin as prompt.
|
|
17
17
|
const args = ["agent", "--local", "--session-id", task.frontmatter.id, "--message", prompt];
|
|
18
18
|
|
package/src/commands/run.ts
CHANGED
|
@@ -36,20 +36,20 @@ interface InvocationResult {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Invoke the agent CLI with a
|
|
39
|
+
* Invoke the agent CLI with a continuation loop for permissions and user input.
|
|
40
40
|
*
|
|
41
41
|
* Both standard and command-triggered execution use this.
|
|
42
42
|
* The `invokeTask` is the ParsedTask whose prompt is passed to the agent
|
|
43
43
|
* (for command-triggered mode this is the per-line augmented task).
|
|
44
44
|
*/
|
|
45
|
-
async function
|
|
45
|
+
async function invokeAgentWithContinuation(
|
|
46
46
|
ctx: InvocationContext,
|
|
47
47
|
invokeTask: ParsedTask,
|
|
48
48
|
): Promise<InvocationResult> {
|
|
49
|
-
let
|
|
49
|
+
let followupPrompt: string | undefined;
|
|
50
50
|
// eslint-disable-next-line no-constant-condition
|
|
51
51
|
while (true) {
|
|
52
|
-
const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask,
|
|
52
|
+
const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask, followupPrompt, ctx.transientPermissions);
|
|
53
53
|
const result = await spawnCommand(command, args, {
|
|
54
54
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
55
55
|
env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId) },
|
|
@@ -70,8 +70,8 @@ async function invokeAgentWithRetry(
|
|
|
70
70
|
attachments: reportFiles.length > 0 ? reportFiles : undefined,
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
// Permission
|
|
74
|
-
if (
|
|
73
|
+
// Permission handling — agent requested permissions
|
|
74
|
+
if (requiredPermissions.length > 0) {
|
|
75
75
|
const response = await requestPermission(ctx.nc, ctx.config, ctx.task, ctx.taskDir, requiredPermissions);
|
|
76
76
|
await publishPermissionResolved(ctx.nc, ctx.config, ctx.taskId, response);
|
|
77
77
|
|
|
@@ -106,11 +106,14 @@ async function invokeAgentWithRetry(
|
|
|
106
106
|
ctx.transientPermissions = [...ctx.transientPermissions, ...newPerms];
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// If the agent actually failed, retry with the new permissions
|
|
110
|
+
if (outcome === "failed") {
|
|
111
|
+
followupPrompt = "Permissions granted, please continue.";
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
// Normal completion (success or
|
|
116
|
+
// Normal completion (success or terminal failure)
|
|
114
117
|
return { outcome };
|
|
115
118
|
}
|
|
116
119
|
}
|
|
@@ -229,7 +232,7 @@ export async function runCommand(taskId: string): Promise<void> {
|
|
|
229
232
|
content: task.body || task.frontmatter.user_prompt,
|
|
230
233
|
});
|
|
231
234
|
|
|
232
|
-
const result = await
|
|
235
|
+
const result = await invokeAgentWithContinuation(ctx, task);
|
|
233
236
|
const outcome = resolveOutcome(taskDir, result.outcome);
|
|
234
237
|
appendRunMessage(taskDir, runId, { role: "status", time: Date.now(), content: "", type: outcome });
|
|
235
238
|
await publishTaskEvent(nc, config, taskDir, taskId, outcome, taskName, runId);
|
|
@@ -316,7 +319,7 @@ async function runCommandTriggeredMode(
|
|
|
316
319
|
body: "",
|
|
317
320
|
};
|
|
318
321
|
|
|
319
|
-
const result = await
|
|
322
|
+
const result = await invokeAgentWithContinuation(ctx, perLineTask);
|
|
320
323
|
if (result.outcome === "finished") {
|
|
321
324
|
invocationsSucceeded++;
|
|
322
325
|
} else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { parseTaskOutcome, parseReportFiles, parsePermissions
|
|
3
|
+
import { parseTaskOutcome, parseReportFiles, parsePermissions } from "../src/commands/run.js";
|
|
4
4
|
|
|
5
5
|
describe("parseTaskOutcome", () => {
|
|
6
6
|
it("returns 'finished' for success marker", () => {
|
|
@@ -59,16 +59,3 @@ describe("parsePermissions", () => {
|
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
describe("parseInputRequests", () => {
|
|
63
|
-
it("extracts input descriptions", () => {
|
|
64
|
-
const output = "[PALMIER_INPUT] What is the API key?\n[PALMIER_INPUT] Database connection string?";
|
|
65
|
-
assert.deepEqual(parseInputRequests(output), [
|
|
66
|
-
"What is the API key?",
|
|
67
|
-
"Database connection string?",
|
|
68
|
-
]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("returns empty array when no inputs", () => {
|
|
72
|
-
assert.deepEqual(parseInputRequests("no inputs"), []);
|
|
73
|
-
});
|
|
74
|
-
});
|