palmier 0.7.8 → 0.8.0
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 +1 -1
- package/dist/commands/run.js +55 -0
- package/dist/commands/serve.js +22 -2
- package/dist/event-queues.d.ts +36 -0
- package/dist/event-queues.js +53 -0
- package/dist/mcp-tools.d.ts +2 -0
- package/dist/mcp-tools.js +4 -2
- package/dist/platform/linux.js +11 -8
- package/dist/platform/windows.d.ts +5 -6
- package/dist/platform/windows.js +19 -13
- package/dist/pwa/assets/index-FP1Mipr6.js +120 -0
- package/dist/pwa/assets/index-bLTn8zBj.css +1 -0
- package/dist/pwa/assets/{web-BNr628AV.js → web-BpM3fNCn.js} +1 -1
- package/dist/pwa/assets/{web-DyQPewAi.js → web-CF-N8Di6.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.js +25 -9
- package/dist/task.js +1 -1
- package/dist/transports/http-transport.js +18 -5
- package/dist/types.d.ts +10 -6
- package/package.json +1 -1
- package/palmier-server/README.md +3 -3
- package/palmier-server/pwa/src/App.css +117 -36
- package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +46 -0
- package/palmier-server/pwa/src/components/RunDetailView.tsx +58 -15
- package/palmier-server/pwa/src/components/SessionComposer.tsx +20 -10
- package/palmier-server/pwa/src/components/{RunsView.tsx → SessionsView.tsx} +33 -25
- package/palmier-server/pwa/src/components/TaskCard.tsx +33 -35
- package/palmier-server/pwa/src/components/TaskForm.tsx +274 -293
- package/palmier-server/pwa/src/components/{TaskListView.tsx → TasksView.tsx} +20 -13
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +16 -8
- package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +102 -0
- package/palmier-server/pwa/src/pages/Dashboard.tsx +9 -26
- package/palmier-server/pwa/src/types.ts +5 -9
- package/palmier-server/spec.md +23 -23
- package/src/commands/run.ts +61 -0
- package/src/commands/serve.ts +22 -2
- package/src/event-queues.ts +56 -0
- package/src/mcp-tools.ts +6 -2
- package/src/platform/linux.ts +10 -8
- package/src/platform/windows.ts +19 -13
- package/src/rpc-handler.ts +28 -11
- package/src/task.ts +1 -1
- package/src/transports/http-transport.ts +17 -5
- package/src/types.ts +10 -7
- package/dist/pwa/assets/index-8cTctVnD.js +0 -120
- package/dist/pwa/assets/index-CSUkBBsQ.css +0 -1
package/src/mcp-tools.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { StringCodec, type NatsConnection } from "nats";
|
|
2
2
|
import { registerPending } from "./pending-requests.js";
|
|
3
3
|
import { getCapabilityDevice } from "./device-capabilities.js";
|
|
4
|
-
import { getNotifications } from "./notification-store.js";
|
|
5
|
-
import { getSmsMessages } from "./sms-store.js";
|
|
4
|
+
import { getNotifications, onNotificationsChanged } from "./notification-store.js";
|
|
5
|
+
import { getSmsMessages, onSmsChanged } from "./sms-store.js";
|
|
6
6
|
import type { HostConfig } from "./types.js";
|
|
7
7
|
|
|
8
8
|
export class ToolError extends Error {
|
|
@@ -750,6 +750,8 @@ export interface ResourceDefinition {
|
|
|
750
750
|
restPath: string;
|
|
751
751
|
/** Return the current resource content. */
|
|
752
752
|
read: () => unknown;
|
|
753
|
+
/** Register a listener for content changes. Returns an unsubscribe function. */
|
|
754
|
+
subscribe: (listener: () => void) => () => void;
|
|
753
755
|
}
|
|
754
756
|
|
|
755
757
|
const deviceNotificationsResource: ResourceDefinition = {
|
|
@@ -762,6 +764,7 @@ const deviceNotificationsResource: ResourceDefinition = {
|
|
|
762
764
|
mimeType: "application/json",
|
|
763
765
|
restPath: "/notifications",
|
|
764
766
|
read: getNotifications,
|
|
767
|
+
subscribe: onNotificationsChanged,
|
|
765
768
|
};
|
|
766
769
|
|
|
767
770
|
const deviceSmsResource: ResourceDefinition = {
|
|
@@ -774,6 +777,7 @@ const deviceSmsResource: ResourceDefinition = {
|
|
|
774
777
|
mimeType: "application/json",
|
|
775
778
|
restPath: "/sms-messages",
|
|
776
779
|
read: getSmsMessages,
|
|
780
|
+
subscribe: onSmsChanged,
|
|
777
781
|
};
|
|
778
782
|
|
|
779
783
|
export const agentResources: ResourceDefinition[] = [deviceNotificationsResource, deviceSmsResource];
|
package/src/platform/linux.ts
CHANGED
|
@@ -196,15 +196,17 @@ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
|
|
|
196
196
|
fs.writeFileSync(path.join(UNIT_DIR, serviceName), serviceContent, "utf-8");
|
|
197
197
|
daemonReload();
|
|
198
198
|
|
|
199
|
-
// Only create and enable a timer if
|
|
200
|
-
if (!task.frontmatter.
|
|
201
|
-
const
|
|
199
|
+
// Only create and enable a timer if the schedule exists and is enabled
|
|
200
|
+
if (!task.frontmatter.schedule_enabled) return;
|
|
201
|
+
const scheduleType = task.frontmatter.schedule_type;
|
|
202
|
+
const scheduleValues = task.frontmatter.schedule_values;
|
|
203
|
+
if (!scheduleType || !scheduleValues?.length) return;
|
|
202
204
|
const onCalendarLines: string[] = [];
|
|
203
|
-
for (const
|
|
204
|
-
if (
|
|
205
|
-
onCalendarLines.push(`OnCalendar=${cronToOnCalendar(
|
|
206
|
-
} else if (
|
|
207
|
-
onCalendarLines.push(`OnActiveSec=${
|
|
205
|
+
for (const value of scheduleValues) {
|
|
206
|
+
if (scheduleType === "crons") {
|
|
207
|
+
onCalendarLines.push(`OnCalendar=${cronToOnCalendar(value)}`);
|
|
208
|
+
} else if (scheduleType === "specific_times") {
|
|
209
|
+
onCalendarLines.push(`OnActiveSec=${value}`);
|
|
208
210
|
}
|
|
209
211
|
}
|
|
210
212
|
|
package/src/platform/windows.ts
CHANGED
|
@@ -14,22 +14,23 @@ const DAEMON_TASK_NAME = "PalmierDaemon";
|
|
|
14
14
|
const DOW_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Convert a
|
|
17
|
+
* Convert a single schedule value to a Task Scheduler XML trigger element.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* `specific_times` values are ISO datetime strings like "2026-03-28T09:00".
|
|
20
|
+
*
|
|
21
|
+
* `crons` values are cron expressions. Only these patterns (produced by the PWA UI) are handled:
|
|
20
22
|
* hourly: "0 * * * *"
|
|
21
23
|
* daily: "MM HH * * *"
|
|
22
24
|
* weekly: "MM HH * * D"
|
|
23
25
|
* monthly: "MM HH D * *"
|
|
24
26
|
*/
|
|
25
|
-
export function
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
return `<TimeTrigger><StartBoundary>${trigger.value}:00</StartBoundary></TimeTrigger>`;
|
|
27
|
+
export function scheduleValueToXml(scheduleType: "crons" | "specific_times", value: string): string {
|
|
28
|
+
if (scheduleType === "specific_times") {
|
|
29
|
+
return `<TimeTrigger><StartBoundary>${value}:00</StartBoundary></TimeTrigger>`;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const parts =
|
|
32
|
-
if (parts.length !== 5) throw new Error(`Invalid cron expression: ${
|
|
32
|
+
const parts = value.trim().split(/\s+/);
|
|
33
|
+
if (parts.length !== 5) throw new Error(`Invalid cron expression: ${value}`);
|
|
33
34
|
const [minute, hour, dayOfMonth, , dayOfWeek] = parts;
|
|
34
35
|
const st = `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:00`;
|
|
35
36
|
// StartBoundary needs a full date; use a past date as the anchor
|
|
@@ -191,14 +192,19 @@ export class WindowsPlatform implements PlatformService {
|
|
|
191
192
|
const script = process.argv[1] || "palmier";
|
|
192
193
|
const tr = `"${process.execPath}" "${script}" run ${taskId}`;
|
|
193
194
|
|
|
194
|
-
// Build trigger XML elements
|
|
195
|
+
// Build trigger XML elements. Event-based schedule types (on_new_notification,
|
|
196
|
+
// on_new_sms) carry no values and are driven by the run process, not the OS
|
|
197
|
+
// scheduler — they intentionally produce only the dummy trigger below.
|
|
195
198
|
const triggerElements: string[] = [];
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
const scheduleType = task.frontmatter.schedule_type;
|
|
200
|
+
const scheduleValues = task.frontmatter.schedule_values;
|
|
201
|
+
const isTimerSchedule = scheduleType === "crons" || scheduleType === "specific_times";
|
|
202
|
+
if (task.frontmatter.schedule_enabled && isTimerSchedule && scheduleValues?.length) {
|
|
203
|
+
for (const value of scheduleValues) {
|
|
198
204
|
try {
|
|
199
|
-
triggerElements.push(
|
|
205
|
+
triggerElements.push(scheduleValueToXml(scheduleType, value));
|
|
200
206
|
} catch (err) {
|
|
201
|
-
console.error(`Invalid
|
|
207
|
+
console.error(`Invalid schedule value: ${err}`);
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
}
|
package/src/rpc-handler.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { publishHostEvent } from "./events.js";
|
|
|
14
14
|
import { getCapabilityDevice, setCapabilityDevice, clearCapabilityDevice, type DeviceCapability } from "./device-capabilities.js";
|
|
15
15
|
import { currentVersion, performUpdate } from "./update-checker.js";
|
|
16
16
|
import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
|
|
17
|
+
import { clearTaskQueue } from "./event-queues.js";
|
|
17
18
|
import type { HostConfig, ParsedTask, RpcMessage, ConversationMessage } from "./types.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -162,7 +163,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
162
163
|
// is active. Includes any prompts already waiting so a reconnecting
|
|
163
164
|
// PWA can render their modals without replaying events.
|
|
164
165
|
const capabilities: Record<string, string | null> = {};
|
|
165
|
-
for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd"] as const) {
|
|
166
|
+
for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd", "email"] as const) {
|
|
166
167
|
capabilities[cap] = getCapabilityDevice(cap)?.clientToken ?? null;
|
|
167
168
|
}
|
|
168
169
|
return {
|
|
@@ -194,8 +195,9 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
194
195
|
const params = request.params as {
|
|
195
196
|
user_prompt: string;
|
|
196
197
|
agent: string;
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
schedule_type?: "crons" | "specific_times" | "on_new_notification" | "on_new_sms";
|
|
199
|
+
schedule_values?: string[];
|
|
200
|
+
schedule_enabled?: boolean;
|
|
199
201
|
requires_confirmation?: boolean;
|
|
200
202
|
yolo_mode?: boolean;
|
|
201
203
|
foreground_mode?: boolean;
|
|
@@ -214,9 +216,10 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
214
216
|
name,
|
|
215
217
|
user_prompt: params.user_prompt,
|
|
216
218
|
agent: params.agent,
|
|
217
|
-
|
|
218
|
-
triggers_enabled: params.triggers_enabled ?? true,
|
|
219
|
+
schedule_enabled: params.schedule_enabled ?? true,
|
|
219
220
|
requires_confirmation: params.requires_confirmation ?? true,
|
|
221
|
+
...(params.schedule_type ? { schedule_type: params.schedule_type } : {}),
|
|
222
|
+
...(params.schedule_values?.length ? { schedule_values: params.schedule_values } : {}),
|
|
220
223
|
...(params.yolo_mode ? { yolo_mode: true } : {}),
|
|
221
224
|
...(params.foreground_mode ? { foreground_mode: true } : {}),
|
|
222
225
|
...(params.command ? { command: params.command } : {}),
|
|
@@ -235,8 +238,9 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
235
238
|
id: string;
|
|
236
239
|
user_prompt?: string;
|
|
237
240
|
agent?: string;
|
|
238
|
-
|
|
239
|
-
|
|
241
|
+
schedule_type?: "crons" | "specific_times" | "on_new_notification" | "on_new_sms" | null;
|
|
242
|
+
schedule_values?: string[] | null;
|
|
243
|
+
schedule_enabled?: boolean;
|
|
240
244
|
requires_confirmation?: boolean;
|
|
241
245
|
yolo_mode?: boolean;
|
|
242
246
|
foreground_mode?: boolean;
|
|
@@ -253,8 +257,21 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
253
257
|
// Merge updates
|
|
254
258
|
if (params.user_prompt !== undefined) existing.frontmatter.user_prompt = params.user_prompt;
|
|
255
259
|
if (params.agent !== undefined) existing.frontmatter.agent = params.agent;
|
|
256
|
-
if (params.
|
|
257
|
-
|
|
260
|
+
if (params.schedule_type !== undefined) {
|
|
261
|
+
if (params.schedule_type) {
|
|
262
|
+
existing.frontmatter.schedule_type = params.schedule_type;
|
|
263
|
+
} else {
|
|
264
|
+
delete existing.frontmatter.schedule_type;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (params.schedule_values !== undefined) {
|
|
268
|
+
if (params.schedule_values && params.schedule_values.length > 0) {
|
|
269
|
+
existing.frontmatter.schedule_values = params.schedule_values;
|
|
270
|
+
} else {
|
|
271
|
+
delete existing.frontmatter.schedule_values;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (params.schedule_enabled !== undefined) existing.frontmatter.schedule_enabled = params.schedule_enabled;
|
|
258
275
|
if (params.requires_confirmation !== undefined)
|
|
259
276
|
existing.frontmatter.requires_confirmation = params.requires_confirmation;
|
|
260
277
|
if (params.yolo_mode !== undefined) {
|
|
@@ -290,6 +307,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
290
307
|
const params = request.params as { id: string };
|
|
291
308
|
|
|
292
309
|
getPlatform().removeTaskTimer(params.id);
|
|
310
|
+
clearTaskQueue(params.id);
|
|
293
311
|
removeFromTaskList(config.projectRoot, params.id);
|
|
294
312
|
|
|
295
313
|
return { ok: true, task_id: params.id };
|
|
@@ -314,8 +332,7 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
|
|
|
314
332
|
name,
|
|
315
333
|
user_prompt: params.user_prompt,
|
|
316
334
|
agent: params.agent,
|
|
317
|
-
|
|
318
|
-
triggers_enabled: false,
|
|
335
|
+
schedule_enabled: false,
|
|
319
336
|
requires_confirmation: params.requires_confirmation ?? false,
|
|
320
337
|
...(params.yolo_mode ? { yolo_mode: true } : {}),
|
|
321
338
|
...(params.foreground_mode ? { foreground_mode: true } : {}),
|
package/src/task.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function parseTaskContent(content: string): ParsedTask {
|
|
|
36
36
|
|
|
37
37
|
frontmatter.name ??= frontmatter.user_prompt?.slice(0, 60) ?? "";
|
|
38
38
|
frontmatter.agent ??= "claude";
|
|
39
|
-
frontmatter.
|
|
39
|
+
frontmatter.schedule_enabled ??= true;
|
|
40
40
|
|
|
41
41
|
return { frontmatter };
|
|
42
42
|
}
|
|
@@ -9,8 +9,7 @@ import type { HostConfig, RpcMessage, RequiredPermission } from "../types.js";
|
|
|
9
9
|
import { agentToolMap, agentResources, ToolError, type ToolContext } from "../mcp-tools.js";
|
|
10
10
|
import { handleMcpRequest, getAgentName, getResourceSubscriptions } from "../mcp-handler.js";
|
|
11
11
|
import { getTaskDir } from "../task.js";
|
|
12
|
-
import {
|
|
13
|
-
import { onSmsChanged } from "../sms-store.js";
|
|
12
|
+
import { popEvent } from "../event-queues.js";
|
|
14
13
|
|
|
15
14
|
// ── Bundled PWA asset serving ───────────────────────────────────────────
|
|
16
15
|
|
|
@@ -123,9 +122,9 @@ export async function startHttpTransport(
|
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
for (const resource of agentResources) {
|
|
126
|
+
resource.subscribe(() => broadcastResourceUpdated(resource.uri));
|
|
127
|
+
}
|
|
129
128
|
|
|
130
129
|
// If a pairing code is provided, pre-register it
|
|
131
130
|
if (pairingCode) {
|
|
@@ -281,6 +280,19 @@ export async function startHttpTransport(
|
|
|
281
280
|
return;
|
|
282
281
|
}
|
|
283
282
|
|
|
283
|
+
// ── Event queue pop (used by event-triggered palmier run) ─────────
|
|
284
|
+
|
|
285
|
+
if (req.method === "POST" && pathname === "/task-event/pop") {
|
|
286
|
+
if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
|
|
287
|
+
const taskId = url.searchParams.get("taskId");
|
|
288
|
+
if (!taskId) {
|
|
289
|
+
sendJson(res, 400, { error: "taskId query parameter is required" });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
sendJson(res, 200, popEvent(taskId));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
284
296
|
// ── Localhost-only endpoints (no auth) ─────────────────────────────
|
|
285
297
|
|
|
286
298
|
if (req.method === "POST" && pathname === "/event") {
|
package/src/types.ts
CHANGED
|
@@ -21,8 +21,16 @@ export interface TaskFrontmatter {
|
|
|
21
21
|
name: string;
|
|
22
22
|
user_prompt: string;
|
|
23
23
|
agent: string;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Task schedule.
|
|
26
|
+
* - `crons`: `schedule_values` holds cron expressions (e.g. "0 9 * * *")
|
|
27
|
+
* - `specific_times`: `schedule_values` holds local datetime strings (e.g. "2026-04-20T09:00")
|
|
28
|
+
* - `on_new_notification`: fires on each new Android notification from NATS; no `schedule_values`
|
|
29
|
+
* - `on_new_sms`: fires on each new SMS from NATS; no `schedule_values`
|
|
30
|
+
*/
|
|
31
|
+
schedule_type?: "crons" | "specific_times" | "on_new_notification" | "on_new_sms";
|
|
32
|
+
schedule_values?: string[];
|
|
33
|
+
schedule_enabled: boolean;
|
|
26
34
|
requires_confirmation: boolean;
|
|
27
35
|
yolo_mode?: boolean;
|
|
28
36
|
foreground_mode?: boolean;
|
|
@@ -30,11 +38,6 @@ export interface TaskFrontmatter {
|
|
|
30
38
|
command?: string;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
export interface Trigger {
|
|
34
|
-
type: "cron" | "once";
|
|
35
|
-
value: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
41
|
export interface ParsedTask {
|
|
39
42
|
frontmatter: TaskFrontmatter;
|
|
40
43
|
}
|