@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.
- package/BUS_EVENT_SCHEMA.md +29 -1
- package/daemon.js +29 -10
- package/package.json +1 -1
package/BUS_EVENT_SCHEMA.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Telepty Bus Event Schema Standard
|
|
2
2
|
|
|
3
|
-
Version:
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 ${
|
|
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
|
-
|
|
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;
|