@dmsdc-ai/aigentry-telepty 0.1.53 → 0.1.55
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/BUS_EVENT_SCHEMA.md +7 -0
- package/cli.js +7 -0
- package/daemon.js +61 -1
- package/package.json +1 -1
package/BUS_EVENT_SCHEMA.md
CHANGED
|
@@ -53,6 +53,13 @@ Every session is uniquely identified by a locator triple:
|
|
|
53
53
|
- Override: `TELEPTY_MACHINE_ID` env var
|
|
54
54
|
- Exposed in: `GET /api/meta` (`machine_id` field), session `locator` object, bus event `source_host`
|
|
55
55
|
|
|
56
|
+
### Global Session ID Uniqueness (P4)
|
|
57
|
+
- Convention: `{project}-{NNN}` (e.g. `aigentry-devkit-001`)
|
|
58
|
+
- Cross-machine uniqueness: guaranteed by `locator.machine_id` prefix in bus events
|
|
59
|
+
- Collision resolution: `resolveSessionAlias` returns local session only; remote sessions discovered via `source_host` field
|
|
60
|
+
- If two machines have `aigentry-devkit-001`, inject uses `target@host` to disambiguate
|
|
61
|
+
- Short form `aigentry-devkit-001` resolves to LOCAL session; remote requires explicit `@host`
|
|
62
|
+
|
|
56
63
|
### Peer Auth
|
|
57
64
|
- Localhost: always trusted
|
|
58
65
|
- Tailscale (100.x.y.z): trusted by default
|
package/cli.js
CHANGED
|
@@ -208,6 +208,13 @@ function getDiscoveryHosts() {
|
|
|
208
208
|
.filter(Boolean);
|
|
209
209
|
extraHosts.forEach((host) => hosts.add(host));
|
|
210
210
|
|
|
211
|
+
// Also include relay peers for cross-machine session discovery
|
|
212
|
+
const relayPeers = String(process.env.TELEPTY_RELAY_PEERS || '')
|
|
213
|
+
.split(',')
|
|
214
|
+
.map((host) => host.trim())
|
|
215
|
+
.filter(Boolean);
|
|
216
|
+
relayPeers.forEach((host) => hosts.add(host));
|
|
217
|
+
|
|
211
218
|
if (REMOTE_HOST && REMOTE_HOST !== '127.0.0.1') {
|
|
212
219
|
return Array.from(hosts);
|
|
213
220
|
}
|
package/daemon.js
CHANGED
|
@@ -39,6 +39,54 @@ app.use(express.json());
|
|
|
39
39
|
// Peer allowlist: comma-separated IPs/CIDRs in TELEPTY_PEER_ALLOWLIST env
|
|
40
40
|
const PEER_ALLOWLIST = (process.env.TELEPTY_PEER_ALLOWLIST || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
41
41
|
|
|
42
|
+
// Cross-machine bus relay: forward bus events to peer daemons
|
|
43
|
+
const RELAY_PEERS = (process.env.TELEPTY_RELAY_PEERS || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
44
|
+
const RELAY_SEEN = new Set(); // dedup by message_id
|
|
45
|
+
|
|
46
|
+
function relayToPeers(msg) {
|
|
47
|
+
if (RELAY_PEERS.length === 0) return;
|
|
48
|
+
if (!msg.message_id) msg.message_id = crypto.randomUUID();
|
|
49
|
+
if (RELAY_SEEN.has(msg.message_id)) return; // already relayed
|
|
50
|
+
RELAY_SEEN.add(msg.message_id);
|
|
51
|
+
// Prevent unbounded growth
|
|
52
|
+
if (RELAY_SEEN.size > 10000) {
|
|
53
|
+
const arr = [...RELAY_SEEN];
|
|
54
|
+
arr.splice(0, 5000);
|
|
55
|
+
RELAY_SEEN.clear();
|
|
56
|
+
arr.forEach(id => RELAY_SEEN.add(id));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
msg.source_host = msg.source_host || MACHINE_ID;
|
|
60
|
+
msg._relayed_from = MACHINE_ID;
|
|
61
|
+
|
|
62
|
+
for (const peer of RELAY_PEERS) {
|
|
63
|
+
fetch(`http://${peer}:${PORT}/api/bus/publish`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json', 'x-telepty-token': EXPECTED_TOKEN },
|
|
66
|
+
body: JSON.stringify(msg),
|
|
67
|
+
signal: AbortSignal.timeout(3000)
|
|
68
|
+
}).catch(() => {}); // fire-and-forget
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// JWT auth: set TELEPTY_JWT_SECRET to enable. Tokens in Authorization: Bearer <token>
|
|
73
|
+
const JWT_SECRET = process.env.TELEPTY_JWT_SECRET || null;
|
|
74
|
+
|
|
75
|
+
function verifyJwt(token) {
|
|
76
|
+
if (!JWT_SECRET || !token) return false;
|
|
77
|
+
try {
|
|
78
|
+
// Simple HS256 JWT verification (no external deps)
|
|
79
|
+
const [headerB64, payloadB64, sigB64] = token.split('.');
|
|
80
|
+
if (!headerB64 || !payloadB64 || !sigB64) return false;
|
|
81
|
+
const expected = crypto.createHmac('sha256', JWT_SECRET)
|
|
82
|
+
.update(`${headerB64}.${payloadB64}`).digest('base64url');
|
|
83
|
+
if (sigB64 !== expected) return false;
|
|
84
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
|
|
85
|
+
if (payload.exp && Date.now() / 1000 > payload.exp) return false;
|
|
86
|
+
return payload;
|
|
87
|
+
} catch { return false; }
|
|
88
|
+
}
|
|
89
|
+
|
|
42
90
|
function isAllowedPeer(ip) {
|
|
43
91
|
if (!ip) return false;
|
|
44
92
|
const cleanIp = ip.replace('::ffff:', '');
|
|
@@ -65,6 +113,12 @@ app.use((req, res, next) => {
|
|
|
65
113
|
return next();
|
|
66
114
|
}
|
|
67
115
|
|
|
116
|
+
// JWT Bearer token
|
|
117
|
+
const authHeader = req.headers['authorization'] || '';
|
|
118
|
+
if (authHeader.startsWith('Bearer ') && verifyJwt(authHeader.slice(7))) {
|
|
119
|
+
return next();
|
|
120
|
+
}
|
|
121
|
+
|
|
68
122
|
console.warn(`[AUTH] Rejected unauthorized request from ${clientIp}`);
|
|
69
123
|
res.status(401).json({ error: 'Unauthorized: Invalid or missing token.' });
|
|
70
124
|
});
|
|
@@ -976,6 +1030,8 @@ app.post('/api/bus/publish', (req, res) => {
|
|
|
976
1030
|
|
|
977
1031
|
// Auto-route if this is a turn_request
|
|
978
1032
|
busAutoRoute(payload);
|
|
1033
|
+
// Relay to peer daemons (dedup prevents loops)
|
|
1034
|
+
if (!payload._relayed_from) relayToPeers(payload);
|
|
979
1035
|
|
|
980
1036
|
res.json({ success: true, delivered: deliveredCount });
|
|
981
1037
|
});
|
|
@@ -1386,6 +1442,8 @@ busWss.on('connection', (ws, req) => {
|
|
|
1386
1442
|
|
|
1387
1443
|
// Auto-route turn_request events (shared logic with HTTP publish)
|
|
1388
1444
|
busAutoRoute(msg);
|
|
1445
|
+
// Relay to peer daemons (dedup prevents loops)
|
|
1446
|
+
if (!msg._relayed_from) relayToPeers(msg);
|
|
1389
1447
|
} catch (e) {
|
|
1390
1448
|
console.error('[BUS] Invalid message format', e);
|
|
1391
1449
|
}
|
|
@@ -1401,7 +1459,9 @@ server.on('upgrade', (req, socket, head) => {
|
|
|
1401
1459
|
const url = new URL(req.url, 'http://' + req.headers.host);
|
|
1402
1460
|
const token = url.searchParams.get('token');
|
|
1403
1461
|
|
|
1404
|
-
|
|
1462
|
+
const wsAuthHeader = req.headers['authorization'] || '';
|
|
1463
|
+
const wsJwtValid = wsAuthHeader.startsWith('Bearer ') && verifyJwt(wsAuthHeader.slice(7));
|
|
1464
|
+
if (!isAllowedPeer(req.socket.remoteAddress) && token !== EXPECTED_TOKEN && !wsJwtValid) {
|
|
1405
1465
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
1406
1466
|
socket.destroy();
|
|
1407
1467
|
return;
|