@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.
@@ -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
- if (!isAllowedPeer(req.socket.remoteAddress) && token !== EXPECTED_TOKEN) {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",