patchcord 0.5.32 → 0.5.33
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 +1 -1
- package/scripts/subscribe.mjs +27 -20
package/package.json
CHANGED
package/scripts/subscribe.mjs
CHANGED
|
@@ -23,19 +23,27 @@ const RECONNECT_BACKOFF_MS = [1000, 2000, 4000, 8000, 15_000, 30_000];
|
|
|
23
23
|
const FRESHNESS_CHECK_INTERVAL_MS = 30_000;
|
|
24
24
|
const FRESHNESS_STALE_MS = 90_000;
|
|
25
25
|
|
|
26
|
+
// Short HH:MM:SS prefix so the Monitor output can be scanned at a glance.
|
|
27
|
+
// Local time — Monitor's reader is always a human looking at one machine.
|
|
28
|
+
function logErr(msg) {
|
|
29
|
+
const d = new Date();
|
|
30
|
+
const ts = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
|
|
31
|
+
process.stderr.write(`[${ts}] ${msg}\n`);
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
// Guarantee a terminal stderr line on any unhandled failure so the agent
|
|
27
35
|
// reading Monitor's output file always sees WHY the process died.
|
|
28
36
|
process.on("uncaughtException", (err) => {
|
|
29
|
-
|
|
37
|
+
logErr(`subscribe: fatal: uncaught: ${err?.stack || err?.message || err}`);
|
|
30
38
|
process.exit(1);
|
|
31
39
|
});
|
|
32
40
|
process.on("unhandledRejection", (err) => {
|
|
33
|
-
|
|
41
|
+
logErr(`subscribe: fatal: unhandled rejection: ${err?.stack || err?.message || err}`);
|
|
34
42
|
process.exit(1);
|
|
35
43
|
});
|
|
36
44
|
|
|
37
45
|
function die(msg, code = 1) {
|
|
38
|
-
|
|
46
|
+
logErr(msg);
|
|
39
47
|
process.exit(code);
|
|
40
48
|
}
|
|
41
49
|
|
|
@@ -178,7 +186,7 @@ function removePidfile(path) {
|
|
|
178
186
|
async function run() {
|
|
179
187
|
const cwd = process.cwd();
|
|
180
188
|
const { baseUrl, token } = readMcpConfig(cwd);
|
|
181
|
-
|
|
189
|
+
logErr(`subscribe: cwd=${cwd} server=${baseUrl}`);
|
|
182
190
|
|
|
183
191
|
let ticket = await fetchTicket(baseUrl, token);
|
|
184
192
|
const pidfile = `/tmp/patchcord_subscribe_${ticket.namespace_ids[0]}_${ticket.agent_id}.pid`;
|
|
@@ -195,9 +203,7 @@ async function run() {
|
|
|
195
203
|
process.exit(0);
|
|
196
204
|
});
|
|
197
205
|
|
|
198
|
-
|
|
199
|
-
`subscribe: agent=${ticket.agent_id} namespaces=${ticket.namespace_ids.join(",")}\n`
|
|
200
|
-
);
|
|
206
|
+
logErr(`subscribe: agent=${ticket.agent_id} namespaces=${ticket.namespace_ids.join(",")}`);
|
|
201
207
|
|
|
202
208
|
let backoffIdx = 0;
|
|
203
209
|
|
|
@@ -210,16 +216,16 @@ async function run() {
|
|
|
210
216
|
});
|
|
211
217
|
backoffIdx = 0; // clean disconnect resets backoff
|
|
212
218
|
} catch (e) {
|
|
213
|
-
|
|
219
|
+
logErr(`subscribe: ${e.message}`);
|
|
214
220
|
}
|
|
215
221
|
const delay = RECONNECT_BACKOFF_MS[Math.min(backoffIdx, RECONNECT_BACKOFF_MS.length - 1)];
|
|
216
222
|
backoffIdx++;
|
|
217
|
-
|
|
223
|
+
logErr(`subscribe: reconnecting in ${delay}ms`);
|
|
218
224
|
await new Promise((r) => setTimeout(r, delay));
|
|
219
225
|
try {
|
|
220
226
|
ticket = await fetchTicket(baseUrl, token);
|
|
221
227
|
} catch (e) {
|
|
222
|
-
|
|
228
|
+
logErr(`subscribe: ticket refresh failed: ${e.message}`);
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
};
|
|
@@ -255,7 +261,7 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
|
|
|
255
261
|
};
|
|
256
262
|
|
|
257
263
|
ws.on("open", () => {
|
|
258
|
-
|
|
264
|
+
logErr("subscribe: connected");
|
|
259
265
|
for (const topic of ticket.topics) {
|
|
260
266
|
ws.send(
|
|
261
267
|
JSON.stringify({
|
|
@@ -277,7 +283,7 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
|
|
|
277
283
|
// failure here just means we miss queued messages this round;
|
|
278
284
|
// the next reconnect retries.
|
|
279
285
|
drainQueueOnce(baseUrl, token).catch((e) => {
|
|
280
|
-
|
|
286
|
+
logErr(`subscribe: queue check failed: ${e.message}`);
|
|
281
287
|
});
|
|
282
288
|
heartbeatTimer = setInterval(() => {
|
|
283
289
|
try {
|
|
@@ -300,9 +306,7 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
|
|
|
300
306
|
freshnessTimer = setInterval(() => {
|
|
301
307
|
const stale = Date.now() - lastEventAt;
|
|
302
308
|
if (stale > FRESHNESS_STALE_MS) {
|
|
303
|
-
|
|
304
|
-
`subscribe: ws stale ${Math.round(stale / 1000)}s, forcing reconnect\n`
|
|
305
|
-
);
|
|
309
|
+
logErr(`subscribe: ws stale ${Math.round(stale / 1000)}s, forcing reconnect`);
|
|
306
310
|
done(new Error(`ws stale ${Math.round(stale / 1000)}s`));
|
|
307
311
|
}
|
|
308
312
|
}, FRESHNESS_CHECK_INTERVAL_MS);
|
|
@@ -340,13 +344,16 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
|
|
|
340
344
|
})
|
|
341
345
|
);
|
|
342
346
|
}
|
|
343
|
-
|
|
347
|
+
// Routine refresh success — don't log. Over days this fills the
|
|
348
|
+
// Monitor output with ~250 identical lines and buries the rare
|
|
349
|
+
// interesting events (connects, errors). Refresh failures DO log,
|
|
350
|
+
// because those are the ones worth scanning for.
|
|
344
351
|
scheduleRefresh(fresh.jwt_expires_in);
|
|
345
352
|
} catch (e) {
|
|
346
353
|
// Transient network/server error — do NOT close the live
|
|
347
354
|
// connection. The current JWT is still valid for ~2 more min
|
|
348
355
|
// (JWT_REFRESH_SAFETY_MARGIN_SEC). Retry sooner.
|
|
349
|
-
|
|
356
|
+
logErr(`subscribe: token refresh failed, retrying in 30s: ${e.message}`);
|
|
350
357
|
refreshTimer = setTimeout(doRefresh, 30_000);
|
|
351
358
|
}
|
|
352
359
|
};
|
|
@@ -384,20 +391,20 @@ function runOnce(ticket, baseUrl, token, refreshTicket) {
|
|
|
384
391
|
});
|
|
385
392
|
|
|
386
393
|
ws.on("error", (err) => {
|
|
387
|
-
|
|
394
|
+
logErr(`subscribe: ws error: ${err.message}`);
|
|
388
395
|
done(err);
|
|
389
396
|
});
|
|
390
397
|
|
|
391
398
|
ws.on("close", (info) => {
|
|
392
399
|
const codeStr = info?.code != null ? `code=${info.code}` : "code=none";
|
|
393
400
|
const reasonStr = info?.reason ? ` reason=${JSON.stringify(info.reason)}` : "";
|
|
394
|
-
|
|
401
|
+
logErr(`subscribe: ws closed (${codeStr}${reasonStr})`);
|
|
395
402
|
done();
|
|
396
403
|
});
|
|
397
404
|
});
|
|
398
405
|
}
|
|
399
406
|
|
|
400
407
|
run().catch((e) => {
|
|
401
|
-
|
|
408
|
+
logErr(`subscribe: fatal: ${e.message}`);
|
|
402
409
|
process.exit(1);
|
|
403
410
|
});
|