@chatpanel/bridge 0.10.42 → 0.11.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/package.json +6 -2
- package/scripts/sync-channels.mjs +93 -0
- package/scripts/sync-events.mjs +9 -1
- package/scripts/sync-pii.mjs +71 -0
- package/src/channels/adapters/telegram.js +248 -0
- package/src/channels/bridge.js +59 -0
- package/src/channels/eventlog.js +57 -0
- package/src/channels/invoke.js +172 -0
- package/src/channels/normalize.js +58 -0
- package/src/channels/pairing.js +80 -0
- package/src/channels/service.js +270 -0
- package/src/channels/stream.js +86 -0
- package/src/engines/claude.js +41 -1
- package/src/events/capability.js +134 -0
- package/src/events/event.js +183 -0
- package/src/events/reach.js +31 -0
- package/src/events/ref.js +60 -0
- package/src/events/view.js +96 -0
- package/src/pii/index.js +29 -0
- package/src/pii/net.js +111 -0
- package/src/pii/pii-detect.js +177 -0
- package/src/pii/pii-redact.js +399 -0
- package/src/pii/pipeline.js +137 -0
- package/src/pii/sanitize.js +179 -0
- package/src/pii/tool-harness.js +152 -0
- package/src/pii/tool-rank.js +110 -0
- package/src/sanitize.js +6 -136
- package/src/server.js +86 -1
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.
|
|
70
|
+
const VERSION = '0.11.1';
|
|
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
|
|
|
@@ -244,6 +244,57 @@ function ensureToken() {
|
|
|
244
244
|
log('error', `could not initialise bridge token: ${e?.message || e}`);
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// CHANNELS — a messaging surface (Telegram today) driving a local agent.
|
|
249
|
+
//
|
|
250
|
+
// Hosted HERE because a channel has to be running when nobody is looking: the point is to
|
|
251
|
+
// reach your machine from a phone with the browser closed, and the bridge is the only
|
|
252
|
+
// always-on local process a ChatPanel user already has. The alternative — a second daemon, or
|
|
253
|
+
// an npm install, or a service worker Chrome suspends — is another thing a non-technical
|
|
254
|
+
// person has to install and keep alive, which is the same as not shipping it.
|
|
255
|
+
//
|
|
256
|
+
// The bridge owns transport and auth; @chatpanel/channels (vendored to src/channels) owns the
|
|
257
|
+
// contract: verify a bot, hold its token 0600, enroll a phone by one-time code, cap it with
|
|
258
|
+
// `reach`, redact both ways. Loaded lazily so the redaction engine never touches a boot where
|
|
259
|
+
// no channel is configured.
|
|
260
|
+
let channelsSvc = null;
|
|
261
|
+
async function channelService() {
|
|
262
|
+
if (channelsSvc) return channelsSvc;
|
|
263
|
+
const { createChannelService } = await import('./channels/service.js');
|
|
264
|
+
channelsSvc = createChannelService({
|
|
265
|
+
home: join(os.homedir(), '.chatpanel'),
|
|
266
|
+
dataDir: join(os.homedir(), '.chatpanel', 'channels'),
|
|
267
|
+
// It talks to THIS bridge as a privileged local client — same port, same token it just
|
|
268
|
+
// read. No second address to configure and get wrong.
|
|
269
|
+
bridge: { baseUrl: `http://127.0.0.1:${PORT}`, token: AUTH_TOKEN },
|
|
270
|
+
logger: { log: (m) => log('info', m), warn: (m) => log('info', m), error: (m) => log('error', m) },
|
|
271
|
+
});
|
|
272
|
+
return channelsSvc;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function handleChannels(req, res, action) {
|
|
276
|
+
try {
|
|
277
|
+
const svc = await channelService();
|
|
278
|
+
const body = req.method === 'POST' ? await readBody(req) : {};
|
|
279
|
+
if (action === 'status') return json(res, 200, await svc.status());
|
|
280
|
+
if (action === 'connect') {
|
|
281
|
+
// The token is verified with Telegram before it is written, so a typo fails HERE, in the
|
|
282
|
+
// settings screen, with a reason — not later as a silent poll loop nobody reads the logs of.
|
|
283
|
+
const r = await svc.connect(body);
|
|
284
|
+
return json(res, 200, { ok: true, ...r });
|
|
285
|
+
}
|
|
286
|
+
if (action === 'pair') return json(res, 200, await svc.pair());
|
|
287
|
+
if (action === 'unpair') return json(res, 200, await svc.unpair(String(body.actorId || '')));
|
|
288
|
+
if (action === 'settings') return json(res, 200, await svc.update(body));
|
|
289
|
+
if (action === 'disconnect') return json(res, 200, await svc.stop({ forget: !!body.forget }));
|
|
290
|
+
return json(res, 404, { error: 'unknown channel action' });
|
|
291
|
+
} catch (e) {
|
|
292
|
+
// A readable reason, because every one of these is something a person can fix: a bad token,
|
|
293
|
+
// a bot not created yet, no network.
|
|
294
|
+
return json(res, 400, { error: e?.message || String(e) });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
247
298
|
function tokenOk(req) {
|
|
248
299
|
if (!AUTH_TOKEN) return false;
|
|
249
300
|
const h = String(req.headers['authorization'] || '');
|
|
@@ -278,7 +329,26 @@ const PRIVILEGED_POST = new Set([
|
|
|
278
329
|
// endpoint that touches a run is already guarded. An unauthenticated hole next to nine
|
|
279
330
|
// guarded neighbours is a hole regardless of how little it grants.
|
|
280
331
|
'/cancel',
|
|
332
|
+
// A channel is a way into this machine from the internet. Everything that configures one —
|
|
333
|
+
// and the code that enrolls a phone — is as privileged as /chat itself.
|
|
334
|
+
'/channels/connect',
|
|
335
|
+
'/channels/pair',
|
|
336
|
+
'/channels/unpair',
|
|
337
|
+
'/channels/settings',
|
|
338
|
+
'/channels/disconnect',
|
|
281
339
|
]);
|
|
340
|
+
// /channels is NOT here, for the same reason /skills is not, and it was a regression to add
|
|
341
|
+
// it: a privileged GET is unreachable from the extension. The panel holds `<all_urls>`, so
|
|
342
|
+
// its fetches bypass CORS altogether — no preflight fires — and the Fetch spec attaches
|
|
343
|
+
// `Origin` only to requests whose method is not GET or HEAD. That is the whole asymmetry:
|
|
344
|
+
// every privileged POST here works, and a privileged GET can only ever answer the settings
|
|
345
|
+
// page with "forbidden: this endpoint requires the ChatPanel extension or a valid bridge
|
|
346
|
+
// token", which is what shipped in 0.11.0.
|
|
347
|
+
//
|
|
348
|
+
// Nothing is opened up by removing it. `originAllowed` still refuses any web page (it is the
|
|
349
|
+
// check doing the work), so the only caller admitted is a local no-Origin process — which can
|
|
350
|
+
// already read ~/.chatpanel/channels/ off the disk. /debug stays privileged: it exposes
|
|
351
|
+
// configuration a local process cannot otherwise see.
|
|
282
352
|
const PRIVILEGED_GET = new Set(['/debug']);
|
|
283
353
|
|
|
284
354
|
// /skills* is NOT privileged, and that is a considered position rather than a
|
|
@@ -1123,6 +1193,10 @@ const server = createServer(async (req, res) => {
|
|
|
1123
1193
|
if (req.method === 'GET') { res.writeHead(405); return res.end(); } // no server-initiated stream
|
|
1124
1194
|
if (req.method === 'DELETE') { deleteSession(sid); res.writeHead(204); return res.end(); }
|
|
1125
1195
|
}
|
|
1196
|
+
if (req.method === 'GET' && url.pathname === '/channels') return handleChannels(req, res, 'status');
|
|
1197
|
+
if (req.method === 'POST' && url.pathname.startsWith('/channels/')) {
|
|
1198
|
+
return handleChannels(req, res, url.pathname.slice('/channels/'.length));
|
|
1199
|
+
}
|
|
1126
1200
|
if (req.method === 'POST' && url.pathname === '/cancel') return handleCancel(req, res);
|
|
1127
1201
|
if (req.method === 'POST' && url.pathname === '/tool-result') return handleToolResult(req, res);
|
|
1128
1202
|
if (req.method === 'POST' && url.pathname === '/mcp-local') return handleMcpLocal(req, res);
|
|
@@ -1237,6 +1311,17 @@ function startServer() {
|
|
|
1237
1311
|
log('info', ` ${a.ok ? '✓' : '✕'} ${label}${a.ok ? '' : ' — ' + (a.reason || 'unavailable')}`);
|
|
1238
1312
|
}
|
|
1239
1313
|
log('info', 'Open the ChatPanel side panel; installed agents (Claude Code, Codex, Antigravity) appear automatically.');
|
|
1314
|
+
// A channel someone connected last week must come back by itself after a reboot — nobody
|
|
1315
|
+
// is at the keyboard to press start, which is the entire premise of driving this from a
|
|
1316
|
+
// phone. Nothing loads and nothing runs until a bot has actually been connected.
|
|
1317
|
+
channelService()
|
|
1318
|
+
.then((svc) => svc.startIfConfigured())
|
|
1319
|
+
.then((r) => {
|
|
1320
|
+
if (r?.skipped) return;
|
|
1321
|
+
if (r?.ok) log('info', 'channels: telegram connected — polling for messages');
|
|
1322
|
+
else log('error', `channels: telegram not started — ${r?.error || 'unknown error'}`);
|
|
1323
|
+
})
|
|
1324
|
+
.catch((e) => log('error', `channels: ${e?.message || e}`));
|
|
1240
1325
|
});
|
|
1241
1326
|
}
|
|
1242
1327
|
|