palmier 0.5.1 → 0.5.3
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 +9 -9
- package/dist/agents/agent-instructions.md +7 -11
- package/dist/agents/agent.d.ts +8 -3
- package/dist/agents/agent.js +7 -1
- package/dist/agents/claude.d.ts +2 -1
- package/dist/agents/claude.js +10 -5
- package/dist/agents/codex.d.ts +2 -1
- package/dist/agents/codex.js +10 -6
- package/dist/agents/copilot.d.ts +2 -1
- package/dist/agents/copilot.js +10 -3
- package/dist/agents/gemini.d.ts +2 -1
- package/dist/agents/gemini.js +11 -7
- package/dist/agents/kimi.d.ts +9 -0
- package/dist/agents/kimi.js +35 -0
- package/dist/agents/openclaw.d.ts +2 -1
- package/dist/agents/openclaw.js +3 -1
- package/dist/agents/qwen.d.ts +9 -0
- package/dist/agents/qwen.js +32 -0
- package/dist/agents/shared-prompt.d.ts +1 -1
- package/dist/agents/shared-prompt.js +7 -3
- package/dist/client-store.d.ts +12 -0
- package/dist/client-store.js +57 -0
- package/dist/commands/clients.d.ts +4 -0
- package/dist/commands/clients.js +27 -0
- package/dist/commands/info.js +5 -5
- package/dist/commands/init.js +1 -1
- package/dist/commands/pair.js +4 -4
- package/dist/commands/run.js +21 -8
- package/dist/commands/serve.js +1 -1
- package/dist/events.js +1 -1
- package/dist/index.js +13 -13
- package/dist/rpc-handler.js +13 -6
- package/dist/task.d.ts +13 -3
- package/dist/task.js +39 -7
- package/dist/transports/http-transport.js +30 -13
- package/dist/transports/nats-transport.js +4 -4
- package/dist/types.d.ts +3 -2
- package/package.json +1 -1
- package/src/agents/agent-instructions.md +7 -11
- package/src/agents/agent.ts +16 -4
- package/src/agents/claude.ts +11 -6
- package/src/agents/codex.ts +11 -7
- package/src/agents/copilot.ts +10 -4
- package/src/agents/gemini.ts +12 -8
- package/src/agents/kimi.ts +37 -0
- package/src/agents/openclaw.ts +4 -2
- package/src/agents/qwen.ts +34 -0
- package/src/agents/shared-prompt.ts +7 -3
- package/src/client-store.ts +68 -0
- package/src/commands/clients.ts +29 -0
- package/src/commands/info.ts +5 -5
- package/src/commands/init.ts +1 -1
- package/src/commands/pair.ts +4 -4
- package/src/commands/run.ts +22 -8
- package/src/commands/serve.ts +1 -1
- package/src/events.ts +1 -1
- package/src/index.ts +13 -13
- package/src/rpc-handler.ts +15 -6
- package/src/task.ts +43 -8
- package/src/transports/http-transport.ts +32 -13
- package/src/transports/nats-transport.ts +4 -4
- package/src/types.ts +4 -3
- package/test/agent-instructions.test.ts +48 -0
- package/test/agent-output-parsing.test.ts +12 -0
- package/dist/commands/sessions.d.ts +0 -4
- package/dist/commands/sessions.js +0 -27
- package/dist/session-store.d.ts +0 -12
- package/dist/session-store.js +0 -57
- package/src/commands/sessions.ts +0 -29
- package/src/session-store.ts +0 -68
package/src/agents/copilot.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { getAgentInstructions } from "./shared-prompt.js";
|
|
|
5
5
|
import { SHELL } from "../platform/index.js";
|
|
6
6
|
|
|
7
7
|
export class CopilotAgent implements AgentTool {
|
|
8
|
+
supportsPermissions = false;
|
|
8
9
|
getPlanGenerationCommandLine(prompt: string): CommandLine {
|
|
9
10
|
return {
|
|
10
11
|
command: "copilot",
|
|
@@ -12,12 +13,17 @@ export class CopilotAgent implements AgentTool {
|
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
16
|
-
const
|
|
16
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
|
|
17
|
+
const yolo = extraPermissions === "yolo";
|
|
18
|
+
const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
|
|
17
19
|
const args = ["-p", prompt];
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (yolo) {
|
|
22
|
+
args.push("--yolo");
|
|
23
|
+
} else {
|
|
24
|
+
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
25
|
+
args.push(`--allow-tool=${["web_fetch", ...allPerms.map((p) => p.name)].join(",")}`);
|
|
26
|
+
}
|
|
21
27
|
if (followupPrompt) { args.push("--continue"); }
|
|
22
28
|
return { command: "copilot", args};
|
|
23
29
|
}
|
package/src/agents/gemini.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { getAgentInstructions } from "./shared-prompt.js";
|
|
|
5
5
|
import { SHELL } from "../platform/index.js";
|
|
6
6
|
|
|
7
7
|
export class GeminiAgent implements AgentTool {
|
|
8
|
+
supportsPermissions = true;
|
|
8
9
|
getPlanGenerationCommandLine(prompt: string): CommandLine {
|
|
9
10
|
return {
|
|
10
11
|
command: "gemini",
|
|
@@ -12,21 +13,24 @@ export class GeminiAgent implements AgentTool {
|
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
|
|
17
|
+
const yolo = extraPermissions === "yolo";
|
|
18
|
+
const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
|
|
19
|
+
const args = ["--approval-mode", yolo ? "yolo" : "auto_edit"];
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (!yolo) {
|
|
22
|
+
const tools = ["run_shell_command", "web_fetch"];
|
|
23
|
+
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
21
24
|
for (const p of allPerms) {
|
|
22
|
-
|
|
25
|
+
tools.push(p.name);
|
|
23
26
|
}
|
|
27
|
+
args.push("--allowed-tools", tools.join(","));
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
if (followupPrompt) {args.push("--resume");} // continue mode for followups
|
|
27
|
-
args.push("--prompt",
|
|
31
|
+
args.push("--prompt", "-"); // read prompt from stdin to avoid command line length limits
|
|
28
32
|
|
|
29
|
-
return { command: "gemini", args };
|
|
33
|
+
return { command: "gemini", args, stdin: prompt };
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
async init(): Promise<boolean> {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import type { AgentTool, CommandLine } from "./agent.js";
|
|
4
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
5
|
+
import { SHELL } from "../platform/index.js";
|
|
6
|
+
|
|
7
|
+
export class KimiAgent implements AgentTool {
|
|
8
|
+
supportsPermissions = false;
|
|
9
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine {
|
|
10
|
+
return {
|
|
11
|
+
command: "kimi",
|
|
12
|
+
args: ["-p", prompt],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
|
|
17
|
+
const yolo = extraPermissions === "yolo";
|
|
18
|
+
const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
|
|
19
|
+
const args = [];
|
|
20
|
+
|
|
21
|
+
if (yolo) {
|
|
22
|
+
args.push("--yolo");
|
|
23
|
+
}
|
|
24
|
+
if (followupPrompt) { args.push("--continue"); }
|
|
25
|
+
args.push("-p", prompt);
|
|
26
|
+
return { command: "kimi", args };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async init(): Promise<boolean> {
|
|
30
|
+
try {
|
|
31
|
+
execSync("kimi --version", { stdio: "ignore", shell: SHELL });
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/agents/openclaw.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AgentTool, CommandLine } from "./agent.js";
|
|
|
4
4
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
5
5
|
|
|
6
6
|
export class OpenClawAgent implements AgentTool {
|
|
7
|
+
supportsPermissions = false;
|
|
7
8
|
getPlanGenerationCommandLine(prompt: string): CommandLine {
|
|
8
9
|
return {
|
|
9
10
|
command: "openclaw",
|
|
@@ -11,8 +12,9 @@ export class OpenClawAgent implements AgentTool {
|
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine {
|
|
15
|
-
const
|
|
15
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
|
|
16
|
+
const yolo = extraPermissions === "yolo";
|
|
17
|
+
const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
|
|
16
18
|
// OpenClaw does not support stdin as prompt.
|
|
17
19
|
const args = ["agent", "--local", "--session-id", task.frontmatter.id, "--message", prompt];
|
|
18
20
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import type { AgentTool, CommandLine } from "./agent.js";
|
|
4
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
5
|
+
import { SHELL } from "../platform/index.js";
|
|
6
|
+
|
|
7
|
+
export class QwenAgent implements AgentTool {
|
|
8
|
+
supportsPermissions = false;
|
|
9
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine {
|
|
10
|
+
return {
|
|
11
|
+
command: "qwen",
|
|
12
|
+
args: ["-p", prompt],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
|
|
17
|
+
const yolo = extraPermissions === "yolo";
|
|
18
|
+
const prompt = followupPrompt ?? (getAgentInstructions(task.frontmatter.id, yolo || !this.supportsPermissions) + "\n\n" + (task.body || task.frontmatter.user_prompt));
|
|
19
|
+
const args = ["--approval-mode", yolo ? "yolo" : "auto-edit"];
|
|
20
|
+
|
|
21
|
+
if (followupPrompt) { args.push("-c"); }
|
|
22
|
+
args.push("-p", prompt);
|
|
23
|
+
return { command: "qwen", args };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async init(): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
execSync("qwen --version", { stdio: "ignore", shell: SHELL });
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -13,11 +13,15 @@ const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(
|
|
|
13
13
|
/**
|
|
14
14
|
* Agent instructions with the serve daemon's HTTP port and task ID baked in.
|
|
15
15
|
*/
|
|
16
|
-
export function getAgentInstructions(taskId: string): string {
|
|
17
|
-
const port = loadConfig().httpPort ??
|
|
18
|
-
|
|
16
|
+
export function getAgentInstructions(taskId: string, skipPermissions?: boolean): string {
|
|
17
|
+
const port = loadConfig().httpPort ?? 9966;
|
|
18
|
+
let instructions = AGENT_INSTRUCTIONS_TEMPLATE
|
|
19
19
|
.replace(/\{\{PORT\}\}/g, String(port))
|
|
20
20
|
.replace(/\{\{TASK_ID\}\}/g, taskId);
|
|
21
|
+
if (skipPermissions) {
|
|
22
|
+
instructions = instructions.replace(/## Permissions\r?\n[\s\S]*?(?=## |\r?\n---)/m, "");
|
|
23
|
+
}
|
|
24
|
+
return instructions;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { randomBytes } from "crypto";
|
|
4
|
+
import { CONFIG_DIR } from "./config.js";
|
|
5
|
+
|
|
6
|
+
const CLIENTS_FILE = path.join(CONFIG_DIR, "clients.json");
|
|
7
|
+
|
|
8
|
+
export interface ClientEntry {
|
|
9
|
+
token: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readFile(): ClientEntry[] {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs.existsSync(CLIENTS_FILE)) return [];
|
|
17
|
+
const raw = fs.readFileSync(CLIENTS_FILE, "utf-8");
|
|
18
|
+
return JSON.parse(raw) as ClientEntry[];
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeFile(clients: ClientEntry[]): void {
|
|
25
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
26
|
+
fs.writeFileSync(CLIENTS_FILE, JSON.stringify(clients, null, 2), "utf-8");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function loadClients(): ClientEntry[] {
|
|
30
|
+
return readFile();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function addClient(label?: string): ClientEntry {
|
|
34
|
+
const clients = readFile();
|
|
35
|
+
const entry: ClientEntry = {
|
|
36
|
+
token: randomBytes(32).toString("hex"),
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
...(label ? { label } : {}),
|
|
39
|
+
};
|
|
40
|
+
clients.push(entry);
|
|
41
|
+
writeFile(clients);
|
|
42
|
+
return entry;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function revokeClient(token: string): boolean {
|
|
46
|
+
const clients = readFile();
|
|
47
|
+
const idx = clients.findIndex((c) => c.token === token);
|
|
48
|
+
if (idx === -1) return false;
|
|
49
|
+
clients.splice(idx, 1);
|
|
50
|
+
writeFile(clients);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function revokeAllClients(): number {
|
|
55
|
+
const clients = readFile();
|
|
56
|
+
const count = clients.length;
|
|
57
|
+
writeFile([]);
|
|
58
|
+
return count;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function validateClient(token: string): boolean {
|
|
62
|
+
const clients = readFile();
|
|
63
|
+
return clients.some((c) => c.token === token);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function hasClients(): boolean {
|
|
67
|
+
return readFile().length > 0;
|
|
68
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { loadClients, revokeClient, revokeAllClients } from "../client-store.js";
|
|
2
|
+
|
|
3
|
+
export async function clientsListCommand(): Promise<void> {
|
|
4
|
+
const clients = loadClients();
|
|
5
|
+
if (clients.length === 0) {
|
|
6
|
+
console.log("No active clients.");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
console.log(`${clients.length} active client(s):\n`);
|
|
11
|
+
for (const c of clients) {
|
|
12
|
+
const label = c.label ? ` (${c.label})` : "";
|
|
13
|
+
console.log(` ${c.token}${label} created ${c.createdAt}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function clientsRevokeCommand(token: string): Promise<void> {
|
|
18
|
+
if (revokeClient(token)) {
|
|
19
|
+
console.log("Client revoked.");
|
|
20
|
+
} else {
|
|
21
|
+
console.error("Client not found.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function clientsRevokeAllCommand(): Promise<void> {
|
|
27
|
+
const count = revokeAllClients();
|
|
28
|
+
console.log(`Revoked ${count} client(s).`);
|
|
29
|
+
}
|
package/src/commands/info.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { loadConfig } from "../config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { loadClients } from "../client-store.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Print host connection info for setting up clients.
|
|
6
6
|
*/
|
|
7
7
|
export async function infoCommand(): Promise<void> {
|
|
8
8
|
const config = loadConfig();
|
|
9
|
-
const
|
|
9
|
+
const clients = loadClients();
|
|
10
10
|
|
|
11
11
|
console.log(`Host ID: ${config.hostId}`);
|
|
12
12
|
console.log(`Project root: ${config.projectRoot}`);
|
|
@@ -18,10 +18,10 @@ export async function infoCommand(): Promise<void> {
|
|
|
18
18
|
console.log(`Agents: (none detected — run \`palmier agents\`)`);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
console.log(`
|
|
21
|
+
// Clients
|
|
22
|
+
console.log(`Clients: ${clients.length} active`);
|
|
23
23
|
|
|
24
|
-
if (
|
|
24
|
+
if (clients.length === 0) {
|
|
25
25
|
console.log("");
|
|
26
26
|
console.log("No paired clients. Run `palmier pair` to connect a device.");
|
|
27
27
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -44,7 +44,7 @@ export async function initCommand(): Promise<void> {
|
|
|
44
44
|
const lanAnswer = await ask("Enable LAN access (direct HTTP from local network)? (y/N): ");
|
|
45
45
|
const lanEnabled = lanAnswer.trim().toLowerCase() === "y";
|
|
46
46
|
|
|
47
|
-
let httpPort =
|
|
47
|
+
let httpPort = 9966;
|
|
48
48
|
const portLabel = lanEnabled ? "HTTP port for local and LAN access" : "HTTP port for local access";
|
|
49
49
|
const portAnswer = await ask(`${portLabel} (default ${httpPort}): `);
|
|
50
50
|
const parsed = parseInt(portAnswer.trim(), 10);
|
package/src/commands/pair.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as http from "node:http";
|
|
|
2
2
|
import { StringCodec } from "nats";
|
|
3
3
|
import { loadConfig } from "../config.js";
|
|
4
4
|
import { connectNats } from "../nats-client.js";
|
|
5
|
-
import {
|
|
5
|
+
import { addClient } from "../client-store.js";
|
|
6
6
|
import type { HostConfig } from "../types.js";
|
|
7
7
|
|
|
8
8
|
const CODE_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; // no O/0/I/1/L
|
|
@@ -17,10 +17,10 @@ export function generatePairingCode(): string {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function buildPairResponse(config: HostConfig, label?: string) {
|
|
20
|
-
const
|
|
20
|
+
const client = addClient(label);
|
|
21
21
|
return {
|
|
22
22
|
hostId: config.hostId,
|
|
23
|
-
|
|
23
|
+
clientToken: client.token,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -67,7 +67,7 @@ function httpPairRegister(port: number, code: string): Promise<boolean> {
|
|
|
67
67
|
export async function pairCommand(): Promise<void> {
|
|
68
68
|
const config = loadConfig();
|
|
69
69
|
const code = generatePairingCode();
|
|
70
|
-
const httpPort = config.httpPort ??
|
|
70
|
+
const httpPort = config.httpPort ?? 9966;
|
|
71
71
|
|
|
72
72
|
let paired = false;
|
|
73
73
|
|
package/src/commands/run.ts
CHANGED
|
@@ -62,10 +62,15 @@ async function invokeAgentWithRetries(
|
|
|
62
62
|
}, 500);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(
|
|
65
|
+
const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(
|
|
66
|
+
invokeTask, undefined, ctx.task.frontmatter.yolo_mode ? "yolo" : ctx.transientPermissions,
|
|
67
|
+
);
|
|
68
|
+
const truncate = (s: string, max = 100) => s.length > max ? s.slice(0, max) + "…" : s;
|
|
69
|
+
const displayArgs = args.map((a) => truncate(a));
|
|
70
|
+
console.log(`[invoke] ${command} ${displayArgs.join(" ")}${stdin ? ` (stdin: ${truncate(stdin, 100)})` : ""}`);
|
|
66
71
|
const result = await spawnCommand(command, args, {
|
|
67
72
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
68
|
-
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 ??
|
|
73
|
+
env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 9966) },
|
|
69
74
|
echoStdout: true,
|
|
70
75
|
resolveOnFailure: true,
|
|
71
76
|
stdin,
|
|
@@ -304,7 +309,7 @@ async function runCommandTriggeredMode(
|
|
|
304
309
|
|
|
305
310
|
const child = spawnStreamingCommand(commandStr, {
|
|
306
311
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
307
|
-
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 ??
|
|
312
|
+
env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 9966) },
|
|
308
313
|
});
|
|
309
314
|
|
|
310
315
|
let linesProcessed = 0;
|
|
@@ -444,7 +449,7 @@ async function requestPermission(
|
|
|
444
449
|
taskDir: string,
|
|
445
450
|
requiredPermissions: RequiredPermission[],
|
|
446
451
|
): Promise<"granted" | "granted_all" | "aborted"> {
|
|
447
|
-
const port = config.httpPort ??
|
|
452
|
+
const port = config.httpPort ?? 9966;
|
|
448
453
|
const res = await fetch(`http://localhost:${port}/request-permission`, {
|
|
449
454
|
method: "POST",
|
|
450
455
|
headers: { "Content-Type": "application/json" },
|
|
@@ -472,7 +477,7 @@ async function requestConfirmation(
|
|
|
472
477
|
task: ParsedTask,
|
|
473
478
|
taskDir: string,
|
|
474
479
|
): Promise<boolean> {
|
|
475
|
-
const port = config.httpPort ??
|
|
480
|
+
const port = config.httpPort ?? 9966;
|
|
476
481
|
const res = await fetch(`http://localhost:${port}/request-confirmation`, {
|
|
477
482
|
method: "POST",
|
|
478
483
|
headers: { "Content-Type": "application/json" },
|
|
@@ -500,7 +505,8 @@ export function parseReportFiles(output: string): string[] {
|
|
|
500
505
|
let match;
|
|
501
506
|
while ((match = regex.exec(output)) !== null) {
|
|
502
507
|
const name = match[1].trim();
|
|
503
|
-
|
|
508
|
+
// Skip placeholder examples echoed from the prompt (e.g. "<filename>")
|
|
509
|
+
if (name && !name.startsWith("<")) files.push(name);
|
|
504
510
|
}
|
|
505
511
|
return files;
|
|
506
512
|
}
|
|
@@ -515,6 +521,8 @@ export function parsePermissions(output: string): RequiredPermission[] {
|
|
|
515
521
|
let match;
|
|
516
522
|
while ((match = regex.exec(output)) !== null) {
|
|
517
523
|
const raw = match[1].trim();
|
|
524
|
+
// Skip placeholder examples echoed from the prompt (e.g. "<tool_name> | <description>")
|
|
525
|
+
if (raw.startsWith("<")) continue;
|
|
518
526
|
const sep = raw.indexOf("|");
|
|
519
527
|
if (sep !== -1) {
|
|
520
528
|
perms.push({ name: raw.slice(0, sep).trim(), description: raw.slice(sep + 1).trim() });
|
|
@@ -531,8 +539,14 @@ export function parsePermissions(output: string): RequiredPermission[] {
|
|
|
531
539
|
*/
|
|
532
540
|
export function parseTaskOutcome(output: string): TaskRunningState {
|
|
533
541
|
const lastChunk = output.slice(-500);
|
|
534
|
-
|
|
535
|
-
|
|
542
|
+
const regex = new RegExp(`^\\${TASK_FAILURE_MARKER}$|^\\${TASK_SUCCESS_MARKER}$`, "gm");
|
|
543
|
+
let last: string | null = null;
|
|
544
|
+
let match;
|
|
545
|
+
while ((match = regex.exec(lastChunk)) !== null) {
|
|
546
|
+
last = match[0];
|
|
547
|
+
}
|
|
548
|
+
if (last === TASK_FAILURE_MARKER) return "failed";
|
|
549
|
+
if (last === TASK_SUCCESS_MARKER) return "finished";
|
|
536
550
|
return "finished";
|
|
537
551
|
}
|
|
538
552
|
|
package/src/commands/serve.ts
CHANGED
|
@@ -114,7 +114,7 @@ export async function serveCommand(): Promise<void> {
|
|
|
114
114
|
}, POLL_INTERVAL_MS);
|
|
115
115
|
|
|
116
116
|
const handleRpc = createRpcHandler(config, nc);
|
|
117
|
-
const httpPort = config.httpPort ??
|
|
117
|
+
const httpPort = config.httpPort ?? 9966;
|
|
118
118
|
|
|
119
119
|
// Start NATS transport (loops forever, fire-and-forget)
|
|
120
120
|
if (nc) {
|
package/src/events.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { serveCommand } from "./commands/serve.js";
|
|
|
12
12
|
|
|
13
13
|
import { pairCommand } from "./commands/pair.js";
|
|
14
14
|
import { restartCommand } from "./commands/restart.js";
|
|
15
|
-
import {
|
|
15
|
+
import { clientsListCommand, clientsRevokeCommand, clientsRevokeAllCommand } from "./commands/clients.js";
|
|
16
16
|
|
|
17
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
@@ -68,29 +68,29 @@ program
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
.command("
|
|
73
|
-
.description("Manage paired
|
|
71
|
+
const clientsCmd = program
|
|
72
|
+
.command("clients")
|
|
73
|
+
.description("Manage paired clients");
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
clientsCmd
|
|
76
76
|
.command("list")
|
|
77
|
-
.description("List active
|
|
77
|
+
.description("List active clients")
|
|
78
78
|
.action(async () => {
|
|
79
|
-
await
|
|
79
|
+
await clientsListCommand();
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
clientsCmd
|
|
83
83
|
.command("revoke <token>")
|
|
84
|
-
.description("Revoke a
|
|
84
|
+
.description("Revoke a client by token")
|
|
85
85
|
.action(async (token: string) => {
|
|
86
|
-
await
|
|
86
|
+
await clientsRevokeCommand(token);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
clientsCmd
|
|
90
90
|
.command("revoke-all")
|
|
91
|
-
.description("Revoke all
|
|
91
|
+
.description("Revoke all clients")
|
|
92
92
|
.action(async () => {
|
|
93
|
-
await
|
|
93
|
+
await clientsRevokeAllCommand();
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
// No subcommand → default to serve
|
package/src/rpc-handler.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { getPlatform } from "./platform/index.js";
|
|
|
11
11
|
import { spawnCommand } from "./spawn-command.js";
|
|
12
12
|
import crossSpawn from "cross-spawn";
|
|
13
13
|
import { getAgent } from "./agents/agent.js";
|
|
14
|
-
import {
|
|
14
|
+
import { validateClient } from "./client-store.js";
|
|
15
15
|
import { publishHostEvent } from "./events.js";
|
|
16
16
|
import { currentVersion, performUpdate } from "./update-checker.js";
|
|
17
17
|
import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
|
|
@@ -166,8 +166,8 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
async function handleRpc(request: RpcMessage): Promise<unknown> {
|
|
169
|
-
//
|
|
170
|
-
if (!request.localhost && (!request.
|
|
169
|
+
// Client token validation: skip for trusted localhost requests
|
|
170
|
+
if (!request.localhost && (!request.clientToken || !validateClient(request.clientToken))) {
|
|
171
171
|
return { error: "Unauthorized" };
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -188,6 +188,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
188
188
|
triggers?: Array<{ type: "cron" | "once"; value: string }>;
|
|
189
189
|
triggers_enabled?: boolean;
|
|
190
190
|
requires_confirmation?: boolean;
|
|
191
|
+
yolo_mode?: boolean;
|
|
191
192
|
command?: string;
|
|
192
193
|
};
|
|
193
194
|
|
|
@@ -218,6 +219,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
218
219
|
triggers: params.triggers ?? [],
|
|
219
220
|
triggers_enabled: params.triggers_enabled ?? true,
|
|
220
221
|
requires_confirmation: params.requires_confirmation ?? true,
|
|
222
|
+
...(params.yolo_mode ? { yolo_mode: true } : {}),
|
|
221
223
|
...(params.command ? { command: params.command } : {}),
|
|
222
224
|
},
|
|
223
225
|
body,
|
|
@@ -238,6 +240,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
238
240
|
triggers?: Array<{ type: "cron" | "once"; value: string }>;
|
|
239
241
|
triggers_enabled?: boolean;
|
|
240
242
|
requires_confirmation?: boolean;
|
|
243
|
+
yolo_mode?: boolean;
|
|
241
244
|
command?: string;
|
|
242
245
|
};
|
|
243
246
|
|
|
@@ -256,6 +259,10 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
256
259
|
if (params.triggers_enabled !== undefined) existing.frontmatter.triggers_enabled = params.triggers_enabled;
|
|
257
260
|
if (params.requires_confirmation !== undefined)
|
|
258
261
|
existing.frontmatter.requires_confirmation = params.requires_confirmation;
|
|
262
|
+
if (params.yolo_mode !== undefined) {
|
|
263
|
+
existing.frontmatter.yolo_mode = params.yolo_mode || undefined;
|
|
264
|
+
if (params.yolo_mode) delete existing.frontmatter.permissions;
|
|
265
|
+
}
|
|
259
266
|
if (params.command !== undefined) {
|
|
260
267
|
if (params.command) {
|
|
261
268
|
existing.frontmatter.command = params.command;
|
|
@@ -302,6 +309,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
302
309
|
user_prompt: string;
|
|
303
310
|
agent: string;
|
|
304
311
|
requires_confirmation?: boolean;
|
|
312
|
+
yolo_mode?: boolean;
|
|
305
313
|
command?: string;
|
|
306
314
|
};
|
|
307
315
|
|
|
@@ -317,6 +325,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
317
325
|
triggers: [],
|
|
318
326
|
triggers_enabled: false,
|
|
319
327
|
requires_confirmation: params.requires_confirmation ?? false,
|
|
328
|
+
...(params.yolo_mode ? { yolo_mode: true } : {}),
|
|
320
329
|
...(params.command ? { command: params.command } : {}),
|
|
321
330
|
},
|
|
322
331
|
body: "",
|
|
@@ -390,7 +399,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
390
399
|
// Fire-and-forget: invoke agent inline as a child of the serve process
|
|
391
400
|
const followupAgent = getAgent(followupTask.frontmatter.agent);
|
|
392
401
|
const { command: cmd, args: cmdArgs, stdin } = followupAgent.getTaskRunCommandLine(
|
|
393
|
-
followupTask, params.message, followupTask.frontmatter.permissions,
|
|
402
|
+
followupTask, params.message, followupTask.frontmatter.yolo_mode ? "yolo" : followupTask.frontmatter.permissions,
|
|
394
403
|
);
|
|
395
404
|
|
|
396
405
|
// Spawn directly via crossSpawn so we can track and kill the child
|
|
@@ -564,8 +573,8 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
564
573
|
const reports: Array<{ file: string; content?: string; error?: string }> = [];
|
|
565
574
|
const runDir = path.join(config.projectRoot, "tasks", params.id, params.run_id);
|
|
566
575
|
for (const file of params.report_files) {
|
|
567
|
-
if (!file.endsWith(".md")) {
|
|
568
|
-
reports.push({ file, error: "must end with .md" });
|
|
576
|
+
if (!file.endsWith(".md") && !file.endsWith(".txt")) {
|
|
577
|
+
reports.push({ file, error: "must end with .md or .txt" });
|
|
569
578
|
continue;
|
|
570
579
|
}
|
|
571
580
|
const basename = path.basename(file);
|