palmier 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CLAUDE.md +5 -1
  2. package/README.md +135 -45
  3. package/dist/agents/agent.d.ts +26 -0
  4. package/dist/agents/agent.js +32 -0
  5. package/dist/agents/claude.d.ts +8 -0
  6. package/dist/agents/claude.js +35 -0
  7. package/dist/agents/codex.d.ts +8 -0
  8. package/dist/agents/codex.js +41 -0
  9. package/dist/agents/gemini.d.ts +8 -0
  10. package/dist/agents/gemini.js +39 -0
  11. package/dist/agents/openclaw.d.ts +8 -0
  12. package/dist/agents/openclaw.js +25 -0
  13. package/dist/agents/shared-prompt.d.ts +11 -0
  14. package/dist/agents/shared-prompt.js +26 -0
  15. package/dist/commands/agents.d.ts +2 -0
  16. package/dist/commands/agents.js +19 -0
  17. package/dist/commands/info.d.ts +5 -0
  18. package/dist/commands/info.js +40 -0
  19. package/dist/commands/init.d.ts +7 -2
  20. package/dist/commands/init.js +139 -49
  21. package/dist/commands/mcpserver.d.ts +2 -0
  22. package/dist/commands/mcpserver.js +75 -0
  23. package/dist/commands/pair.d.ts +6 -0
  24. package/dist/commands/pair.js +166 -0
  25. package/dist/commands/plan-generation.md +32 -0
  26. package/dist/commands/run.d.ts +0 -1
  27. package/dist/commands/run.js +258 -114
  28. package/dist/commands/serve.d.ts +1 -1
  29. package/dist/commands/serve.js +16 -228
  30. package/dist/commands/sessions.d.ts +4 -0
  31. package/dist/commands/sessions.js +30 -0
  32. package/dist/commands/task-generation.md +1 -1
  33. package/dist/config.d.ts +5 -5
  34. package/dist/config.js +24 -6
  35. package/dist/index.js +58 -5
  36. package/dist/nats-client.d.ts +3 -3
  37. package/dist/nats-client.js +2 -2
  38. package/dist/rpc-handler.d.ts +6 -0
  39. package/dist/rpc-handler.js +367 -0
  40. package/dist/session-store.d.ts +12 -0
  41. package/dist/session-store.js +57 -0
  42. package/dist/spawn-command.d.ts +26 -0
  43. package/dist/spawn-command.js +48 -0
  44. package/dist/systemd.d.ts +2 -2
  45. package/dist/task.d.ts +45 -2
  46. package/dist/task.js +155 -14
  47. package/dist/transports/http-transport.d.ts +6 -0
  48. package/dist/transports/http-transport.js +157 -0
  49. package/dist/transports/nats-transport.d.ts +6 -0
  50. package/dist/transports/nats-transport.js +69 -0
  51. package/dist/types.d.ts +30 -13
  52. package/package.json +4 -3
  53. package/src/agents/agent.ts +62 -0
  54. package/src/agents/claude.ts +39 -0
  55. package/src/agents/codex.ts +46 -0
  56. package/src/agents/gemini.ts +43 -0
  57. package/src/agents/openclaw.ts +29 -0
  58. package/src/agents/shared-prompt.ts +26 -0
  59. package/src/commands/agents.ts +20 -0
  60. package/src/commands/info.ts +44 -0
  61. package/src/commands/init.ts +229 -121
  62. package/src/commands/mcpserver.ts +92 -0
  63. package/src/commands/pair.ts +195 -0
  64. package/src/commands/plan-generation.md +32 -0
  65. package/src/commands/run.ts +323 -129
  66. package/src/commands/serve.ts +26 -287
  67. package/src/commands/sessions.ts +32 -0
  68. package/src/config.ts +30 -10
  69. package/src/index.ts +67 -6
  70. package/src/nats-client.ts +4 -4
  71. package/src/rpc-handler.ts +421 -0
  72. package/src/session-store.ts +68 -0
  73. package/src/spawn-command.ts +78 -0
  74. package/src/systemd.ts +2 -2
  75. package/src/task.ts +166 -16
  76. package/src/transports/http-transport.ts +180 -0
  77. package/src/transports/nats-transport.ts +82 -0
  78. package/src/types.ts +36 -13
  79. package/src/commands/task-generation.md +0 -28
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 } from "./types.js";
4
+ import type { ParsedTask, TaskFrontmatter, TaskStatus, HistoryEntry } from "./types.js";
5
5
 
