@dmsdc-ai/aigentry-telepty 0.1.82 → 0.1.84

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/cli.js CHANGED
@@ -871,6 +871,7 @@ async function main() {
871
871
  }
872
872
  delete process.env.TELEPTY_SESSION_ID;
873
873
  process.env.TELEPTY_SESSION_ID = sessionId;
874
+ process.env.TELEPTY_AVAILABLE = 'true';
874
875
 
875
876
  await ensureDaemonRunning({ requiredCapabilities: ['wrapped-sessions'] });
876
877
 
@@ -914,7 +915,7 @@ async function main() {
914
915
  // Spawn local PTY (preserves isTTY, env, shell config)
915
916
  const pty = require('node-pty');
916
917
  const sessionCwd = process.cwd();
917
- const sessionEnv = { ...process.env, TELEPTY_SESSION_ID: sessionId };
918
+ const sessionEnv = { ...process.env, TELEPTY_SESSION_ID: sessionId, TELEPTY_AVAILABLE: 'true' };
918
919
  let child = null;
919
920
  let sessionStartTime = Date.now();
920
921
  let crashCount = 0;
package/daemon.js CHANGED
@@ -222,7 +222,20 @@ function getSessionHealthStatus(session, options = {}) {
222
222
  }
223
223
 
224
224
  if (session.type === 'aterm') {
225
- if (session.deliveryEndpoint || (session.delivery && session.delivery.address)) {
225
+ const endpoint = session.deliveryEndpoint || (session.delivery && session.delivery.address);
226
+ if (endpoint) {
227
+ const isSocketPath = endpoint.startsWith('/');
228
+ if (isSocketPath) {
229
+ try {
230
+ fs.accessSync(endpoint, fs.constants.F_OK);
231
+ return 'CONNECTED';
232
+ } catch {
233
+ session.deliveryEndpoint = null;
234
+ if (session.delivery) session.delivery.address = null;
235
+ session.lastDisconnectedAt = session.lastDisconnectedAt || new Date().toISOString();
236
+ return 'DISCONNECTED';
237
+ }
238
+ }
226
239
  return 'CONNECTED';
227
240
  }
228
241
  if (disconnectedMs !== null && disconnectedMs >= staleMs) {
@@ -1883,6 +1896,23 @@ setInterval(() => {
1883
1896
  session._idleEmitted = false;
1884
1897
  }
1885
1898
 
1899
+ // Periodically verify aterm socket existence — triggers health transition
1900
+ if (session.type === 'aterm') {
1901
+ const atermEndpoint = session.deliveryEndpoint || (session.delivery && session.delivery.address);
1902
+ if (atermEndpoint && atermEndpoint.startsWith('/')) {
1903
+ try {
1904
+ fs.accessSync(atermEndpoint, fs.constants.F_OK);
1905
+ } catch {
1906
+ session.deliveryEndpoint = null;
1907
+ if (session.delivery) session.delivery.address = null;
1908
+ if (!session.lastDisconnectedAt) {
1909
+ session.lastDisconnectedAt = new Date().toISOString();
1910
+ }
1911
+ console.log(`[SWEEP] aterm socket gone for ${id}: ${atermEndpoint}`);
1912
+ }
1913
+ }
1914
+ }
1915
+
1886
1916
  if (healthStatus === 'STALE' && !session._staleEmitted) {
1887
1917
  session._staleEmitted = true;
1888
1918
  emitSessionLifecycleEvent('session_stale', id, session, {
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCP Telepty Server — Session management tools for AI CLIs
5
+ *
6
+ * Tools:
7
+ * telepty_list_sessions List all active telepty sessions
8
+ * telepty_inject_session Inject text into a session
9
+ * telepty_session_status Get detailed status of a session
10
+ */
11
+
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { z } from "zod";
15
+ import fs from "fs";
16
+ import path from "path";
17
+ import os from "os";
18
+
19
+ const PKG_VERSION = (() => {
20
+ try {
21
+ const p = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
22
+ return p.version || "0.0.0";
23
+ } catch { return "0.0.0"; }
24
+ })();
25
+
26
+ // ── Config ──
27
+
28
+ function getAuthToken() {
29
+ try {
30
+ const configPath = path.join(os.homedir(), ".telepty", "config.json");
31
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
32
+ return config.authToken || "";
33
+ } catch { return ""; }
34
+ }
35
+
36
+ function getDaemonUrl() {
37
+ const port = process.env.TELEPTY_PORT || "3848";
38
+ const host = process.env.TELEPTY_HOST || "127.0.0.1";
39
+ return `http://${host}:${port}`;
40
+ }
41
+
42
+ async function daemonFetch(endpoint, options = {}) {
43
+ const url = `${getDaemonUrl()}${endpoint}`;
44
+ const token = getAuthToken();
45
+ const headers = {
46
+ "Content-Type": "application/json",
47
+ ...(token ? { "x-telepty-token": token } : {}),
48
+ ...options.headers,
49
+ };
50
+ const res = await fetch(url, { ...options, headers });
51
+ if (!res.ok) {
52
+ const body = await res.text().catch(() => "");
53
+ throw new Error(`telepty daemon ${res.status}: ${body || res.statusText}`);
54
+ }
55
+ return res.json();
56
+ }
57
+
58
+ // ── MCP Server ──
59
+
60
+ const server = new McpServer({
61
+ name: "telepty",
62
+ version: PKG_VERSION,
63
+ });
64
+
65
+ // Tool: telepty_list_sessions
66
+ server.tool(
67
+ "telepty_list_sessions",
68
+ "List all active telepty sessions with their status, type, and last activity.",
69
+ {},
70
+ async () => {
71
+ try {
72
+ const sessions = await daemonFetch("/api/sessions");
73
+ if (!Array.isArray(sessions) || sessions.length === 0) {
74
+ return { content: [{ type: "text", text: "No active sessions." }] };
75
+ }
76
+ let text = `## Active Sessions (${sessions.length})\n\n`;
77
+ text += "| ID | Type | Status | Last Activity |\n";
78
+ text += "|----|------|--------|---------------|\n";
79
+ for (const s of sessions) {
80
+ const status = s.connected ? "connected" : (s.stale ? "stale" : "disconnected");
81
+ const lastAct = s.lastActivityAt ? new Date(s.lastActivityAt).toLocaleString() : "—";
82
+ text += `| ${s.id} | ${s.type || "—"} | ${status} | ${lastAct} |\n`;
83
+ }
84
+ return { content: [{ type: "text", text }] };
85
+ } catch (err) {
86
+ return { content: [{ type: "text", text: `Error listing sessions: ${err.message}` }], isError: true };
87
+ }
88
+ }
89
+ );
90
+
91
+ // Tool: telepty_session_status
92
+ server.tool(
93
+ "telepty_session_status",
94
+ "Get detailed status of a specific telepty session.",
95
+ {
96
+ session_id: z.string().describe("Session ID to query"),
97
+ },
98
+ async ({ session_id }) => {
99
+ try {
100
+ const sessions = await daemonFetch("/api/sessions");
101
+ const session = Array.isArray(sessions)
102
+ ? sessions.find(s => s.id === session_id || s.id.startsWith(session_id))
103
+ : null;
104
+ if (!session) {
105
+ return { content: [{ type: "text", text: `Session "${session_id}" not found.` }] };
106
+ }
107
+ const status = session.connected ? "connected" : (session.stale ? "stale" : "disconnected");
108
+ let text = `## Session: ${session.id}\n\n`;
109
+ text += `- **Type:** ${session.type || "unknown"}\n`;
110
+ text += `- **Status:** ${status}\n`;
111
+ text += `- **Command:** ${session.command || "—"}\n`;
112
+ text += `- **CWD:** ${session.cwd || "—"}\n`;
113
+ text += `- **Backend:** ${session.backend || "—"}\n`;
114
+ text += `- **Created:** ${session.createdAt ? new Date(session.createdAt).toLocaleString() : "—"}\n`;
115
+ text += `- **Last Activity:** ${session.lastActivityAt ? new Date(session.lastActivityAt).toLocaleString() : "—"}\n`;
116
+ if (session.stateReport) {
117
+ text += `- **State Report:** ${session.stateReport}\n`;
118
+ }
119
+ return { content: [{ type: "text", text }] };
120
+ } catch (err) {
121
+ return { content: [{ type: "text", text: `Error querying session: ${err.message}` }], isError: true };
122
+ }
123
+ }
124
+ );
125
+
126
+ // Tool: telepty_inject_session
127
+ server.tool(
128
+ "telepty_inject_session",
129
+ "Inject text into a telepty session. Use this to send messages or prompts to other AI sessions.",
130
+ {
131
+ session_id: z.string().describe("Target session ID"),
132
+ text: z.string().describe("Text to inject"),
133
+ from: z.string().optional().describe("Sender session ID (return address)"),
134
+ no_enter: z.boolean().default(false).describe("If true, do not send Enter after text"),
135
+ },
136
+ async ({ session_id, text, from, no_enter }) => {
137
+ try {
138
+ const body = { prompt: text };
139
+ if (from) body.from = from;
140
+ if (no_enter) body.no_enter = true;
141
+ const result = await daemonFetch(`/api/sessions/${encodeURIComponent(session_id)}/inject`, {
142
+ method: "POST",
143
+ body: JSON.stringify(body),
144
+ });
145
+ const deliveryInfo = result.deliveryPath ? ` (via ${result.deliveryPath})` : "";
146
+ return { content: [{ type: "text", text: `Injected to ${session_id}${deliveryInfo}.` }] };
147
+ } catch (err) {
148
+ return { content: [{ type: "text", text: `Error injecting to ${session_id}: ${err.message}` }], isError: true };
149
+ }
150
+ }
151
+ );
152
+
153
+ // ── Start ──
154
+
155
+ async function main() {
156
+ const transport = new StdioServerTransport();
157
+ await server.connect(transport);
158
+ }
159
+
160
+ main().catch((err) => {
161
+ console.error("telepty MCP server fatal:", err);
162
+ process.exit(1);
163
+ });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.82",
3
+ "version": "0.1.84",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",
7
7
  "telepty": "cli.js",
8
- "telepty-install": "install.js"
8
+ "telepty-install": "install.js",
9
+ "telepty-mcp": "mcp-server/index.mjs"
9
10
  },
10
11
  "scripts": {
11
12
  "test": "node --test test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js",
@@ -20,6 +21,8 @@
20
21
  "remote",
21
22
  "inject",
22
23
  "ai-cli",
24
+ "mcp",
25
+ "model-context-protocol",
23
26
  "claude",
24
27
  "codex",
25
28
  "gemini",
@@ -38,6 +41,7 @@
38
41
  "url": "https://github.com/dmsdc-ai/aigentry-telepty/issues"
39
42
  },
40
43
  "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.26.0",
41
45
  "blessed": "^0.1.81",
42
46
  "cors": "^2.8.6",
43
47
  "express": "^5.2.1",
@@ -45,6 +49,7 @@
45
49
  "prompts": "^2.4.2",
46
50
  "update-notifier": "^5.1.0",
47
51
  "uuid": "^13.0.0",
48
- "ws": "^8.19.0"
52
+ "ws": "^8.19.0",
53
+ "zod": "^3.24.0"
49
54
  }
50
55
  }
@@ -0,0 +1,71 @@
1
+ # telepty Session Communication / 세션 통신
2
+
3
+ AI CLI sessions use `telepty` to communicate with other sessions.
4
+
5
+ ## Environment Detection / 환경 감지
6
+
7
+ ```bash
8
+ # Check if telepty is available
9
+ echo "$TELEPTY_AVAILABLE" # "true" if telepty is installed and daemon running
10
+
11
+ # Get current session ID
12
+ echo "$TELEPTY_SESSION_ID" # e.g., "aigentry-brain-claude"
13
+ ```
14
+
15
+ If `$TELEPTY_AVAILABLE` is not set, telepty commands are unavailable.
16
+
17
+ ## Commands / 명령어
18
+
19
+ | English action | 한국어 동작 | Command |
20
+ |----------------|------------|---------|
21
+ | List sessions | 세션 목록 보기 | `telepty list` |
22
+ | Send to session | 세션에 메시지 보내기 | `telepty inject <session> 'message'` |
23
+ | Send with return address | 발신자 포함 전송 | `telepty inject --from $TELEPTY_SESSION_ID <session> 'message'` |
24
+ | Send without enter | 엔터 없이 전송 | `telepty inject --no-enter <session> 'message'` |
25
+ | Broadcast to all | 전체 브로드캐스트 | `telepty broadcast 'message'` |
26
+ | Multicast to group | 그룹 멀티캐스트 | `telepty multicast <id1,id2> 'message'` |
27
+ | Reply to last sender | 마지막 발신자에게 답장 | `telepty reply 'message'` |
28
+ | Check session status | 세션 상태 확인 | `telepty list` (check connected/stale) |
29
+ | Read session screen | 세션 화면 읽기 | `curl -s http://127.0.0.1:3848/api/sessions/<id>/screen -H "x-telepty-token: $TOKEN"` |
30
+ | Kill session | 세션 종료 | `telepty delete <session>` |
31
+ | Clean stale sessions | 비활성 세션 정리 | `telepty clean` |
32
+ | Rename session | 세션 이름 변경 | `telepty rename <old> <new>` |
33
+ | Listen to event bus | 이벤트 버스 수신 | `telepty listen` |
34
+ | Attach to session | 세션에 접속 | `telepty attach <session>` |
35
+
36
+ ## Natural Language → Command / 자연어 → 명령어
37
+
38
+ | English request | 한국어 요청 | Command |
39
+ |----------------|------------|---------|
40
+ | `list sessions` | `세션 목록 보여줘` | `telepty list` |
41
+ | `send to brain` | `brain에 메시지 보내줘` | `telepty inject aigentry-brain-claude 'msg'` |
42
+ | `broadcast message` | `전체에 메시지 보내줘` | `telepty broadcast 'msg'` |
43
+ | `reply to sender` | `답장해줘` | `telepty reply 'msg'` |
44
+ | `kill session` | `세션 종료해줘` | `telepty delete <session>` |
45
+ | `clean stale` | `비활성 정리해줘` | `telepty clean` |
46
+ | `check status` | `상태 확인해줘` | `telepty list` |
47
+ | `rename session` | `세션 이름 바꿔줘` | `telepty rename <old> <new>` |
48
+ | `read screen` | `화면 읽어줘` | REST API `/api/sessions/<id>/screen` |
49
+
50
+ ## Cross-Machine Sessions / 크로스 머신 세션
51
+
52
+ When the same session ID exists on multiple hosts, use `session_id@host`:
53
+
54
+ ```bash
55
+ telepty inject brain-claude@macbook 'message'
56
+ ```
57
+
58
+ ## Rules / 규칙
59
+
60
+ 1. **Return address**: Always include `--from $TELEPTY_SESSION_ID` when expecting a reply.
61
+ 2. **Submit method**: Never rely on PTY `\r` directly. Use `telepty inject` which handles submit automatically.
62
+ 3. **Env detection**: Check `$TELEPTY_AVAILABLE` before using telepty commands.
63
+ 4. **Session ID format**: Use `<project>-<cli>` pattern (e.g., `aigentry-brain-claude`).
64
+
65
+ ## Reporting / 보고
66
+
67
+ On task completion, report to orchestrator:
68
+
69
+ ```bash
70
+ telepty inject --from $TELEPTY_SESSION_ID aigentry-orchestrator-claude 'REPORT: <summary>'
71
+ ```