@dmsdc-ai/aigentry-telepty 0.0.19 → 0.1.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.
@@ -0,0 +1 @@
1
+ {"topic": "Critique and verify the Foreground-Only Session Lifecycle for telepty. The user proposes that all AI workers/sessions MUST run in visible, physical terminal windows. When the user closes the window, the session dies. No invisible background sessions exist. What are the edge cases, UX flaws, or technical limitations of this approach?", "speakers": ["claude", "gemini", "codex"], "role_preset": "critic"}
@@ -0,0 +1 @@
1
+ {"topic": "Re-evaluate the telepty architecture based on the user`s strict boundaries: 1. A session equals a physical, visible terminal window. If the window closes, the session MUST die. No zombies. 2. Background persistence is NOT telepty`s job (users can use tmux if they want, but telepty doesn`t manage it). 3. Unexpected disconnects (network drop, sleep) are treated as session death, and recovery is handled via a higher-level `Handoff` of context to a new session, NOT by keeping zombie processes alive. Double-check this philosophy for flaws, focusing on how AI agents will communicate (Pub/Sub) within this strict ephemeral foreground model.", "speakers": ["claude", "codex", "gemini"], "role_preset": "review"}
@@ -0,0 +1 @@
1
+ {"topic": "Final architectural verification of the Telepty Boundary Model. The user asserts a hard line on responsibility: 1. If a session/process dies, context is lost. That is the CLI/Terminal`s problem, not telepty`s. 2. If a Pub message is sent to a dead Sub, it is lost. Telepty just drops it or relies on Ack. If the sub reconnects in time, it gets it; if not, drop it. 3. If Wi-Fi drops, CLI dies, agent dies, scripts die. Telepty does not care about split-brain or script recovery. Is this stateless, hyper-minimalist `dumb pipe` philosophy technically viable for our goals?", "speakers": ["claude", "gemini", "codex"], "role_preset": "consensus"}
@@ -4,7 +4,7 @@
4
4
  Help the user interact with the `telepty` daemon, check their current session ID, list active sessions, and inject commands into remote or local PTY sessions.
5
5
 
6
6
  **Trigger:**
7
- When the user asks about their current session ID (e.g. "내 세션 ID가 뭐야?"), wants to check active sessions ("세션 목록 보여줘"), or wants to inject a prompt/command into a specific session ("dustcraw한테 메시지 보내줘").
7
+ When the user asks about their current session ID (e.g. "내 세션 ID가 뭐야?"), wants to check active sessions ("세션 목록 보여줘"), wants to inject a prompt/command into a specific session ("dustcraw한테 메시지 보내줘"), or wants to update telepty to the latest version ("업데이트 해줘").
8
8
 
9
9
  **Instructions:**
10
10
  1. **To check the current session ID:**
@@ -17,3 +17,5 @@ When the user asks about their current session ID (e.g. "내 세션 ID가 뭐야
17
17
  - For a single session: Run `telepty inject <target_session_id> "<message or command>"`.
18
18
  - For broadcasting to ALL active sessions: Run `telepty broadcast "<message or command>"`.
19
19
  - For multicasting to multiple specific sessions: Run `telepty multicast <id1>,<id2> "<message or command>"`.
20
+ 4. **To update telepty:**
21
+ - Run `telepty update`.
package/daemon.js CHANGED
@@ -178,25 +178,34 @@ app.delete('/api/sessions/:id', (req, res) => {
178
178
  }
179
179
  });
180
180
 
181
+ app.post('/api/bus/publish', (req, res) => {
182
+ const payload = req.body;
183
+
184
+ if (!payload || typeof payload !== 'object') {
185
+ return res.status(400).json({ error: 'Payload must be a JSON object' });
186
+ }
187
+
188
+ let deliveredCount = 0;
189
+
190
+ busClients.forEach(client => {
191
+ if (client.readyState === 1) { // WebSocket.OPEN
192
+ client.send(JSON.stringify(payload));
193
+ deliveredCount++;
194
+ }
195
+ });
196
+
197
+ res.json({ success: true, delivered: deliveredCount });
198
+ });
199
+
181
200
  const server = app.listen(PORT, HOST, () => {
182
201
  console.log(`🚀 aigentry-telepty daemon listening on http://${HOST}:${PORT}`);
183
202
  });
184
203
 
185
- const wss = new WebSocketServer({ server });
186
204
 
