palmier 0.5.0 → 0.5.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.
@@ -372,7 +372,11 @@ async function requestPermission(config, task, taskDir, requiredPermissions) {
372
372
  permissions: requiredPermissions,
373
373
  }),
374
374
  });
375
- const { response } = await res.json();
375
+ const body = await res.json();
376
+ const response = body.response;
377
+ if (!response || !["granted", "granted_all", "aborted"].includes(response)) {
378
+ throw new Error(`Permission request failed: ${body.error ?? `unexpected response: ${JSON.stringify(body)}`}`);
379
+ }
376
380
  writeTaskStatus(taskDir, {
377
381
  running_state: response === "aborted" ? "aborted" : "started",
378
382
  time_stamp: Date.now(),
@@ -386,7 +390,11 @@ async function requestConfirmation(config, task, taskDir) {
386
390
  headers: { "Content-Type": "application/json" },
387
391
  body: JSON.stringify({ taskId: task.frontmatter.id, taskName: task.frontmatter.name }),
388
392
  });
389
- const { confirmed } = await res.json();
393
+ const body = await res.json();
394
+ if (typeof body.confirmed !== "boolean") {
395
+ throw new Error(`Confirmation request failed: ${body.error ?? `unexpected response: ${JSON.stringify(body)}`}`);
396
+ }
397
+ const { confirmed } = body;
390
398
  writeTaskStatus(taskDir, {
391
399
  running_state: confirmed ? "started" : "aborted",
392
400
  time_stamp: Date.now(),
@@ -106,7 +106,7 @@ export class WindowsPlatform {
106
106
  this.startDaemonTask();
107
107
  process.exit(0);
108
108
  }
109
- // Kill old daemon first, then spawn new one.
109
+ // Kill old daemon by PID
110
110
  if (oldPid) {
111
111
  try {
112
112
  execFileSync("taskkill", ["/pid", oldPid, "/f", "/t"], { windowsHide: true, stdio: "pipe" });
@@ -115,6 +115,22 @@ export class WindowsPlatform {
115
115
  // Process may have already exited
116
116
  }
117
117
  }
118
+ // Also kill any stale palmier serve processes (e.g. leftover from a previous daemon)
119
+ try {
120
+ const out = execFileSync("wmic", ["process", "where", `CommandLine like '%palmier%serve%' and ProcessId != '${process.pid}'`, "get", "ProcessId"], { encoding: "utf-8", windowsHide: true, stdio: "pipe" });
121
+ for (const line of out.split("\n")) {
122
+ const pid = line.trim();
123
+ if (pid && /^\d+$/.test(pid)) {
124
+ try {
125
+ execFileSync("taskkill", ["/pid", pid, "/f", "/t"], { windowsHide: true, stdio: "pipe" });
126
+ }
127
+ catch { }
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ // wmic may not be available on all Windows versions
133
+ }
118
134
  this.startDaemonTask();
119
135
  }
120
136
  /** Create or update the Task Scheduler entry for the daemon. */
@@ -126,10 +126,17 @@ const activeFollowups = new Map();
126
126
  export function createRpcHandler(config, nc) {
127
127
  function flattenTask(task) {
128
128
  const taskDir = getTaskDir(config.projectRoot, task.frontmatter.id);
129
+ const status = readTaskStatus(taskDir);
130
+ const pending = getPending(task.frontmatter.id);
129
131
  return {
130
132
  ...task.frontmatter,
131
133
  body: task.body,
132
- status: readTaskStatus(taskDir),
134
+ status: status ? {
135
+ ...status,
136
+ ...(pending?.type === "confirmation" ? { pending_confirmation: true } : {}),
137
+ ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
138
+ ...(pending?.type === "input" ? { pending_input: pending.params } : {}),
139
+ } : undefined,
133
140
  };
134
141
  }
135
142
  async function handleRpc(request) {
@@ -242,13 +242,14 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
242
242
  }
243
243
  const taskDir = getTaskDir(config.projectRoot, taskId);
244
244
  const task = parseTaskFile(taskDir);
245
+ const pendingPromise = registerPending(taskId, "input", descriptions);
245
246
  await publishEvent(taskId, {
246
247
  event_type: "input-request",
247
248
  host_id: config.hostId,
248
249
  input_descriptions: descriptions,
249
250
  name: task.frontmatter.name,
250
251
  });
251
- const response = await registerPending(taskId, "input", descriptions);
252
+ const response = await pendingPromise;
252
253
  if (response.length === 1 && response[0] === "aborted") {
253
254
  await publishEvent(taskId, { event_type: "input-resolved", host_id: config.hostId, status: "aborted" });
254
255
  if (runId) {
@@ -283,11 +284,12 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
283
284
  sendJson(res, 400, { error: "taskId is required" });
284
285
  return;
285
286
  }
287
+ const pendingPromise = registerPending(taskId, "confirmation");
286
288
  await publishEvent(taskId, {
287
289
  event_type: "confirm-request",
288
290
  host_id: config.hostId,
289
291
  });
290
- const response = await registerPending(taskId, "confirmation");
292
+ const response = await pendingPromise;
291
293
  const confirmed = response[0] === "confirmed";
292
294
  await publishEvent(taskId, {
293
295
  event_type: "confirm-resolved",
@@ -314,13 +316,14 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
314
316
  sendJson(res, 400, { error: "taskId and permissions are required" });
315
317
  return;
316
318
  }
319
+ const pendingPromise = registerPending(taskId, "permission", permissions);
317
320
  await publishEvent(taskId, {
318
321
  event_type: "permission-request",
319
322
  host_id: config.hostId,
320
323
  required_permissions: permissions,
321
324
  name: taskName,
322
325
  });
323
- const response = await registerPending(taskId, "permission", permissions);
326
+ const response = await pendingPromise;
324
327
  const status = response[0];
325
328
  await publishEvent(taskId, {
326
329
  event_type: "permission-resolved",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",
@@ -454,7 +454,11 @@ async function requestPermission(
454
454
  permissions: requiredPermissions,
455
455
  }),
456
456
  });
457
- const { response } = await res.json() as { response: "granted" | "granted_all" | "aborted" };
457
+ const body = await res.json() as { response?: string; error?: string };
458
+ const response = body.response as "granted" | "granted_all" | "aborted" | undefined;
459
+ if (!response || !["granted", "granted_all", "aborted"].includes(response)) {
460
+ throw new Error(`Permission request failed: ${body.error ?? `unexpected response: ${JSON.stringify(body)}`}`);
461
+ }
458
462
  writeTaskStatus(taskDir, {
459
463
  running_state: response === "aborted" ? "aborted" : "started",
460
464
  time_stamp: Date.now(),
@@ -474,7 +478,11 @@ async function requestConfirmation(
474
478
  headers: { "Content-Type": "application/json" },
475
479
  body: JSON.stringify({ taskId: task.frontmatter.id, taskName: task.frontmatter.name }),
476
480
  });
477
- const { confirmed } = await res.json() as { confirmed: boolean };
481
+ const body = await res.json() as { confirmed?: boolean; error?: string };
482
+ if (typeof body.confirmed !== "boolean") {
483
+ throw new Error(`Confirmation request failed: ${body.error ?? `unexpected response: ${JSON.stringify(body)}`}`);
484
+ }
485
+ const { confirmed } = body;
478
486
  writeTaskStatus(taskDir, {
479
487
  running_state: confirmed ? "started" : "aborted",
480
488
  time_stamp: Date.now(),
@@ -127,7 +127,7 @@ export class WindowsPlatform implements PlatformService {
127
127
  process.exit(0);
128
128
  }
129
129
 
130
- // Kill old daemon first, then spawn new one.
130
+ // Kill old daemon by PID
131
131
  if (oldPid) {
132
132
  try {
133
133
  execFileSync("taskkill", ["/pid", oldPid, "/f", "/t"], { windowsHide: true, stdio: "pipe" });
@@ -136,6 +136,19 @@ export class WindowsPlatform implements PlatformService {
136
136
  }
137
137
  }
138
138
 
139
+ // Also kill any stale palmier serve processes (e.g. leftover from a previous daemon)
140
+ try {
141
+ const out = execFileSync("wmic", ["process", "where", `CommandLine like '%palmier%serve%' and ProcessId != '${process.pid}'`, "get", "ProcessId"], { encoding: "utf-8", windowsHide: true, stdio: "pipe" });
142
+ for (const line of out.split("\n")) {
143
+ const pid = line.trim();
144
+ if (pid && /^\d+$/.test(pid)) {
145
+ try { execFileSync("taskkill", ["/pid", pid, "/f", "/t"], { windowsHide: true, stdio: "pipe" }); } catch {}
146
+ }
147
+ }
148
+ } catch {
149
+ // wmic may not be available on all Windows versions
150
+ }
151
+
139
152
  this.startDaemonTask();
140
153
  }
141
154
 
@@ -151,10 +151,17 @@ const activeFollowups = new Map<string, ChildProcess>();
151
151
  export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
152
152
  function flattenTask(task: ParsedTask) {
153
153
  const taskDir = getTaskDir(config.projectRoot, task.frontmatter.id);
154
+ const status = readTaskStatus(taskDir);
155
+ const pending = getPending(task.frontmatter.id);
154
156
  return {
155
157
  ...task.frontmatter,
156
158
  body: task.body,
157
- status: readTaskStatus(taskDir),
159
+ status: status ? {
160
+ ...status,
161
+ ...(pending?.type === "confirmation" ? { pending_confirmation: true } : {}),
162
+ ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
163
+ ...(pending?.type === "input" ? { pending_input: pending.params } : {}),
164
+ } : undefined,
158
165
  };
159
166
  }
160
167
 
@@ -262,6 +262,8 @@ export async function startHttpTransport(
262
262
  const taskDir = getTaskDir(config.projectRoot, taskId);
263
263
  const task = parseTaskFile(taskDir);
264
264
 
265
+ const pendingPromise = registerPending(taskId, "input", descriptions);
266
+
265
267
  await publishEvent(taskId, {
266
268
  event_type: "input-request",
267
269
  host_id: config.hostId,
@@ -269,7 +271,7 @@ export async function startHttpTransport(
269
271
  name: task.frontmatter.name,
270
272
  });
271
273
 
272
- const response = await registerPending(taskId, "input", descriptions);
274
+ const response = await pendingPromise;
273
275
 
274
276
  if (response.length === 1 && response[0] === "aborted") {
275
277
  await publishEvent(taskId, { event_type: "input-resolved", host_id: config.hostId, status: "aborted" });
@@ -300,12 +302,14 @@ export async function startHttpTransport(
300
302
  const { taskId } = JSON.parse(body) as { taskId: string };
301
303
  if (!taskId) { sendJson(res, 400, { error: "taskId is required" }); return; }
302
304
 
305
+ const pendingPromise = registerPending(taskId, "confirmation");
306
+
303
307
  await publishEvent(taskId, {
304
308
  event_type: "confirm-request",
305
309
  host_id: config.hostId,
306
310
  });
307
311
 
308
- const response = await registerPending(taskId, "confirmation");
312
+ const response = await pendingPromise;
309
313
  const confirmed = response[0] === "confirmed";
310
314
 
311
315
  await publishEvent(taskId, {
@@ -335,6 +339,8 @@ export async function startHttpTransport(
335
339
  return;
336
340
  }
337
341
 
342
+ const pendingPromise = registerPending(taskId, "permission", permissions);
343
+
338
344
  await publishEvent(taskId, {
339
345
  event_type: "permission-request",
340
346
  host_id: config.hostId,
@@ -342,7 +348,7 @@ export async function startHttpTransport(
342
348
  name: taskName,
343
349
  });
344
350
 
345
- const response = await registerPending(taskId, "permission", permissions);
351
+ const response = await pendingPromise;
346
352
  const status = response[0] as "granted" | "granted_all" | "aborted";
347
353
 
348
354
  await publishEvent(taskId, {