baychat 0.33.2 → 0.33.3

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baychat",
3
- "version": "0.33.2",
3
+ "version": "0.33.3",
4
4
  "displayName": "BayChat",
5
5
  "description": "Wakes this Claude Code session the moment a BayChat message arrives: no Monitor, no 30-minute deadline. Uses the baychat CLI already on this computer (npm install -g baychat, then baychat login).",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HELD_FRESH_MS = exports.QUIET_MS = exports.CARD_POLL_MS = exports.ASK_AGAIN_MS = exports.HINT_COOLDOWN_MS = exports.HINT_SPREAD_MS = exports.HINT_MIN_MS = exports.CHECK_INTERVAL_MS = exports.AUTO_UPDATE_ENV = void 0;
3
+ exports.HELD_FRESH_MS = exports.QUIET_MS = exports.CARD_POLL_MS = exports.ASK_AGAIN_MS = exports.HINT_RECHECK_MS = exports.HINT_COOLDOWN_MS = exports.HINT_SPREAD_MS = exports.HINT_MIN_MS = exports.CHECK_INTERVAL_MS = exports.AUTO_UPDATE_ENV = void 0;
4
4
  exports.planUpdate = planUpdate;
5
5
  exports.autoUpdateOffReason = autoUpdateOffReason;
6
6
  exports.nextCheckAt = nextCheckAt;
@@ -54,9 +54,12 @@ const FIRST_CHECK_SPREAD_MS = 10 * MINUTE;
54
54
  * 1–5 minutes — spread so a fleet that hears the same frame never asks npm together. */
55
55
  exports.HINT_MIN_MS = MINUTE;
56
56
  exports.HINT_SPREAD_MS = 4 * MINUTE;
57
- /** A hint right after a check is not taken: a server that names a release on every reconnect
58
- * must not become an npm query on every reconnect. */
57
+ /** A hint never brings a check closer than this to the last one: a server that names a release
58
+ * on every reconnect must not become an npm query on every reconnect. */
59
59
  exports.HINT_COOLDOWN_MS = 15 * MINUTE;
60
+ /** The server can name a release before npm has it (deployed first, published after): npm is
61
+ * asked again this long after each answer that is still older, then the hint is given up. */
62
+ exports.HINT_RECHECK_MS = [15 * MINUTE, 30 * MINUTE, HOUR];
60
63
  /** A check that failed (offline, npm down) is tried again sooner than a day, then backs off. */
61
64
  const CHECK_RETRY_MS = HOUR;
62
65
  /** A failed install of one version waits a day, then two, … up to a week. A newer version resets it. */
