alvin-bot 5.1.5 → 5.1.6
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/CHANGELOG.md +23 -0
- package/dist/services/watchdog-brake.js +39 -0
- package/dist/services/watchdog.js +2 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [5.1.6] — 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Planned restarts really stop counting as crashes now
|
|
8
|
+
|
|
9
|
+
v5.1.5 added a flag that marks self-updates and `/update` / `/restart`
|
|
10
|
+
as intentional so they don't inflate the crash counter. Half of it
|
|
11
|
+
didn't actually work: the code that reads the saved state back on the
|
|
12
|
+
next boot rebuilt it field by field and silently dropped that very
|
|
13
|
+
flag, so the crash detector never saw it and planned restarts were
|
|
14
|
+
still scored as crashes. (The other half of v5.1.5 — not counting
|
|
15
|
+
benign log lines as errors — was unaffected and has been working.)
|
|
16
|
+
|
|
17
|
+
This release makes the read path preserve the flag, so a planned
|
|
18
|
+
restart is now genuinely treated as a clean exit. The state-parsing
|
|
19
|
+
logic was pulled into a tested pure function so the read-back round
|
|
20
|
+
trip can't silently regress like this again.
|
|
21
|
+
|
|
22
|
+
### What this means for you
|
|
23
|
+
|
|
24
|
+
If you updated to 5.1.5 and still saw the crash count tick up by one
|
|
25
|
+
each time the bot updated itself, that stops now. The error-trend
|
|
26
|
+
half of the 5.1.5 fix already worked; this completes the crash half.
|
|
27
|
+
|
|
5
28
|
## [5.1.5] — 2026-05-15
|
|
6
29
|
|
|
7
30
|
### Health monitor no longer cries wolf about its own log lines
|
|
@@ -27,6 +27,45 @@ export const DEFAULTS = {
|
|
|
27
27
|
* crashes with ≥5 min gaps sailed right past the brake. 1 h is safer. */
|
|
28
28
|
RESET_AFTER_MS: 60 * 60_000,
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Validate + normalize a parsed beacon JSON into a BeaconData (or null
|
|
32
|
+
* if the core fields are missing/wrong-typed). Pure so the read-path
|
|
33
|
+
* field mapping is unit-testable — extracted after v5.1.5 shipped a
|
|
34
|
+
* broken expectedRestart: the old readBeacon() rebuilt the object
|
|
35
|
+
* field-by-field and silently dropped expectedRestart, so the flag
|
|
36
|
+
* never reached decideBrakeAction and intentional restarts were still
|
|
37
|
+
* counted as crashes. Whatever round-trips here is what the brake sees.
|
|
38
|
+
*/
|
|
39
|
+
export function normalizeBeacon(parsed) {
|
|
40
|
+
if (!parsed)
|
|
41
|
+
return null;
|
|
42
|
+
if (typeof parsed.lastBeat === "number" &&
|
|
43
|
+
typeof parsed.pid === "number" &&
|
|
44
|
+
typeof parsed.bootTime === "number" &&
|
|
45
|
+
typeof parsed.crashCount === "number" &&
|
|
46
|
+
typeof parsed.crashWindowStart === "number" &&
|
|
47
|
+
typeof parsed.version === "string") {
|
|
48
|
+
return {
|
|
49
|
+
lastBeat: parsed.lastBeat,
|
|
50
|
+
pid: parsed.pid,
|
|
51
|
+
bootTime: parsed.bootTime,
|
|
52
|
+
crashCount: parsed.crashCount,
|
|
53
|
+
crashWindowStart: parsed.crashWindowStart,
|
|
54
|
+
version: parsed.version,
|
|
55
|
+
// Older beacons don't have daily-counter fields — default them to
|
|
56
|
+
// 0/now so the brake logic treats this run as the start of the
|
|
57
|
+
// first daily window.
|
|
58
|
+
dailyCrashCount: typeof parsed.dailyCrashCount === "number" ? parsed.dailyCrashCount : 0,
|
|
59
|
+
dailyCrashWindowStart: typeof parsed.dailyCrashWindowStart === "number"
|
|
60
|
+
? parsed.dailyCrashWindowStart
|
|
61
|
+
: Date.now(),
|
|
62
|
+
// The whole point of the v5.1.6 fix: propagate expectedRestart so
|
|
63
|
+
// a planned restart is not scored as a crash on the next boot.
|
|
64
|
+
expectedRestart: parsed.expectedRestart === true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
30
69
|
/**
|
|
31
70
|
* Given the previous beacon (or null on first boot) and the current time,
|
|
32
71
|
* decide whether the bot should proceed with boot or engage the crash-loop
|
|
@@ -29,7 +29,7 @@ import { execSync } from "child_process";
|
|
|
29
29
|
import { BOT_VERSION } from "../version.js";
|
|
30
30
|
import { emitCritical } from "./critical-notify.js";
|
|
31
31
|
import { writeDiagnosticBundle } from "./auto-diagnostic.js";
|
|
32
|
-
import { decideBrakeAction, shouldResetCrashCounter, DEFAULTS, } from "./watchdog-brake.js";
|
|
32
|
+
import { decideBrakeAction, shouldResetCrashCounter, normalizeBeacon, DEFAULTS, } from "./watchdog-brake.js";
|
|
33
33
|
const DATA_DIR = process.env.ALVIN_DATA_DIR || resolve(os.homedir(), ".alvin-bot");
|
|
34
34
|
const STATE_DIR = resolve(DATA_DIR, "state");
|
|
35
35
|
const BEACON_FILE = resolve(STATE_DIR, "watchdog.json");
|
|
@@ -50,30 +50,7 @@ function ensureStateDir() {
|
|
|
50
50
|
function readBeacon() {
|
|
51
51
|
try {
|
|
52
52
|
const raw = fs.readFileSync(BEACON_FILE, "utf-8");
|
|
53
|
-
|
|
54
|
-
if (typeof parsed.lastBeat === "number" &&
|
|
55
|
-
typeof parsed.pid === "number" &&
|
|
56
|
-
typeof parsed.bootTime === "number" &&
|
|
57
|
-
typeof parsed.crashCount === "number" &&
|
|
58
|
-
typeof parsed.crashWindowStart === "number" &&
|
|
59
|
-
typeof parsed.version === "string") {
|
|
60
|
-
// Older beacons don't have daily-counter fields — default them to
|
|
61
|
-
// 0/now so the brake logic treats this run as the start of the
|
|
62
|
-
// first daily window.
|
|
63
|
-
return {
|
|
64
|
-
lastBeat: parsed.lastBeat,
|
|
65
|
-
pid: parsed.pid,
|
|
66
|
-
bootTime: parsed.bootTime,
|
|
67
|
-
crashCount: parsed.crashCount,
|
|
68
|
-
crashWindowStart: parsed.crashWindowStart,
|
|
69
|
-
version: parsed.version,
|
|
70
|
-
dailyCrashCount: typeof parsed.dailyCrashCount === "number" ? parsed.dailyCrashCount : 0,
|
|
71
|
-
dailyCrashWindowStart: typeof parsed.dailyCrashWindowStart === "number"
|
|
72
|
-
? parsed.dailyCrashWindowStart
|
|
73
|
-
: Date.now(),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
53
|
+
return normalizeBeacon(JSON.parse(raw));
|
|
77
54
|
}
|
|
78
55
|
catch {
|
|
79
56
|
return null;
|