palmier 0.9.6 → 0.9.7

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 (250) hide show
  1. package/README.md +28 -13
  2. package/dist/agents/agent.d.ts +0 -1
  3. package/dist/agents/agent.js +0 -1
  4. package/dist/agents/aider.d.ts +0 -1
  5. package/dist/agents/aider.js +0 -1
  6. package/dist/agents/claude.d.ts +0 -1
  7. package/dist/agents/claude.js +0 -1
  8. package/dist/agents/cline.d.ts +0 -1
  9. package/dist/agents/cline.js +0 -1
  10. package/dist/agents/codex.d.ts +0 -1
  11. package/dist/agents/codex.js +0 -1
  12. package/dist/agents/copilot.d.ts +0 -1
  13. package/dist/agents/copilot.js +0 -1
  14. package/dist/agents/cursor.d.ts +0 -1
  15. package/dist/agents/cursor.js +0 -1
  16. package/dist/agents/deepagents.d.ts +0 -1
  17. package/dist/agents/deepagents.js +0 -1
  18. package/dist/agents/droid.d.ts +0 -1
  19. package/dist/agents/droid.js +0 -1
  20. package/dist/agents/gemini.d.ts +0 -1
  21. package/dist/agents/gemini.js +0 -1
  22. package/dist/agents/goose.d.ts +0 -1
  23. package/dist/agents/goose.js +0 -1
  24. package/dist/agents/hermes.d.ts +0 -1
  25. package/dist/agents/hermes.js +0 -1
  26. package/dist/agents/kimi.d.ts +0 -1
  27. package/dist/agents/kimi.js +0 -1
  28. package/dist/agents/kiro.d.ts +0 -1
  29. package/dist/agents/kiro.js +0 -1
  30. package/dist/agents/openclaw.d.ts +0 -1
  31. package/dist/agents/openclaw.js +0 -1
  32. package/dist/agents/opencode.d.ts +0 -1
  33. package/dist/agents/opencode.js +0 -1
  34. package/dist/agents/qoder.d.ts +0 -1
  35. package/dist/agents/qoder.js +0 -1
  36. package/dist/agents/qwen.d.ts +0 -1
  37. package/dist/agents/qwen.js +0 -1
  38. package/dist/agents/shared-prompt.d.ts +0 -1
  39. package/dist/agents/shared-prompt.js +0 -1
  40. package/dist/client-store.d.ts +0 -1
  41. package/dist/client-store.js +0 -1
  42. package/dist/commands/clients.d.ts +0 -1
  43. package/dist/commands/clients.js +0 -1
  44. package/dist/commands/info.d.ts +0 -1
  45. package/dist/commands/info.js +0 -1
  46. package/dist/commands/init.d.ts +0 -1
  47. package/dist/commands/init.js +1 -2
  48. package/dist/commands/pair.d.ts +0 -1
  49. package/dist/commands/pair.js +0 -1
  50. package/dist/commands/restart.d.ts +0 -1
  51. package/dist/commands/restart.js +0 -1
  52. package/dist/commands/run.d.ts +0 -1
  53. package/dist/commands/run.js +0 -1
  54. package/dist/commands/serve.d.ts +0 -1
  55. package/dist/commands/serve.js +0 -1
  56. package/dist/commands/uninstall.d.ts +0 -1
  57. package/dist/commands/uninstall.js +0 -1
  58. package/dist/config.d.ts +0 -1
  59. package/dist/config.js +0 -1
  60. package/dist/event-queues.d.ts +0 -1
  61. package/dist/event-queues.js +0 -1
  62. package/dist/events.d.ts +0 -1
  63. package/dist/events.js +0 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.js +0 -1
  66. package/dist/linked-device.d.ts +0 -1
  67. package/dist/linked-device.js +0 -1
  68. package/dist/mcp-handler.d.ts +0 -1
  69. package/dist/mcp-handler.js +0 -1
  70. package/dist/mcp-tools.d.ts +0 -1
  71. package/dist/mcp-tools.js +0 -1
  72. package/dist/nats-client.d.ts +0 -1
  73. package/dist/nats-client.js +0 -1
  74. package/dist/network.d.ts +0 -1
  75. package/dist/network.js +0 -1
  76. package/dist/notification-store.d.ts +0 -1
  77. package/dist/notification-store.js +0 -1
  78. package/dist/pending-requests.d.ts +0 -1
  79. package/dist/pending-requests.js +0 -1
  80. package/dist/platform/index.d.ts +0 -1
  81. package/dist/platform/index.js +0 -1
  82. package/dist/platform/linux.d.ts +0 -1
  83. package/dist/platform/linux.js +0 -1
  84. package/dist/platform/macos.d.ts +0 -1
  85. package/dist/platform/macos.js +0 -1
  86. package/dist/platform/platform.d.ts +0 -1
  87. package/dist/platform/platform.js +0 -1
  88. package/dist/platform/windows.d.ts +0 -1
  89. package/dist/platform/windows.js +0 -1
  90. package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
  91. package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
  92. package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
  93. package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
  94. package/dist/pwa/index.html +2 -2
  95. package/dist/pwa/service-worker.js +1 -1
  96. package/dist/rpc-handler.d.ts +0 -1
  97. package/dist/rpc-handler.js +0 -1
  98. package/dist/sms-store.d.ts +0 -1
  99. package/dist/sms-store.js +0 -1
  100. package/dist/spawn-command.d.ts +0 -1
  101. package/dist/spawn-command.js +0 -1
  102. package/dist/task.d.ts +0 -1
  103. package/dist/task.js +0 -1
  104. package/dist/transports/http-transport.d.ts +0 -1
  105. package/dist/transports/http-transport.js +0 -1
  106. package/dist/transports/nats-transport.d.ts +0 -1
  107. package/dist/transports/nats-transport.js +0 -1
  108. package/dist/types.d.ts +0 -1
  109. package/dist/types.js +0 -1
  110. package/dist/update-checker.d.ts +0 -1
  111. package/dist/update-checker.js +0 -1
  112. package/package.json +5 -1
  113. package/.github/workflows/ci.yml +0 -16
  114. package/.github/workflows/publish.yml +0 -37
  115. package/CLAUDE.md +0 -22
  116. package/palmier-server/.github/workflows/ci.yml +0 -21
  117. package/palmier-server/.github/workflows/deploy.yml +0 -38
  118. package/palmier-server/CLAUDE.md +0 -17
  119. package/palmier-server/PRODUCTION.md +0 -358
  120. package/palmier-server/README.md +0 -231
  121. package/palmier-server/nats.conf +0 -19
  122. package/palmier-server/package.json +0 -15
  123. package/palmier-server/pnpm-lock.yaml +0 -7639
  124. package/palmier-server/pnpm-workspace.yaml +0 -3
  125. package/palmier-server/pwa/index.html +0 -16
  126. package/palmier-server/pwa/logo/logo_20260421.png +0 -0
  127. package/palmier-server/pwa/package.json +0 -34
  128. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  129. package/palmier-server/pwa/public/favicon.ico +0 -0
  130. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  131. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  132. package/palmier-server/pwa/src/App.css +0 -3012
  133. package/palmier-server/pwa/src/App.tsx +0 -59
  134. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  135. package/palmier-server/pwa/src/api.ts +0 -67
  136. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  137. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
  138. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  139. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  140. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  141. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  142. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  143. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  144. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  145. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  146. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  147. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
  148. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  149. package/palmier-server/pwa/src/constants.ts +0 -2
  150. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  151. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  152. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  153. package/palmier-server/pwa/src/formatTime.ts +0 -44
  154. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  155. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  156. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  157. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  158. package/palmier-server/pwa/src/main.tsx +0 -14
  159. package/palmier-server/pwa/src/native/Device.ts +0 -49
  160. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  161. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  162. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  163. package/palmier-server/pwa/src/service-worker.ts +0 -142
  164. package/palmier-server/pwa/src/types.ts +0 -75
  165. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  166. package/palmier-server/pwa/tsconfig.json +0 -21
  167. package/palmier-server/pwa/tsconfig.node.json +0 -19
  168. package/palmier-server/pwa/vite.config.ts +0 -47
  169. package/palmier-server/server/.env.example +0 -20
  170. package/palmier-server/server/package.json +0 -36
  171. package/palmier-server/server/src/db.ts +0 -44
  172. package/palmier-server/server/src/fcm.ts +0 -74
  173. package/palmier-server/server/src/index.ts +0 -688
  174. package/palmier-server/server/src/nats-jwt.ts +0 -299
  175. package/palmier-server/server/src/nats-setup.ts +0 -48
  176. package/palmier-server/server/src/nats.ts +0 -33
  177. package/palmier-server/server/src/notify.ts +0 -34
  178. package/palmier-server/server/src/push.ts +0 -68
  179. package/palmier-server/server/src/routes/device.ts +0 -224
  180. package/palmier-server/server/src/routes/fcm.ts +0 -64
  181. package/palmier-server/server/src/routes/hosts.ts +0 -56
  182. package/palmier-server/server/src/routes/push.ts +0 -101
  183. package/palmier-server/server/tsconfig.json +0 -20
  184. package/palmier-server/spec.md +0 -533
  185. package/src/agents/agent-instructions.md +0 -28
  186. package/src/agents/agent.ts +0 -114
  187. package/src/agents/aider.ts +0 -35
  188. package/src/agents/claude.ts +0 -39
  189. package/src/agents/cline.ts +0 -35
  190. package/src/agents/codex.ts +0 -40
  191. package/src/agents/copilot.ts +0 -37
  192. package/src/agents/cursor.ts +0 -36
  193. package/src/agents/deepagents.ts +0 -36
  194. package/src/agents/droid.ts +0 -35
  195. package/src/agents/gemini.ts +0 -43
  196. package/src/agents/goose.ts +0 -33
  197. package/src/agents/hermes.ts +0 -36
  198. package/src/agents/kimi.ts +0 -35
  199. package/src/agents/kiro.ts +0 -36
  200. package/src/agents/openclaw.ts +0 -29
  201. package/src/agents/opencode.ts +0 -36
  202. package/src/agents/qoder.ts +0 -36
  203. package/src/agents/qwen.ts +0 -32
  204. package/src/agents/shared-prompt.ts +0 -30
  205. package/src/client-store.ts +0 -68
  206. package/src/commands/clients.ts +0 -29
  207. package/src/commands/info.ts +0 -29
  208. package/src/commands/init.ts +0 -165
  209. package/src/commands/pair.ts +0 -137
  210. package/src/commands/restart.ts +0 -6
  211. package/src/commands/run.ts +0 -608
  212. package/src/commands/serve.ts +0 -211
  213. package/src/commands/uninstall.ts +0 -9
  214. package/src/config.ts +0 -36
  215. package/src/cross-spawn.d.ts +0 -5
  216. package/src/event-queues.ts +0 -41
  217. package/src/events.ts +0 -29
  218. package/src/index.ts +0 -111
  219. package/src/linked-device.ts +0 -52
  220. package/src/mcp-handler.ts +0 -200
  221. package/src/mcp-tools.ts +0 -839
  222. package/src/nats-client.ts +0 -19
  223. package/src/network.ts +0 -96
  224. package/src/notification-store.ts +0 -30
  225. package/src/pending-requests.ts +0 -73
  226. package/src/platform/index.ts +0 -20
  227. package/src/platform/linux.ts +0 -296
  228. package/src/platform/macos.ts +0 -329
  229. package/src/platform/platform.ts +0 -31
  230. package/src/platform/windows.ts +0 -299
  231. package/src/rpc-handler.ts +0 -691
  232. package/src/sms-store.ts +0 -28
  233. package/src/spawn-command.ts +0 -123
  234. package/src/task.ts +0 -343
  235. package/src/transports/http-transport.ts +0 -478
  236. package/src/transports/nats-transport.ts +0 -76
  237. package/src/types.ts +0 -89
  238. package/src/update-checker.ts +0 -40
  239. package/test/agent-instructions.test.ts +0 -209
  240. package/test/agent-output-parsing.test.ts +0 -74
  241. package/test/linux-cron.test.ts +0 -41
  242. package/test/macos-plist.test.ts +0 -112
  243. package/test/notification-store.test.ts +0 -57
  244. package/test/pairing.test.ts +0 -35
  245. package/test/result-state.test.ts +0 -110
  246. package/test/task-parsing.test.ts +0 -82
  247. package/test/taskrun-messages.test.ts +0 -224
  248. package/test/tsconfig.json +0 -9
  249. package/test/windows-xml.test.ts +0 -89
  250. package/tsconfig.json +0 -19
