sensorium-mcp 3.1.0-refactor.10 → 3.1.0-refactor.12
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/dist/config/settings-store.d.ts +41 -2
- package/dist/config/settings-store.d.ts.map +1 -1
- package/dist/config/settings-store.js +149 -11
- package/dist/config/settings-store.js.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/services/flight-recorder.service.d.ts +37 -0
- package/dist/services/flight-recorder.service.d.ts.map +1 -0
- package/dist/services/flight-recorder.service.js +172 -0
- package/dist/services/flight-recorder.service.js.map +1 -0
- package/package.json +1 -1
|
@@ -2,9 +2,48 @@
|
|
|
2
2
|
* Settings persistence core — atomic read/modify/write of settings.json
|
|
3
3
|
* under the user's home directory. All config domain modules go through
|
|
4
4
|
* readSettings/updateSettings; nothing else touches the file directly.
|
|
5
|
+
*
|
|
6
|
+
* Fail-closed policy (D5 in the 2026-07-26 process-lifecycle plan): a corrupt
|
|
7
|
+
* settings.json must never be silently replaced by `{}` — a single bad read
|
|
8
|
+
* followed by any write used to wipe the whole config. Instead the corrupt
|
|
9
|
+
* file is quarantined, and reads fall back to settings.json.bak, then to the
|
|
10
|
+
* in-memory last-known-good copy, and only throw when no trusted source
|
|
11
|
+
* exists at all.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Read and parse the settings file.
|
|
15
|
+
*
|
|
16
|
+
* - ENOENT → `{}` (legitimate first run).
|
|
17
|
+
* - Any other failure → quarantine the corrupt file, then fall back to
|
|
18
|
+
* settings.json.bak (restoring it as the live file), then to the in-memory
|
|
19
|
+
* last-known-good copy (also restored as the live file, so the ENOENT
|
|
20
|
+
* fast-path never fabricates {} on the read after a quarantine). If no
|
|
21
|
+
* trusted source exists, THROW — callers must
|
|
22
|
+
* not proceed on a fabricated empty config, because a later write would
|
|
23
|
+
* persist the wipe.
|
|
5
24
|
*/
|
|
6
|
-
/** Read and parse the settings file, returning an empty object on any failure. */
|
|
7
25
|
export declare function readSettings(): Record<string, unknown>;
|
|
8
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Non-destructive boot-time config health check. Returns a description of a
|
|
28
|
+
* fatal problem, or null when boot may proceed. Unlike readSettings() it never
|
|
29
|
+
* quarantines or writes anything, so a supervisor restart loop keeps hitting
|
|
30
|
+
* the same state (and the same loud message) instead of silently degrading to
|
|
31
|
+
* an ENOENT→{} read on the second boot.
|
|
32
|
+
*/
|
|
33
|
+
export declare function checkSettingsAtBoot(): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Boot guard against the corrupt-config crash-loop. readSettings() rightly
|
|
36
|
+
* throws when settings.json is corrupt and no trusted fallback exists, but at
|
|
37
|
+
* boot (lastGoodSettings is still null) that surfaces as an opaque uncaught
|
|
38
|
+
* throw from whichever of its many callers runs first — the supervisor
|
|
39
|
+
* restarts, and the loop repeats with no operator signal. Decide explicitly
|
|
40
|
+
* instead: log an actionable line and exit before any config consumer loads.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertSettingsSafeAtBoot(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Read settings, apply a mutator, and atomically persist the result.
|
|
45
|
+
* Propagates readSettings failures: a mutation is never applied to (and never
|
|
46
|
+
* persists) a fabricated empty config after a failed read.
|
|
47
|
+
*/
|
|
9
48
|
export declare function updateSettings(mutator: (s: Record<string, unknown>) => void): void;
|
|
10
49
|
//# sourceMappingURL=settings-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-store.d.ts","sourceRoot":"","sources":["../../src/config/settings-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"settings-store.d.ts","sourceRoot":"","sources":["../../src/config/settings-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkFH;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8CtD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAoBnD;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI,CAIlF"}
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* Settings persistence core — atomic read/modify/write of settings.json
|
|
3
3
|
* under the user's home directory. All config domain modules go through
|
|
4
4
|
* readSettings/updateSettings; nothing else touches the file directly.
|
|
5
|
+
*
|
|
6
|
+
* Fail-closed policy (D5 in the 2026-07-26 process-lifecycle plan): a corrupt
|
|
7
|
+
* settings.json must never be silently replaced by `{}` — a single bad read
|
|
8
|
+
* followed by any write used to wipe the whole config. Instead the corrupt
|
|
9
|
+
* file is quarantined, and reads fall back to settings.json.bak, then to the
|
|
10
|
+
* in-memory last-known-good copy, and only throw when no trusted source
|
|
11
|
+
* exists at all.
|
|
5
12
|
*/
|
|
6
13
|
import { chmodSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
7
14
|
import { homedir } from "node:os";
|
|
@@ -9,6 +16,13 @@ import { join } from "node:path";
|
|
|
9
16
|
import { log } from "../lib/logger.js";
|
|
10
17
|
const SETTINGS_PATH = join(homedir(), ".remote-copilot-mcp", "settings.json");
|
|
11
18
|
const SETTINGS_TMP_PATH = SETTINGS_PATH + ".tmp";
|
|
19
|
+
const SETTINGS_BAK_PATH = SETTINGS_PATH + ".bak";
|
|
20
|
+
/**
|
|
21
|
+
* Last successfully parsed settings. Fallback of last resort when both
|
|
22
|
+
* settings.json and its backup are unreadable; never written to disk except
|
|
23
|
+
* as the base of an explicit updateSettings mutation.
|
|
24
|
+
*/
|
|
25
|
+
let lastGoodSettings = null;
|
|
12
26
|
/**
|
|
13
27
|
* Rename with a short bounded retry. On Windows, antivirus/indexer services
|
|
14
28
|
* transiently lock the destination, making renameSync throw EPERM/EACCES even
|
|
@@ -32,31 +46,155 @@ function renameSyncRetry(from, to) {
|
|
|
32
46
|
}
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
|
-
|
|
36
|
-
function atomicWriteSettings(settings) {
|
|
37
|
-
writeFileSync(SETTINGS_TMP_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
38
|
-
renameSyncRetry(SETTINGS_TMP_PATH, SETTINGS_PATH);
|
|
49
|
+
function restrictToOwner(path) {
|
|
39
50
|
// settings.json holds MCP auth headers/tokens — restrict to owner (no-op on
|
|
40
51
|
// Windows NTFS, effective on POSIX where the server may also run).
|
|
41
52
|
try {
|
|
42
|
-
chmodSync(
|
|
53
|
+
chmodSync(path, 0o600);
|
|
43
54
|
}
|
|
44
55
|
catch {
|
|
45
56
|
/* best-effort */
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
|
-
/**
|
|
59
|
+
/** Atomically persist settings: write to .tmp then rename over the original. */
|
|
60
|
+
function atomicWriteSettings(settings) {
|
|
61
|
+
const json = JSON.stringify(settings, null, 2);
|
|
62
|
+
writeFileSync(SETTINGS_TMP_PATH, json, "utf-8");
|
|
63
|
+
renameSyncRetry(SETTINGS_TMP_PATH, SETTINGS_PATH);
|
|
64
|
+
restrictToOwner(SETTINGS_PATH);
|
|
65
|
+
lastGoodSettings = structuredClone(settings);
|
|
66
|
+
// Backup written only after a successful primary write, so it always holds
|
|
67
|
+
// a complete, parseable snapshot (at worst one version behind). Written via
|
|
68
|
+
// the same tmp+rename as the primary — a torn .bak would defeat its whole
|
|
69
|
+
// purpose as the recovery source for a torn settings.json.
|
|
70
|
+
try {
|
|
71
|
+
const bakTmp = SETTINGS_BAK_PATH + ".tmp";
|
|
72
|
+
writeFileSync(bakTmp, json, "utf-8");
|
|
73
|
+
renameSyncRetry(bakTmp, SETTINGS_BAK_PATH);
|
|
74
|
+
restrictToOwner(SETTINGS_BAK_PATH);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
log.warn(`[config] Failed to write settings backup: ${err}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Move the unreadable settings file aside so it is preserved for forensics. */
|
|
81
|
+
function quarantineCorruptSettings() {
|
|
82
|
+
const quarantinePath = `${SETTINGS_PATH}.corrupt-${Date.now()}`;
|
|
83
|
+
try {
|
|
84
|
+
renameSyncRetry(SETTINGS_PATH, quarantinePath);
|
|
85
|
+
return quarantinePath;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
log.warn(`[config] Failed to quarantine corrupt settings: ${err}`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Read and parse the settings file.
|
|
94
|
+
*
|
|
95
|
+
* - ENOENT → `{}` (legitimate first run).
|
|
96
|
+
* - Any other failure → quarantine the corrupt file, then fall back to
|
|
97
|
+
* settings.json.bak (restoring it as the live file), then to the in-memory
|
|
98
|
+
* last-known-good copy (also restored as the live file, so the ENOENT
|
|
99
|
+
* fast-path never fabricates {} on the read after a quarantine). If no
|
|
100
|
+
* trusted source exists, THROW — callers must
|
|
101
|
+
* not proceed on a fabricated empty config, because a later write would
|
|
102
|
+
* persist the wipe.
|
|
103
|
+
*/
|
|
49
104
|
export function readSettings() {
|
|
50
105
|
try {
|
|
51
|
-
|
|
106
|
+
const parsed = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
107
|
+
lastGoodSettings = structuredClone(parsed);
|
|
108
|
+
return parsed;
|
|
52
109
|
}
|
|
53
110
|
catch (err) {
|
|
54
|
-
if (err?.code
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
if (err?.code === "ENOENT")
|
|
112
|
+
return {};
|
|
113
|
+
log.error(`[config] settings.json is unreadable (NOT substituting empty config): ${err}`);
|
|
114
|
+
const quarantined = quarantineCorruptSettings();
|
|
115
|
+
if (quarantined)
|
|
116
|
+
log.error(`[config] Corrupt settings quarantined at ${quarantined}`);
|
|
117
|
+
try {
|
|
118
|
+
const backup = JSON.parse(readFileSync(SETTINGS_BAK_PATH, "utf-8"));
|
|
119
|
+
atomicWriteSettings(backup);
|
|
120
|
+
log.error(`[config] Restored settings.json from backup ${SETTINGS_BAK_PATH}`);
|
|
121
|
+
return backup;
|
|
122
|
+
}
|
|
123
|
+
catch (bakErr) {
|
|
124
|
+
if (bakErr?.code !== "ENOENT")
|
|
125
|
+
log.error(`[config] Settings backup is also unreadable: ${bakErr}`);
|
|
126
|
+
}
|
|
127
|
+
if (lastGoodSettings !== null) {
|
|
128
|
+
log.error(`[config] Serving in-memory last-known-good settings (disk copies unreadable)`);
|
|
129
|
+
const restored = structuredClone(lastGoodSettings);
|
|
130
|
+
// Restore the live file too, mirroring the .bak branch. The corrupt file
|
|
131
|
+
// was just quarantined away, so without this the NEXT read hits the
|
|
132
|
+
// ENOENT fast-path, returns {}, and a subsequent updateSettings persists
|
|
133
|
+
// the wipe — the invariant is: after any non-throwing readSettings(),
|
|
134
|
+
// settings.json exists on disk and equals what was returned.
|
|
135
|
+
try {
|
|
136
|
+
atomicWriteSettings(restored);
|
|
137
|
+
}
|
|
138
|
+
catch (writeErr) {
|
|
139
|
+
log.error(`[config] Failed to restore settings.json from last-known-good: ${writeErr}`);
|
|
140
|
+
}
|
|
141
|
+
return restored;
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`settings.json is corrupt and no backup or last-known-good copy exists; ` +
|
|
144
|
+
`refusing to substitute an empty config (corrupt file ${quarantined ?? "left in place"})`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Non-destructive boot-time config health check. Returns a description of a
|
|
149
|
+
* fatal problem, or null when boot may proceed. Unlike readSettings() it never
|
|
150
|
+
* quarantines or writes anything, so a supervisor restart loop keeps hitting
|
|
151
|
+
* the same state (and the same loud message) instead of silently degrading to
|
|
152
|
+
* an ENOENT→{} read on the second boot.
|
|
153
|
+
*/
|
|
154
|
+
export function checkSettingsAtBoot() {
|
|
155
|
+
let primaryErr;
|
|
156
|
+
try {
|
|
157
|
+
JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
158
|
+
return null; // parseable — normal boot
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (err?.code === "ENOENT")
|
|
162
|
+
return null; // genuine first run
|
|
163
|
+
primaryErr = err;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
JSON.parse(readFileSync(SETTINGS_BAK_PATH, "utf-8"));
|
|
167
|
+
return null; // corrupt primary but trusted backup — readSettings() will restore it
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
/* backup also missing/unreadable */
|
|
57
171
|
}
|
|
172
|
+
return (`settings.json is corrupt and no trusted backup exists — refusing to fabricate ` +
|
|
173
|
+
`an empty config. Fix or delete ${SETTINGS_PATH} (and ${SETTINGS_BAK_PATH}) before ` +
|
|
174
|
+
`restarting; the corrupt file has been left in place untouched. Parse error: ${primaryErr}`);
|
|
58
175
|
}
|
|
59
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* Boot guard against the corrupt-config crash-loop. readSettings() rightly
|
|
178
|
+
* throws when settings.json is corrupt and no trusted fallback exists, but at
|
|
179
|
+
* boot (lastGoodSettings is still null) that surfaces as an opaque uncaught
|
|
180
|
+
* throw from whichever of its many callers runs first — the supervisor
|
|
181
|
+
* restarts, and the loop repeats with no operator signal. Decide explicitly
|
|
182
|
+
* instead: log an actionable line and exit before any config consumer loads.
|
|
183
|
+
*/
|
|
184
|
+
export function assertSettingsSafeAtBoot() {
|
|
185
|
+
const problem = checkSettingsAtBoot();
|
|
186
|
+
if (problem === null)
|
|
187
|
+
return;
|
|
188
|
+
const msg = `[config] FATAL AT BOOT: ${problem}`;
|
|
189
|
+
log.error(msg);
|
|
190
|
+
console.error(msg);
|
|
191
|
+
process.exit(78); // EX_CONFIG (sysexits): distinguishable from a generic crash
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Read settings, apply a mutator, and atomically persist the result.
|
|
195
|
+
* Propagates readSettings failures: a mutation is never applied to (and never
|
|
196
|
+
* persists) a fabricated empty config after a failed read.
|
|
197
|
+
*/
|
|
60
198
|
export function updateSettings(mutator) {
|
|
61
199
|
const settings = readSettings();
|
|
62
200
|
mutator(settings);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-store.js","sourceRoot":"","sources":["../../src/config/settings-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"settings-store.js","sourceRoot":"","sources":["../../src/config/settings-store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,13 @@ if (process.argv.includes("--supervisor")) {
|
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
37
|
// Normal server startup ─────────────────────────────────────────────────────
|
|
38
|
+
// Corrupt-config boot guard: if settings.json is unreadable and no trusted
|
|
39
|
+
// backup exists, readSettings() (rightly) throws — but at boot, before any
|
|
40
|
+
// last-known-good copy exists, that becomes an opaque uncaught-exception
|
|
41
|
+
// crash-loop under the supervisor. Decide explicitly and loudly instead,
|
|
42
|
+
// before any config consumer loads.
|
|
43
|
+
const { assertSettingsSafeAtBoot } = await import("./config/settings-store.js");
|
|
44
|
+
assertSettingsSafeAtBoot();
|
|
38
45
|
const { config } = await import("./config/index.js");
|
|
39
46
|
const { startDispatcher, setBrokerSentMessageRepository } = await import("./services/dispatcher/index.js");
|
|
40
47
|
const { initMemoryDb } = await import("./data/memory/index.js");
|
|
@@ -54,6 +61,12 @@ else {
|
|
|
54
61
|
const { BackgroundJobRunner } = await import("./services/background-runner.js");
|
|
55
62
|
const { ThreadLifecycleService } = await import("./services/thread-lifecycle.service.js");
|
|
56
63
|
const { startSelfUpdatePoller } = await import("./services/self-update.service.js");
|
|
64
|
+
const { startFlightRecorder } = await import("./services/flight-recorder.service.js");
|
|
65
|
+
// Flight-recorder heartbeat (plan §4.4): a worker thread writes
|
|
66
|
+
// ~/.remote-copilot-mcp/server-heartbeat.json every 5s with the measured
|
|
67
|
+
// event-loop lag, so the supervisor can distinguish a stalled-but-alive
|
|
68
|
+
// server from a dead one. Started first — it must observe even a boot hang.
|
|
69
|
+
startFlightRecorder();
|
|
57
70
|
// ---------------------------------------------------------------------------
|
|
58
71
|
// Process lifecycle instrumentation
|
|
59
72
|
// ---------------------------------------------------------------------------
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flight-recorder heartbeat — the "progress" liveness signal from the
|
|
3
|
+
* 2026-07-26 process-lifecycle plan (§4.4).
|
|
4
|
+
*
|
|
5
|
+
* A dedicated worker thread (immune to main-loop stalls) writes a small
|
|
6
|
+
* heartbeat file every ~5s containing `{ts, eventLoopLagMs}`. The main
|
|
7
|
+
* thread pings the worker every second with its own measured timer drift;
|
|
8
|
+
* the worker combines that with how overdue the last ping is (its own
|
|
9
|
+
* monotonic clock). The result: when the main event loop freezes, the file
|
|
10
|
+
* KEEPS updating with a rising lag number — so the supervisor can tell
|
|
11
|
+
* "alive but stalled" from "dead", and the lag at kill time is on record.
|
|
12
|
+
*
|
|
13
|
+
* Clocks are never compared across threads: the main thread measures its
|
|
14
|
+
* lag with its own performance.now() delta, the worker measures beat
|
|
15
|
+
* staleness with its own. Both are monotonic within their thread.
|
|
16
|
+
*/
|
|
17
|
+
/** Where the supervisor looks for the heartbeat (keep in sync with SupervisorWorker.cs). */
|
|
18
|
+
export declare const HEARTBEAT_FILE_PATH: string;
|
|
19
|
+
export interface FlightRecorderOptions {
|
|
20
|
+
filePath?: string;
|
|
21
|
+
/** How often the main thread pings the worker (and measures its own lag). */
|
|
22
|
+
beatIntervalMs?: number;
|
|
23
|
+
/** How often the worker writes the heartbeat file. */
|
|
24
|
+
writeIntervalMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface FlightRecorderHandle {
|
|
27
|
+
stop(): Promise<void>;
|
|
28
|
+
/** Test hook: crash the current worker thread to exercise the respawn path. */
|
|
29
|
+
crashWorkerForTest(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Start the flight recorder. Never throws — a broken recorder must not take
|
|
33
|
+
* down the server it instruments. Both the worker and the main-thread timer
|
|
34
|
+
* are unref'd, so they never keep the process alive.
|
|
35
|
+
*/
|
|
36
|
+
export declare function startFlightRecorder(options?: FlightRecorderOptions): FlightRecorderHandle;
|
|
37
|
+
//# sourceMappingURL=flight-recorder.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flight-recorder.service.d.ts","sourceRoot":"","sources":["../../src/services/flight-recorder.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,QAAkE,CAAC;AAKnG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,+EAA+E;IAC/E,kBAAkB,IAAI,IAAI,CAAC;CAC5B;AAuDD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,CA8F7F"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flight-recorder heartbeat — the "progress" liveness signal from the
|
|
3
|
+
* 2026-07-26 process-lifecycle plan (§4.4).
|
|
4
|
+
*
|
|
5
|
+
* A dedicated worker thread (immune to main-loop stalls) writes a small
|
|
6
|
+
* heartbeat file every ~5s containing `{ts, eventLoopLagMs}`. The main
|
|
7
|
+
* thread pings the worker every second with its own measured timer drift;
|
|
8
|
+
* the worker combines that with how overdue the last ping is (its own
|
|
9
|
+
* monotonic clock). The result: when the main event loop freezes, the file
|
|
10
|
+
* KEEPS updating with a rising lag number — so the supervisor can tell
|
|
11
|
+
* "alive but stalled" from "dead", and the lag at kill time is on record.
|
|
12
|
+
*
|
|
13
|
+
* Clocks are never compared across threads: the main thread measures its
|
|
14
|
+
* lag with its own performance.now() delta, the worker measures beat
|
|
15
|
+
* staleness with its own. Both are monotonic within their thread.
|
|
16
|
+
*/
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { Worker } from "node:worker_threads";
|
|
20
|
+
import { log } from "../lib/logger.js";
|
|
21
|
+
/** Where the supervisor looks for the heartbeat (keep in sync with SupervisorWorker.cs). */
|
|
22
|
+
export const HEARTBEAT_FILE_PATH = join(homedir(), ".remote-copilot-mcp", "server-heartbeat.json");
|
|
23
|
+
const DEFAULT_BEAT_INTERVAL_MS = 1_000;
|
|
24
|
+
const DEFAULT_WRITE_INTERVAL_MS = 5_000;
|
|
25
|
+
/**
|
|
26
|
+
* How many times a dead heartbeat worker is respawned before giving up. The
|
|
27
|
+
* heartbeat is the supervisor's stalled-vs-dead evidence — losing it silently
|
|
28
|
+
* would put every future probe failure back on the kill-blind hung path.
|
|
29
|
+
*/
|
|
30
|
+
const MAX_WORKER_RESPAWNS = 3;
|
|
31
|
+
// Worker body as a CommonJS eval string: avoids a separate compiled worker
|
|
32
|
+
// file, so it works identically under tsx (dev) and dist (production).
|
|
33
|
+
const WORKER_SOURCE = `
|
|
34
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
35
|
+
const fs = require("node:fs");
|
|
36
|
+
const { filePath, beatIntervalMs, writeIntervalMs } = workerData;
|
|
37
|
+
|
|
38
|
+
let lastBeatAt = performance.now();
|
|
39
|
+
let lastMainLagMs = 0;
|
|
40
|
+
|
|
41
|
+
parentPort.on("message", (m) => {
|
|
42
|
+
// Test hook: lets the respawn path be exercised through a real worker crash.
|
|
43
|
+
if (m.crashForTest) throw new Error("flight-recorder test crash");
|
|
44
|
+
lastBeatAt = performance.now();
|
|
45
|
+
lastMainLagMs = m.lagMs;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
setInterval(() => {
|
|
49
|
+
// How overdue is the main thread's ping, by the worker's own clock?
|
|
50
|
+
const msSinceLastBeat = performance.now() - lastBeatAt;
|
|
51
|
+
const overdueMs = Math.max(0, msSinceLastBeat - beatIntervalMs);
|
|
52
|
+
const eventLoopLagMs = Math.round(Math.max(lastMainLagMs, overdueMs));
|
|
53
|
+
const payload = JSON.stringify({
|
|
54
|
+
ts: Date.now(),
|
|
55
|
+
pid: process.pid,
|
|
56
|
+
eventLoopLagMs,
|
|
57
|
+
mainThreadLagMs: Math.round(lastMainLagMs),
|
|
58
|
+
msSinceLastBeat: Math.round(msSinceLastBeat),
|
|
59
|
+
});
|
|
60
|
+
// Prefer atomic tmp+rename; fall back to a direct write if the destination
|
|
61
|
+
// is transiently locked (Windows AV/indexer). Readers tolerate a rare torn
|
|
62
|
+
// read by treating unparseable content as "no heartbeat".
|
|
63
|
+
const tmp = filePath + ".tmp";
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(tmp, payload);
|
|
66
|
+
fs.renameSync(tmp, filePath);
|
|
67
|
+
} catch {
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(filePath, payload);
|
|
70
|
+
} catch {
|
|
71
|
+
/* heartbeat is best-effort; never throw from the recorder */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}, writeIntervalMs);
|
|
75
|
+
`;
|
|
76
|
+
/**
|
|
77
|
+
* Start the flight recorder. Never throws — a broken recorder must not take
|
|
78
|
+
* down the server it instruments. Both the worker and the main-thread timer
|
|
79
|
+
* are unref'd, so they never keep the process alive.
|
|
80
|
+
*/
|
|
81
|
+
export function startFlightRecorder(options = {}) {
|
|
82
|
+
const filePath = options.filePath ?? HEARTBEAT_FILE_PATH;
|
|
83
|
+
const beatIntervalMs = options.beatIntervalMs ?? DEFAULT_BEAT_INTERVAL_MS;
|
|
84
|
+
const writeIntervalMs = options.writeIntervalMs ?? DEFAULT_WRITE_INTERVAL_MS;
|
|
85
|
+
let worker = null;
|
|
86
|
+
let beatTimer = null;
|
|
87
|
+
let stopped = false;
|
|
88
|
+
let respawns = 0;
|
|
89
|
+
function stopBeat() {
|
|
90
|
+
if (beatTimer)
|
|
91
|
+
clearInterval(beatTimer);
|
|
92
|
+
beatTimer = null;
|
|
93
|
+
}
|
|
94
|
+
function startBeat(w) {
|
|
95
|
+
let lastFire = performance.now();
|
|
96
|
+
beatTimer = setInterval(() => {
|
|
97
|
+
const now = performance.now();
|
|
98
|
+
// Timer drift = how late this 1s tick fired — the event-loop lag.
|
|
99
|
+
const lagMs = Math.max(0, now - lastFire - beatIntervalMs);
|
|
100
|
+
lastFire = now;
|
|
101
|
+
w.postMessage({ lagMs });
|
|
102
|
+
}, beatIntervalMs);
|
|
103
|
+
beatTimer.unref();
|
|
104
|
+
}
|
|
105
|
+
// A dead worker means the supervisor loses its stalled-vs-dead evidence for
|
|
106
|
+
// the rest of the process's life — never let that happen silently. Stop the
|
|
107
|
+
// beat timer (no more postMessage into a corpse) and respawn a bounded
|
|
108
|
+
// number of times; past the cap, say loudly that the signal is down.
|
|
109
|
+
function onWorkerDown(cause) {
|
|
110
|
+
stopBeat();
|
|
111
|
+
worker = null;
|
|
112
|
+
if (stopped)
|
|
113
|
+
return;
|
|
114
|
+
if (respawns >= MAX_WORKER_RESPAWNS) {
|
|
115
|
+
log.error(`[flight-recorder] heartbeat worker down (${cause}) after ${respawns} respawns — ` +
|
|
116
|
+
`giving up: the progress signal is DOWN for the rest of this process`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
respawns++;
|
|
120
|
+
log.error(`[flight-recorder] heartbeat worker down (${cause}) — respawning (${respawns}/${MAX_WORKER_RESPAWNS})`);
|
|
121
|
+
try {
|
|
122
|
+
spawnWorker();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// Never let the recorder take down the server it instruments — this
|
|
126
|
+
// runs inside a worker event handler, where a throw would be uncaught.
|
|
127
|
+
worker = null;
|
|
128
|
+
log.error(`[flight-recorder] respawn failed (progress signal is DOWN): ${err}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function spawnWorker() {
|
|
132
|
+
const w = new Worker(WORKER_SOURCE, {
|
|
133
|
+
eval: true,
|
|
134
|
+
workerData: { filePath, beatIntervalMs, writeIntervalMs },
|
|
135
|
+
});
|
|
136
|
+
worker = w;
|
|
137
|
+
w.unref();
|
|
138
|
+
// "error" is always followed by "exit"; the worker===w guard makes the
|
|
139
|
+
// second event (or a stale exit after a respawn) a no-op.
|
|
140
|
+
w.on("error", (err) => {
|
|
141
|
+
if (worker === w)
|
|
142
|
+
onWorkerDown(`error: ${err}`);
|
|
143
|
+
});
|
|
144
|
+
w.on("exit", (code) => {
|
|
145
|
+
if (worker === w)
|
|
146
|
+
onWorkerDown(`exit code ${code}`);
|
|
147
|
+
});
|
|
148
|
+
startBeat(w);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
spawnWorker();
|
|
152
|
+
log.info(`[flight-recorder] heartbeat → ${filePath} every ${writeIntervalMs}ms`);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
worker = null;
|
|
156
|
+
stopBeat();
|
|
157
|
+
log.warn(`[flight-recorder] failed to start (continuing without heartbeat): ${err}`);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
async stop() {
|
|
161
|
+
stopped = true;
|
|
162
|
+
stopBeat();
|
|
163
|
+
if (worker)
|
|
164
|
+
await worker.terminate().catch(() => { });
|
|
165
|
+
worker = null;
|
|
166
|
+
},
|
|
167
|
+
crashWorkerForTest() {
|
|
168
|
+
worker?.postMessage({ crashForTest: true });
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=flight-recorder.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flight-recorder.service.js","sourceRoot":"","sources":["../../src/services/flight-recorder.service.ts"],"names":[],"mappings":"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"}
|