187
- wss.on('connection', (ws, req) => {
188
- const isLocalhost = req.socket.remoteAddress === '127.0.0.1' || req.socket.remoteAddress === '::1' || req.socket.remoteAddress === '::ffff:127.0.0.1';
189
- const isTailscale = req.socket.remoteAddress && req.socket.remoteAddress.startsWith('100.');
190
-
191
- const url = new URL(req.url, `http://${req.headers.host}`);
192
- const token = url.searchParams.get('token');
193
-
194
- if (!isLocalhost && !isTailscale && token !== EXPECTED_TOKEN) {
195
- console.warn(`[WS-AUTH] Rejected unauthorized WebSocket from ${req.socket.remoteAddress}`);
196
- ws.close(1008, 'Unauthorized');
197
- return;
198
- }
205
+ const wss = new WebSocketServer({ noServer: true });
199
206
 
207
+ wss.on('connection', (ws, req) => {
208
+ const url = new URL(req.url, 'http://' + req.headers.host);
200
209
  const sessionId = url.pathname.split('/').pop();
201
210
  const session = sessions[sessionId];
202
211
 
@@ -206,7 +215,7 @@ wss.on('connection', (ws, req) => {
206
215
  }
207
216
 
208
217
  session.clients.add(ws);
209
- console.log(`[WS] Client attached to session ${sessionId} (Total: ${session.clients.size})`);
218
+ console.log(`[WS] Client attached to session \${sessionId} (Total: \${session.clients.size})`);
210
219
 
211
220
  ws.on('message', (message) => {
212
221
  try {
@@ -223,6 +232,59 @@ wss.on('connection', (ws, req) => {
223
232
 
224
233
  ws.on('close', () => {
225
234
  session.clients.delete(ws);
226
- console.log(`[WS] Client detached from session ${sessionId} (Total: ${session.clients.size})`);
235
+ console.log(`[WS] Client detached from session \${sessionId} (Total: \${session.clients.size})`);
236
+ });
237
+ });
238
+
239
+ const busWss = new WebSocketServer({ noServer: true });
240
+ const busClients = new Set();
241
+
242
+ busWss.on('connection', (ws, req) => {
243
+ busClients.add(ws);
244
+ console.log('[BUS] New agent connected to event bus');
245
+
246
+ ws.on('message', (message) => {
247
+ try {
248
+ const msg = JSON.parse(message);
249
+ // For MVP, simply broadcast any valid JSON message to all other bus clients
250
+ busClients.forEach(client => {
251
+ if (client !== ws && client.readyState === 1) {
252
+ client.send(JSON.stringify(msg));
253
+ }
254
+ });
255
+ } catch (e) {
256
+ console.error('[BUS] Invalid message format', e);
257
+ }
227
258
  });
259
+
260
+ ws.on('close', () => {
261
+ busClients.delete(ws);
262
+ console.log('[BUS] Agent disconnected from event bus');
263
+ });
264
+ });
265
+
266
+ server.on('upgrade', (req, socket, head) => {
267
+ const url = new URL(req.url, 'http://' + req.headers.host);
268
+ const token = url.searchParams.get('token');
269
+
270
+ const isLocalhost = req.socket.remoteAddress === '127.0.0.1' || req.socket.remoteAddress === '::1' || req.socket.remoteAddress === '::ffff:127.0.0.1';
271
+ const isTailscale = req.socket.remoteAddress && req.socket.remoteAddress.startsWith('100.');
272
+
273
+ if (!isLocalhost && !isTailscale && token !== EXPECTED_TOKEN) {
274
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
275
+ socket.destroy();
276
+ return;
277
+ }
278
+
279
+ if (url.pathname.startsWith('/api/sessions/')) {
280
+ wss.handleUpgrade(req, socket, head, (ws) => {
281
+ wss.emit('connection', ws, req);
282
+ });
283
+ } else if (url.pathname === '/api/bus') {
284
+ busWss.handleUpgrade(req, socket, head, (ws) => {
285
+ busWss.emit('connection', ws, req);
286
+ });
287
+ } else {
288
+ socket.destroy();
289
+ }
228
290
  });
package/mcp.js CHANGED
@@ -26,6 +26,14 @@ const tools = [
26
26
  broadcast: z.boolean().optional().describe('If true, injects the prompt into ALL active sessions on the remote daemon. Overrides session_ids.'),
27
27
  prompt: z.string().describe('Text to inject into stdin.')
28
28
  })
29
+ },
30
+ {
31
+ name: 'telepty_publish_bus_event',
32
+ description: 'Publish a structured JSON event to the telepty in-memory event bus. This is a fire-and-forget broadcast to any AI agents currently listening. If no agents are listening, the message is dropped.',
33
+ schema: z.object({
34
+ remote_url: z.string().describe('Tailscale IP/Host and port (e.g., 100.100.100.5:3848) of the remote daemon.'),
35
+ payload: z.record(z.any()).describe('The structured JSON payload to broadcast to the event bus. Must be an object.')
36
+ })
29
37
  }
30
38
  ];
31
39
 
@@ -83,6 +91,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
83
91
 
84
92
  return { content: [{ type: 'text', text: msg }] };
85
93
  }
94
+
95
+ if (name === 'telepty_publish_bus_event') {
96
+ const baseUrl = args.remote_url.startsWith('http') ? args.remote_url : `http://${args.remote_url}`;
97
+
98
+ const res = await fetch(`${baseUrl}/api/bus/publish`, {
99
+ method: 'POST', headers: { 'Content-Type': 'application/json', 'x-telepty-token': TOKEN }, body: JSON.stringify(args.payload)
100
+ });
101
+ const data = await res.json();
102
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
103
+
104
+ return { content: [{ type: 'text', text: `✅ Successfully published event to the bus. Delivered to ${data.delivered} active listeners.` }] };
105
+ }
106
+
86
107
  throw new Error(`Unknown tool: ${name}`);
87
108
  } catch (err) {
88
109
  return { content: [{ type: 'text', text: `❌ Error: ${err.message}` }], isError: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.0.19",
3
+ "version": "0.1.1",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "telepty": "cli.js",