@@ -17,10 +17,6 @@ class AutoUpdater {
17
17
  running = false;
18
18
  stopped = false;
19
19
  lastCardPoll = 0;
20
- /** A release the server named (hint.ts), and when npm is asked about it. In memory only: a
21
- * restarted relay hears the next `ready` frame anyway. */
22
- hinted;
23
- hintCheckAt;
24
20
  constructor(deps) {
25
21
  this.deps = deps;
26
22
  this.startedAt = deps.now();
@@ -28,6 +24,7 @@ class AutoUpdater {
28
24
  }
29
25
  /** Finish an update the previous relay installed, then look at the state once a minute. */
30
26
  start() {
27
+ this.freshStart();
31
28
  void this.runExclusive(() => this.finishPendingUpdate());
32
29
  this.timer = setInterval(() => void this.tick(), exports.TICK_MS);
33
30
  this.timer.unref();
@@ -50,34 +47,86 @@ class AutoUpdater {
50
47
  failure: state.failure?.reason,
51
48
  };
52
49
  }
50
+ /**
51
+ * New code is a new chance. Check failures recorded by ANOTHER version say nothing about this
52
+ * one — the failure may be the very bug this version fixes. Real case, 2026-10-21: relays that
53
+ * failed 11 times on 0.33.0 ("npm was not found on PATH") carried that backoff into 0.33.1, which
54
+ * fixed it, and would not have asked npm again for a day. So the schedule starts over, and the
55
+ * first check comes 5–15 minutes after this start.
56
+ */
57
+ freshStart() {
58
+ const state = this.deps.loadState();
59
+ if (!state.current || state.current === this.deps.current)
60
+ return;
61
+ if (state.lastCheckAt === undefined && !state.checkFailures)
62
+ return;
63
+ const was = state.current;
64
+ state.lastCheckAt = undefined;
65
+ state.checkFailures = undefined;
66
+ state.lastCheckError = undefined;
67
+ state.current = this.deps.current;
68
+ this.deps.saveState(state);
69
+ this.deps.log(`auto-update: now running ${this.deps.current} (checks were recorded by ${was}); asking npm afresh`);
70
+ }
53
71
  /**
54
72
  * The server's feed named `version` as the newest BayChat (hint.ts). Newer than this copy, and
55
73
  * newer than what npm last said: ask npm in 1–5 minutes instead of at the daily check. Only the
56
74
  * moment moves — npm still names the version, and planUpdate still decides what happens.
57
75
  *
58
- * The moment never moves earlier than the backoff allows: not within HINT_COOLDOWN_MS of the
59
- * last check, and not while a failed check waits out its retry (npm down stays npm down,
60
- * however often the server reconnects).
76
+ * The moment never moves earlier than the backoff allows (`hintDue`): not within
77
+ * HINT_COOLDOWN_MS of the last check, not before a failed check's retry. The hint is kept, on
78
+ * disk, rather than dropped: a server deployed before the npm publish names a version npm does
79
+ * not have yet (2026-10-21, 0.33.2: the hint's check came 1 minute before the publish), so npm
80
+ * is asked again after each HINT_RECHECK_MS step until it names that version, then given up.
61
81
  */
62
82
  hint(version) {
63
83
  const { mode } = this.mode();
64
84
  if (mode === "off" || mode === "none")
65
85
  return;
66
86
  const ahead = (0, self_upgrade_1.compareVersions)(version, this.deps.current);
67
- if (ahead === undefined || ahead <= 0 || this.hinted === version)
87
+ if (ahead === undefined || ahead <= 0)
68
88
  return;
69
89
  const state = this.deps.loadState();
70
90
  const known = state.latest ? (0, self_upgrade_1.compareVersions)(state.latest, version) : undefined;
71
91
  if (known !== undefined && known >= 0)
72
92
  return; // npm already named it (or newer)
73
- const now = this.deps.now();
74
- if (state.lastCheckAt !== undefined && now - state.lastCheckAt < policy_1.HINT_COOLDOWN_MS)
93
+ if (validHint(state.hint)?.version === version)
94
+ return; // waiting on it, or gave up on it
95
+ const nextAt = this.hintDue(state, this.deps.now() + policy_1.HINT_MIN_MS + this.deps.random() * policy_1.HINT_SPREAD_MS);
96
+ state.hint = { version, attempts: 0, nextAt };
97
+ this.deps.saveState(state);
98
+ this.deps.log(`auto-update: the server names ${version}; asking npm in ${Math.ceil((nextAt - this.deps.now()) / 60_000)} min`);
99
+ }
100
+ /** The earliest a hinted check may run: `wanted`, or later if the backoff says so. */
101
+ hintDue(state, wanted) {
102
+ let at = wanted;
103
+ if (state.lastCheckAt !== undefined)
104
+ at = Math.max(at, state.lastCheckAt + policy_1.HINT_COOLDOWN_MS);
105
+ if ((state.checkFailures ?? 0) > 0)
106
+ at = Math.max(at, (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread));
107
+ return at;
108
+ }
109
+ /** After a check: the hint is answered (npm names it, or this copy has it), retried, or given up. */
110
+ settleHint(state, now, wasDue) {
111
+ const hint = validHint(state.hint);
112
+ if (!hint)
113
+ return void (state.hint = undefined);
114
+ const latest = state.latest ? (0, self_upgrade_1.compareVersions)(state.latest, hint.version) : undefined;
115
+ if ((latest !== undefined && latest >= 0) || (0, self_upgrade_1.compareVersions)(hint.version, this.deps.current) !== 1) {
116
+ state.hint = undefined;
117
+ return;
118
+ }
119
+ if (!wasDue || hint.nextAt === undefined)
75
120
  return;
76
- if ((state.checkFailures ?? 0) > 0 && now < (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread))
121
+ const attempts = hint.attempts + 1;
122
+ const wait = policy_1.HINT_RECHECK_MS[attempts - 1];
123
+ if (wait === undefined) {
124
+ state.hint = { version: hint.version, attempts };
125
+ this.deps.log(`auto-update: npm still does not have ${hint.version}; the daily check takes over`);
77
126
  return;
78
- this.hinted = version;
79
- this.hintCheckAt = now + policy_1.HINT_MIN_MS + this.deps.random() * policy_1.HINT_SPREAD_MS;
80
- this.deps.log(`auto-update: the server names ${version}; asking npm within minutes`);
127
+ }
128
+ state.hint = { version: hint.version, attempts, nextAt: now + wait };
129
+ this.deps.log(`auto-update: npm does not have ${hint.version} yet; asking again in ${wait / 60_000} min`);
81
130
  }
82
131
  /** One look at the state. Never throws; never runs twice at once. */
83
132
  async tick() {
@@ -97,11 +146,10 @@ class AutoUpdater {
97
146
  if (mode === "off" || mode === "none")
98
147
  return;
99
148
  const now = this.deps.now();
100
- const hinted = this.hintCheckAt !== undefined && now >= this.hintCheckAt;
101
- if (hinted || now >= (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread)) {
102
- this.hintCheckAt = undefined;
103
- await this.check(state, mode);
104
- }
149
+ const hintAt = validHint(state.hint)?.nextAt;
150
+ const hinted = hintAt !== undefined && now >= hintAt;
151
+ if (hinted || now >= (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread))
152
+ await this.check(state, mode, hinted);
105
153
  if (mode !== "auto")
106
154
  return;
107
155
  if (state.card?.requestId && now - this.lastCardPoll >= policy_1.CARD_POLL_MS)
@@ -129,20 +177,25 @@ class AutoUpdater {
129
177
  return { mode: "notify", reason: "for a relay started by hand (install with `baychat update`)" };
130
178
  return { mode: "auto" };
131
179
  }
132
- /** Ask npm, and decide. */
133
- async check(state, mode) {
180
+ /** Ask npm, and decide. `hinted`: the check a server's hint asked for (counts as a hint attempt). */
181
+ async check(state, mode, hinted = false) {
134
182
  const looked = await this.deps.lookupLatest();
135
- state.lastCheckAt = this.deps.now();
183
+ const now = this.deps.now();
184
+ state.lastCheckAt = now;
136
185
  if (!looked.ok) {
137
- // Offline, npm down: quiet. Tried again within the hour, then less often.
186
+ // Offline, npm down: quiet. Tried again within the hour, then less often — a waiting hint too.
138
187
  state.checkFailures = (state.checkFailures ?? 0) + 1;
139
188
  state.lastCheckError = looked.reason;
189
+ const hint = validHint(state.hint);
190
+ if (hint?.nextAt !== undefined)
191
+ state.hint = { ...hint, nextAt: this.hintDue(state, now) };
140
192
  this.deps.log(`auto-update: could not ask npm (${looked.reason})`);
141
193
  return this.deps.saveState(state);
142
194
  }
143
195
  state.checkFailures = 0;
144
196
  state.lastCheckError = undefined;
145
197
  state.latest = looked.release.version;
198
+ this.settleHint(state, now, hinted);
146
199
  const plan = (0, policy_1.planUpdate)(this.deps.current, looked.release);
147
200
  if (plan.kind === "current") {
148
201
  // Updated some other way (`baychat update`), or nothing new: nothing may stay on the phone.
@@ -355,6 +408,15 @@ class AutoUpdater {
355
408
  }
356
409
  }
357
410
  exports.AutoUpdater = AutoUpdater;
411
+ /** The persisted hint, if it is one: the state file is ours, but a hand edit or an older shape must
412
+ * not stop the updater. */
413
+ function validHint(raw) {
414
+ if (!raw || typeof raw !== "object" || typeof raw.version !== "string" || typeof raw.attempts !== "number")
415
+ return undefined;
416
+ if (raw.nextAt !== undefined && typeof raw.nextAt !== "number")
417
+ return undefined;
418
+ return raw;
419
+ }
358
420
  function errText(err) {
359
421
  return err instanceof Error ? err.message : String(err);
360
422
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baychat",
3
- "version": "0.33.2",
3
+ "version": "0.33.3",
4
4
  "description": "BayChat connector CLI — pair an agent session (Claude Code, Codex, Cursor) with BayChat, or have the relay run DeepSeek or the Cursor CLI for you",
5
5
  "bin": {
6
6
  "baychat": "dist/index.js"