@chatpanel/bridge 0.11.1 → 0.11.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.
- package/package.json +1 -1
- package/src/channels/service.js +14 -4
- package/src/server.js +1 -1
- package/src/update.js +29 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/bridge",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local bridge that exposes the AI coding agents installed on your machine \u2014 Claude Code (CLI), Codex (CLI), and Antigravity CLI (formerly Gemini CLI, which remains available for business/enterprise) \u2014 to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
|
|
6
6
|
"keywords": [
|
package/src/channels/service.js
CHANGED
|
@@ -68,12 +68,15 @@ export function createChannelService({
|
|
|
68
68
|
logger = console,
|
|
69
69
|
fetchImpl = undefined, // injected in tests
|
|
70
70
|
now = () => Date.now(),
|
|
71
|
+
// The adapter is injectable for the same reason fetchImpl is: the thing worth asserting
|
|
72
|
+
// about the loop is WHAT it is handed, and a real Telegram long-poll cannot be asked.
|
|
73
|
+
startAdapter = startTelegram,
|
|
71
74
|
} = {}) {
|
|
72
75
|
const tokenFile = path.join(home, 'telegram-token');
|
|
73
76
|
const configFile = path.join(dataDir, 'config.json');
|
|
74
77
|
const pairingFile = path.join(dataDir, 'pairing.json');
|
|
75
78
|
|
|
76
|
-
let pairing =
|
|
79
|
+
let pairing = null; // created by load(), then kept — see the note there
|
|
77
80
|
let settings = { ...DEFAULT_SETTINGS };
|
|
78
81
|
let appender = null;
|
|
79
82
|
let bot = null; // { id, username, name } once verified
|
|
@@ -83,7 +86,7 @@ export function createChannelService({
|
|
|
83
86
|
let attempt = 0;
|
|
84
87
|
let stopped = true; // deliberate stop — suppresses the restart
|
|
85
88
|
|
|
86
|
-
const savePairing = () => writeJson(pairingFile, pairing.toJSON());
|
|
89
|
+
const savePairing = () => writeJson(pairingFile, pairing ? pairing.toJSON() : {});
|
|
87
90
|
const saveSettings = () => writeJson(configFile, settings);
|
|
88
91
|
|
|
89
92
|
async function readToken() {
|
|
@@ -100,7 +103,14 @@ export function createChannelService({
|
|
|
100
103
|
|
|
101
104
|
async function load() {
|
|
102
105
|
await mkdir(dataDir, { recursive: true });
|
|
103
|
-
|
|
106
|
+
// Built ONCE and never replaced. spawnLoop() hands this exact object to the adapter, which
|
|
107
|
+
// holds it for the life of a polling loop — so rebuilding it here (as every service call
|
|
108
|
+
// used to) broke pairing in both directions at once: `pair()` minted the code into a fresh
|
|
109
|
+
// store the adapter could not see, so every redeem answered "unknown or expired code" no
|
|
110
|
+
// matter how many codes you generated; and `savePairing()` serialises whichever store this
|
|
111
|
+
// variable currently points at, so a redeem that DID land would have been persisted from
|
|
112
|
+
// the wrong object. Two aliases of one thing is the bug — there is only ever one store.
|
|
113
|
+
if (!pairing) pairing = createPairingStore(await readJson(pairingFile, {}), { now });
|
|
104
114
|
settings = { ...DEFAULT_SETTINGS, ...(await readJson(configFile, {})) };
|
|
105
115
|
if (!appender) appender = await createEventLog({ file: path.join(dataDir, 'events.jsonl'), host: 'channel' });
|
|
106
116
|
}
|
|
@@ -110,7 +120,7 @@ export function createChannelService({
|
|
|
110
120
|
function spawnLoop(botToken) {
|
|
111
121
|
controller = new AbortController();
|
|
112
122
|
running = true;
|
|
113
|
-
const done =
|
|
123
|
+
const done = startAdapter({
|
|
114
124
|
botToken,
|
|
115
125
|
baseUrl: bridge.baseUrl,
|
|
116
126
|
token: bridge.token,
|
package/src/server.js
CHANGED
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
// Hardcoded (not read from package.json) so it survives Bun's single-file
|
|
68
68
|
// --compile, where package.json isn't on a readable FS. CI fails the publish if
|
|
69
69
|
// this drifts from package.json, so the two can't silently diverge.
|
|
70
|
-
const VERSION = '0.11.
|
|
70
|
+
const VERSION = '0.11.3';
|
|
71
71
|
const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
|
|
72
72
|
const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
|
|
73
73
|
|
package/src/update.js
CHANGED
|
@@ -78,7 +78,20 @@ export async function checkForUpdate(current, { force = false } = {}) {
|
|
|
78
78
|
|
|
79
79
|
let latest = null;
|
|
80
80
|
let assetUrl = null;
|
|
81
|
+
// Whether `latest` is a real answer or yesterday's. GitHub's unauthenticated API is 60
|
|
82
|
+
// requests/hour per IP, and a developer machine burns that easily — so the fallback below
|
|
83
|
+
// is not rare, it fires regularly. Saying WHICH it is matters: acting on a stale "latest"
|
|
84
|
+
// is how a forced check reported "already on the latest version" minutes after a newer one
|
|
85
|
+
// was published, and how an earlier update landed on a version that was not the newest and
|
|
86
|
+
// called it a success.
|
|
87
|
+
let stale = false;
|
|
88
|
+
let error = '';
|
|
81
89
|
const cache = await readCache();
|
|
90
|
+
const fallBackToCache = () => {
|
|
91
|
+
latest = cache?.latest || null;
|
|
92
|
+
assetUrl = cache?.assetUrl || null;
|
|
93
|
+
stale = true;
|
|
94
|
+
};
|
|
82
95
|
if (!force && cache && Date.now() - cache.checkedAt < CHECK_EVERY_MS) {
|
|
83
96
|
latest = cache.latest;
|
|
84
97
|
assetUrl = cache.assetUrl;
|
|
@@ -91,12 +104,12 @@ export async function checkForUpdate(current, { force = false } = {}) {
|
|
|
91
104
|
assetUrl = want ? (data.assets || []).find((a) => a.name === want)?.browser_download_url || null : null;
|
|
92
105
|
await writeCache({ checkedAt: Date.now(), latest, assetUrl });
|
|
93
106
|
} else {
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
error = res.status === 403 ? 'GitHub rate limit (60/hour per IP) — try again later' : `HTTP ${res.status}`;
|
|
108
|
+
fallBackToCache();
|
|
96
109
|
}
|
|
97
|
-
} catch {
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
} catch (e) {
|
|
111
|
+
error = e?.message || String(e);
|
|
112
|
+
fallBackToCache();
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
const updateAvailable = !!latest && cmp(latest, current) > 0;
|
|
@@ -110,6 +123,8 @@ export async function checkForUpdate(current, { force = false } = {}) {
|
|
|
110
123
|
mode,
|
|
111
124
|
canSelfUpdate,
|
|
112
125
|
assetUrl,
|
|
126
|
+
stale,
|
|
127
|
+
error,
|
|
113
128
|
npmCommand: mode === 'npm' ? 'npm i -g @chatpanel/bridge@latest' : null,
|
|
114
129
|
};
|
|
115
130
|
}
|
|
@@ -122,10 +137,16 @@ export async function selfUpdate(current) {
|
|
|
122
137
|
throw new Error('Self-update applies only to the standalone binary. Update the npm/npx version with npm.');
|
|
123
138
|
}
|
|
124
139
|
const info = await checkForUpdate(current, { force: true });
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
// A forced check that fell back to the cache has not checked anything. Acting on it is how
|
|
141
|
+
// an update reported success while installing a version that was already superseded, and how
|
|
142
|
+
// it later refused with "already on the latest version" while a newer one sat published.
|
|
143
|
+
// Say what actually happened and leave the binary alone.
|
|
144
|
+
if (info.stale) {
|
|
145
|
+
throw new Error(`Could not reach the update server${info.error ? ` (${info.error})` : ''}. `
|
|
146
|
+
+ 'Refusing to act on a stale check — try again in a few minutes.');
|
|
128
147
|
}
|
|
148
|
+
if (!info.assetUrl) throw new Error('No downloadable build for this platform — use `npx @chatpanel/bridge`.');
|
|
149
|
+
if (!info.updateAvailable) throw new Error(`Already on the latest version (v${current}).`);
|
|
129
150
|
|
|
130
151
|
const target = process.execPath; // the running binary's own path
|
|
131
152
|
const dir = path.dirname(target);
|