palmier 0.9.28 → 0.9.30

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.
@@ -16,7 +16,7 @@ import * as readline from "readline";
16
16
  import { execFileSync } from "child_process";
17
17
  import { spawnStreamingCommand } from "./spawn-command.js";
18
18
  import { getTaskDir, listTasks, parseTaskFile } from "./task.js";
19
- import { enqueueEvent } from "./event-queues.js";
19
+ import { dispatchTrigger } from "./trigger-dispatch.js";
20
20
  import { getPlatform } from "./platform/index.js";
21
21
  const runners = new Map();
22
22
  const stopping = new Set();
@@ -49,12 +49,8 @@ function spawnRunner(config, taskId, command) {
49
49
  rl.on("line", (line) => {
50
50
  if (!line.trim())
51
51
  return;
52
- const { shouldStart } = enqueueEvent(taskId, line);
53
- if (shouldStart) {
54
- platform.startTask(taskId).catch((err) => {
55
- console.error(`[command-runner] failed to start run for ${taskId}:`, err);
56
- });
57
- }
52
+ console.log(`[command-runner] ${taskId} stdout line (${line.length} chars): ${line.slice(0, 100)}`);
53
+ dispatchTrigger(taskId, line);
58
54
  });
59
55
  child.stderr?.on("data", (d) => process.stderr.write(d));
60
56
  const handleExit = () => {
@@ -14,7 +14,7 @@ import { CONFIG_DIR } from "../config.js";
14
14
  import { StringCodec } from "nats";
15
15
  import { addNotification } from "../notification-store.js";
16
16
  import { addSmsMessage } from "../sms-store.js";
17
- import { enqueueEvent } from "../event-queues.js";
17
+ import { dispatchTrigger } from "../trigger-dispatch.js";
18
18
  import { startEnabledCommandRunners } from "../command-runners.js";
19
19
  const POLL_INTERVAL_MS = 30_000;
20
20
  const DAEMON_PID_FILE = path.join(CONFIG_DIR, "daemon.pid");
@@ -157,12 +157,7 @@ export async function serveCommand() {
157
157
  if (!normalizedSender || !task.frontmatter.schedule_values.some((s) => normalizeSender(s) === normalizedSender))
158
158
  continue;
159
159
  }
160
- const { shouldStart } = enqueueEvent(task.frontmatter.id, payload);
161
- if (shouldStart) {
162
- platform.startTask(task.frontmatter.id).catch((err) => {
163
- console.error(`[event-trigger] Failed to start ${task.frontmatter.id}:`, err);
164
- });
165
- }
160
+ dispatchTrigger(task.frontmatter.id, payload);
166
161
  }
167
162
  }
168
163
  const notifSub = nc.subscribe(`host.${config.hostId}.device.notifications`);
@@ -4,8 +4,11 @@
4
4
  * `palmier run` process drains via /task-event/pop.
5
5
  *
6
6
  * Invariants:
7
- * - popEvent clears activeRuns atomically when the queue empties, so a
8
- * fresh startTask cannot race the tearing-down run.
7
+ * - popEvent clears activeRuns when the queue empties. This races the run's
8
+ * own teardown (the process/unit is still alive briefly after the empty
9
+ * pop), so a trigger arriving in that window can set activeRuns yet fail to
10
+ * launch a run (a oneshot `systemctl start` no-ops on an active unit). The
11
+ * dispatch watchdog in trigger-dispatch.ts reconciles that stranded state.
9
12
  * - enqueueEvent returns shouldStart=true only on the idle→active edge.
10
13
  */
11
14
  export declare function enqueueEvent(taskId: string, payload: string): {
@@ -16,5 +19,11 @@ export declare function popEvent(taskId: string): {
16
19
  } | {
17
20
  empty: true;
18
21
  };
22
+ export declare function hasPendingEvents(taskId: string): boolean;
23
+ export declare function pendingCount(taskId: string): number;
24
+ /** Drop a stranded active flag so a fresh run can be launched (watchdog only). */
25
+ export declare function resetActiveRun(taskId: string): void;
26
+ /** Re-acquire the active flag without enqueuing (watchdog relaunch only). */
27
+ export declare function markActiveRun(taskId: string): void;
19
28
  /** Remove any state for a task (called from task.delete). */
20
29
  export declare function clearTaskQueue(taskId: string): void;
@@ -4,8 +4,11 @@
4
4
  * `palmier run` process drains via /task-event/pop.
5
5
  *
6
6
  * Invariants:
7
- * - popEvent clears activeRuns atomically when the queue empties, so a
8
- * fresh startTask cannot race the tearing-down run.
7
+ * - popEvent clears activeRuns when the queue empties. This races the run's
8
+ * own teardown (the process/unit is still alive briefly after the empty
9
+ * pop), so a trigger arriving in that window can set activeRuns yet fail to
10
+ * launch a run (a oneshot `systemctl start` no-ops on an active unit). The
11
+ * dispatch watchdog in trigger-dispatch.ts reconciles that stranded state.
9
12
  * - enqueueEvent returns shouldStart=true only on the idle→active edge.
10
13
  */
11
14
  const MAX_QUEUE_SIZE = 100;
@@ -30,6 +33,21 @@ export function popEvent(taskId) {
30
33
  activeRuns.delete(taskId);
31
34
  return { empty: true };
32
35
  }
36
+ export function hasPendingEvents(taskId) {
37
+ const queue = queues.get(taskId);
38
+ return !!queue && queue.length > 0;
39
+ }
40
+ export function pendingCount(taskId) {
41
+ return queues.get(taskId)?.length ?? 0;
42
+ }
43
+ /** Drop a stranded active flag so a fresh run can be launched (watchdog only). */
44
+ export function resetActiveRun(taskId) {
45
+ activeRuns.delete(taskId);
46
+ }
47
+ /** Re-acquire the active flag without enqueuing (watchdog relaunch only). */
48
+ export function markActiveRun(taskId) {
49
+ activeRuns.add(taskId);
50
+ }
33
51
  /** Remove any state for a task (called from task.delete). */
34
52
  export function clearTaskQueue(taskId) {
35
53
  queues.delete(taskId);