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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.32",
3
+ "version": "0.5.33",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -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
- process.stderr.write(`subscribe: fatal: uncaught: ${err?.stack || err?.message || err}\n`);
37
+ logErr(`subscribe: fatal: uncaught: ${err?.stack || err?.message || err}`);
30
38
  process.exit(1);
31
39
  });
32
40
  process.on("unhandledRejection", (err) => {
33
- process.stderr.write(`subscribe: fatal: unhandled rejection: ${err?.stack || err?.message || err}\n`);
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
- process.stderr.write(msg + "\n");
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
- process.stderr.write(`subscribe: cwd=${cwd} server=${baseUrl}\n`);
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
- process.stderr.write(
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
- process.stderr.write(`subscribe: ${e.message}\n`);
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
- process.stderr.write(`subscribe: reconnecting in ${delay}ms\n`);
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
- process.stderr.write(`subscribe: ticket refresh failed: ${e.message}\n`);
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
- process.stderr.write("subscribe: connected\n");
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
- process.stderr.write(`subscribe: queue check failed: ${e.message}\n`);
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
- process.stderr.write(
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
- process.stderr.write("subscribe: token refreshed\n");
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
- process.stderr.write(`subscribe: token refresh failed, retrying in 30s: ${e.message}\n`);
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
- process.stderr.write(`subscribe: ws error: ${err.message}\n`);
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
- process.stderr.write(`subscribe: ws closed (${codeStr}${reasonStr})\n`);
401
+ logErr(`subscribe: ws closed (${codeStr}${reasonStr})`);
395
402
  done();
396
403
  });
397
404
  });
398
405
  }
399
406
 
400
407
  run().catch((e) => {
401
- process.stderr.write(`subscribe: fatal: ${e.message}\n`);
408
+ logErr(`subscribe: fatal: ${e.message}`);
402
409
  process.exit(1);
403
410
  });