hermes-web-ui 0.3.7 → 0.4.0
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/README.md +28 -12
- package/dist/client/assets/{Add-Cc7cgBoB.js → Add-BChxDDdy.js} +1 -1
- package/dist/client/assets/{Button-EoeZgIFH.js → Button-uvjCWO-i.js} +1 -1
- package/dist/client/assets/{ChannelsView-Bfbq3w7n.js → ChannelsView-D3a0g0rb.js} +1 -1
- package/dist/client/assets/{ChatView-Vfi_jEpI.css → ChatView-DI3XN8vz.css} +1 -1
- package/dist/client/assets/ChatView-T8LH7dwU.js +127 -0
- package/dist/client/assets/{Close-CKHcXisf.js → Close-DbXijZpL.js} +1 -1
- package/dist/client/assets/{FormItem-CvZvjrtr.js → FormItem-BRiLD3TC.js} +1 -1
- package/dist/client/assets/{GatewaysView-Dp4-TFPE.js → GatewaysView-rfzh1nqy.js} +1 -1
- package/dist/client/assets/{Input-Bk7XdoG-.js → Input-CjlUbV0M.js} +1 -1
- package/dist/client/assets/{InputNumber-Dn0EHi-K.js → InputNumber-CPEjoOAv.js} +1 -1
- package/dist/client/assets/{JobsView-D4JN73Zz.js → JobsView-D6AFr9Xe.js} +2 -2
- package/dist/client/assets/{LoginView--hy5CI5O.js → LoginView-DJB_TSHN.js} +1 -1
- package/dist/client/assets/{LogsView-Dz2ZeYad.js → LogsView-Ul8pAp42.js} +1 -1
- package/dist/client/assets/{MarkdownRenderer-BbedVxvo.js → MarkdownRenderer-CY7d2L7Z.js} +1 -1
- package/dist/client/assets/{MemoryView-D2EHM35l.js → MemoryView-BR5Dl_fa.js} +1 -1
- package/dist/client/assets/{Modal-C5-Iw50K.js → Modal-DDUFP8vh.js} +1 -1
- package/dist/client/assets/{ModelsView-CtrRf4vK.js → ModelsView-DAi9Jdmk.js} +1 -1
- package/dist/client/assets/{Popconfirm-C-M2anVL.js → Popconfirm-Cnb_uQVo.js} +1 -1
- package/dist/client/assets/{Popover-mIRPCy7U.js → Popover-DiVI0l6E.js} +1 -1
- package/dist/client/assets/{ProfilesView-Dy9PivgB.js → ProfilesView-BB0Zrfhi.js} +1 -1
- package/dist/client/assets/{Select-uZBC8HC2.js → Select-BqUA1wxE.js} +1 -1
- package/dist/client/assets/{SettingRow-D9R65bDj.js → SettingRow-CK0bHtaz.js} +1 -1
- package/dist/client/assets/{SettingsView-DWEEXqSY.js → SettingsView-BKKk44FG.js} +1 -1
- package/dist/client/assets/{SkillsView-CdZSRy9_.js → SkillsView-whSlyc23.js} +1 -1
- package/dist/client/assets/{Spin-ChbFBUOD.js → Spin-DwHJdgNz.js} +1 -1
- package/dist/client/assets/{Suffix-DgzfIwzx.js → Suffix-D6x-7akR.js} +1 -1
- package/dist/client/assets/{Switch--HhY1uSh.js → Switch-BvHRSSqt.js} +1 -1
- package/dist/client/assets/{Tag-B2zrHMmZ.js → Tag-BMMlXaEi.js} +1 -1
- package/dist/client/assets/{TerminalView-BwfnH803.js → TerminalView-el6o2Q0a.js} +1 -1
- package/dist/client/assets/{Tooltip-9tdvSKGi.js → Tooltip-BM4wl764.js} +1 -1
- package/dist/client/assets/{UsageView-zL3a7F86.js → UsageView-DQ_YKoEV.js} +1 -1
- package/dist/client/assets/{Warning-CXXqHzLa.js → Warning-CEC7rgvY.js} +1 -1
- package/dist/client/assets/{_plugin-vue_export-helper-Cnn0Z73x.js → _plugin-vue_export-helper-DgUZPfuZ.js} +1 -1
- package/dist/client/assets/app-DPUhLGXq.js +1 -0
- package/dist/client/assets/{app-BMobzABI.js → app-DUt8TNq3.js} +1 -1
- package/dist/client/assets/{browser-CQRjhbaB.js → browser-vxCOMmsq.js} +1 -1
- package/dist/client/assets/chat-i_Ge_Lfr.js +6 -0
- package/dist/client/assets/composables-jrQPIjcq.js +1 -0
- package/dist/client/assets/{fade-in.cssr-lwO9nLky.js → fade-in.cssr-DVg2CkO3.js} +1 -1
- package/dist/client/assets/{index-Tg6M43Om.js → index-COwJ2oY0.js} +1 -1
- package/dist/client/assets/{jobs-Z2HS0j2d.js → jobs-Czr1RcSG.js} +1 -1
- package/dist/client/assets/{light-DgIst23O.js → light-3rSjNeC-.js} +1 -1
- package/dist/client/assets/{light-oE8MEiWL.js → light-CKDlpgGU.js} +1 -1
- package/dist/client/assets/{light-Dx6qj2pM.js → light-CiIDFs7y.js} +1 -1
- package/dist/client/assets/{light-DZ0Ns16h.js → light-CoJqT8Vu.js} +1 -1
- package/dist/client/assets/{light-CjCy-Dkn.js → light-DPRJ1OEN.js} +1 -1
- package/dist/client/assets/{light-DzpNsLai.js → light-vTpJevRf.js} +1 -1
- package/dist/client/assets/{models-DLQiHB7r.js → models-BzEeJuoO.js} +1 -1
- package/dist/client/assets/{pinia-Dp_b1vdW.js → pinia-BoNLlsLy.js} +1 -1
- package/dist/client/assets/{profiles-CNTHYFZE.js → profiles-B-DFTmc2.js} +1 -1
- package/dist/client/assets/{router-Dj-Nmg7q.js → router-HHMeDEaP.js} +2 -2
- package/dist/client/assets/{sessions-C0kvgvBm.js → sessions-BmxS_BoH.js} +1 -1
- package/dist/client/assets/{skills-G7EoEvdS.js → skills-Be8Mzr1r.js} +1 -1
- package/dist/client/assets/{use-message-BgToAqhv.js → use-message-DBTY4945.js} +1 -1
- package/dist/client/assets/{useTheme-BUShiwRu.js → useTheme-D58Cg7k2.js} +1 -1
- package/dist/client/index.html +27 -27
- package/dist/server/index.js +19 -194
- package/dist/server/routes/health.d.ts +4 -0
- package/dist/server/routes/health.js +109 -0
- package/dist/server/routes/hermes/group-chat.d.ts +4 -0
- package/dist/server/routes/hermes/group-chat.js +146 -0
- package/dist/server/routes/update.d.ts +2 -0
- package/dist/server/routes/update.js +69 -0
- package/dist/server/routes/upload.js +41 -11
- package/dist/server/services/auth.js +1 -1
- package/dist/server/services/gateway-bootstrap.d.ts +2 -0
- package/dist/server/services/gateway-bootstrap.js +51 -0
- package/dist/server/services/group-chat/coordinator.d.ts +14 -0
- package/dist/server/services/group-chat/coordinator.js +230 -0
- package/dist/server/services/group-chat/index.d.ts +5 -0
- package/dist/server/services/group-chat/index.js +115 -0
- package/dist/server/services/group-chat/rooms-db.d.ts +56 -0
- package/dist/server/services/group-chat/rooms-db.js +199 -0
- package/dist/server/services/hermes/group-chat/agent-clients.d.ts +133 -0
- package/dist/server/services/hermes/group-chat/agent-clients.js +364 -0
- package/dist/server/services/hermes/group-chat/index.d.ts +72 -0
- package/dist/server/services/hermes/group-chat/index.js +307 -0
- package/dist/server/services/shutdown.d.ts +1 -0
- package/dist/server/services/shutdown.js +37 -0
- package/package.json +1 -1
- package/dist/client/assets/ChatView-CDdyTo72.js +0 -127
- package/dist/client/assets/app-Bqu9Uz-1.js +0 -1
- package/dist/client/assets/chat-BIdq6ZXF.js +0 -6
- package/dist/client/assets/composables-ClIU-Ad1.js +0 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRoom = createRoom;
|
|
4
|
+
exports.listRooms = listRooms;
|
|
5
|
+
exports.getRoom = getRoom;
|
|
6
|
+
exports.updateRoom = updateRoom;
|
|
7
|
+
exports.deleteRoom = deleteRoom;
|
|
8
|
+
exports.addRoomAgent = addRoomAgent;
|
|
9
|
+
exports.removeRoomAgent = removeRoomAgent;
|
|
10
|
+
exports.getRoomAgents = getRoomAgents;
|
|
11
|
+
exports.updateRoomAgentSession = updateRoomAgentSession;
|
|
12
|
+
exports.addMessage = addMessage;
|
|
13
|
+
exports.getRoomMessages = getRoomMessages;
|
|
14
|
+
exports.getLatestRound = getLatestRound;
|
|
15
|
+
exports.getRecentContext = getRecentContext;
|
|
16
|
+
const os_1 = require("os");
|
|
17
|
+
const path_1 = require("path");
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Database singleton
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const SQLITE_AVAILABLE = (() => {
|
|
22
|
+
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
23
|
+
return major > 22 || (major === 22 && minor >= 5);
|
|
24
|
+
})();
|
|
25
|
+
function dbPath() {
|
|
26
|
+
return (0, path_1.join)((0, os_1.homedir)(), '.hermes-web-ui', 'rooms.db');
|
|
27
|
+
}
|
|
28
|
+
let _db = null;
|
|
29
|
+
function getDb() {
|
|
30
|
+
if (_db)
|
|
31
|
+
return _db;
|
|
32
|
+
if (!SQLITE_AVAILABLE) {
|
|
33
|
+
throw new Error('Group chat requires Node.js >= 22.5 for SQLite support');
|
|
34
|
+
}
|
|
35
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
36
|
+
const dir = (0, path_1.join)((0, os_1.homedir)(), '.hermes-web-ui');
|
|
37
|
+
const { mkdirSync } = require('fs');
|
|
38
|
+
mkdirSync(dir, { recursive: true });
|
|
39
|
+
_db = new DatabaseSync(dbPath(), { open: true });
|
|
40
|
+
_db.exec('PRAGMA journal_mode=WAL');
|
|
41
|
+
_db.exec('PRAGMA foreign_keys=ON');
|
|
42
|
+
initSchema(_db);
|
|
43
|
+
return _db;
|
|
44
|
+
}
|
|
45
|
+
function initSchema(db) {
|
|
46
|
+
db.exec(`
|
|
47
|
+
CREATE TABLE IF NOT EXISTS rooms (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
name TEXT NOT NULL,
|
|
50
|
+
settings TEXT DEFAULT '{}',
|
|
51
|
+
created_at INTEGER NOT NULL,
|
|
52
|
+
updated_at INTEGER NOT NULL
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS room_agents (
|
|
56
|
+
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
57
|
+
agent_name TEXT NOT NULL,
|
|
58
|
+
profile TEXT NOT NULL,
|
|
59
|
+
session_id TEXT DEFAULT '',
|
|
60
|
+
role_prompt TEXT DEFAULT '',
|
|
61
|
+
PRIMARY KEY (room_id, agent_name)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS room_messages (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
67
|
+
role TEXT NOT NULL,
|
|
68
|
+
agent_name TEXT DEFAULT '',
|
|
69
|
+
content TEXT NOT NULL,
|
|
70
|
+
mentions TEXT DEFAULT '[]',
|
|
71
|
+
round INTEGER DEFAULT 0,
|
|
72
|
+
created_at INTEGER NOT NULL
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_room_messages_room
|
|
76
|
+
ON room_messages(room_id, created_at);
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Room CRUD
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
function createRoom(name) {
|
|
83
|
+
const db = getDb();
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const id = crypto.randomUUID();
|
|
86
|
+
db.prepare('INSERT INTO rooms (id, name, settings, created_at, updated_at) VALUES (?, ?, ?, ?, ?)')
|
|
87
|
+
.run(id, name, '{}', now, now);
|
|
88
|
+
return { id, name, settings: '{}', created_at: now, updated_at: now };
|
|
89
|
+
}
|
|
90
|
+
function listRooms() {
|
|
91
|
+
const db = getDb();
|
|
92
|
+
return db.prepare('SELECT * FROM rooms ORDER BY updated_at DESC').all();
|
|
93
|
+
}
|
|
94
|
+
function getRoom(roomId) {
|
|
95
|
+
const db = getDb();
|
|
96
|
+
return db.prepare('SELECT * FROM rooms WHERE id = ?').get(roomId);
|
|
97
|
+
}
|
|
98
|
+
function updateRoom(roomId, updates) {
|
|
99
|
+
const db = getDb();
|
|
100
|
+
const sets = [];
|
|
101
|
+
const values = [];
|
|
102
|
+
if (updates.name !== undefined) {
|
|
103
|
+
sets.push('name = ?');
|
|
104
|
+
values.push(updates.name);
|
|
105
|
+
}
|
|
106
|
+
if (updates.settings !== undefined) {
|
|
107
|
+
sets.push('settings = ?');
|
|
108
|
+
values.push(updates.settings);
|
|
109
|
+
}
|
|
110
|
+
if (sets.length === 0)
|
|
111
|
+
return false;
|
|
112
|
+
sets.push('updated_at = ?');
|
|
113
|
+
values.push(Date.now());
|
|
114
|
+
values.push(roomId);
|
|
115
|
+
const result = db.prepare(`UPDATE rooms SET ${sets.join(', ')} WHERE id = ?`).run(...values);
|
|
116
|
+
return result.changes > 0;
|
|
117
|
+
}
|
|
118
|
+
function deleteRoom(roomId) {
|
|
119
|
+
const db = getDb();
|
|
120
|
+
db.prepare('DELETE FROM room_messages WHERE room_id = ?').run(roomId);
|
|
121
|
+
db.prepare('DELETE FROM room_agents WHERE room_id = ?').run(roomId);
|
|
122
|
+
const result = db.prepare('DELETE FROM rooms WHERE id = ?').run(roomId);
|
|
123
|
+
return result.changes > 0;
|
|
124
|
+
}
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Room Agents
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
function addRoomAgent(roomId, agent) {
|
|
129
|
+
const db = getDb();
|
|
130
|
+
const row = db.prepare('INSERT OR IGNORE INTO room_agents (room_id, agent_name, profile, session_id, role_prompt) VALUES (?, ?, ?, ?, ?)').run(roomId, agent.agent_name, agent.profile, '', agent.role_prompt || '');
|
|
131
|
+
if (row.changes === 0)
|
|
132
|
+
return null;
|
|
133
|
+
return getRoomAgent(roomId, agent.agent_name);
|
|
134
|
+
}
|
|
135
|
+
function removeRoomAgent(roomId, agentName) {
|
|
136
|
+
const db = getDb();
|
|
137
|
+
const result = db.prepare('DELETE FROM room_agents WHERE room_id = ? AND agent_name = ?')
|
|
138
|
+
.run(roomId, agentName);
|
|
139
|
+
return result.changes > 0;
|
|
140
|
+
}
|
|
141
|
+
function getRoomAgents(roomId) {
|
|
142
|
+
const db = getDb();
|
|
143
|
+
return db.prepare('SELECT * FROM room_agents WHERE room_id = ?').all(roomId);
|
|
144
|
+
}
|
|
145
|
+
function getRoomAgent(roomId, agentName) {
|
|
146
|
+
const db = getDb();
|
|
147
|
+
return db.prepare('SELECT * FROM room_agents WHERE room_id = ? AND agent_name = ?')
|
|
148
|
+
.get(roomId, agentName);
|
|
149
|
+
}
|
|
150
|
+
function updateRoomAgentSession(roomId, agentName, sessionId) {
|
|
151
|
+
const db = getDb();
|
|
152
|
+
db.prepare('UPDATE room_agents SET session_id = ? WHERE room_id = ? AND agent_name = ?')
|
|
153
|
+
.run(sessionId, roomId, agentName);
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Messages
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
function addMessage(msg) {
|
|
159
|
+
const db = getDb();
|
|
160
|
+
const id = crypto.randomUUID();
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
const mentions = msg.mentions ? JSON.stringify(msg.mentions) : '[]';
|
|
163
|
+
const round = msg.round ?? getLatestRound(msg.room_id);
|
|
164
|
+
db.prepare('INSERT INTO room_messages (id, room_id, role, agent_name, content, mentions, round, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(id, msg.room_id, msg.role, msg.agent_name || '', msg.content, mentions, round, now);
|
|
165
|
+
// Update room timestamp
|
|
166
|
+
db.prepare('UPDATE rooms SET updated_at = ? WHERE id = ?').run(now, msg.room_id);
|
|
167
|
+
return { id, room_id: msg.room_id, role: msg.role, agent_name: msg.agent_name || '', content: msg.content, mentions, round, created_at: now };
|
|
168
|
+
}
|
|
169
|
+
function getRoomMessages(roomId, limit = 100, offset = 0) {
|
|
170
|
+
const db = getDb();
|
|
171
|
+
return db.prepare('SELECT * FROM room_messages WHERE room_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?').all(roomId, limit, offset);
|
|
172
|
+
}
|
|
173
|
+
function getLatestRound(roomId) {
|
|
174
|
+
const db = getDb();
|
|
175
|
+
const row = db.prepare('SELECT MAX(round) as max_round FROM room_messages WHERE room_id = ?')
|
|
176
|
+
.get(roomId);
|
|
177
|
+
return (row?.max_round ?? -1) + 1;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get recent context messages relevant to a specific agent.
|
|
181
|
+
* Returns messages where:
|
|
182
|
+
* - mentions is empty (public message, no @mentions)
|
|
183
|
+
* - mentions includes agentName
|
|
184
|
+
* - message is FROM this agent
|
|
185
|
+
*/
|
|
186
|
+
function getRecentContext(roomId, agentName, limit = 20) {
|
|
187
|
+
const db = getDb();
|
|
188
|
+
return db.prepare(`
|
|
189
|
+
SELECT * FROM room_messages
|
|
190
|
+
WHERE room_id = ?
|
|
191
|
+
AND (
|
|
192
|
+
json_array_length(mentions) = 0
|
|
193
|
+
OR agent_name = ?
|
|
194
|
+
OR mentions LIKE ?
|
|
195
|
+
)
|
|
196
|
+
ORDER BY created_at DESC
|
|
197
|
+
LIMIT ?
|
|
198
|
+
`).all(roomId, agentName, `%"${agentName}"%`, limit);
|
|
199
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { GatewayManager } from '../gateway-manager';
|
|
2
|
+
interface AgentConfig {
|
|
3
|
+
profile: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
invited: number;
|
|
7
|
+
}
|
|
8
|
+
interface MessageData {
|
|
9
|
+
id: string;
|
|
10
|
+
roomId: string;
|
|
11
|
+
senderId: string;
|
|
12
|
+
senderName: string;
|
|
13
|
+
content: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
interface MemberData {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
joinedAt: number;
|
|
20
|
+
}
|
|
21
|
+
interface JoinResult {
|
|
22
|
+
roomId: string;
|
|
23
|
+
roomName: string;
|
|
24
|
+
members: MemberData[];
|
|
25
|
+
messages: MessageData[];
|
|
26
|
+
rooms: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface AgentEventHandler {
|
|
29
|
+
onMessage?: (data: {
|
|
30
|
+
roomId: string;
|
|
31
|
+
msg: MessageData;
|
|
32
|
+
}) => void;
|
|
33
|
+
onTyping?: (data: {
|
|
34
|
+
roomId: string;
|
|
35
|
+
userId: string;
|
|
36
|
+
userName: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
onStopTyping?: (data: {
|
|
39
|
+
roomId: string;
|
|
40
|
+
userId: string;
|
|
41
|
+
userName: string;
|
|
42
|
+
}) => void;
|
|
43
|
+
onMemberJoined?: (data: {
|
|
44
|
+
roomId: string;
|
|
45
|
+
memberId: string;
|
|
46
|
+
memberName: string;
|
|
47
|
+
members: MemberData[];
|
|
48
|
+
}) => void;
|
|
49
|
+
onMemberLeft?: (data: {
|
|
50
|
+
roomId: string;
|
|
51
|
+
memberId: string;
|
|
52
|
+
memberName: string;
|
|
53
|
+
members: MemberData[];
|
|
54
|
+
}) => void;
|
|
55
|
+
}
|
|
56
|
+
declare class AgentClient {
|
|
57
|
+
readonly agentId: string;
|
|
58
|
+
readonly profile: string;
|
|
59
|
+
readonly name: string;
|
|
60
|
+
private socket;
|
|
61
|
+
private joinedRooms;
|
|
62
|
+
private handlers;
|
|
63
|
+
private port;
|
|
64
|
+
private _reconnecting;
|
|
65
|
+
private gatewayManager;
|
|
66
|
+
constructor(config: AgentConfig, handlers?: AgentEventHandler);
|
|
67
|
+
get connected(): boolean;
|
|
68
|
+
get id(): string | undefined;
|
|
69
|
+
setGatewayManager(manager: GatewayManager): void;
|
|
70
|
+
connect(port?: number): Promise<void>;
|
|
71
|
+
disconnect(): void;
|
|
72
|
+
joinRoom(roomId: string): Promise<JoinResult>;
|
|
73
|
+
sendMessage(roomId: string, content: string): Promise<string>;
|
|
74
|
+
startTyping(roomId: string): void;
|
|
75
|
+
stopTyping(roomId: string): void;
|
|
76
|
+
getJoinedRooms(): string[];
|
|
77
|
+
private deleteSession;
|
|
78
|
+
private ensureConnected;
|
|
79
|
+
/**
|
|
80
|
+
* Forward a user message to Hermes gateway and stream the reply back to the room.
|
|
81
|
+
*/
|
|
82
|
+
private handleUserMessage;
|
|
83
|
+
private bindEvents;
|
|
84
|
+
}
|
|
85
|
+
export declare class AgentClients {
|
|
86
|
+
private rooms;
|
|
87
|
+
/**
|
|
88
|
+
* Create an agent client and connect it to the server.
|
|
89
|
+
* The agent will NOT auto-join any room — call addAgentToRoom separately.
|
|
90
|
+
*/
|
|
91
|
+
createAgent(config: AgentConfig, handlers?: AgentEventHandler, port?: number): Promise<AgentClient>;
|
|
92
|
+
/**
|
|
93
|
+
* Connect an agent to a room.
|
|
94
|
+
*/
|
|
95
|
+
addAgentToRoom(roomId: string, client: AgentClient): Promise<JoinResult>;
|
|
96
|
+
/**
|
|
97
|
+
* Remove an agent from a room and disconnect it.
|
|
98
|
+
*/
|
|
99
|
+
removeAgentFromRoom(roomId: string, agentId: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* Get all agents in a room.
|
|
102
|
+
*/
|
|
103
|
+
getAgents(roomId: string): AgentClient[];
|
|
104
|
+
/**
|
|
105
|
+
* Get a specific agent in a room.
|
|
106
|
+
*/
|
|
107
|
+
getAgent(roomId: string, agentId: string): AgentClient | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Get all room IDs that have agents.
|
|
110
|
+
*/
|
|
111
|
+
getRoomIds(): string[];
|
|
112
|
+
/**
|
|
113
|
+
* Send a message from a specific agent in a room.
|
|
114
|
+
*/
|
|
115
|
+
sendMessage(roomId: string, agentId: string, content: string): Promise<string>;
|
|
116
|
+
/**
|
|
117
|
+
* Broadcast a message from all agents in a room.
|
|
118
|
+
*/
|
|
119
|
+
broadcastFromRoom(roomId: string, content: string): Promise<string[]>;
|
|
120
|
+
/**
|
|
121
|
+
* Disconnect all agents in a room.
|
|
122
|
+
*/
|
|
123
|
+
disconnectRoom(roomId: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Disconnect all agents in all rooms.
|
|
126
|
+
*/
|
|
127
|
+
disconnectAll(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Set gateway manager for all existing and future agents.
|
|
130
|
+
*/
|
|
131
|
+
setGatewayManager(manager: GatewayManager): void;
|
|
132
|
+
}
|
|
133
|
+
export {};
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentClients = void 0;
|
|
4
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
5
|
+
const auth_1 = require("../../auth");
|
|
6
|
+
// ─── Agent Client (single connection) ─────────────────────────
|
|
7
|
+
class AgentClient {
|
|
8
|
+
agentId;
|
|
9
|
+
profile;
|
|
10
|
+
name;
|
|
11
|
+
socket = null;
|
|
12
|
+
joinedRooms = new Set();
|
|
13
|
+
handlers;
|
|
14
|
+
port = 8648;
|
|
15
|
+
_reconnecting = false;
|
|
16
|
+
gatewayManager = null;
|
|
17
|
+
constructor(config, handlers = {}) {
|
|
18
|
+
this.agentId = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
19
|
+
this.profile = config.profile;
|
|
20
|
+
this.name = config.name;
|
|
21
|
+
this.handlers = handlers;
|
|
22
|
+
}
|
|
23
|
+
get connected() {
|
|
24
|
+
return this.socket?.connected ?? false;
|
|
25
|
+
}
|
|
26
|
+
get id() {
|
|
27
|
+
return this.socket?.id;
|
|
28
|
+
}
|
|
29
|
+
setGatewayManager(manager) {
|
|
30
|
+
this.gatewayManager = manager;
|
|
31
|
+
}
|
|
32
|
+
async connect(port = 8648) {
|
|
33
|
+
this.port = port;
|
|
34
|
+
const token = await (0, auth_1.getToken)();
|
|
35
|
+
this.socket = (0, socket_io_client_1.io)(`http://127.0.0.1:${port}/api/hermes/group-chat`, {
|
|
36
|
+
auth: {
|
|
37
|
+
token: token || undefined,
|
|
38
|
+
name: this.name,
|
|
39
|
+
},
|
|
40
|
+
transports: ['websocket'],
|
|
41
|
+
reconnection: true,
|
|
42
|
+
reconnectionAttempts: Infinity,
|
|
43
|
+
reconnectionDelay: 1000,
|
|
44
|
+
reconnectionDelayMax: 30000,
|
|
45
|
+
});
|
|
46
|
+
this.bindEvents();
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const timeout = setTimeout(() => reject(new Error('Connection timeout')), 10000);
|
|
49
|
+
this.socket.on('connect', () => {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
this.socket.on('connect_error', (err) => {
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
reject(err);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
disconnect() {
|
|
60
|
+
if (this.socket) {
|
|
61
|
+
this.socket.disconnect();
|
|
62
|
+
this.socket = null;
|
|
63
|
+
this.joinedRooms.clear();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async joinRoom(roomId) {
|
|
67
|
+
this.ensureConnected();
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
this.socket.emit('join', { roomId }, (res) => {
|
|
70
|
+
if ('error' in res) {
|
|
71
|
+
reject(new Error(res.error));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.joinedRooms.add(roomId);
|
|
75
|
+
resolve(res);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
sendMessage(roomId, content) {
|
|
81
|
+
this.ensureConnected();
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
this.socket.emit('message', { roomId, content }, (res) => {
|
|
84
|
+
if (res.error) {
|
|
85
|
+
reject(new Error(res.error));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
resolve(res.id);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
startTyping(roomId) {
|
|
94
|
+
this.ensureConnected();
|
|
95
|
+
this.socket.emit('typing', { roomId });
|
|
96
|
+
}
|
|
97
|
+
stopTyping(roomId) {
|
|
98
|
+
this.ensureConnected();
|
|
99
|
+
this.socket.emit('stop_typing', { roomId });
|
|
100
|
+
}
|
|
101
|
+
getJoinedRooms() {
|
|
102
|
+
return Array.from(this.joinedRooms);
|
|
103
|
+
}
|
|
104
|
+
async deleteSession(upstream, apiKey, runId) {
|
|
105
|
+
try {
|
|
106
|
+
await fetch(`${upstream}/v1/sessions/${runId}`, {
|
|
107
|
+
method: 'DELETE',
|
|
108
|
+
headers: {
|
|
109
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
110
|
+
},
|
|
111
|
+
signal: AbortSignal.timeout(10000),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.warn(`[AgentClients] ${this.name}: failed to delete session ${runId}: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
ensureConnected() {
|
|
119
|
+
if (!this.socket?.connected) {
|
|
120
|
+
throw new Error(`Agent "${this.name}" is not connected`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ─── Hermes Gateway Integration ────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* Forward a user message to Hermes gateway and stream the reply back to the room.
|
|
126
|
+
*/
|
|
127
|
+
async handleUserMessage(roomId, msg) {
|
|
128
|
+
if (!this.gatewayManager)
|
|
129
|
+
return;
|
|
130
|
+
// Ignore own messages and messages mentioning own name
|
|
131
|
+
if (msg.senderId === this.socket?.id)
|
|
132
|
+
return;
|
|
133
|
+
if (!msg.content.toLowerCase().includes(`@${this.name.toLowerCase()}`))
|
|
134
|
+
return;
|
|
135
|
+
const upstream = this.gatewayManager.getUpstream(this.profile);
|
|
136
|
+
const apiKey = this.gatewayManager.getApiKey(this.profile);
|
|
137
|
+
if (!upstream) {
|
|
138
|
+
console.error(`[AgentClients] ${this.name}: no gateway upstream for profile "${this.profile}"`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
// Notify room that agent is typing
|
|
143
|
+
this.startTyping(roomId);
|
|
144
|
+
// Generate unique session_id per agent per interaction
|
|
145
|
+
const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
146
|
+
// Start a run on Hermes gateway
|
|
147
|
+
const runRes = await fetch(`${upstream}/v1/runs`, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
'Content-Type': 'application/json',
|
|
151
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
input: msg.content,
|
|
155
|
+
session_id: sessionId,
|
|
156
|
+
}),
|
|
157
|
+
signal: AbortSignal.timeout(120000),
|
|
158
|
+
});
|
|
159
|
+
if (!runRes.ok) {
|
|
160
|
+
const text = await runRes.text().catch(() => '');
|
|
161
|
+
console.error(`[AgentClients] ${this.name}: gateway run failed (${runRes.status}): ${text}`);
|
|
162
|
+
this.stopTyping(roomId);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const { run_id } = await runRes.json();
|
|
166
|
+
if (!run_id) {
|
|
167
|
+
this.stopTyping(roomId);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Stream events from Hermes
|
|
171
|
+
const eventsUrl = new URL(`${upstream}/v1/runs/${run_id}/events`);
|
|
172
|
+
if (apiKey)
|
|
173
|
+
eventsUrl.searchParams.set('token', apiKey);
|
|
174
|
+
const source = new EventSource(eventsUrl.toString());
|
|
175
|
+
let fullContent = '';
|
|
176
|
+
source.onmessage = (e) => {
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(e.data);
|
|
179
|
+
if (parsed.event === 'run.completed') {
|
|
180
|
+
source.close();
|
|
181
|
+
if (fullContent) {
|
|
182
|
+
this.stopTyping(roomId);
|
|
183
|
+
this.sendMessage(roomId, fullContent);
|
|
184
|
+
}
|
|
185
|
+
this.deleteSession(upstream, apiKey, sessionId).catch(() => { });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (parsed.event === 'run.failed') {
|
|
189
|
+
source.close();
|
|
190
|
+
this.stopTyping(roomId);
|
|
191
|
+
this.deleteSession(upstream, apiKey, sessionId).catch(() => { });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Accumulate message deltas
|
|
195
|
+
if (parsed.event === 'message' && parsed.delta) {
|
|
196
|
+
fullContent += parsed.delta;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// ignore parse errors
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
source.onerror = () => {
|
|
204
|
+
source.close();
|
|
205
|
+
this.stopTyping(roomId);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
console.error(`[AgentClients] ${this.name}: error handling message: ${err.message}`);
|
|
210
|
+
this.stopTyping(roomId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
bindEvents() {
|
|
214
|
+
const s = this.socket;
|
|
215
|
+
s.on('message', (msg) => {
|
|
216
|
+
// Forward to Hermes gateway for AI response
|
|
217
|
+
this.handleUserMessage(msg.roomId, msg).catch((err) => {
|
|
218
|
+
console.error(`[AgentClients] ${this.name}: handleUserMessage error: ${err.message}`);
|
|
219
|
+
});
|
|
220
|
+
// Also notify external handlers
|
|
221
|
+
this.handlers.onMessage?.({ roomId: msg.roomId, msg });
|
|
222
|
+
});
|
|
223
|
+
s.on('typing', (data) => {
|
|
224
|
+
this.handlers.onTyping?.(data);
|
|
225
|
+
});
|
|
226
|
+
s.on('stop_typing', (data) => {
|
|
227
|
+
this.handlers.onStopTyping?.(data);
|
|
228
|
+
});
|
|
229
|
+
s.on('member_joined', (data) => {
|
|
230
|
+
this.handlers.onMemberJoined?.(data);
|
|
231
|
+
});
|
|
232
|
+
s.on('member_left', (data) => {
|
|
233
|
+
this.handlers.onMemberLeft?.(data);
|
|
234
|
+
});
|
|
235
|
+
// Auto rejoin rooms on reconnect
|
|
236
|
+
s.io.on('reconnect', async () => {
|
|
237
|
+
if (this._reconnecting)
|
|
238
|
+
return;
|
|
239
|
+
this._reconnecting = true;
|
|
240
|
+
console.log(`[AgentClients] ${this.name} reconnecting, rejoining ${this.joinedRooms.size} rooms...`);
|
|
241
|
+
const rooms = Array.from(this.joinedRooms);
|
|
242
|
+
for (const roomId of rooms) {
|
|
243
|
+
try {
|
|
244
|
+
await this.joinRoom(roomId);
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
console.error(`[AgentClients] ${this.name} failed to rejoin room ${roomId}: ${err.message}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
this._reconnecting = false;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// ─── AgentClients (roomId -> agents) ──────────────────────────
|
|
255
|
+
class AgentClients {
|
|
256
|
+
rooms = new Map();
|
|
257
|
+
/**
|
|
258
|
+
* Create an agent client and connect it to the server.
|
|
259
|
+
* The agent will NOT auto-join any room — call addAgentToRoom separately.
|
|
260
|
+
*/
|
|
261
|
+
async createAgent(config, handlers, port) {
|
|
262
|
+
const client = new AgentClient(config, handlers);
|
|
263
|
+
await client.connect(port);
|
|
264
|
+
console.log(`[AgentClients] Connected: ${client.name} (${client.agentId})`);
|
|
265
|
+
return client;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Connect an agent to a room.
|
|
269
|
+
*/
|
|
270
|
+
async addAgentToRoom(roomId, client) {
|
|
271
|
+
let room = this.rooms.get(roomId);
|
|
272
|
+
if (!room) {
|
|
273
|
+
room = new Map();
|
|
274
|
+
this.rooms.set(roomId, room);
|
|
275
|
+
}
|
|
276
|
+
room.set(client.agentId, client);
|
|
277
|
+
const result = await client.joinRoom(roomId);
|
|
278
|
+
console.log(`[AgentClients] ${client.name} joined room: ${roomId}`);
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Remove an agent from a room and disconnect it.
|
|
283
|
+
*/
|
|
284
|
+
removeAgentFromRoom(roomId, agentId) {
|
|
285
|
+
const room = this.rooms.get(roomId);
|
|
286
|
+
if (!room)
|
|
287
|
+
return;
|
|
288
|
+
const client = room.get(agentId);
|
|
289
|
+
if (client) {
|
|
290
|
+
client.disconnect();
|
|
291
|
+
room.delete(agentId);
|
|
292
|
+
console.log(`[AgentClients] ${client.name} left room: ${roomId}`);
|
|
293
|
+
}
|
|
294
|
+
if (room.size === 0) {
|
|
295
|
+
this.rooms.delete(roomId);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get all agents in a room.
|
|
300
|
+
*/
|
|
301
|
+
getAgents(roomId) {
|
|
302
|
+
const room = this.rooms.get(roomId);
|
|
303
|
+
return room ? Array.from(room.values()) : [];
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get a specific agent in a room.
|
|
307
|
+
*/
|
|
308
|
+
getAgent(roomId, agentId) {
|
|
309
|
+
return this.rooms.get(roomId)?.get(agentId);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get all room IDs that have agents.
|
|
313
|
+
*/
|
|
314
|
+
getRoomIds() {
|
|
315
|
+
return Array.from(this.rooms.keys());
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Send a message from a specific agent in a room.
|
|
319
|
+
*/
|
|
320
|
+
async sendMessage(roomId, agentId, content) {
|
|
321
|
+
const client = this.getAgent(roomId, agentId);
|
|
322
|
+
if (!client) {
|
|
323
|
+
throw new Error(`Agent "${agentId}" not found in room "${roomId}"`);
|
|
324
|
+
}
|
|
325
|
+
return client.sendMessage(roomId, content);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Broadcast a message from all agents in a room.
|
|
329
|
+
*/
|
|
330
|
+
async broadcastFromRoom(roomId, content) {
|
|
331
|
+
const agents = this.getAgents(roomId);
|
|
332
|
+
return Promise.all(agents.map((agent) => agent.sendMessage(roomId, content)));
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Disconnect all agents in a room.
|
|
336
|
+
*/
|
|
337
|
+
disconnectRoom(roomId) {
|
|
338
|
+
const room = this.rooms.get(roomId);
|
|
339
|
+
if (!room)
|
|
340
|
+
return;
|
|
341
|
+
room.forEach((client) => client.disconnect());
|
|
342
|
+
this.rooms.delete(roomId);
|
|
343
|
+
console.log(`[AgentClients] All agents disconnected from room: ${roomId}`);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Disconnect all agents in all rooms.
|
|
347
|
+
*/
|
|
348
|
+
disconnectAll() {
|
|
349
|
+
this.rooms.forEach((room) => {
|
|
350
|
+
room.forEach((client) => client.disconnect());
|
|
351
|
+
});
|
|
352
|
+
this.rooms.clear();
|
|
353
|
+
console.log('[AgentClients] All agents disconnected');
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Set gateway manager for all existing and future agents.
|
|
357
|
+
*/
|
|
358
|
+
setGatewayManager(manager) {
|
|
359
|
+
this.rooms.forEach((room) => {
|
|
360
|
+
room.forEach((client) => client.setGatewayManager(manager));
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
exports.AgentClients = AgentClients;
|