baychat 0.33.0 → 0.33.1
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/.claude-plugin/plugin.json +1 -1
- package/dist/auto-update/hint.js +20 -0
- package/dist/auto-update/policy.js +8 -1
- package/dist/auto-update/relay-glue.js +3 -0
- package/dist/auto-update/updater.js +37 -1
- package/dist/relay/ws.js +5 -0
- package/dist/self-upgrade.js +38 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.1",
|
|
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": {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.onReleaseAnnounced = onReleaseAnnounced;
|
|
4
|
+
exports.announceRelease = announceRelease;
|
|
5
|
+
let listener;
|
|
6
|
+
let log = (line) => console.error(line);
|
|
7
|
+
function onReleaseAnnounced(fn, logLine) {
|
|
8
|
+
listener = fn;
|
|
9
|
+
if (logLine)
|
|
10
|
+
log = logLine;
|
|
11
|
+
}
|
|
12
|
+
function announceRelease(version) {
|
|
13
|
+
try {
|
|
14
|
+
listener?.(version);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
// A hint that cannot be taken is a hint missed; the daily check still comes.
|
|
18
|
+
log(`auto-update: could not take the server's release hint (${err instanceof Error ? err.message : String(err)})`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -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.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_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;
|
|
@@ -50,6 +50,13 @@ const CHECK_SPREAD_MS = HOUR;
|
|
|
50
50
|
/** The first check after a start: soon, but not in the busy first minutes. */
|
|
51
51
|
const FIRST_CHECK_MIN_MS = 5 * MINUTE;
|
|
52
52
|
const FIRST_CHECK_SPREAD_MS = 10 * MINUTE;
|
|
53
|
+
/** The server named a newer release in the feed's `ready` frame (hint.ts): npm is asked within
|
|
54
|
+
* 1–5 minutes — spread so a fleet that hears the same frame never asks npm together. */
|
|
55
|
+
exports.HINT_MIN_MS = MINUTE;
|
|
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. */
|
|
59
|
+
exports.HINT_COOLDOWN_MS = 15 * MINUTE;
|
|
53
60
|
/** A check that failed (offline, npm down) is tried again sooner than a day, then backs off. */
|
|
54
61
|
const CHECK_RETRY_MS = HOUR;
|
|
55
62
|
/** A failed install of one version waits a day, then two, … up to a week. A newer version resets it. */
|
|
@@ -10,6 +10,7 @@ const refresh_clients_1 = require("../refresh-clients");
|
|
|
10
10
|
const waiting_cards_1 = require("../waiting-cards");
|
|
11
11
|
const policy_1 = require("./policy");
|
|
12
12
|
const store_1 = require("./store");
|
|
13
|
+
const hint_1 = require("./hint");
|
|
13
14
|
const updater_1 = require("./updater");
|
|
14
15
|
/**
|
|
15
16
|
* The automatic update wired to a real relay: npm, the disk, the device API for the card, and the
|
|
@@ -50,6 +51,8 @@ function startRelayUpdater(opts) {
|
|
|
50
51
|
};
|
|
51
52
|
const updater = new updater_1.AutoUpdater(deps);
|
|
52
53
|
updater.start();
|
|
54
|
+
// The server's feed names the newest release in each `ready` frame: check npm soon, not tomorrow.
|
|
55
|
+
(0, hint_1.onReleaseAnnounced)((version) => updater.hint(version), opts.log);
|
|
53
56
|
return updater;
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
@@ -17,6 +17,10 @@ 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;
|
|
20
24
|
constructor(deps) {
|
|
21
25
|
this.deps = deps;
|
|
22
26
|
this.startedAt = deps.now();
|
|
@@ -46,6 +50,35 @@ class AutoUpdater {
|
|
|
46
50
|
failure: state.failure?.reason,
|
|
47
51
|
};
|
|
48
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The server's feed named `version` as the newest BayChat (hint.ts). Newer than this copy, and
|
|
55
|
+
* newer than what npm last said: ask npm in 1–5 minutes instead of at the daily check. Only the
|
|
56
|
+
* moment moves — npm still names the version, and planUpdate still decides what happens.
|
|
57
|
+
*
|
|
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).
|
|
61
|
+
*/
|
|
62
|
+
hint(version) {
|
|
63
|
+
const { mode } = this.mode();
|
|
64
|
+
if (mode === "off" || mode === "none")
|
|
65
|
+
return;
|
|
66
|
+
const ahead = (0, self_upgrade_1.compareVersions)(version, this.deps.current);
|
|
67
|
+
if (ahead === undefined || ahead <= 0 || this.hinted === version)
|
|
68
|
+
return;
|
|
69
|
+
const state = this.deps.loadState();
|
|
70
|
+
const known = state.latest ? (0, self_upgrade_1.compareVersions)(state.latest, version) : undefined;
|
|
71
|
+
if (known !== undefined && known >= 0)
|
|
72
|
+
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)
|
|
75
|
+
return;
|
|
76
|
+
if ((state.checkFailures ?? 0) > 0 && now < (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread))
|
|
77
|
+
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`);
|
|
81
|
+
}
|
|
49
82
|
/** One look at the state. Never throws; never runs twice at once. */
|
|
50
83
|
async tick() {
|
|
51
84
|
await this.runExclusive(async () => {
|
|
@@ -64,8 +97,11 @@ class AutoUpdater {
|
|
|
64
97
|
if (mode === "off" || mode === "none")
|
|
65
98
|
return;
|
|
66
99
|
const now = this.deps.now();
|
|
67
|
-
|
|
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;
|
|
68
103
|
await this.check(state, mode);
|
|
104
|
+
}
|
|
69
105
|
if (mode !== "auto")
|
|
70
106
|
return;
|
|
71
107
|
if (state.card?.requestId && now - this.lastCardPoll >= policy_1.CARD_POLL_MS)
|
package/dist/relay/ws.js
CHANGED
|
@@ -27,6 +27,7 @@ const context_notice_1 = require("./context-notice");
|
|
|
27
27
|
const project_notes_1 = require("./project-notes");
|
|
28
28
|
const updates_1 = require("./updates");
|
|
29
29
|
const server_registration_1 = require("./server-registration");
|
|
30
|
+
const hint_1 = require("../auto-update/hint");
|
|
30
31
|
/** The published endpoint. Changing it breaks every deployed server — a wire constant. */
|
|
31
32
|
exports.AGENT_WS_PATH = "/api/agent-api/ws";
|
|
32
33
|
/** How long we wait for the server's `ready` after sending `hello`. */
|
|
@@ -132,6 +133,8 @@ function runWebSocketFeed(opts) {
|
|
|
132
133
|
onSessions?.(frame.sessions, frame.registrations);
|
|
133
134
|
if (frame.cursor)
|
|
134
135
|
onCursor?.(frame.cursor);
|
|
136
|
+
if (frame.latestCli)
|
|
137
|
+
(0, hint_1.announceRelease)(frame.latestCli);
|
|
135
138
|
onReady?.(frame.cursor ?? null);
|
|
136
139
|
return;
|
|
137
140
|
}
|
|
@@ -288,6 +291,8 @@ function parseFrame(raw) {
|
|
|
288
291
|
cursor: str(frame.cursor),
|
|
289
292
|
sessions: names(frame.sessions),
|
|
290
293
|
registrations: (0, server_registration_1.parseRegistrations)(frame.registrations),
|
|
294
|
+
// Plain x.y.z only; anything else is dropped (a hint, never what installs: auto-update/hint.ts).
|
|
295
|
+
latestCli: typeof frame.latestCli === "string" && /^\d+\.\d+\.\d+$/.test(frame.latestCli) ? frame.latestCli : undefined,
|
|
291
296
|
};
|
|
292
297
|
case "cursor":
|
|
293
298
|
return {
|
package/dist/self-upgrade.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.upgradePackage = upgradePackage;
|
|
|
42
42
|
exports.lookupLatest = lookupLatest;
|
|
43
43
|
exports.installRelease = installRelease;
|
|
44
44
|
exports.describeOutcome = describeOutcome;
|
|
45
|
+
exports.npmCommand = npmCommand;
|
|
45
46
|
exports.realNpm = realNpm;
|
|
46
47
|
exports.readPackageVersion = readPackageVersion;
|
|
47
48
|
exports.packageEntry = packageEntry;
|
|
@@ -249,6 +250,30 @@ function npmFailure(r) {
|
|
|
249
250
|
const lines = r.stderr.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
250
251
|
return lines.slice(-3).join(" ") || `npm exited with ${r.status}`;
|
|
251
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Which npm to run, and the PATH to run it with: the npm installed beside the Node running this
|
|
255
|
+
* code, when there is one — else plain `npm` from PATH.
|
|
256
|
+
*
|
|
257
|
+
* A relay under systemd or launchd does not read a shell profile, so with Node from nvm (or any
|
|
258
|
+
* version manager) its PATH holds no npm at all: every automatic update on such a computer failed
|
|
259
|
+
* with "npm was not found on PATH" (Karmen's PC, 2026-10-21: 11 failed checks, never one update).
|
|
260
|
+
* The Node that is running is the Node that installed this copy, and its npm sits next to it. That
|
|
261
|
+
* directory also goes first on the child's PATH, because npm itself starts with
|
|
262
|
+
* `#!/usr/bin/env node`, which must find a node too.
|
|
263
|
+
*
|
|
264
|
+
* Which global folder that npm calls its own (Volta, asdf, a custom `prefix`) does not matter:
|
|
265
|
+
* installRelease always passes `--prefix <the running copy's prefix>`, which npm puts above its
|
|
266
|
+
* own config (checked 2026-10-21 with npm_config_prefix pointing elsewhere: the install landed in
|
|
267
|
+
* `--prefix`), and then believes only the version on disk there.
|
|
268
|
+
*/
|
|
269
|
+
function npmCommand(execPath, platform, pathEnv, exists) {
|
|
270
|
+
const dir = path.dirname(execPath);
|
|
271
|
+
const beside = path.join(dir, platform === "win32" ? "npm.cmd" : "npm");
|
|
272
|
+
if (!exists(beside))
|
|
273
|
+
return { command: "npm", path: pathEnv };
|
|
274
|
+
const sep = platform === "win32" ? ";" : ":";
|
|
275
|
+
return { command: beside, path: pathEnv ? `${dir}${sep}${pathEnv}` : dir };
|
|
276
|
+
}
|
|
252
277
|
/** Run npm for real, without blocking the event loop (the relay keeps serving while npm runs).
|
|
253
278
|
* Windows' npm is a `.cmd` shim, which Node will only start through a shell, so every argument
|
|
254
279
|
* is first checked against what cmd.exe could misread and then quoted. */
|
|
@@ -257,8 +282,20 @@ function realNpm(args, timeoutMs) {
|
|
|
257
282
|
if (windows && args.some((a) => commands_1.WINDOWS_UNSAFE.test(a))) {
|
|
258
283
|
return Promise.resolve({ status: null, stdout: "", stderr: "", error: new Error("a path npm needs cannot be passed safely through cmd.exe") });
|
|
259
284
|
}
|
|
285
|
+
const found = npmCommand(process.execPath, process.platform, process.env.PATH, fs.existsSync);
|
|
286
|
+
// A Node directory cmd.exe could misread is not run through it: PATH's npm then, as before.
|
|
287
|
+
const npm = windows && commands_1.WINDOWS_UNSAFE.test(found.command) ? { command: "npm", path: process.env.PATH } : found;
|
|
288
|
+
// Through cmd.exe the command is a string too: a path with spaces must arrive as one word.
|
|
289
|
+
const command = windows && npm.command !== "npm" ? (0, commands_1.quoteForCmd)(npm.command) : npm.command;
|
|
260
290
|
return new Promise((resolve) => {
|
|
261
|
-
(0, child_process_1.execFile)(
|
|
291
|
+
(0, child_process_1.execFile)(command, windows ? args.map(commands_1.quoteForCmd) : args, {
|
|
292
|
+
encoding: "utf8",
|
|
293
|
+
timeout: timeoutMs,
|
|
294
|
+
shell: windows,
|
|
295
|
+
windowsHide: true,
|
|
296
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
297
|
+
env: { ...process.env, ...(npm.path !== undefined ? { PATH: npm.path } : {}) },
|
|
298
|
+
}, (err, stdout, stderr) => {
|
|
262
299
|
if (!err)
|
|
263
300
|
return resolve({ status: 0, stdout, stderr });
|
|
264
301
|
const code = err.code;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.1",
|
|
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"
|