@dmsdc-ai/aigentry-telepty 0.1.50 → 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.
- package/BUS_EVENT_SCHEMA.md +171 -0
- package/daemon.js +119 -72
- package/package.json +1 -1
|
@@ -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) {
|
|
@@ -824,15 +863,88 @@ app.delete('/api/sessions/:id', (req, res) => {
|
|
|
824
863
|
}
|
|
825
864
|
});
|
|
826
865
|
|
|
866
|
+
// Shared auto-router: handles turn_request events from any source (WS or HTTP)
|
|
867
|
+
function busAutoRoute(msg) {
|
|
868
|
+
const eventType = msg.type || msg.kind;
|
|
869
|
+
const isRoutable = (eventType === 'turn_request' || eventType === 'deliberation_route_turn') && (msg.target || msg.target_session_id);
|
|
870
|
+
if (!isRoutable) return;
|
|
871
|
+
|
|
872
|
+
const rawTarget = (msg.target || msg.target_session_id).split('@')[0];
|
|
873
|
+
console.log(`[BUS-ROUTE] Received ${eventType}: target=${rawTarget}`);
|
|
874
|
+
const targetId = resolveSessionAlias(rawTarget);
|
|
875
|
+
const targetSession = targetId ? sessions[targetId] : null;
|
|
876
|
+
if (!targetSession) {
|
|
877
|
+
console.log(`[BUS-ROUTE] Target ${rawTarget} not found`);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const prompt = (msg.payload && msg.payload.prompt) || msg.content || msg.prompt || JSON.stringify(msg);
|
|
882
|
+
const inject_id = crypto.randomUUID();
|
|
883
|
+
|
|
884
|
+
// Write to session (kitty primary, WS fallback)
|
|
885
|
+
const sock = findKittySocket();
|
|
886
|
+
if (!targetSession.kittyWindowId && sock) targetSession.kittyWindowId = findKittyWindowId(sock, targetId);
|
|
887
|
+
const wid = targetSession.kittyWindowId;
|
|
888
|
+
let delivered = false;
|
|
889
|
+
|
|
890
|
+
if (wid && sock && targetSession.type === 'wrapped') {
|
|
891
|
+
try {
|
|
892
|
+
const escaped = prompt.replace(/\\/g, '\\\\').replace(/'/g, "'\\''");
|
|
893
|
+
require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} '${escaped}'`, {
|
|
894
|
+
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
895
|
+
});
|
|
896
|
+
setTimeout(() => {
|
|
897
|
+
try {
|
|
898
|
+
require('child_process').execSync(`kitty @ --to unix:${sock} send-key --match id:${wid} Return`, {
|
|
899
|
+
timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
900
|
+
});
|
|
901
|
+
} catch {}
|
|
902
|
+
}, 500);
|
|
903
|
+
delivered = true;
|
|
904
|
+
} catch {}
|
|
905
|
+
}
|
|
906
|
+
if (!delivered) {
|
|
907
|
+
if (targetSession.type === 'wrapped' && targetSession.ownerWs && targetSession.ownerWs.readyState === 1) {
|
|
908
|
+
targetSession.ownerWs.send(JSON.stringify({ type: 'inject', data: prompt }));
|
|
909
|
+
setTimeout(() => {
|
|
910
|
+
if (targetSession.ownerWs && targetSession.ownerWs.readyState === 1) {
|
|
911
|
+
targetSession.ownerWs.send(JSON.stringify({ type: 'inject', data: '\r' }));
|
|
912
|
+
}
|
|
913
|
+
}, 300);
|
|
914
|
+
delivered = true;
|
|
915
|
+
} else if (targetSession.ptyProcess) {
|
|
916
|
+
targetSession.ptyProcess.write(prompt);
|
|
917
|
+
setTimeout(() => targetSession.ptyProcess.write('\r'), 300);
|
|
918
|
+
delivered = true;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Emit inject_written ack
|
|
923
|
+
const ackMsg = JSON.stringify({
|
|
924
|
+
type: 'inject_written',
|
|
925
|
+
inject_id,
|
|
926
|
+
sender: 'daemon',
|
|
927
|
+
target_agent: targetId,
|
|
928
|
+
source_type: 'bus_auto_route',
|
|
929
|
+
delivered,
|
|
930
|
+
timestamp: new Date().toISOString()
|
|
931
|
+
});
|
|
932
|
+
busClients.forEach(client => {
|
|
933
|
+
if (client.readyState === 1) client.send(ackMsg);
|
|
934
|
+
});
|
|
935
|
+
targetSession.lastActivityAt = new Date().toISOString();
|
|
936
|
+
console.log(`[BUS-ROUTE] ${eventType} → ${targetId}: ${delivered ? 'delivered' : 'failed'}`);
|
|
937
|
+
}
|
|
938
|
+
|
|
827
939
|
app.post('/api/bus/publish', (req, res) => {
|
|
828
940
|
const payload = req.body;
|
|
829
|
-
|
|
941
|
+
|
|
830
942
|
if (!payload || typeof payload !== 'object') {
|
|
831
943
|
return res.status(400).json({ error: 'Payload must be a JSON object' });
|
|
832
944
|
}
|
|
833
945
|
|
|
834
946
|
let deliveredCount = 0;
|
|
835
|
-
|
|
947
|
+
|
|
836
948
|
busClients.forEach(client => {
|
|
837
949
|
if (client.readyState === 1) { // WebSocket.OPEN
|
|
838
950
|
client.send(JSON.stringify(payload));
|
|
@@ -840,6 +952,9 @@ app.post('/api/bus/publish', (req, res) => {
|
|
|
840
952
|
}
|
|
841
953
|
});
|
|
842
954
|
|
|
955
|
+
// Auto-route if this is a turn_request
|
|
956
|
+
busAutoRoute(payload);
|
|
957
|
+
|
|
843
958
|
res.json({ success: true, delivered: deliveredCount });
|
|
844
959
|
});
|
|
845
960
|
|
|
@@ -1247,76 +1362,8 @@ busWss.on('connection', (ws, req) => {
|
|
|
1247
1362
|
}
|
|
1248
1363
|
});
|
|
1249
1364
|
|
|
1250
|
-
// Auto-
|
|
1251
|
-
|
|
1252
|
-
const eventType = msg.type || msg.kind;
|
|
1253
|
-
const isRoutable = (eventType === 'turn_request' || eventType === 'deliberation_route_turn') && (msg.target || msg.target_session_id);
|
|
1254
|
-
if (isRoutable) {
|
|
1255
|
-
// Parse target: may include @host suffix (e.g. "session-001@100.x.y.z")
|
|
1256
|
-
const rawTarget = (msg.target || msg.target_session_id).split('@')[0];
|
|
1257
|
-
console.log(`[BUS-ROUTE] Received ${eventType}: target=${rawTarget}`);
|
|
1258
|
-
const targetId = resolveSessionAlias(rawTarget);
|
|
1259
|
-
const targetSession = targetId ? sessions[targetId] : null;
|
|
1260
|
-
if (targetSession) {
|
|
1261
|
-
// Extract prompt from payload.prompt (deliberation schema) or fallbacks
|
|
1262
|
-
const prompt = (msg.payload && msg.payload.prompt) || msg.content || msg.prompt || JSON.stringify(msg);
|
|
1263
|
-
const inject_id = crypto.randomUUID();
|
|
1264
|
-
|
|
1265
|
-
// Write to session (kitty primary, WS fallback)
|
|
1266
|
-
const sock = findKittySocket();
|
|
1267
|
-
if (!targetSession.kittyWindowId && sock) targetSession.kittyWindowId = findKittyWindowId(sock, targetId);
|
|
1268
|
-
const wid = targetSession.kittyWindowId;
|
|
1269
|
-
let delivered = false;
|
|
1270
|
-
|
|
1271
|
-
if (wid && sock && targetSession.type === 'wrapped') {
|
|
1272
|
-
try {
|
|
1273
|
-
const escaped = prompt.replace(/\\/g, '\\\\').replace(/'/g, "'\\''");
|
|
1274
|
-
require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} '${escaped}'`, {
|
|
1275
|
-
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
1276
|
-
});
|
|
1277
|
-
setTimeout(() => {
|
|
1278
|
-
try {
|
|
1279
|
-
require('child_process').execSync(`kitty @ --to unix:${sock} send-key --match id:${wid} Return`, {
|
|
1280
|
-
timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
1281
|
-
});
|
|
1282
|
-
} catch {}
|
|
1283
|
-
}, 500);
|
|
1284
|
-
delivered = true;
|
|
1285
|
-
} catch {}
|
|
1286
|
-
}
|
|
1287
|
-
if (!delivered) {
|
|
1288
|
-
// WS fallback
|
|
1289
|
-
if (targetSession.type === 'wrapped' && targetSession.ownerWs && targetSession.ownerWs.readyState === 1) {
|
|
1290
|
-
targetSession.ownerWs.send(JSON.stringify({ type: 'inject', data: prompt }));
|
|
1291
|
-
setTimeout(() => {
|
|
1292
|
-
if (targetSession.ownerWs && targetSession.ownerWs.readyState === 1) {
|
|
1293
|
-
targetSession.ownerWs.send(JSON.stringify({ type: 'inject', data: '\r' }));
|
|
1294
|
-
}
|
|
1295
|
-
}, 300);
|
|
1296
|
-
delivered = true;
|
|
1297
|
-
} else if (targetSession.ptyProcess) {
|
|
1298
|
-
targetSession.ptyProcess.write(prompt);
|
|
1299
|
-
setTimeout(() => targetSession.ptyProcess.write('\r'), 300);
|
|
1300
|
-
delivered = true;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// Emit inject_written ack
|
|
1305
|
-
const ackMsg = JSON.stringify({
|
|
1306
|
-
type: 'inject_written',
|
|
1307
|
-
inject_id,
|
|
1308
|
-
sender: 'daemon',
|
|
1309
|
-
target_agent: targetId,
|
|
1310
|
-
source_type: 'bus_auto_route',
|
|
1311
|
-
delivered,
|
|
1312
|
-
timestamp: new Date().toISOString()
|
|
1313
|
-
});
|
|
1314
|
-
busClients.forEach(client => {
|
|
1315
|
-
if (client.readyState === 1) client.send(ackMsg);
|
|
1316
|
-
});
|
|
1317
|
-
console.log(`[BUS-ROUTE] turn_request → ${targetId}: ${delivered ? 'delivered' : 'failed'}`);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1365
|
+
// Auto-route turn_request events (shared logic with HTTP publish)
|
|
1366
|
+
busAutoRoute(msg);
|
|
1320
1367
|
} catch (e) {
|
|
1321
1368
|
console.error('[BUS] Invalid message format', e);
|
|
1322
1369
|
}
|