@dmsdc-ai/aigentry-telepty 0.1.52 → 0.1.53

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.
@@ -1,6 +1,6 @@
1
1
  # Telepty Bus Event Schema Standard
2
2
 
3
- Version: 1.0 (2026-03-15)
3
+ Version: 2.0 (2026-03-15)
4
4
  Agreed by: telepty, deliberation, devkit, brain, orchestrator
5
5
 
6
6
  ## Transport
@@ -13,9 +13,11 @@ Agreed by: telepty, deliberation, devkit, brain, orchestrator
13
13
 
14
14
  ```json
15
15
  {
16
+ "version": 1,
16
17
  "message_id": "string (UUID or prefixed ID)",
17
18
  "kind": "string (event type)",
18
19
  "source": "string (sender identifier)",
20
+ "source_host": "string (machine_id of sender, e.g. hostname or Tailscale IP)",
19
21
  "target": "string | null (target session ID, optional @host suffix)",
20
22
  "ts": "ISO 8601 timestamp"
21
23
  }
@@ -25,12 +27,38 @@ Agreed by: telepty, deliberation, devkit, brain, orchestrator
25
27
 
26
28
  | Field | Type | Description |
27
29
  |-------|------|-------------|
30
+ | `version` | number | Envelope schema version (currently 1) |
28
31
  | `kind` | string | Event type (NOT `type` — `kind` is canonical) |
29
32
  | `target` | string | Target telepty session ID. May include `@host` suffix for remote |
30
33
  | `source` | string | Sender identifier (format: `project:session_id`) |
34
+ | `source_host` | string | Machine ID of sender (hostname or TELEPTY_MACHINE_ID) |
31
35
  | `message_id` | string | Unique message identifier |
32
36
  | `ts` | string | ISO 8601 timestamp |
33
37
 
38
+ ## Cross-Machine Addressing
39
+
40
+ ### Session Locator
41
+ Every session is uniquely identified by a locator triple:
42
+ ```json
43
+ { "machine_id": "hostname", "session_id": "aigentry-devkit-001", "project_id": "aigentry-devkit" }
44
+ ```
45
+
46
+ ### Remote Target Format
47
+ `target` field supports `@host` suffix: `aigentry-devkit-001@100.100.100.5`
48
+ - Router strips suffix, resolves session on local daemon
49
+ - For cross-machine relay (P3), daemon forwards to target host
50
+
51
+ ### Machine ID
52
+ - Default: `os.hostname()`
53
+ - Override: `TELEPTY_MACHINE_ID` env var
54
+ - Exposed in: `GET /api/meta` (`machine_id` field), session `locator` object, bus event `source_host`
55
+
56
+ ### Peer Auth
57
+ - Localhost: always trusted
58
+ - Tailscale (100.x.y.z): trusted by default
59
+ - Custom peers: `TELEPTY_PEER_ALLOWLIST=ip1,ip2` env var
60
+ - All others: require `x-telepty-token` header
61
+
34
62
  ## Routable Events (Auto-Router)
35
63
 
36
64
  ### `turn_request`
package/daemon.js CHANGED
@@ -10,6 +10,7 @@ const { claimDaemonState, clearDaemonState } = require('./daemon-control');
10
10
 
11
11
  const config = getConfig();
12
12
  const EXPECTED_TOKEN = config.authToken;
13
+ const MACHINE_ID = process.env.TELEPTY_MACHINE_ID || os.hostname();
13
14
  const fs = require('fs');
14
15
  const SESSION_PERSIST_PATH = require('path').join(os.homedir(), '.config', 'aigentry-telepty', 'sessions.json');
15
16
 
@@ -35,13 +36,28 @@ const app = express();
35
36
  app.use(cors());
36
37
  app.use(express.json());
37
38
 
39
+ // Peer allowlist: comma-separated IPs/CIDRs in TELEPTY_PEER_ALLOWLIST env
40
+ const PEER_ALLOWLIST = (process.env.TELEPTY_PEER_ALLOWLIST || '').split(',').map(s => s.trim()).filter(Boolean);
41
+
42
+ function isAllowedPeer(ip) {
43
+ if (!ip) return false;
44
+ const cleanIp = ip.replace('::ffff:', '');
45
+ // Localhost always allowed
46
+ if (cleanIp === '127.0.0.1' || ip === '::1') return true;
47
+ // Tailscale range (100.x.y.z)
48
+ if (cleanIp.startsWith('100.')) return true;
49
+ // Peer allowlist
50
+ if (PEER_ALLOWLIST.length > 0) return PEER_ALLOWLIST.includes(cleanIp);
51
+ // No allowlist = allow all authenticated
52
+ return false;
53
+ }
54
+
38
55
  // Authentication Middleware
39
56
  app.use((req, res, next) => {
40
- const isLocalhost = req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
41
- const isTailscale = req.ip && req.ip.startsWith('100.');
42
-
43
- if (isLocalhost || isTailscale) {
44
- return next(); // Trust local and Tailscale networks
57
+ const clientIp = req.ip;
58
+
59
+ if (isAllowedPeer(clientIp)) {
60
+ return next(); // Trust local, Tailscale, and allowlisted peers
45
61
  }
46
62
 
47
63
  const token = req.headers['x-telepty-token'] || req.query.token;
@@ -49,7 +65,7 @@ app.use((req, res, next) => {
49
65
  return next();
50
66
  }
51
67
 
52
- console.warn(`[AUTH] Rejected unauthorized request from ${req.ip}`);
68
+ console.warn(`[AUTH] Rejected unauthorized request from ${clientIp}`);
53
69
  res.status(401).json({ error: 'Unauthorized: Invalid or missing token.' });
54
70
  });
55
71
 
@@ -295,8 +311,10 @@ app.get('/api/sessions', (req, res) => {
295
311
  const now = Date.now();
296
312
  let list = Object.entries(sessions).map(([id, session]) => {
297
313
  const idleSeconds = session.lastActivityAt ? Math.floor((now - new Date(session.lastActivityAt).getTime()) / 1000) : null;
314
+ const projectId = session.cwd ? session.cwd.split('/').pop() : null;
298
315
  return {
299
316
  id,
317
+ locator: { machine_id: MACHINE_ID, session_id: id, project_id: projectId },
300
318
  type: session.type || 'spawned',
301
319
  command: session.command,
302
320
  cwd: session.cwd,
@@ -318,8 +336,10 @@ app.get('/api/sessions/:id', (req, res) => {
318
336
  if (!resolvedId) return res.status(404).json({ error: 'Session not found' });
319
337
  const session = sessions[resolvedId];
320
338
  const idleSeconds = session.lastActivityAt ? Math.floor((Date.now() - new Date(session.lastActivityAt).getTime()) / 1000) : null;
339
+ const projectId = session.cwd ? session.cwd.split('/').pop() : null;
321
340
  res.json({
322
341
  id: resolvedId,
342
+ locator: { machine_id: MACHINE_ID, session_id: resolvedId, project_id: projectId },
323
343
  alias: requestedId !== resolvedId ? requestedId : null,
324
344
  type: session.type || 'spawned',
325
345
  command: session.command,
@@ -340,6 +360,7 @@ app.get('/api/meta', (req, res) => {
340
360
  pid: process.pid,
341
361
  host: HOST,
342
362
  port: Number(PORT),
363
+ machine_id: MACHINE_ID,
343
364
  capabilities: ['sessions', 'wrapped-sessions', 'skill-installer', 'singleton-daemon', 'handoff-inbox', 'deliberation-threads']
344
365
  });
345
366
  });
@@ -924,6 +945,7 @@ function busAutoRoute(msg) {
924
945
  type: 'inject_written',
925
946
  inject_id,
926
947
  sender: 'daemon',
948
+ source_host: MACHINE_ID,
927
949
  target_agent: targetId,
928
950
  source_type: 'bus_auto_route',
929
951
  delivered,
@@ -1379,10 +1401,7 @@ server.on('upgrade', (req, socket, head) => {
1379
1401
  const url = new URL(req.url, 'http://' + req.headers.host);
1380
1402
  const token = url.searchParams.get('token');
1381
1403
 
1382
- const isLocalhost = req.socket.remoteAddress === '127.0.0.1' || req.socket.remoteAddress === '::1' || req.socket.remoteAddress === '::ffff:127.0.0.1';
1383
- const isTailscale = req.socket.remoteAddress && req.socket.remoteAddress.startsWith('100.');
1384
-
1385
- if (!isLocalhost && !isTailscale && token !== EXPECTED_TOKEN) {
1404
+ if (!isAllowedPeer(req.socket.remoteAddress) && token !== EXPECTED_TOKEN) {
1386
1405
  socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
1387
1406
  socket.destroy();
1388
1407
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",