macroclaw 0.19.0 → 0.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macroclaw",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cron.test.ts CHANGED
@@ -254,7 +254,7 @@ describe("CronScheduler", () => {
254
254
 
255
255
  it("does not write file when no non-recurring jobs fired", () => {
256
256
  writeScheduleConfig({
257
- jobs: [{ name: "recurring", cron: nonMatchingCron(), prompt: "nope", recurring: false }],
257
+ jobs: [{ name: "recurring", cron: nonMatchingCron(), prompt: "nope" }],
258
258
  });
259
259
 
260
260
  const onJob = makeOnJob();
@@ -318,9 +318,9 @@ describe("missed non-recurring events", () => {
318
318
  expect(onJob).not.toHaveBeenCalled();
319
319
  });
320
320
 
321
- it("does not fire non-recurring jobs older than threshold", () => {
321
+ it("fires non-recurring job missed by more than 5 minutes", () => {
322
322
  writeScheduleConfig({
323
- jobs: [{ name: "old", cron: minutesAgoCron(10), prompt: "too late", recurring: false }],
323
+ jobs: [{ name: "old", cron: minutesAgoCron(10), prompt: "still fires", recurring: false }],
324
324
  });
325
325
 
326
326
  const onJob = makeOnJob();
@@ -328,8 +328,13 @@ describe("missed non-recurring events", () => {
328
328
  cron.start();
329
329
  cron.stop();
330
330
 
331
- expect(onJob).not.toHaveBeenCalled();
331
+ expect(onJob).toHaveBeenCalledTimes(1);
332
+ const call = onJob.mock.calls[0];
333
+ expect(call[0]).toBe("old");
334
+ expect(call[1]).toContain("[missed event, should have fired");
335
+ expect(call[1]).toContain("still fires");
336
+
332
337
  const updated = readScheduleConfig();
333
- expect(updated.jobs).toHaveLength(1);
338
+ expect(updated.jobs).toHaveLength(0);
334
339
  });
335
340
  });
package/src/cron.ts CHANGED
@@ -25,7 +25,6 @@ export interface CronSchedulerConfig {
25
25
  }
26
26
 
27
27
  const TICK_INTERVAL = 10_000; // 10 seconds
28
- const MISSED_THRESHOLD = 300_000; // 5 minutes
29
28
 
30
29
  export class CronScheduler {
31
30
  #lastMinute = -1;
@@ -88,8 +87,8 @@ export class CronScheduler {
88
87
  if (job.recurring === false) {
89
88
  firedNonRecurring.push(i);
90
89
  }
91
- } else if (job.recurring === false && diff < MISSED_THRESHOLD) {
92
- // Non-recurring job missed (service was down) — fire with missed prefix
90
+ } else if (job.recurring === false && diff >= 60_000) {
91
+ // Non-recurring job in the past — fire regardless of how late
93
92
  const missedMinutes = Math.round(diff / 60_000);
94
93
  const firedAt = prev.toISOString();
95
94
  const missedPrompt = `[missed event, should have fired ${missedMinutes} min ago at ${firedAt}] ${job.prompt}`;
@@ -1,10 +1,6 @@
1
1
  ---
2
2
  name: schedule
3
- description: >
4
- Schedule events, reminders, and recurring tasks. Use when the user wants to:
5
- set a reminder, schedule something for later, create a recurring task,
6
- set up a periodic check, automate a prompt on a schedule, or plan a
7
- one-time or repeating event at a specific time.
3
+ description: "Schedule events, reminders, and recurring tasks. Use when the user wants to: set a reminder, schedule something for later, create a recurring task, set up a periodic check, automate a prompt on a schedule, or plan a one-time or repeating event at a specific time."
8
4
  ---
9
5
 
10
6
  Schedule a new event by adding it to `data/schedule.json`.
@@ -20,12 +16,13 @@ Schedule a new event by adding it to `data/schedule.json`.
20
16
 
21
17
  ## How to schedule
22
18
 
23
- 1. Read `data/schedule.json` (create with `{"jobs": []}` if missing)
24
- 2. Convert the user's request to a cron expression (see reference below)
25
- 3. **Be proactive about timing**: if the user says "next week" or "tomorrow" without a specific time, pick the best time based on what you know (their routine, calendar, context)
26
- 4. Append the new job to the `jobs` array
27
- 5. Write the updated file
28
- 6. Confirm: what was scheduled, when it will fire, and offer to adjust
19
+ 1. Run `date` to get the current date and time
20
+ 2. Read `data/schedule.json` (create with `{"jobs": []}` if missing)
21
+ 3. Convert the user's request to a cron expression (see reference below)
22
+ 4. **Be proactive about timing**: if the user says "next week" or "tomorrow" without a specific time, pick the best time based on what you know (their routine, calendar, context)
23
+ 5. Append the new job to the `jobs` array
24
+ 6. Write the updated file
25
+ 7. Confirm: what was scheduled, when it will fire, and offer to adjust
29
26
 
30
27
  ## Natural language → cron
31
28
 
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: self-update
3
+ description: "Update macroclaw to the latest version. Use when the user asks to update, upgrade, self-update, or update yourself. Handles the service restart safely by using a detached process and scheduled follow-up."
4
+ ---
5
+
6
+ Update macroclaw to the latest version.
7
+
8
+ ## Steps
9
+
10
+ 1. **Check service status**: Run `macroclaw service status` and verify it shows `Installed: yes` and `Running: yes`. If not, tell the user the service isn't running and stop.
11
+
12
+ 2. **Generate LOG_FILE_PATH**: Run `echo "/tmp/macroclaw-update-$(date -u +%Y-%m-%dT%H-%M-%SZ).log"` and use the output as `<LOG_FILE_PATH>` in the following steps.
13
+
14
+ 3. **Schedule follow-up**: Using the `schedule` skill, create a one-shot event 1 minute from now:
15
+ - Name: `self-update-check`
16
+ - Prompt: `Check macroclaw update result. Read <LOG_FILE_PATH> — if it contains "Updated macroclaw", report the version change. If it contains "already up to date", say so. If the file doesn't exist or is empty, the update may still be in progress — schedule another check in 1 minute.`
17
+ - Model: `haiku`
18
+ - Recurring: `false`
19
+
20
+ 4. **Run the update**: Execute the update script bundled with this skill:
21
+ ```
22
+ bash ${CLAUDE_SKILL_DIR}/scripts/update.sh <LOG_FILE_PATH>
23
+ ```
24
+
25
+ ## Important
26
+
27
+ - The `macroclaw service update` command stops the service, installs the latest version, and starts it again. Stopping the service kills all processes in the cgroup — including this Claude Code session.
28
+ - Do NOT use a background agent — it gets killed along with the main process.
29
+ - Always run step 4 LAST — everything after it may not execute.
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [[ $# -ne 1 ]]; then
5
+ echo "Usage: $0 <log-file>" >&2
6
+ exit 1
7
+ fi
8
+
9
+ LOG_FILE="$1"
10
+
11
+ case "$(uname -s)" in
12
+ Linux)
13
+ sudo systemd-run \
14
+ --unit="macroclaw-update-$(date -u +%Y%m%dT%H%M%SZ)" \
15
+ --collect \
16
+ --no-block \
17
+ --property="User=$(id -un)" \
18
+ --property="Group=$(id -gn)" \
19
+ --setenv="HOME=$HOME" \
20
+ --setenv="PATH=$PATH" \
21
+ /bin/bash -lc "exec macroclaw service update > \"$LOG_FILE\" 2>&1"
22
+
23
+ # --collect: automatically remove the transient unit after it finishes.
24
+ # --no-block: return immediately instead of waiting for the started job.
25
+ ;;
26
+ Darwin)
27
+ nohup setsid /bin/bash -lc "exec macroclaw service update > \"$LOG_FILE\" 2>&1" >/dev/null 2>&1 &
28
+
29
+ # Best-effort only:
30
+ # macOS launchd has no simple systemd-run equivalent for transient jobs.
31
+ # nohup + setsid detaches the updater from the current process/session so it
32
+ # has a better chance of surviving when the main launchd service stops.
33
+ # A separate launchd job would still be the more robust long-term solution.
34
+ ;;
35
+ *)
36
+ echo "Unsupported platform: $(uname -s)" >&2
37
+ exit 1
38
+ ;;
39
+ esac