@dmsdc-ai/aigentry-telepty 0.1.51 → 0.1.52

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,171 @@
1
+ # Telepty Bus Event Schema Standard
2
+
3
+ Version: 1.0 (2026-03-15)
4
+ Agreed by: telepty, deliberation, devkit, brain, orchestrator
5
+
6
+ ## Transport
7
+
8
+ - **HTTP**: `POST /api/bus/publish` with JSON body
9
+ - **WebSocket**: `ws://HOST:3848/api/bus` send JSON message
10
+ - Both paths trigger bus auto-router for routable events
11
+
12
+ ## Envelope Structure (All Events)
13
+
14
+ ```json
15
+ {
16
+ "message_id": "string (UUID or prefixed ID)",
17
+ "kind": "string (event type)",
18
+ "source": "string (sender identifier)",
19
+ "target": "string | null (target session ID, optional @host suffix)",
20
+ "ts": "ISO 8601 timestamp"
21
+ }
22
+ ```
23
+
24
+ ### Canonical Field Names
25
+
26
+ | Field | Type | Description |
27
+ |-------|------|-------------|
28
+ | `kind` | string | Event type (NOT `type` — `kind` is canonical) |
29
+ | `target` | string | Target telepty session ID. May include `@host` suffix for remote |
30
+ | `source` | string | Sender identifier (format: `project:session_id`) |
31
+ | `message_id` | string | Unique message identifier |
32
+ | `ts` | string | ISO 8601 timestamp |
33
+
34
+ ## Routable Events (Auto-Router)
35
+
36
+ ### `turn_request`
37
+
38
+ Published by deliberation to request a turn from a session. Telepty daemon auto-routes to target session PTY.
39
+
40
+ ```json
41
+ {
42
+ "message_id": "turn_request-<uuid>",
43
+ "session_id": "<deliberation_session_id>",
44
+ "project": "<project_name>",
45
+ "kind": "turn_request",
46
+ "source": "deliberation:<deliberation_session_id>",
47
+ "target": "<telepty_session_id>[@<host>]",
48
+ "reply_to": "<deliberation_session_id>",
49
+ "trace": ["project:<name>", "speaker:<id>", "turn:<turn_id>"],
50
+ "payload": {
51
+ "turn_id": "string",
52
+ "round": "number",
53
+ "max_rounds": "number",
54
+ "speaker": "string (target telepty session ID)",
55
+ "role": "string | null",
56
+ "prompt": "string (full prompt text — inject as-is to PTY)",
57
+ "prompt_sha1": "string (40-char SHA1)",
58
+ "history_entries": "number",
59
+ "transport_timeout_ms": "number",
60
+ "semantic_timeout_ms": "number"
61
+ },
62
+ "ts": "ISO 8601"
63
+ }
64
+ ```
65
+
66
+ **Important Notes:**
67
+ - `session_id` is the DELIBERATION session ID, NOT the target telepty session
68
+ - `target` is the telepty session ID to inject into
69
+ - `payload.prompt` is the full text to write to PTY (no further processing needed)
70
+ - `@host` suffix on target: strip before resolving, use for remote routing
71
+
72
+ **Auto-Router Behavior:**
73
+ 1. Daemon receives turn_request via HTTP POST or WS
74
+ 2. Extracts `target` field, strips `@host` suffix
75
+ 3. Resolves session via `resolveSessionAlias()`
76
+ 4. Delivers `payload.prompt` to session PTY (kitty primary, WS fallback)
77
+ 5. Emits `inject_written` ack on bus
78
+
79
+ ### `inject_written` (ACK)
80
+
81
+ Emitted by telepty after successful auto-route delivery.
82
+
83
+ ```json
84
+ {
85
+ "type": "inject_written",
86
+ "inject_id": "UUID",
87
+ "sender": "daemon",
88
+ "target_agent": "<session_id>",
89
+ "source_type": "bus_auto_route",
90
+ "delivered": true,
91
+ "timestamp": "ISO 8601"
92
+ }
93
+ ```
94
+
95
+ ## Session Lifecycle Events
96
+
97
+ ### `session_register`
98
+ ```json
99
+ { "type": "session_register", "sender": "daemon", "session_id": "string", "command": "string", "cwd": "string", "timestamp": "ISO 8601" }
100
+ ```
101
+
102
+ ### `session.replaced`
103
+ ```json
104
+ { "type": "session.replaced", "sender": "daemon", "old_id": "string", "new_id": "string", "alias": "string", "timestamp": "ISO 8601" }
105
+ ```
106
+
107
+ ### `session.idle`
108
+ ```json
109
+ { "type": "session.idle", "session_id": "string", "idleSeconds": "number", "lastActivityAt": "ISO 8601", "timestamp": "ISO 8601" }
110
+ ```
111
+
112
+ ### `session_health` (periodic, every 10s)
113
+ ```json
114
+ { "type": "session_health", "session_id": "string", "payload": { "alive": true, "pid": "number|null", "type": "string", "clients": "number", "idleSeconds": "number|null" }, "timestamp": "ISO 8601" }
115
+ ```
116
+
117
+ ## Inject Events
118
+
119
+ ### `inject_written`
120
+ ```json
121
+ { "type": "inject_written", "inject_id": "UUID", "sender": "daemon", "target_agent": "string", "content": "string", "from": "string|null", "reply_to": "string|null", "thread_id": "string|null", "reply_expected": "boolean", "timestamp": "ISO 8601" }
122
+ ```
123
+
124
+ ### `message_routed`
125
+ ```json
126
+ { "type": "message_routed", "message_id": "UUID", "from": "string", "to": "string", "reply_to": "string", "inject_id": "UUID", "deliberation_session_id": "string|null", "thread_id": "string|null", "timestamp": "ISO 8601" }
127
+ ```
128
+
129
+ ## Handoff Events
130
+
131
+ ### `handoff.created` / `handoff.claimed` / `handoff.executing` / `handoff.completed`
132
+ ```json
133
+ { "type": "handoff.<status>", "handoff_id": "UUID", "source_session_id": "string|null", "deliberation_id": "string|null", "auto_execute": "boolean", "task_count": "number", "timestamp": "ISO 8601" }
134
+ ```
135
+
136
+ ## Thread Events
137
+
138
+ ### `thread.opened`
139
+ ```json
140
+ { "type": "thread.opened", "thread_id": "UUID", "topic": "string", "orchestrator_session_id": "string|null", "participant_session_ids": ["string"], "timestamp": "ISO 8601" }
141
+ ```
142
+
143
+ ### `thread.closed`
144
+ ```json
145
+ { "type": "thread.closed", "thread_id": "UUID", "topic": "string", "message_count": "number", "timestamp": "ISO 8601" }
146
+ ```
147
+
148
+ ## Termination Signal Detection
149
+
150
+ Messages containing these strings suppress auto-reply guide footer:
151
+ - `no further reply needed`
152
+ - `thread closed` / `closed on X side`
153
+ - `ack received` / `ack-only`
154
+ - `회신 불필요` / `스레드 종료`
155
+
156
+ ## Inject API Reference
157
+
158
+ ### `POST /api/sessions/:id/inject`
159
+
160
+ ```json
161
+ {
162
+ "prompt": "string (REQUIRED — canonical body field)",
163
+ "from": "string (sender session ID)",
164
+ "reply_to": "string (defaults to from if omitted)",
165
+ "thread_id": "string (optional)",
166
+ "reply_expected": "boolean (optional)",
167
+ "no_enter": "boolean (skip Enter after inject)"
168
+ }
169
+ ```
170
+
171
+ **Note:** The canonical body field is `prompt`, NOT `text`, `content`, or `message`.
package/daemon.js CHANGED
@@ -10,6 +10,26 @@ const { claimDaemonState, clearDaemonState } = require('./daemon-control');
10
10
 
