palmier 0.4.1 → 0.4.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 +13 -27
- package/dist/agents/agent-instructions.md +40 -0
- package/dist/agents/claude.js +2 -8
- package/dist/agents/codex.js +0 -6
- package/dist/agents/copilot.js +0 -20
- package/dist/agents/gemini.js +0 -6
- package/dist/agents/shared-prompt.d.ts +1 -2
- package/dist/agents/shared-prompt.js +5 -18
- package/dist/commands/notify.d.ts +9 -0
- package/dist/commands/notify.js +43 -0
- package/dist/commands/request-input.d.ts +11 -0
- package/dist/commands/request-input.js +63 -0
- package/dist/commands/run.d.ts +0 -5
- package/dist/commands/run.js +67 -72
- package/dist/commands/serve.js +7 -6
- package/dist/index.js +15 -5
- package/dist/platform/windows.js +17 -2
- package/dist/rpc-handler.js +42 -27
- package/dist/spawn-command.d.ts +1 -1
- package/dist/spawn-command.js +13 -1
- package/dist/task.d.ts +12 -1
- package/dist/task.js +36 -1
- package/dist/types.d.ts +9 -0
- package/dist/update-checker.d.ts +0 -8
- package/dist/update-checker.js +0 -36
- package/package.json +2 -3
- package/src/agents/agent-instructions.md +40 -0
- package/src/agents/claude.ts +2 -7
- package/src/agents/codex.ts +0 -5
- package/src/agents/copilot.ts +0 -19
- package/src/agents/gemini.ts +0 -5
- package/src/agents/shared-prompt.ts +10 -18
- package/src/commands/notify.ts +44 -0
- package/src/commands/request-input.ts +65 -0
- package/src/commands/run.ts +78 -96
- package/src/commands/serve.ts +7 -7
- package/src/index.ts +16 -5
- package/src/platform/windows.ts +17 -2
- package/src/rpc-handler.ts +51 -26
- package/src/spawn-command.ts +13 -2
- package/src/task.ts +47 -2
- package/src/types.ts +10 -0
- package/src/update-checker.ts +0 -36
- package/dist/commands/mcpserver.d.ts +0 -2
- package/dist/commands/mcpserver.js +0 -93
- package/src/commands/mcpserver.ts +0 -113
package/src/task.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
|
-
import type { ParsedTask, TaskFrontmatter, TaskStatus, HistoryEntry } from "./types.js";
|
|
4
|
+
import type { ParsedTask, TaskFrontmatter, TaskStatus, HistoryEntry, ConversationMessage } from "./types.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Parse a TASK.md file from the given task directory.
|
|
@@ -159,11 +159,56 @@ export function createResultFile(
|
|
|
159
159
|
): string {
|
|
160
160
|
const resultFileName = `RESULT-${startTime}.md`;
|
|
161
161
|
const taskSnapshotName = `TASK-${startTime}.md`;
|
|
162
|
-
const content = `---\ntask_name: ${taskName}\nrunning_state: started\nstart_time: ${startTime}\ntask_file: ${taskSnapshotName}\n---\n`;
|
|
162
|
+
const content = `---\ntask_name: ${taskName}\nrunning_state: started\nstart_time: ${startTime}\ntask_file: ${taskSnapshotName}\n---\n\n`;
|
|
163
163
|
fs.writeFileSync(path.join(taskDir, resultFileName), content, "utf-8");
|
|
164
164
|
return resultFileName;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Append a conversation message to a RESULT file.
|
|
169
|
+
*/
|
|
170
|
+
export function appendResultMessage(
|
|
171
|
+
taskDir: string,
|
|
172
|
+
resultFile: string,
|
|
173
|
+
msg: ConversationMessage,
|
|
174
|
+
): void {
|
|
175
|
+
const attrs = [`role="${msg.role}"`, `time="${msg.time}"`];
|
|
176
|
+
if (msg.type) attrs.push(`type="${msg.type}"`);
|
|
177
|
+
if (msg.attachments?.length) attrs.push(`attachments="${msg.attachments.join(",")}"`);
|
|
178
|
+
|
|
179
|
+
const delimiter = `<!-- palmier:message ${attrs.join(" ")} -->`;
|
|
180
|
+
const entry = `${delimiter}\n\n${msg.content}\n\n`;
|
|
181
|
+
fs.appendFileSync(path.join(taskDir, resultFile), entry, "utf-8");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Update frontmatter fields in a RESULT file without touching the body.
|
|
186
|
+
*/
|
|
187
|
+
export function finalizeResultFrontmatter(
|
|
188
|
+
taskDir: string,
|
|
189
|
+
resultFile: string,
|
|
190
|
+
updates: { end_time?: number; running_state?: string },
|
|
191
|
+
): void {
|
|
192
|
+
const filePath = path.join(taskDir, resultFile);
|
|
193
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
194
|
+
const fmEnd = raw.indexOf("\n---\n", 4); // skip opening ---
|
|
195
|
+
if (fmEnd === -1) return;
|
|
196
|
+
|
|
197
|
+
let frontmatter = raw.slice(0, fmEnd);
|
|
198
|
+
const body = raw.slice(fmEnd);
|
|
199
|
+
|
|
200
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
201
|
+
const regex = new RegExp(`^${key}:.*$`, "m");
|
|
202
|
+
if (regex.test(frontmatter)) {
|
|
203
|
+
frontmatter = frontmatter.replace(regex, `${key}: ${value}`);
|
|
204
|
+
} else {
|
|
205
|
+
frontmatter += `\n${key}: ${value}`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(filePath, frontmatter + body, "utf-8");
|
|
210
|
+
}
|
|
211
|
+
|
|
167
212
|
/**
|
|
168
213
|
* Append a history entry to the project-level history.jsonl file.
|
|
169
214
|
*/
|
package/src/types.ts
CHANGED
|
@@ -53,6 +53,8 @@ export type TaskRunningState = "started" | "finished" | "aborted" | "failed";
|
|
|
53
53
|
export interface TaskStatus {
|
|
54
54
|
running_state: TaskRunningState;
|
|
55
55
|
time_stamp: number;
|
|
56
|
+
/** PID of the palmier run process (used on Windows to kill the process tree). */
|
|
57
|
+
pid?: number;
|
|
56
58
|
/** Set when the task has `requires_confirmation` and is awaiting user approval. */
|
|
57
59
|
pending_confirmation?: boolean;
|
|
58
60
|
/** Set when the agent requests permissions not yet granted. Contains the permissions needed. */
|
|
@@ -73,6 +75,14 @@ export interface RequiredPermission {
|
|
|
73
75
|
description: string;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
export interface ConversationMessage {
|
|
79
|
+
role: "assistant" | "user" | "status";
|
|
80
|
+
time: number;
|
|
81
|
+
content: string;
|
|
82
|
+
type?: "input" | "permission" | "confirmation" | "started" | "finished" | "failed" | "aborted";
|
|
83
|
+
attachments?: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
export interface RpcMessage {
|
|
77
87
|
method: string;
|
|
78
88
|
params: Record<string, unknown>;
|
package/src/update-checker.ts
CHANGED
|
@@ -12,41 +12,6 @@ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "
|
|
|
12
12
|
export const isDevBuild = fs.existsSync(path.join(packageRoot, ".git"));
|
|
13
13
|
export const currentVersion = isDevBuild ? `${pkg.version}-dev` : pkg.version;
|
|
14
14
|
|
|
15
|
-
let latestVersion: string | null = null;
|
|
16
|
-
let lastCheckTime = 0;
|
|
17
|
-
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Check the npm registry for the latest version of palmier.
|
|
21
|
-
*/
|
|
22
|
-
export async function checkForUpdate(): Promise<void> {
|
|
23
|
-
if (isDevBuild) return;
|
|
24
|
-
const now = Date.now();
|
|
25
|
-
if (now - lastCheckTime < CHECK_INTERVAL_MS) return;
|
|
26
|
-
lastCheckTime = now;
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const res = await fetch("https://registry.npmjs.org/palmier/latest", {
|
|
30
|
-
signal: AbortSignal.timeout(10_000),
|
|
31
|
-
});
|
|
32
|
-
if (!res.ok) return;
|
|
33
|
-
const data = (await res.json()) as { version?: string };
|
|
34
|
-
if (data.version) {
|
|
35
|
-
latestVersion = data.version;
|
|
36
|
-
console.log(`[update] Latest version: ${data.version} (current: ${currentVersion})`);
|
|
37
|
-
}
|
|
38
|
-
} catch {
|
|
39
|
-
// Network errors are expected (offline, etc.)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get the latest version from npm, or null if not yet checked.
|
|
45
|
-
*/
|
|
46
|
-
export function getLatestVersion(): string | null {
|
|
47
|
-
return latestVersion;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
15
|
/**
|
|
51
16
|
* Run the update and restart the daemon.
|
|
52
17
|
* Returns an error message if the update fails.
|
|
@@ -63,7 +28,6 @@ export async function performUpdate(): Promise<string | null> {
|
|
|
63
28
|
return `Update failed. Please run manually:\nnpm update -g palmier`;
|
|
64
29
|
}
|
|
65
30
|
console.log("[update] Update installed, restarting daemon...");
|
|
66
|
-
latestVersion = null;
|
|
67
31
|
// Small delay to allow the RPC response to be sent
|
|
68
32
|
setTimeout(() => {
|
|
69
33
|
getPlatform().restartDaemon().catch((err) => {
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { StringCodec } from "nats";
|
|
5
|
-
import { loadConfig } from "../config.js";
|
|
6
|
-
import { connectNats } from "../nats-client.js";
|
|
7
|
-
import { getTaskDir, parseTaskFile } from "../task.js";
|
|
8
|
-
import { requestUserInput, publishInputResolved } from "../user-input.js";
|
|
9
|
-
export async function mcpserverCommand() {
|
|
10
|
-
const config = loadConfig();
|
|
11
|
-
const nc = await connectNats(config);
|
|
12
|
-
const sc = StringCodec();
|
|
13
|
-
const server = new McpServer({ name: "palmier", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
14
|
-
// send-push-notification requires NATS — only register when server mode is enabled
|
|
15
|
-
if (nc) {
|
|
16
|
-
server.registerTool("send-push-notification", {
|
|
17
|
-
description: "Send a push notification to the user",
|
|
18
|
-
inputSchema: {
|
|
19
|
-
title: z.string().describe("Notification title"),
|
|
20
|
-
body: z.string().describe("Notification body text"),
|
|
21
|
-
},
|
|
22
|
-
}, async (args) => {
|
|
23
|
-
const payload = {
|
|
24
|
-
hostId: config.hostId,
|
|
25
|
-
title: args.title,
|
|
26
|
-
body: args.body,
|
|
27
|
-
};
|
|
28
|
-
try {
|
|
29
|
-
const subject = `host.${config.hostId}.push.send`;
|
|
30
|
-
const reply = await nc.request(subject, sc.encode(JSON.stringify(payload)), {
|
|
31
|
-
timeout: 15_000,
|
|
32
|
-
});
|
|
33
|
-
const result = JSON.parse(sc.decode(reply.data));
|
|
34
|
-
if (result.ok) {
|
|
35
|
-
return {
|
|
36
|
-
content: [{ type: "text", text: "Push notification sent successfully" }],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
return {
|
|
41
|
-
content: [{ type: "text", text: `Failed to send push notification: ${result.error}` }],
|
|
42
|
-
isError: true,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
return {
|
|
48
|
-
content: [{ type: "text", text: `Error sending push notification: ${err}` }],
|
|
49
|
-
isError: true,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
const taskId = process.env.PALMIER_TASK_ID;
|
|
55
|
-
if (taskId) {
|
|
56
|
-
const taskDir = getTaskDir(config.projectRoot, taskId);
|
|
57
|
-
const task = parseTaskFile(taskDir);
|
|
58
|
-
server.registerTool("request-user-input", {
|
|
59
|
-
description: "Request input from the user. The user will see the descriptions and can provide values or abort.",
|
|
60
|
-
inputSchema: {
|
|
61
|
-
descriptions: z.array(z.string()).describe("List of input descriptions to show the user"),
|
|
62
|
-
},
|
|
63
|
-
}, async (args) => {
|
|
64
|
-
try {
|
|
65
|
-
const response = await requestUserInput(nc, config, taskId, task.frontmatter.name, taskDir, args.descriptions);
|
|
66
|
-
await publishInputResolved(nc, config, taskId, response === "aborted" ? "aborted" : "provided");
|
|
67
|
-
if (response === "aborted") {
|
|
68
|
-
return {
|
|
69
|
-
content: [{ type: "text", text: "User aborted the input request." }],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const lines = args.descriptions.map((desc, i) => `${desc}: ${response[i]}`).join("\n");
|
|
73
|
-
return {
|
|
74
|
-
content: [{ type: "text", text: lines }],
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
return {
|
|
79
|
-
content: [{ type: "text", text: `Error requesting user input: ${err}` }],
|
|
80
|
-
isError: true,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
const transport = new StdioServerTransport();
|
|
86
|
-
await server.connect(transport);
|
|
87
|
-
// Graceful shutdown
|
|
88
|
-
transport.onclose = async () => {
|
|
89
|
-
if (nc)
|
|
90
|
-
await nc.drain();
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
//# sourceMappingURL=mcpserver.js.map
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { StringCodec } from "nats";
|
|
5
|
-
import { loadConfig } from "../config.js";
|
|
6
|
-
import { connectNats } from "../nats-client.js";
|
|
7
|
-
import { getTaskDir, parseTaskFile } from "../task.js";
|
|
8
|
-
import { requestUserInput, publishInputResolved } from "../user-input.js";
|
|
9
|
-
export async function mcpserverCommand(): Promise<void> {
|
|
10
|
-
const config = loadConfig();
|
|
11
|
-
const nc = await connectNats(config);
|
|
12
|
-
|
|
13
|
-
const sc = StringCodec();
|
|
14
|
-
|
|
15
|
-
const server = new McpServer(
|
|
16
|
-
{ name: "palmier", version: "1.0.0" },
|
|
17
|
-
{ capabilities: { tools: {} } }
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
// send-push-notification requires NATS — only register when server mode is enabled
|
|
21
|
-
if (nc) {
|
|
22
|
-
server.registerTool(
|
|
23
|
-
"send-push-notification",
|
|
24
|
-
{
|
|
25
|
-
description: "Send a push notification to the user",
|
|
26
|
-
inputSchema: {
|
|
27
|
-
title: z.string().describe("Notification title"),
|
|
28
|
-
body: z.string().describe("Notification body text"),
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
async (args) => {
|
|
32
|
-
const payload = {
|
|
33
|
-
hostId: config.hostId,
|
|
34
|
-
title: args.title,
|
|
35
|
-
body: args.body,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const subject = `host.${config.hostId}.push.send`;
|
|
40
|
-
const reply = await nc!.request(subject, sc.encode(JSON.stringify(payload)), {
|
|
41
|
-
timeout: 15_000,
|
|
42
|
-
});
|
|
43
|
-
const result = JSON.parse(sc.decode(reply.data)) as {
|
|
44
|
-
ok?: boolean;
|
|
45
|
-
error?: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
if (result.ok) {
|
|
49
|
-
return {
|
|
50
|
-
content: [{ type: "text" as const, text: "Push notification sent successfully" }],
|
|
51
|
-
};
|
|
52
|
-
} else {
|
|
53
|
-
return {
|
|
54
|
-
content: [{ type: "text" as const, text: `Failed to send push notification: ${result.error}` }],
|
|
55
|
-
isError: true,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
return {
|
|
60
|
-
content: [{ type: "text" as const, text: `Error sending push notification: ${err}` }],
|
|
61
|
-
isError: true,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const taskId = process.env.PALMIER_TASK_ID;
|
|
69
|
-
if (taskId) {
|
|
70
|
-
const taskDir = getTaskDir(config.projectRoot, taskId);
|
|
71
|
-
const task = parseTaskFile(taskDir);
|
|
72
|
-
|
|
73
|
-
server.registerTool(
|
|
74
|
-
"request-user-input",
|
|
75
|
-
{
|
|
76
|
-
description: "Request input from the user. The user will see the descriptions and can provide values or abort.",
|
|
77
|
-
inputSchema: {
|
|
78
|
-
descriptions: z.array(z.string()).describe("List of input descriptions to show the user"),
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
async (args) => {
|
|
82
|
-
try {
|
|
83
|
-
const response = await requestUserInput(nc, config, taskId, task.frontmatter.name, taskDir, args.descriptions);
|
|
84
|
-
await publishInputResolved(nc, config, taskId, response === "aborted" ? "aborted" : "provided");
|
|
85
|
-
|
|
86
|
-
if (response === "aborted") {
|
|
87
|
-
return {
|
|
88
|
-
content: [{ type: "text" as const, text: "User aborted the input request." }],
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const lines = args.descriptions.map((desc: string, i: number) => `${desc}: ${response[i]}`).join("\n");
|
|
93
|
-
return {
|
|
94
|
-
content: [{ type: "text" as const, text: lines }],
|
|
95
|
-
};
|
|
96
|
-
} catch (err) {
|
|
97
|
-
return {
|
|
98
|
-
content: [{ type: "text" as const, text: `Error requesting user input: ${err}` }],
|
|
99
|
-
isError: true,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const transport = new StdioServerTransport();
|
|
107
|
-
await server.connect(transport);
|
|
108
|
-
|
|
109
|
-
// Graceful shutdown
|
|
110
|
-
transport.onclose = async () => {
|
|
111
|
-
if (nc) await nc.drain();
|
|
112
|
-
};
|
|
113
|
-
}
|