palmier 0.9.5 → 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 (258) hide show
  1. package/README.md +30 -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 +3 -4
  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/apple-touch-icon.png +0 -0
  91. package/dist/pwa/assets/index-D1bIhEbd.css +1 -0
  92. package/dist/pwa/assets/{index-Cvffaohh.js → index-DWvRAUiy.js} +31 -31
  93. package/dist/pwa/assets/{web-qdLcAD7T.js → web-C4iZbqTC.js} +1 -1
  94. package/dist/pwa/assets/{web-ChtbM4nv.js → web-CBFqJGX6.js} +1 -1
  95. package/dist/pwa/assets/{web-hExASsqW.js → web-DL4uXOpS.js} +1 -1
  96. package/dist/pwa/favicon.ico +0 -0
  97. package/dist/pwa/index.html +3 -3
  98. package/dist/pwa/manifest.webmanifest +1 -1
  99. package/dist/pwa/pwa-192x192.png +0 -0
  100. package/dist/pwa/pwa-512x512.png +0 -0
  101. package/dist/pwa/service-worker.js +1 -1
  102. package/dist/rpc-handler.d.ts +0 -1
  103. package/dist/rpc-handler.js +3 -10
  104. package/dist/sms-store.d.ts +0 -1
  105. package/dist/sms-store.js +0 -1
  106. package/dist/spawn-command.d.ts +0 -1
  107. package/dist/spawn-command.js +0 -1
  108. package/dist/task.d.ts +1 -1
  109. package/dist/task.js +14 -1
  110. package/dist/transports/http-transport.d.ts +0 -1
  111. package/dist/transports/http-transport.js +0 -1
  112. package/dist/transports/nats-transport.d.ts +0 -1
  113. package/dist/transports/nats-transport.js +0 -1
  114. package/dist/types.d.ts +0 -1
  115. package/dist/types.js +0 -1
  116. package/dist/update-checker.d.ts +0 -1
  117. package/dist/update-checker.js +0 -1
  118. package/package.json +5 -1
  119. package/.github/workflows/ci.yml +0 -16
  120. package/.github/workflows/publish.yml +0 -37
  121. package/CLAUDE.md +0 -22
  122. package/dist/pwa/assets/index-DBgOYBrB.css +0 -1
  123. package/palmier-server/.github/workflows/ci.yml +0 -21
  124. package/palmier-server/.github/workflows/deploy.yml +0 -38
  125. package/palmier-server/CLAUDE.md +0 -17
  126. package/palmier-server/PRODUCTION.md +0 -358
  127. package/palmier-server/README.md +0 -231
  128. package/palmier-server/nats.conf +0 -19
  129. package/palmier-server/package.json +0 -15
  130. package/palmier-server/pnpm-lock.yaml +0 -7639
  131. package/palmier-server/pnpm-workspace.yaml +0 -3
  132. package/palmier-server/pwa/index.html +0 -16
  133. package/palmier-server/pwa/logo/logo-prompt.md +0 -28
  134. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  135. package/palmier-server/pwa/package.json +0 -34
  136. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  137. package/palmier-server/pwa/public/favicon.ico +0 -0
  138. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  139. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  140. package/palmier-server/pwa/src/App.css +0 -3004
  141. package/palmier-server/pwa/src/App.tsx +0 -59
  142. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  143. package/palmier-server/pwa/src/api.ts +0 -67
  144. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  145. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -114
  146. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  147. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  148. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  149. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  150. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  151. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  152. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  153. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  154. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  155. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -764
  156. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  157. package/palmier-server/pwa/src/constants.ts +0 -2
  158. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  159. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  160. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  161. package/palmier-server/pwa/src/formatTime.ts +0 -44
  162. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  163. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  164. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  165. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  166. package/palmier-server/pwa/src/main.tsx +0 -14
  167. package/palmier-server/pwa/src/native/Device.ts +0 -49
  168. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  169. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  170. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  171. package/palmier-server/pwa/src/service-worker.ts +0 -142
  172. package/palmier-server/pwa/src/types.ts +0 -75
  173. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  174. package/palmier-server/pwa/tsconfig.json +0 -21
  175. package/palmier-server/pwa/tsconfig.node.json +0 -19
  176. package/palmier-server/pwa/vite.config.ts +0 -47
  177. package/palmier-server/server/.env.example +0 -20
  178. package/palmier-server/server/package.json +0 -36
  179. package/palmier-server/server/src/db.ts +0 -44
  180. package/palmier-server/server/src/fcm.ts +0 -74
  181. package/palmier-server/server/src/index.ts +0 -688
  182. package/palmier-server/server/src/nats-jwt.ts +0 -299
  183. package/palmier-server/server/src/nats-setup.ts +0 -48
  184. package/palmier-server/server/src/nats.ts +0 -33
  185. package/palmier-server/server/src/notify.ts +0 -34
  186. package/palmier-server/server/src/push.ts +0 -68
  187. package/palmier-server/server/src/routes/device.ts +0 -224
  188. package/palmier-server/server/src/routes/fcm.ts +0 -64
  189. package/palmier-server/server/src/routes/hosts.ts +0 -56
  190. package/palmier-server/server/src/routes/push.ts +0 -101
  191. package/palmier-server/server/tsconfig.json +0 -20
  192. package/palmier-server/spec.md +0 -533
  193. package/src/agents/agent-instructions.md +0 -28
  194. package/src/agents/agent.ts +0 -114
  195. package/src/agents/aider.ts +0 -35
  196. package/src/agents/claude.ts +0 -39
  197. package/src/agents/cline.ts +0 -35
  198. package/src/agents/codex.ts +0 -40
  199. package/src/agents/copilot.ts +0 -37
  200. package/src/agents/cursor.ts +0 -36
  201. package/src/agents/deepagents.ts +0 -36
  202. package/src/agents/droid.ts +0 -35
  203. package/src/agents/gemini.ts +0 -43
  204. package/src/agents/goose.ts +0 -33
  205. package/src/agents/hermes.ts +0 -36
  206. package/src/agents/kimi.ts +0 -35
  207. package/src/agents/kiro.ts +0 -36
  208. package/src/agents/openclaw.ts +0 -29
  209. package/src/agents/opencode.ts +0 -36
  210. package/src/agents/qoder.ts +0 -36
  211. package/src/agents/qwen.ts +0 -32
  212. package/src/agents/shared-prompt.ts +0 -30
  213. package/src/client-store.ts +0 -68
  214. package/src/commands/clients.ts +0 -29
  215. package/src/commands/info.ts +0 -29
  216. package/src/commands/init.ts +0 -165
  217. package/src/commands/pair.ts +0 -137
  218. package/src/commands/restart.ts +0 -6
  219. package/src/commands/run.ts +0 -608
  220. package/src/commands/serve.ts +0 -211
  221. package/src/commands/uninstall.ts +0 -9
  222. package/src/config.ts +0 -36
  223. package/src/cross-spawn.d.ts +0 -5
  224. package/src/event-queues.ts +0 -41
  225. package/src/events.ts +0 -29
  226. package/src/index.ts +0 -111
  227. package/src/linked-device.ts +0 -52
  228. package/src/mcp-handler.ts +0 -200
  229. package/src/mcp-tools.ts +0 -839
  230. package/src/nats-client.ts +0 -19
  231. package/src/network.ts +0 -96
  232. package/src/notification-store.ts +0 -30
  233. package/src/pending-requests.ts +0 -73
  234. package/src/platform/index.ts +0 -20
  235. package/src/platform/linux.ts +0 -296
  236. package/src/platform/macos.ts +0 -329
  237. package/src/platform/platform.ts +0 -31
  238. package/src/platform/windows.ts +0 -299
  239. package/src/rpc-handler.ts +0 -694
  240. package/src/sms-store.ts +0 -28
  241. package/src/spawn-command.ts +0 -123
  242. package/src/task.ts +0 -330
  243. package/src/transports/http-transport.ts +0 -478
  244. package/src/transports/nats-transport.ts +0 -76
  245. package/src/types.ts +0 -89
  246. package/src/update-checker.ts +0 -40
  247. package/test/agent-instructions.test.ts +0 -209
  248. package/test/agent-output-parsing.test.ts +0 -74
  249. package/test/linux-cron.test.ts +0 -41
  250. package/test/macos-plist.test.ts +0 -112
  251. package/test/notification-store.test.ts +0 -57
  252. package/test/pairing.test.ts +0 -35
  253. package/test/result-state.test.ts +0 -110
  254. package/test/task-parsing.test.ts +0 -82
  255. package/test/taskrun-messages.test.ts +0 -224
  256. package/test/tsconfig.json +0 -9
  257. package/test/windows-xml.test.ts +0 -89
  258. 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,330 +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 removeFromTaskList(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
- let found = false;
59
- const remaining: string[] = [];
60
-
61
- for (const line of lines) {
62
- try {
63
- const entry = JSON.parse(line) as { task_id: string };
64
- if (entry.task_id === taskId) {
65
- found = true;
66
- continue;
67
- }
68
- } catch { /* keep malformed lines */ }
69
- remaining.push(line);
70
- }
71
-
72
- if (!found) return false;
73
- fs.writeFileSync(listPath, remaining.length > 0 ? remaining.join("\n") + "\n" : "", "utf-8");
74
- return true;
75
- }
76
-
77
- export function listTasks(projectRoot: string): ParsedTask[] {
78
- const listPath = path.join(projectRoot, "tasks.jsonl");
79
- if (!fs.existsSync(listPath)) return [];
80
-
81
- const lines = fs.readFileSync(listPath, "utf-8").split("\n").filter(Boolean);
82
- const tasks: ParsedTask[] = [];
83
-
84
- for (const line of lines) {
85
- let taskId: string;
86
- try {
87
- taskId = (JSON.parse(line) as { task_id: string }).task_id;
88
- } catch { continue; }
89
-
90
- const taskDir = getTaskDir(projectRoot, taskId);
91
- try {
92
- tasks.push(parseTaskFile(taskDir));
93
- } catch (err) {
94
- console.error(`Warning: failed to parse task ${taskId}: ${err}`);
95
- }
96
- }
97
-
98
- return tasks.reverse();
99
- }
100
-
101
- export function getTaskDir(projectRoot: string, taskId: string): string {
102
- return path.join(projectRoot, "tasks", taskId);
103
- }
104
-
105
- export function writeTaskStatus(taskDir: string, status: TaskStatus): void {
106
- const filePath = path.join(taskDir, "status.json");
107
- fs.writeFileSync(filePath, JSON.stringify(status), "utf-8");
108
- }
109
-
110
- export function readTaskStatus(taskDir: string): TaskStatus | undefined {
111
- const filePath = path.join(taskDir, "status.json");
112
- try {
113
- return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TaskStatus;
114
- } catch {
115
- return undefined;
116
- }
117
- }
118
-
119
- export interface FollowupStatus {
120
- pid: number;
121
- spawned_at: number;
122
- }
123
-
124
- export function writeFollowupStatus(runDir: string, status: FollowupStatus): void {
125
- fs.writeFileSync(path.join(runDir, "followup.json"), JSON.stringify(status), "utf-8");
126
- }
127
-
128
- export function readFollowupStatus(runDir: string): FollowupStatus | undefined {
129
- try {
130
- return JSON.parse(fs.readFileSync(path.join(runDir, "followup.json"), "utf-8")) as FollowupStatus;
131
- } catch {
132
- return undefined;
133
- }
134
- }
135
-
136
- export function deleteFollowupStatus(runDir: string): void {
137
- try { fs.unlinkSync(path.join(runDir, "followup.json")); } catch { /* ignore */ }
138
- }
139
-
140
- /** Returns the run ID (timestamp string used as directory name). */
141
- export function createRunDir(
142
- taskDir: string,
143
- taskName: string,
144
- startTime: number,
145
- agent?: string,
146
- ): string {
147
- const runId = String(startTime);
148
- const runDir = path.join(taskDir, runId);
149
- fs.mkdirSync(runDir, { recursive: true });
150
- const agentLine = agent ? `\nagent: ${agent}` : "";
151
- const content = `---\ntask_name: ${taskName}${agentLine}\n---\n\n`;
152
- fs.writeFileSync(path.join(runDir, "TASKRUN.md"), content, "utf-8");
153
- return runId;
154
- }
155
-
156
- export function getRunDir(taskDir: string, runId: string): string {
157
- return path.join(taskDir, runId);
158
- }
159
-
160
- export function appendRunMessage(
161
- taskDir: string,
162
- runId: string,
163
- msg: ConversationMessage,
164
- ): void {
165
- const attrs = [`role="${msg.role}"`, `time="${msg.time}"`];
166
- if (msg.type) attrs.push(`type="${msg.type}"`);
167
- if (msg.attachments?.length) attrs.push(`attachments="${msg.attachments.join(",")}"`);
168
-
169
- const delimiter = `<!-- palmier:message ${attrs.join(" ")} -->`;
170
- const entry = `${delimiter}\n\n${msg.content}\n\n`;
171
- fs.appendFileSync(path.join(taskDir, runId, "TASKRUN.md"), entry, "utf-8");
172
- }
173
-
174
- export function beginStreamingMessage(
175
- taskDir: string,
176
- runId: string,
177
- time: number,
178
- ): StreamingMessageWriter {
179
- const filePath = path.join(taskDir, runId, "TASKRUN.md");
180
- const delimiter = `<!-- palmier:message role="assistant" time="${time}" -->`;
181
- fs.appendFileSync(filePath, `${delimiter}\n\n`, "utf-8");
182
- return new StreamingMessageWriter(filePath);
183
- }
184
-
185
- export class StreamingMessageWriter {
186
- constructor(private filePath: string) {}
187
-
188
- /** Append a chunk of content to the current message. */
189
- write(chunk: string): void {
190
- fs.appendFileSync(this.filePath, chunk, "utf-8");
191
- }
192
-
193
- /** Finalize the message. If attachments are provided, rewrites the last assistant delimiter to include them. */
194
- end(attachments?: string[]): void {
195
- fs.appendFileSync(this.filePath, "\n\n", "utf-8");
196
- if (attachments?.length) {
197
- const raw = fs.readFileSync(this.filePath, "utf-8");
198
- // spliceUserMessage may have created a newer assistant delimiter.
199
- const pattern = /<!-- palmier:message role="assistant" time="\d+" -->/g;
200
- let lastMatch: RegExpExecArray | null = null;
201
- let m;
202
- while ((m = pattern.exec(raw)) !== null) lastMatch = m;
203
- if (lastMatch) {
204
- const before = raw.slice(0, lastMatch.index);
205
- const after = raw.slice(lastMatch.index + lastMatch[0].length);
206
- const updated = before + `${lastMatch[0].slice(0, -4)} attachments="${attachments.join(",")}" -->` + after;
207
- fs.writeFileSync(this.filePath, updated, "utf-8");
208
- }
209
- }
210
- }
211
- }
212
-
213
- /**
214
- * Splice a user message into a running assistant stream: close the current
215
- * assistant block, write the user message, open a new assistant block. Direct
216
- * appends only, so an existing StreamingMessageWriter keeps working — its
217
- * subsequent chunks land in the new block.
218
- */
219
- export function spliceUserMessage(
220
- taskDir: string,
221
- runId: string,
222
- userMsg: ConversationMessage,
223
- /** Optional text to append to the current assistant block before ending it. */
224
- assistantAppend?: string,
225
- ): void {
226
- const filePath = path.join(taskDir, runId, "TASKRUN.md");
227
- if (assistantAppend) {
228
- fs.appendFileSync(filePath, assistantAppend, "utf-8");
229
- }
230
- fs.appendFileSync(filePath, "\n\n", "utf-8");
231
- appendRunMessage(taskDir, runId, userMsg);
232
- const delimiter = `<!-- palmier:message role="assistant" time="${Date.now()}" -->`;
233
- fs.appendFileSync(filePath, `${delimiter}\n\n`, "utf-8");
234
- }
235
-
236
- export function readRunMessages(taskDir: string, runId: string): ConversationMessage[] {
237
- const raw = fs.readFileSync(path.join(taskDir, runId, "TASKRUN.md"), "utf-8");
238
- const fmMatch = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
239
- if (!fmMatch) return [];
240
-
241
- const body = fmMatch[1];
242
- const delimiterRegex = /<!-- palmier:message\s+(.*?)\s*-->/g;
243
- const matches = [...body.matchAll(delimiterRegex)];
244
- if (matches.length === 0) return [];
245
-
246
- const messages: ConversationMessage[] = [];
247
- for (let i = 0; i < matches.length; i++) {
248
- const match = matches[i];
249
- const attrs = match[1];
250
- const start = match.index! + match[0].length;
251
- const end = i + 1 < matches.length ? matches[i + 1].index! : body.length;
252
- const content = body.slice(start, end).trim();
253
-
254
- const roleAttr = attrs.match(/role="([^"]*)"/)?.[1] ?? "assistant";
255
- const timeAttr = attrs.match(/time="([^"]*)"/)?.[1] ?? "0";
256
- const typeAttr = attrs.match(/type="([^"]*)"/)?.[1];
257
- const attachmentsAttr = attrs.match(/attachments="([^"]*)"/)?.[1];
258
-
259
- messages.push({
260
- role: roleAttr as ConversationMessage["role"],
261
- time: Number(timeAttr),
262
- content,
263
- ...(typeAttr ? { type: typeAttr as ConversationMessage["type"] } : {}),
264
- ...(attachmentsAttr ? { attachments: attachmentsAttr.split(",").map((f) => f.trim()).filter(Boolean) } : {}),
265
- });
266
- }
267
- return messages;
268
- }
269
-
270
- export function appendHistory(projectRoot: string, entry: HistoryEntry): void {
271
- const historyPath = path.join(projectRoot, "history.jsonl");
272
- fs.appendFileSync(historyPath, JSON.stringify(entry) + "\n", "utf-8");
273
- }
274
-
275
- export function deleteHistoryEntry(
276
- projectRoot: string,
277
- taskId: string,
278
- runId: string,
279
- ): boolean {
280
- const historyPath = path.join(projectRoot, "history.jsonl");
281
- if (!fs.existsSync(historyPath)) return false;
282
-
283
- const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
284
- let found = false;
285
- const remaining: string[] = [];
286
-
287
- for (const line of lines) {
288
- try {
289
- const entry = JSON.parse(line) as HistoryEntry;
290
- if (entry.task_id === taskId && entry.run_id === runId) {
291
- found = true;
292
- continue;
293
- }
294
- } catch { /* keep malformed lines */ }
295
- remaining.push(line);
296
- }
297
-
298
- if (!found) return false;
299
-
300
- fs.writeFileSync(historyPath, remaining.length > 0 ? remaining.join("\n") + "\n" : "", "utf-8");
301
-
302
- const runDir = path.join(projectRoot, "tasks", taskId, runId);
303
- if (fs.existsSync(runDir)) {
304
- fs.rmSync(runDir, { recursive: true, force: true });
305
- }
306
-
307
- return true;
308
- }
309
-
310
- /** Returns entries most-recent-first. */
311
- export function readHistory(
312
- projectRoot: string,
313
- opts: { offset?: number; limit?: number; task_id?: string },
314
- ): { entries: HistoryEntry[]; total: number } {
315
- const historyPath = path.join(projectRoot, "history.jsonl");
316
- if (!fs.existsSync(historyPath)) return { entries: [], total: 0 };
317
-
318
- const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
319
- let all: HistoryEntry[] = [];
320
- for (const line of lines) {
321
- try { all.push(JSON.parse(line)); } catch { /* skip malformed */ }
322
- }
323
- all.reverse();
324
- if (opts.task_id) {
325
- all = all.filter((e) => e.task_id === opts.task_id);
326
- }
327
- const offset = opts.offset ?? 0;
328
- const limit = opts.limit ?? 10;
329
- return { entries: all.slice(offset, offset + limit), total: all.length };
330
- }