11
11
  const config = getConfig();
12
12
  const EXPECTED_TOKEN = config.authToken;
13
+ const fs = require('fs');
14
+ const SESSION_PERSIST_PATH = require('path').join(os.homedir(), '.config', 'aigentry-telepty', 'sessions.json');
15
+
16
+ function persistSessions() {
17
+ try {
18
+ const data = {};
19
+ for (const [id, s] of Object.entries(sessions)) {
20
+ data[id] = { id, type: s.type, command: s.command, cwd: s.cwd, createdAt: s.createdAt, lastActivityAt: s.lastActivityAt || null };
21
+ }
22
+ fs.mkdirSync(require('path').dirname(SESSION_PERSIST_PATH), { recursive: true });
23
+ fs.writeFileSync(SESSION_PERSIST_PATH, JSON.stringify(data, null, 2));
24
+ } catch {}
25
+ }
26
+
27
+ function loadPersistedSessions() {
28
+ try {
29
+ if (!fs.existsSync(SESSION_PERSIST_PATH)) return {};
30
+ return JSON.parse(fs.readFileSync(SESSION_PERSIST_PATH, 'utf8'));
31
+ } catch { return {}; }
32
+ }
13
33
 
14
34
  const app = express();
15
35
  app.use(cors());
@@ -48,6 +68,21 @@ if (!daemonClaim.claimed) {
48
68
  const sessions = {};
49
69
  const handoffs = {};
50
70
  const threads = {};
71
+
72
+ // Restore persisted session metadata (wrapped sessions await reconnect)
73
+ const _persisted = loadPersistedSessions();
74
+ for (const [id, meta] of Object.entries(_persisted)) {
75
+ if (meta.type === 'wrapped') {
76
+ sessions[id] = {
77
+ id, type: 'wrapped', ptyProcess: null, ownerWs: null,
78
+ command: meta.command || 'wrapped', cwd: meta.cwd || process.cwd(),
79
+ createdAt: meta.createdAt || new Date().toISOString(),
80
+ lastActivityAt: meta.lastActivityAt || new Date().toISOString(),
81
+ clients: new Set(), isClosing: false
82
+ };
83
+ console.log(`[PERSIST] Restored session ${id} (awaiting reconnect)`);
84
+ }
85
+ }
51
86
  const STRIPPED_SESSION_ENV_KEYS = [
52
87
  'CLAUDECODE',
53
88
  'CODEX_CI',
@@ -185,6 +220,7 @@ app.post('/api/sessions/spawn', (req, res) => {
185
220
  });
186
221
 
187
222
  console.log(`[SPAWN] Created session ${session_id} (${command})`);
223
+ persistSessions();
188
224
  res.status(201).json({ session_id, command, cwd });
189
225
  } catch (err) {
190
226
  res.status(500).json({ error: err.message });
@@ -250,6 +286,7 @@ app.post('/api/sessions/register', (req, res) => {
250
286
  });
251
287
 
252
288
  console.log(`[REGISTER] Registered wrapped session ${session_id}`);
289
+ persistSessions();
253
290
  res.status(201).json({ session_id, type: 'wrapped', command: sessionRecord.command, cwd });
254
291
  });
255
292
 
@@ -814,9 +851,11 @@ app.delete('/api/sessions/:id', (req, res) => {
814
851
  session.clients.forEach(ws => ws.close(1000, 'Session destroyed'));
815
852
  delete sessions[id];
816
853
  console.log(`[KILL] Wrapped session ${id} removed`);
854
+ persistSessions();
817
855
  } else {
818
856
  session.ptyProcess.kill();
819
857
  console.log(`[KILL] Session ${id} forcefully closed`);
858
+ persistSessions();
820
859
  }
821
860
  res.json({ success: true, status: 'closing' });
822
861
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",