6
6
  /**
7
7
  * Parse a TASK.md file from the given task directory.
@@ -35,7 +35,8 @@ function parseTaskContent(content: string): ParsedTask {
35
35
  throw new Error("TASK.md frontmatter must include at least: id");
36
36
  }
37
37
 
38
- frontmatter.command_line ??= "claude -p --dangerously-skip-permissions";
38
+ frontmatter.name ??= frontmatter.user_prompt?.slice(0, 60) ?? "";
39
+ frontmatter.agent ??= "claude";
39
40
  frontmatter.triggers_enabled ??= true;
40
41
 
41
42
  return { frontmatter, body };
@@ -56,34 +57,66 @@ export function writeTaskFile(taskDir: string, task: ParsedTask): void {
56
57
  }
57
58
 
58
59
  /**
59
- * List all tasks from projectRoot/tasks/{id}/TASK.md.
60
+ * Append a task ID to the project-level tasks.jsonl file.
60
61
  */
61
- export function listTasks(projectRoot: string): ParsedTask[] {
62
- const tasksDir = path.join(projectRoot, "tasks");
62
+ export function appendTaskList(projectRoot: string, taskId: string): void {
63
+ const listPath = path.join(projectRoot, "tasks.jsonl");
64
+ fs.appendFileSync(listPath, JSON.stringify({ task_id: taskId }) + "\n", "utf-8");
65
+ }
63
66
 
64
- if (!fs.existsSync(tasksDir)) {
65
- return [];
67
+ /**
68
+ * Remove a task ID from the project-level tasks.jsonl file.
69
+ * Returns true if the entry was found and removed.
70
+ */
71
+ export function removeFromTaskList(projectRoot: string, taskId: string): boolean {
72
+ const listPath = path.join(projectRoot, "tasks.jsonl");
73
+ if (!fs.existsSync(listPath)) return false;
74
+
75
+ const lines = fs.readFileSync(listPath, "utf-8").split("\n").filter(Boolean);
76
+ let found = false;
77
+ const remaining: string[] = [];
78
+
79
+ for (const line of lines) {
80
+ try {
81
+ const entry = JSON.parse(line) as { task_id: string };
82
+ if (entry.task_id === taskId) {
83
+ found = true;
84
+ continue;
85
+ }
86
+ } catch { /* keep malformed lines */ }
87
+ remaining.push(line);
66
88
  }
67
89
 
68
- const entries = fs.readdirSync(tasksDir, { withFileTypes: true });
69
- const tasks: ParsedTask[] = [];
90
+ if (!found) return false;
91
+ fs.writeFileSync(listPath, remaining.length > 0 ? remaining.join("\n") + "\n" : "", "utf-8");
92
+ return true;
93
+ }
70
94
 
71
- for (const entry of entries) {
72
- if (!entry.isDirectory()) continue;
95
+ /**
96
+ * List all tasks referenced in tasks.jsonl.
97
+ */
98
+ export function listTasks(projectRoot: string): ParsedTask[] {
99
+ const listPath = path.join(projectRoot, "tasks.jsonl");
100
+ if (!fs.existsSync(listPath)) return [];
73
101
 
74
- const taskDir = path.join(tasksDir, entry.name);
75
- const taskFile = path.join(taskDir, "TASK.md");
102
+ const lines = fs.readFileSync(listPath, "utf-8").split("\n").filter(Boolean);
103
+ const tasks: ParsedTask[] = [];
76
104
 
77
- if (!fs.existsSync(taskFile)) continue;
105
+ for (const line of lines) {
106
+ let taskId: string;
107
+ try {
108
+ taskId = (JSON.parse(line) as { task_id: string }).task_id;
109
+ } catch { continue; }
78
110
 
111
+ const taskDir = getTaskDir(projectRoot, taskId);
79
112
  try {
80
113
  tasks.push(parseTaskFile(taskDir));
81
114
  } catch (err) {
82
- console.error(`Warning: failed to parse task in ${taskDir}: ${err}`);
115
+ console.error(`Warning: failed to parse task ${taskId}: ${err}`);
83
116
  }
84
117
  }
85
118
 
86
- return tasks;
119
+ return tasks.reverse();
87
120
  }
88
121
 
89
122
  /**
@@ -92,3 +125,120 @@ export function listTasks(projectRoot: string): ParsedTask[] {
92
125
  export function getTaskDir(projectRoot: string, taskId: string): string {
93
126
  return path.join(projectRoot, "tasks", taskId);
94
127
  }
128
+
129
+ /**
130
+ * Get the creation time (birthtime) of a TASK.md file in ms since epoch.
131
+ */
132
+ export function getTaskCreatedAt(taskDir: string): number {
133
+ const filePath = path.join(taskDir, "TASK.md");
134
+ try {
135
+ return fs.statSync(filePath).birthtimeMs;
136
+ } catch {
137
+ return 0;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Write task status to status.json in the task directory.
143
+ */
144
+ export function writeTaskStatus(taskDir: string, status: TaskStatus): void {
145
+ const filePath = path.join(taskDir, "status.json");
146
+ fs.writeFileSync(filePath, JSON.stringify(status), "utf-8");
147
+ }
148
+
149
+ /**
150
+ * Read task status from status.json in the task directory.
151
+ * Returns undefined if the file doesn't exist.
152
+ */
153
+ export function readTaskStatus(taskDir: string): TaskStatus | undefined {
154
+ const filePath = path.join(taskDir, "status.json");
155
+ try {
156
+ return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TaskStatus;
157
+ } catch {
158
+ return undefined;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Append a history entry to the project-level history.jsonl file.
164
+ */
165
+ export function appendHistory(projectRoot: string, entry: HistoryEntry): void {
166
+ const historyPath = path.join(projectRoot, "history.jsonl");
167
+ fs.appendFileSync(historyPath, JSON.stringify(entry) + "\n", "utf-8");
168
+ }
169
+
170
+ /**
171
+ * Delete a history entry and its associated result/task-snapshot files.
172
+ * Returns true if the entry was found and removed.
173
+ */
174
+ export function deleteHistoryEntry(
175
+ projectRoot: string,
176
+ taskId: string,
177
+ resultFile: string,
178
+ ): boolean {
179
+ const historyPath = path.join(projectRoot, "history.jsonl");
180
+ if (!fs.existsSync(historyPath)) return false;
181
+
182
+ const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
183
+ let found = false;
184
+ const remaining: string[] = [];
185
+
186
+ for (const line of lines) {
187
+ try {
188
+ const entry = JSON.parse(line) as HistoryEntry;
189
+ if (entry.task_id === taskId && entry.result_file === resultFile) {
190
+ found = true;
191
+ continue; // skip this entry
192
+ }
193
+ } catch { /* keep malformed lines */ }
194
+ remaining.push(line);
195
+ }
196
+
197
+ if (!found) return false;
198
+
199
+ // Rewrite history.jsonl without the deleted entry
200
+ fs.writeFileSync(historyPath, remaining.length > 0 ? remaining.join("\n") + "\n" : "", "utf-8");
201
+
202
+ // Delete the result file
203
+ const resultPath = path.join(projectRoot, "tasks", taskId, resultFile);
204
+ if (fs.existsSync(resultPath)) {
205
+ fs.unlinkSync(resultPath);
206
+ }
207
+
208
+ // Delete the corresponding task snapshot (TASK-<timestamp>.md)
209
+ const tsMatch = resultFile.match(/^RESULT-(\d+)\.md$/);
210
+ if (tsMatch) {
211
+ const snapshotFile = `TASK-${tsMatch[1]}.md`;
212
+ const snapshotPath = path.join(projectRoot, "tasks", taskId, snapshotFile);
213
+ if (fs.existsSync(snapshotPath)) {
214
+ fs.unlinkSync(snapshotPath);
215
+ }
216
+ }
217
+
218
+ return true;
219
+ }
220
+
221
+ /**
222
+ * Read history entries from history.jsonl with pagination.
223
+ * Returns entries sorted most-recent-first.
224
+ */
225
+ export function readHistory(
226
+ projectRoot: string,
227
+ opts: { offset?: number; limit?: number; task_id?: string },
228
+ ): { entries: HistoryEntry[]; total: number } {
229
+ const historyPath = path.join(projectRoot, "history.jsonl");
230
+ if (!fs.existsSync(historyPath)) return { entries: [], total: 0 };
231
+
232
+ const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
233
+ let all: HistoryEntry[] = [];
234
+ for (const line of lines) {
235
+ try { all.push(JSON.parse(line)); } catch { /* skip malformed */ }
236
+ }
237
+ all.reverse();
238
+ if (opts.task_id) {
239
+ all = all.filter((e) => e.task_id === opts.task_id);
240
+ }
241
+ const offset = opts.offset ?? 0;
242
+ const limit = opts.limit ?? 10;
243
+ return { entries: all.slice(offset, offset + limit), total: all.length };
244
+ }
@@ -0,0 +1,180 @@
1
+ import * as http from "node:http";
2
+ import { validateSession, hasSessions } from "../session-store.js";
3
+ import type { HostConfig, RpcMessage } from "../types.js";
4
+
5
+ type SseClient = http.ServerResponse;
6
+
7
+ /**
8
+ * Start the HTTP transport: Express-like server with RPC, SSE, and health endpoints.
9
+ */
10
+ export async function startHttpTransport(
11
+ config: HostConfig,
12
+ handleRpc: (req: RpcMessage) => Promise<unknown>,
13
+ ): Promise<void> {
14
+ const port = config.directPort ?? 7400;
15
+ const sseClients = new Set<SseClient>();
16
+
17
+ function broadcastSseEvent(data: unknown) {
18
+ const payload = `data: ${JSON.stringify(data)}\n\n`;
19
+ for (const client of sseClients) {
20
+ client.write(payload);
21
+ }
22
+ }
23
+
24
+ function setCorsHeaders(res: http.ServerResponse) {
25
+ res.setHeader("Access-Control-Allow-Origin", "*");
26
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
27
+ res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
28
+ }
29
+
30
+ function checkAuth(req: http.IncomingMessage): boolean {
31
+ const auth = req.headers.authorization;
32
+ if (!auth || !auth.startsWith("Bearer ")) return false;
33
+ const token = auth.slice(7);
34
+ // Accept the original directToken or any valid session token
35
+ if (token === config.directToken) return true;
36
+ if (hasSessions() && validateSession(token)) return true;
37
+ return false;
38
+ }
39
+
40
+ function extractSessionToken(req: http.IncomingMessage): string | undefined {
41
+ const auth = req.headers.authorization;
42
+ if (!auth || !auth.startsWith("Bearer ")) return undefined;
43
+ return auth.slice(7);
44
+ }
45
+
46
+ function sendJson(res: http.ServerResponse, status: number, body: unknown) {
47
+ res.writeHead(status, { "Content-Type": "application/json" });
48
+ res.end(JSON.stringify(body));
49
+ }
50
+
51
+ function readBody(req: http.IncomingMessage): Promise<string> {
52
+ return new Promise((resolve, reject) => {
53
+ const chunks: Buffer[] = [];
54
+ req.on("data", (chunk: Buffer) => chunks.push(chunk));
55
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
56
+ req.on("error", reject);
57
+ });
58
+ }
59
+
60
+ function isLocalhost(req: http.IncomingMessage): boolean {
61
+ const addr = req.socket.remoteAddress;
62
+ return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
63
+ }
64
+
65
+ const server = http.createServer(async (req, res) => {
66
+ setCorsHeaders(res);
67
+
68
+ // Handle CORS preflight
69
+ if (req.method === "OPTIONS") {
70
+ res.writeHead(204);
71
+ res.end();
72
+ return;
73
+ }
74
+
75
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
76
+ const pathname = url.pathname;
77
+
78
+ // Internal event endpoint — localhost only, no auth
79
+ if (req.method === "POST" && pathname === "/internal/event") {
80
+ if (!isLocalhost(req)) {
81
+ sendJson(res, 403, { error: "localhost only" });
82
+ return;
83
+ }
84
+ try {
85
+ const body = await readBody(req);
86
+ const event = JSON.parse(body);
87
+ broadcastSseEvent(event);
88
+ sendJson(res, 200, { ok: true });
89
+ } catch {
90
+ sendJson(res, 400, { error: "Invalid JSON" });
91
+ }
92
+ return;
93
+ }
94
+
95
+ // All other endpoints require auth
96
+ if (!checkAuth(req)) {
97
+ sendJson(res, 401, { error: "Unauthorized" });
98
+ return;
99
+ }
100
+
101
+ // SSE event stream
102
+ if (req.method === "GET" && pathname === "/events") {
103
+ res.writeHead(200, {
104
+ "Content-Type": "text/event-stream",
105
+ "Cache-Control": "no-cache",
106
+ Connection: "keep-alive",
107
+ "Access-Control-Allow-Origin": "*",
108
+ });
109
+ res.write(":ok\n\n");
110
+
111
+ // Send heartbeat every 5 seconds
112
+ const heartbeat = setInterval(() => {
113
+ res.write("data: {\"heartbeat\":true}\n\n");
114
+ }, 5000);
115
+
116
+ sseClients.add(res);
117
+ req.on("close", () => {
118
+ clearInterval(heartbeat);
119
+ sseClients.delete(res);
120
+ });
121
+ return;
122
+ }
123
+
124
+ // RPC endpoint: POST /rpc/<method>
125
+ if (req.method === "POST" && pathname.startsWith("/rpc/")) {
126
+ const method = pathname.slice("/rpc/".length);
127
+ if (!method) {
128
+ sendJson(res, 400, { error: "Missing RPC method" });
129
+ return;
130
+ }
131
+
132
+ let params: Record<string, unknown> = {};
133
+ try {
134
+ const body = await readBody(req);
135
+ if (body.trim().length > 0) {
136
+ params = JSON.parse(body);
137
+ }
138
+ } catch {
139
+ sendJson(res, 400, { error: "Invalid JSON" });
140
+ return;
141
+ }
142
+
143
+ const sessionToken = extractSessionToken(req);
144
+ console.log(`[http] RPC: ${method}`);
145
+
146
+ try {
147
+ const response = await handleRpc({ method, params, sessionToken });
148
+ console.log(`[http] RPC done: ${method}`, JSON.stringify(response).slice(0, 200));
149
+ sendJson(res, 200, response);
150
+ } catch (err) {
151
+ console.error(`[http] RPC error (${method}):`, err);
152
+ sendJson(res, 500, { error: String(err) });
153
+ }
154
+ return;
155
+ }
156
+
157
+ sendJson(res, 404, { error: "Not found" });
158
+ });
159
+
160
+ return new Promise<void>((resolve, reject) => {
161
+ server.listen(port, () => {
162
+ console.log(`[http] Listening on port ${port}`);
163
+ console.log(`[http] SSE clients can connect to /events`);
164
+
165
+ // Graceful shutdown
166
+ const shutdown = () => {
167
+ console.log("[http] Shutting down...");
168
+ for (const client of sseClients) {
169
+ client.end();
170
+ }
171
+ server.close(() => process.exit(0));
172
+ };
173
+
174
+ process.on("SIGINT", shutdown);
175
+ process.on("SIGTERM", shutdown);
176
+ });
177
+
178
+ server.on("error", reject);
179
+ });
180
+ }
@@ -0,0 +1,82 @@
1
+ import { StringCodec, type Msg, type Subscription } from "nats";
2
+ import { connectNats } from "../nats-client.js";
3
+ import type { HostConfig, RpcMessage } from "../types.js";
4
+
5
+ /**
6
+ * Start the NATS transport: connect, subscribe to RPC subjects, dispatch to handler.
7
+ */
8
+ export async function startNatsTransport(
9
+ config: HostConfig,
10
+ handleRpc: (req: RpcMessage) => Promise<unknown>,
11
+ ): Promise<void> {
12
+ const nc = await connectNats(config);
13
+ const sc = StringCodec();
14
+
15
+ const subject = `host.${config.hostId}.rpc.>`;
16
+ console.log(`[nats] Subscribing to: ${subject}`);
17
+ const sub = nc.subscribe(subject);
18
+
19
+ // Graceful shutdown
20
+ const shutdown = async () => {
21
+ console.log("[nats] Shutting down...");
22
+ sub.unsubscribe();
23
+ await nc.drain();
24
+ process.exit(0);
25
+ };
26
+
27
+ process.on("SIGINT", shutdown);
28
+ process.on("SIGTERM", shutdown);
29
+
30
+ async function processMessage(msg: Msg) {
31
+ // Derive RPC method from subject: ...rpc.<method parts>
32
+ const subjectTokens = msg.subject.split(".");
33
+ const rpcIdx = subjectTokens.indexOf("rpc");
34
+ const method = rpcIdx >= 0 ? subjectTokens.slice(rpcIdx + 1).join(".") : "";
35
+
36
+ // Parse params from message body
37
+ let params: Record<string, unknown> = {};
38
+ if (msg.data && msg.data.length > 0) {
39
+ const raw = sc.decode(msg.data).trim();
40
+ if (raw.length > 0) {
41
+ try {
42
+ params = JSON.parse(raw);
43
+ } catch {
44
+ console.error(`[nats] Failed to parse RPC params for ${method}`);
45
+ if (msg.reply) {
46
+ msg.respond(sc.encode(JSON.stringify({ error: "Invalid JSON" })));
47
+ }
48
+ return;
49
+ }
50
+ }
51
+ }
52
+
53
+ // Extract sessionToken from params (PWA includes it in the payload)
54
+ const sessionToken = typeof params.sessionToken === "string" ? params.sessionToken : undefined;
55
+ delete params.sessionToken;
56
+
57
+ console.log(`[nats] RPC: ${method}`);
58
+
59
+ let response: unknown;
60
+ try {
61
+ response = await handleRpc({ method, params, sessionToken });
62
+ } catch (err) {
63
+ console.error(`[nats] RPC error (${method}):`, err);
64
+ response = { error: String(err) };
65
+ }
66
+
67
+ console.log(`[nats] RPC done: ${method}`, JSON.stringify(response).slice(0, 200));
68
+ if (msg.reply) {
69
+ msg.respond(sc.encode(JSON.stringify(response)));
70
+ }
71
+ }
72
+
73
+ async function consumeSubscription(subscription: Subscription) {
74
+ for await (const msg of subscription) {
75
+ // Handle RPC without blocking the message loop so heartbeats keep flowing
76
+ processMessage(msg);
77
+ }
78
+ }
79
+
80
+ console.log("[nats] Waiting for RPC messages...");
81
+ await consumeSubscription(sub);
82
+ }
package/src/types.ts CHANGED
@@ -1,19 +1,30 @@
1
- export interface AgentConfig {
2
- agentId: string;
3
- userId: string;
4
- natsUrl: string;
5
- natsWsUrl: string;
6
- natsToken: string;
1
+ export interface HostConfig {
2
+ hostId: string;
7
3
  projectRoot: string;
4
+ mode: "nats" | "lan" | "auto";
5
+
6
+ // NATS fields (required when mode === "nats" or "auto")
7
+ natsUrl?: string;
8
+ natsWsUrl?: string;
9
+ natsToken?: string;
10
+
11
+ // Direct/LAN fields (required when mode === "lan" or "auto")
12
+ directPort?: number;
13
+ directToken?: string;
14
+
15
+ // Detected agent CLIs
16
+ agents?: Array<{ key: string; label: string }>;
8
17
  }
9
18
 
10
19
  export interface TaskFrontmatter {
11
20
  id: string;
21
+ name: string;
12
22
  user_prompt: string;
13
- command_line: string;
23
+ agent: string;
14
24
  triggers: Trigger[];
15
25
  triggers_enabled: boolean;
16
26
  requires_confirmation: boolean;
27
+ permissions?: RequiredPermission[];
17
28
  }
18
29
 
19
30
  export interface Trigger {
@@ -26,16 +37,28 @@ export interface ParsedTask {
26
37
  body: string;
27
38
  }
28
39
 
29
- export interface ConfirmPayload {
30
- type: "confirm";
40
+ export type TaskRunningState = "start" | "finish" | "abort" | "fail";
41
+
42
+ export interface TaskStatus {
43
+ running_state: TaskRunningState;
44
+ time_stamp: number;
45
+ pending_confirmation?: boolean;
46
+ pending_permission?: RequiredPermission[];
47
+ user_input?: string;
48
+ }
49
+
50
+ export interface HistoryEntry {
31
51
  task_id: string;
32
- agent_id: string;
33
- user_id: string;
34
- details: Record<string, unknown>;
35
- status: "pending" | "confirmed" | "aborted";
52
+ result_file: string;
53
+ }
54
+
55
+ export interface RequiredPermission {
56
+ name: string;
57
+ description: string;
36
58
  }
37
59
 
38
60
  export interface RpcMessage {
39
61
  method: string;
40
62
  params: Record<string, unknown>;
63
+ sessionToken?: string;
41
64
  }
@@ -1,28 +0,0 @@
1
- You are a planning agent for a personal computer AI agent. Given a task description, produce a detailed Markdown execution plan that the agent can later follow step by step. **Do not execute any part of the task yourself.**
2
-
3
- The plan must include the following sections:
4
-
5
- ### 1. Goal
6
- What the task accomplishes and the expected end state.
7
-
8
- ### 2. Prerequisites
9
- What must be true before starting:
10
- - Required software and versions
11
- - Files or data that must be present
12
- - Permissions or access needed
13
- - Environment state (e.g., running services, network access)
14
-
15
- ### 3. Plan
16
- A numbered sequence of concrete, actionable steps to complete the task.
17
- Use sub-steps for complex actions. Include conditional branches where behavior may vary (e.g., "If file exists, do A; otherwise, do B"). Each step should be specific enough that the agent can execute it without ambiguity.
18
-
19
- ### 4. Edge Cases & Risks
20
- Anything that could go wrong and how to handle it:
21
- - Common failure modes
22
- - Platform-specific differences
23
- - Race conditions or timing issues
24
- - Data loss risks and mitigation
25
-
26
- Format the entire document as Markdown with proper headings, code blocks for commands, and tables where appropriate.
27
-
28
- **Task description:**