package/src/sms-store.ts DELETED
@@ -1,28 +0,0 @@
1
- export interface SmsMessage {
2
- id: string;
3
- sender: string;
4
- body: string;
5
- timestamp: number;
6
- receivedAt: number;
7
- }
8
-
9
- const MAX_MESSAGES = 50;
10
- const messages: SmsMessage[] = [];
11
- const listeners = new Set<() => void>();
12
-
13
- export function addSmsMessage(m: SmsMessage): void {
14
- messages.push(m);
15
- if (messages.length > MAX_MESSAGES) {
16
- messages.shift();
17
- }
18
- for (const cb of listeners) cb();
19
- }
20
-
21
- export function getSmsMessages(): SmsMessage[] {
22
- return [...messages];
23
- }
24
-
25
- export function onSmsChanged(cb: () => void): () => void {
26
- listeners.add(cb);
27
- return () => { listeners.delete(cb); };
28
- }
@@ -1,123 +0,0 @@
1
- import crossSpawn from "cross-spawn";
2
- import { execFileSync, type ChildProcess } from "child_process";
3
-
4
- /** Kill a child process and its entire tree on Windows; plain kill elsewhere. */
5
- function treeKill(child: ChildProcess): void {
6
- if (process.platform === "win32" && child.pid) {
7
- try {
8
- execFileSync("taskkill", ["/pid", String(child.pid), "/f", "/t"], { windowsHide: true, stdio: "pipe" });
9
- return;
10
- } catch { /* fall through */ }
11
- }
12
- child.kill();
13
- }
14
-
15
- export interface SpawnStreamingOptions {
16
- cwd: string;
17
- env?: Record<string, string>;
18
- }
19
-
20
- /**
21
- * Spawn with shell interpretation for piped commands like "tail -f log | grep".
22
- * Returns the ChildProcess with stdout piped so the caller can read it directly
23
- * (contrast with spawnCommand which buffers). stdin is "pipe" (held open, not
24
- * written to): some long-running commands exit on stdin EOF. Agent CLIs like
25
- * `claude -p` conversely hang on an open stdin, which is why spawnCommand
26
- * defaults to "ignore".
27
- */
28
- export function spawnStreamingCommand(
29
- command: string,
30
- opts: SpawnStreamingOptions,
31
- ): ChildProcess {
32
- return crossSpawn(command, [], {
33
- cwd: opts.cwd,
34
- stdio: ["pipe", "pipe", "pipe"],
35
- shell: true,
36
- env: opts.env ? { ...process.env, ...opts.env } : undefined,
37
- windowsHide: true,
38
- });
39
- }
40
-
41
- export interface SpawnCommandOptions {
42
- cwd: string;
43
- env?: Record<string, string>;
44
- timeout?: number;
45
- /** Echo stdout to process.stdout (useful for journald logging). */
46
- echoStdout?: boolean;
47
- /** Resolve with output even on non-zero exit (instead of rejecting). */
48
- resolveOnFailure?: boolean;
49
- /** If provided, write this string to the process's stdin and then close the pipe. */
50
- stdin?: string;
51
- /** Called on each chunk of output (stdout + stderr combined). */
52
- onData?: (chunk: string) => void;
53
- }
54
-
55
- /**
56
- * cross-spawn resolves .cmd shims and escapes args on Windows without shell:true
57
- * (which mishandles special characters). stdin defaults to "ignore" because
58
- * tools like `claude -p` hang on an open stdin pipe; pass opts.stdin to write
59
- * a string and then close the pipe.
60
- */
61
- export interface SpawnCommandResult {
62
- output: string;
63
- exitCode: number | null;
64
- }
65
-
66
- export function spawnCommand(
67
- command: string,
68
- args: string[],
69
- opts: SpawnCommandOptions,
70
- ): Promise<SpawnCommandResult> {
71
- return new Promise<SpawnCommandResult>((resolve, reject) => {
72
- // cmd.exe can't handle literal newlines in arguments.
73
- const finalArgs = process.platform === "win32"
74
- ? args.map((a) => a.replace(/[\r\n]+/g, " "))
75
- : args;
76
- const truncate = (s: string, max = 100) => s.length > max ? s.slice(0, max) + "..." : s;
77
- const displayArgs = finalArgs.map((arg) => truncate(arg));
78
-
79
- console.log(`[spawn] ${command} ${displayArgs.join(" ")}`);
80
-
81
- const child = crossSpawn(command, finalArgs, {
82
- cwd: opts.cwd,
83
- stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
84
- env: opts.env ? { ...process.env, ...opts.env } : undefined,
85
- windowsHide: true,
86
- });
87
-
88
- if (opts.stdin != null) {
89
- child.stdin!.end(opts.stdin);
90
- }
91
-
92
- const chunks: Buffer[] = [];
93
- child.stdout!.on("data", (d: Buffer) => {
94
- chunks.push(d);
95
- if (opts.echoStdout) process.stdout.write(d);
96
- if (opts.onData) opts.onData(d.toString("utf-8"));
97
- });
98
- child.stderr!.on("data", (d: Buffer) => {
99
- process.stderr.write(d);
100
- if (opts.onData) opts.onData(d.toString("utf-8"));
101
- });
102
-
103
- let timer: ReturnType<typeof setTimeout> | undefined;
104
- if (opts.timeout) {
105
- timer = setTimeout(() => {
106
- treeKill(child);
107
- reject(new Error("command timed out"));
108
- }, opts.timeout);
109
- }
110
-
111
- child.on("close", (code: number | null) => {
112
- if (timer) clearTimeout(timer);
113
- const output = Buffer.concat(chunks).toString("utf-8");
114
- if (code === 0 || opts.resolveOnFailure) resolve({ output, exitCode: code });
115
- else reject(new Error(`process exited with code ${code}`));
116
- });
117
-
118
- child.on("error", (err: Error) => {
119
- if (timer) clearTimeout(timer);
120
- reject(err);
121
- });
122
- });
123
- }
package/src/task.ts DELETED
@@ -1,343 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
4
- import type { ParsedTask, TaskFrontmatter, TaskStatus, HistoryEntry, ConversationMessage } from "./types.js";
5
-
6
- export function parseTaskFile(taskDir: string): ParsedTask {
7
- const filePath = path.join(taskDir, "TASK.md");
8
-
9
- if (!fs.existsSync(filePath)) {
10
- throw new Error(`TASK.md not found at: ${filePath}`);
11
- }
12
-
13
- const content = fs.readFileSync(filePath, "utf-8");
14
- return parseTaskContent(content);
15
- }
16
-
17
- export function parseTaskContent(content: string): ParsedTask {
18
- const fmRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
19
- const match = content.match(fmRegex);
20
-
21
- if (!match) {
22
- throw new Error("TASK.md is missing valid YAML frontmatter delimiters (---)");
23
- }
24
-
25
- const frontmatter = parseYaml(match[1]) as TaskFrontmatter;
26
-
27
- if (!frontmatter.id) {
28
- throw new Error("TASK.md frontmatter must include at least: id");
29
- }
30
-
31
- frontmatter.name ??= frontmatter.user_prompt?.slice(0, 60) ?? "";
32
- frontmatter.agent ??= "claude";
33
- frontmatter.schedule_enabled ??= true;
34
-
35
- return { frontmatter };
36
- }
37
-
38
- export function writeTaskFile(taskDir: string, task: ParsedTask): void {
39
- fs.mkdirSync(taskDir, { recursive: true });
40
-
41
- const yamlStr = stringifyYaml(task.frontmatter).trim();
42
- const content = `---\n${yamlStr}\n---\n`;
43
-
44
- const filePath = path.join(taskDir, "TASK.md");
45
- fs.writeFileSync(filePath, content, "utf-8");
46
- }
47
-
48
- export function appendTaskList(projectRoot: string, taskId: string): void {
49
- const listPath = path.join(projectRoot, "tasks.jsonl");
50
- fs.appendFileSync(listPath, JSON.stringify({ task_id: taskId }) + "\n", "utf-8");
51
- }
52
-
53
- export function isTaskInList(projectRoot: string, taskId: string): boolean {
54
- const listPath = path.join(projectRoot, "tasks.jsonl");
55
- if (!fs.existsSync(listPath)) return false;
56
-
57
- const lines = fs.readFileSync(listPath, "utf-8").split("\n").filter(Boolean);
58
- for (const line of lines) {
59
- try {
60
- if ((JSON.parse(line) as { task_id: string }).task_id === taskId) return true;
61
- } catch { /* skip malformed */ }
62
- }
63
- return false;
64
- }
65
-
66
- export function removeFromTaskList(projectRoot: string, taskId: string): boolean {
67
- const listPath = path.join(projectRoot, "tasks.jsonl");
68
- if (!fs.existsSync(listPath)) return false;
69
-
70
- const lines = fs.readFileSync(listPath, "utf-8").split("\n").filter(Boolean);
71
- let found = false;
72
- const remaining: string[] = [];
73
-
74
- for (const line of lines) {
75
- try {
76
- const entry = JSON.parse(line) as { task_id: string };
77
- if (entry.task_id === taskId) {
78
- found = true;
79
- continue;
80
- }
81
- } catch { /* keep malformed lines */ }
82
- remaining.push(line);
83
- }
84
-
85
- if (!found) return false;
86
- fs.writeFileSync(listPath, remaining.length > 0 ? remaining.join("\n") + "\n" : "", "utf-8");
87
- return true;
88
- }
89
-
90
- export function listTasks(projectRoot: string): ParsedTask[] {
91
- const listPath = path.join(projectRoot, "tasks.jsonl");
92
- if (!fs.existsSync(listPath)) return [];
93
-
94
- const lines = fs.readFileSync(listPath, "utf-8").split("\n").filter(Boolean);
95
- const tasks: ParsedTask[] = [];
96
-
97
- for (const line of lines) {
98
- let taskId: string;
99
- try {
100
- taskId = (JSON.parse(line) as { task_id: string }).task_id;
101
- } catch { continue; }
102
-
103
- const taskDir = getTaskDir(projectRoot, taskId);
104
- try {
105
- tasks.push(parseTaskFile(taskDir));
106
- } catch (err) {
107
- console.error(`Warning: failed to parse task ${taskId}: ${err}`);
108
- }
109
- }
110
-
111
- return tasks.reverse();
112
- }
113
-
114
- export function getTaskDir(projectRoot: string, taskId: string): string {
115
- return path.join(projectRoot, "tasks", taskId);
116
- }
117
-
118
- export function writeTaskStatus(taskDir: string, status: TaskStatus): void {
119
- const filePath = path.join(taskDir, "status.json");
120
- fs.writeFileSync(filePath, JSON.stringify(status), "utf-8");
121
- }
122
-
123
- export function readTaskStatus(taskDir: string): TaskStatus | undefined {
124
- const filePath = path.join(taskDir, "status.json");
125
- try {
126
- return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TaskStatus;
127
- } catch {
128
- return undefined;
129
- }
130
- }
131
-
132
- export interface FollowupStatus {
133
- pid: number;
134
- spawned_at: number;
135
- }
136
-
137
- export function writeFollowupStatus(runDir: string, status: FollowupStatus): void {
138
- fs.writeFileSync(path.join(runDir, "followup.json"), JSON.stringify(status), "utf-8");
139
- }
140
-
141
- export function readFollowupStatus(runDir: string): FollowupStatus | undefined {
142
- try {
143
- return JSON.parse(fs.readFileSync(path.join(runDir, "followup.json"), "utf-8")) as FollowupStatus;
144
- } catch {
145
- return undefined;
146
- }
147
- }
148
-
149
- export function deleteFollowupStatus(runDir: string): void {
150
- try { fs.unlinkSync(path.join(runDir, "followup.json")); } catch { /* ignore */ }
151
- }
152
-
153
- /** Returns the run ID (timestamp string used as directory name). */
154
- export function createRunDir(
155
- taskDir: string,
156
- taskName: string,
157
- startTime: number,
158
- agent?: string,
159
- ): string {
160
- const runId = String(startTime);
161
- const runDir = path.join(taskDir, runId);
162
- fs.mkdirSync(runDir, { recursive: true });
163
- const agentLine = agent ? `\nagent: ${agent}` : "";
164
- const content = `---\ntask_name: ${taskName}${agentLine}\n---\n\n`;
165
- fs.writeFileSync(path.join(runDir, "TASKRUN.md"), content, "utf-8");
166
- return runId;
167
- }
168
-
169
- export function getRunDir(taskDir: string, runId: string): string {
170
- return path.join(taskDir, runId);
171
- }
172
-
173
- export function appendRunMessage(
174
- taskDir: string,
175
- runId: string,
176
- msg: ConversationMessage,
177
- ): void {
178
- const attrs = [`role="${msg.role}"`, `time="${msg.time}"`];
179
- if (msg.type) attrs.push(`type="${msg.type}"`);
180
- if (msg.attachments?.length) attrs.push(`attachments="${msg.attachments.join(",")}"`);
181
-
182
- const delimiter = `<!-- palmier:message ${attrs.join(" ")} -->`;
183
- const entry = `${delimiter}\n\n${msg.content}\n\n`;
184
- fs.appendFileSync(path.join(taskDir, runId, "TASKRUN.md"), entry, "utf-8");
185
- }
186
-
187
- export function beginStreamingMessage(
188
- taskDir: string,
189
- runId: string,
190
- time: number,
191
- ): StreamingMessageWriter {
192
- const filePath = path.join(taskDir, runId, "TASKRUN.md");
193
- const delimiter = `<!-- palmier:message role="assistant" time="${time}" -->`;
194
- fs.appendFileSync(filePath, `${delimiter}\n\n`, "utf-8");
195
- return new StreamingMessageWriter(filePath);
196
- }
197
-
198
- export class StreamingMessageWriter {
199
- constructor(private filePath: string) {}
200
-
201
- /** Append a chunk of content to the current message. */
202
- write(chunk: string): void {
203
- fs.appendFileSync(this.filePath, chunk, "utf-8");
204
- }
205
-
206
- /** Finalize the message. If attachments are provided, rewrites the last assistant delimiter to include them. */
207
- end(attachments?: string[]): void {
208
- fs.appendFileSync(this.filePath, "\n\n", "utf-8");
209
- if (attachments?.length) {
210
- const raw = fs.readFileSync(this.filePath, "utf-8");
211
- // spliceUserMessage may have created a newer assistant delimiter.
212
- const pattern = /<!-- palmier:message role="assistant" time="\d+" -->/g;
213
- let lastMatch: RegExpExecArray | null = null;
214
- let m;
215
- while ((m = pattern.exec(raw)) !== null) lastMatch = m;
216
- if (lastMatch) {
217
- const before = raw.slice(0, lastMatch.index);
218
- const after = raw.slice(lastMatch.index + lastMatch[0].length);
219
- const updated = before + `${lastMatch[0].slice(0, -4)} attachments="${attachments.join(",")}" -->` + after;
220
- fs.writeFileSync(this.filePath, updated, "utf-8");
221
- }
222
- }
223
- }
224
- }
225
-
226
- /**
227
- * Splice a user message into a running assistant stream: close the current
228
- * assistant block, write the user message, open a new assistant block. Direct
229
- * appends only, so an existing StreamingMessageWriter keeps working — its
230
- * subsequent chunks land in the new block.
231
- */
232
- export function spliceUserMessage(
233
- taskDir: string,
234
- runId: string,
235
- userMsg: ConversationMessage,
236
- /** Optional text to append to the current assistant block before ending it. */
237
- assistantAppend?: string,
238
- ): void {
239
- const filePath = path.join(taskDir, runId, "TASKRUN.md");
240
- if (assistantAppend) {
241
- fs.appendFileSync(filePath, assistantAppend, "utf-8");
242
- }
243
- fs.appendFileSync(filePath, "\n\n", "utf-8");
244
- appendRunMessage(taskDir, runId, userMsg);
245
- const delimiter = `<!-- palmier:message role="assistant" time="${Date.now()}" -->`;
246
- fs.appendFileSync(filePath, `${delimiter}\n\n`, "utf-8");
247
- }
248
-
249
- export function readRunMessages(taskDir: string, runId: string): ConversationMessage[] {
250
- const raw = fs.readFileSync(path.join(taskDir, runId, "TASKRUN.md"), "utf-8");
251
- const fmMatch = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
252
- if (!fmMatch) return [];
253
-
254
- const body = fmMatch[1];
255
- const delimiterRegex = /<!-- palmier:message\s+(.*?)\s*-->/g;
256
- const matches = [...body.matchAll(delimiterRegex)];
257
- if (matches.length === 0) return [];
258
-
259
- const messages: ConversationMessage[] = [];
260
- for (let i = 0; i < matches.length; i++) {
261
- const match = matches[i];
262
- const attrs = match[1];
263
- const start = match.index! + match[0].length;
264
- const end = i + 1 < matches.length ? matches[i + 1].index! : body.length;
265
- const content = body.slice(start, end).trim();
266
-
267
- const roleAttr = attrs.match(/role="([^"]*)"/)?.[1] ?? "assistant";
268
- const timeAttr = attrs.match(/time="([^"]*)"/)?.[1] ?? "0";
269
- const typeAttr = attrs.match(/type="([^"]*)"/)?.[1];
270
- const attachmentsAttr = attrs.match(/attachments="([^"]*)"/)?.[1];
271
-
272
- messages.push({
273
- role: roleAttr as ConversationMessage["role"],
274
- time: Number(timeAttr),
275
- content,
276
- ...(typeAttr ? { type: typeAttr as ConversationMessage["type"] } : {}),
277
- ...(attachmentsAttr ? { attachments: attachmentsAttr.split(",").map((f) => f.trim()).filter(Boolean) } : {}),
278
- });
279
- }
280
- return messages;
281
- }
282
-
283
- export function appendHistory(projectRoot: string, entry: HistoryEntry): void {
284
- const historyPath = path.join(projectRoot, "history.jsonl");
285
- fs.appendFileSync(historyPath, JSON.stringify(entry) + "\n", "utf-8");
286
- }
287
-
288
- export function deleteHistoryEntry(
289
- projectRoot: string,
290
- taskId: string,
291
- runId: string,
292
- ): boolean {
293
- const historyPath = path.join(projectRoot, "history.jsonl");
294
- if (!fs.existsSync(historyPath)) return false;
295
-
296
- const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
297
- let found = false;
298
- const remaining: string[] = [];
299
-
300
- for (const line of lines) {
301
- try {
302
- const entry = JSON.parse(line) as HistoryEntry;
303
- if (entry.task_id === taskId && entry.run_id === runId) {
304
- found = true;
305
- continue;
306
- }
307
- } catch { /* keep malformed lines */ }
308
- remaining.push(line);
309
- }
310
-
311
- if (!found) return false;
312
-
313
- fs.writeFileSync(historyPath, remaining.length > 0 ? remaining.join("\n") + "\n" : "", "utf-8");
314
-
315
- const runDir = path.join(projectRoot, "tasks", taskId, runId);
316
- if (fs.existsSync(runDir)) {
317
- fs.rmSync(runDir, { recursive: true, force: true });
318
- }
319
-
320
- return true;
321
- }
322
-
323
- /** Returns entries most-recent-first. */
324
- export function readHistory(
325
- projectRoot: string,
326
- opts: { offset?: number; limit?: number; task_id?: string },
327
- ): { entries: HistoryEntry[]; total: number } {
328
- const historyPath = path.join(projectRoot, "history.jsonl");
329
- if (!fs.existsSync(historyPath)) return { entries: [], total: 0 };
330
-
331
- const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
332
- let all: HistoryEntry[] = [];
333
- for (const line of lines) {
334
- try { all.push(JSON.parse(line)); } catch { /* skip malformed */ }
335
- }
336
- all.reverse();
337
- if (opts.task_id) {
338
- all = all.filter((e) => e.task_id === opts.task_id);
339
- }
340
- const offset = opts.offset ?? 0;
341
- const limit = opts.limit ?? 10;
342
- return { entries: all.slice(offset, offset + limit), total: all.length };
343
- }