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,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.updateRoutes = void 0;
|
|
40
|
+
const router_1 = __importDefault(require("@koa/router"));
|
|
41
|
+
exports.updateRoutes = new router_1.default();
|
|
42
|
+
exports.updateRoutes.post('/api/hermes/update', async (ctx) => {
|
|
43
|
+
const isWin = process.platform === 'win32';
|
|
44
|
+
const cmd = isWin
|
|
45
|
+
? 'cmd /c npm install -g hermes-web-ui@latest'
|
|
46
|
+
: 'npm install -g hermes-web-ui@latest';
|
|
47
|
+
try {
|
|
48
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
49
|
+
const output = execSync(cmd, {
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
timeout: 120000,
|
|
52
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
53
|
+
});
|
|
54
|
+
ctx.body = { success: true, message: output.trim() };
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
const { spawn } = require('child_process');
|
|
57
|
+
spawn(isWin ? 'cmd' : 'sh', isWin ? ['/c', 'hermes-web-ui restart'] : ['-c', 'hermes-web-ui restart'], {
|
|
58
|
+
detached: true,
|
|
59
|
+
stdio: 'ignore',
|
|
60
|
+
windowsHide: true,
|
|
61
|
+
}).unref();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}, 2000);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
ctx.status = 500;
|
|
67
|
+
ctx.body = { success: false, message: err.stderr || err.message };
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -23,28 +23,58 @@ exports.uploadRoutes.post('/upload', async (ctx) => {
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
await (0, promises_1.mkdir)(config_1.config.uploadDir, { recursive: true });
|
|
26
|
-
// Read raw body
|
|
26
|
+
// Read raw body as Buffer
|
|
27
27
|
const chunks = [];
|
|
28
28
|
for await (const chunk of ctx.req)
|
|
29
29
|
chunks.push(chunk);
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const raw = Buffer.concat(chunks);
|
|
31
|
+
const boundaryBuf = Buffer.from(boundary);
|
|
32
|
+
const parts = splitMultipart(raw, boundaryBuf);
|
|
32
33
|
const results = [];
|
|
33
34
|
for (const part of parts) {
|
|
34
|
-
const headerEnd = part.indexOf('\r\n\r\n');
|
|
35
|
+
const headerEnd = part.indexOf(Buffer.from('\r\n\r\n'));
|
|
35
36
|
if (headerEnd === -1)
|
|
36
37
|
continue;
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
38
|
+
const headerBuf = part.subarray(0, headerEnd);
|
|
39
|
+
const header = headerBuf.toString('utf-8');
|
|
40
|
+
const data = part.subarray(headerEnd + 4, part.length - 2);
|
|
41
|
+
// Try RFC 5987 filename* first, fall back to filename
|
|
42
|
+
let filename = '';
|
|
43
|
+
const filenameStarMatch = header.match(/filename\*=UTF-8''(.+)/i);
|
|
44
|
+
if (filenameStarMatch) {
|
|
45
|
+
filename = decodeURIComponent(filenameStarMatch[1]);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const filenameMatch = header.match(/filename="([^"]+)"/);
|
|
49
|
+
if (!filenameMatch)
|
|
50
|
+
continue;
|
|
51
|
+
filename = filenameMatch[1];
|
|
52
|
+
}
|
|
43
53
|
const ext = filename.includes('.') ? '.' + filename.split('.').pop() : '';
|
|
44
54
|
const savedName = (0, crypto_1.randomBytes)(8).toString('hex') + ext;
|
|
45
55
|
const savedPath = `${config_1.config.uploadDir}/${savedName}`;
|
|
46
|
-
await (0, promises_1.writeFile)(savedPath,
|
|
56
|
+
await (0, promises_1.writeFile)(savedPath, data);
|
|
47
57
|
results.push({ name: filename, path: savedPath });
|
|
48
58
|
}
|
|
49
59
|
ctx.body = { files: results };
|
|
50
60
|
});
|
|
61
|
+
/**
|
|
62
|
+
* Split a multipart Buffer by boundary, returning part Buffers.
|
|
63
|
+
* Avoids string decoding so multi-byte characters (e.g. Chinese filenames) are preserved.
|
|
64
|
+
*/
|
|
65
|
+
function splitMultipart(raw, boundary) {
|
|
66
|
+
const parts = [];
|
|
67
|
+
let start = 0;
|
|
68
|
+
while (true) {
|
|
69
|
+
const idx = raw.indexOf(boundary, start);
|
|
70
|
+
if (idx === -1)
|
|
71
|
+
break;
|
|
72
|
+
if (start > 0) {
|
|
73
|
+
// Skip the \r\n after boundary
|
|
74
|
+
const partStart = start + 2;
|
|
75
|
+
parts.push(raw.subarray(partStart, idx));
|
|
76
|
+
}
|
|
77
|
+
start = idx + boundary.length;
|
|
78
|
+
}
|
|
79
|
+
return parts;
|
|
80
|
+
}
|
|
@@ -46,7 +46,7 @@ async function authMiddleware(token) {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
// Skip non-API paths (static files, health check, SPA)
|
|
49
|
-
const path = ctx.path;
|
|
49
|
+
const path = ctx.path.toLowerCase();
|
|
50
50
|
if (path === '/health' ||
|
|
51
51
|
(!path.startsWith('/api') && !path.startsWith('/v1') && path !== '/webhook')) {
|
|
52
52
|
await next();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getGatewayManagerInstance = getGatewayManagerInstance;
|
|
37
|
+
exports.initGatewayManager = initGatewayManager;
|
|
38
|
+
let gatewayManager = null;
|
|
39
|
+
function getGatewayManagerInstance() {
|
|
40
|
+
return gatewayManager;
|
|
41
|
+
}
|
|
42
|
+
async function initGatewayManager() {
|
|
43
|
+
const { GatewayManager } = await Promise.resolve().then(() => __importStar(require('./hermes/gateway-manager')));
|
|
44
|
+
const { getActiveProfileName } = await Promise.resolve().then(() => __importStar(require('./hermes/hermes-profile')));
|
|
45
|
+
const { setGatewayManager } = await Promise.resolve().then(() => __importStar(require('../routes/hermes/gateways')));
|
|
46
|
+
const activeProfile = getActiveProfileName();
|
|
47
|
+
gatewayManager = new GatewayManager(activeProfile);
|
|
48
|
+
setGatewayManager(gatewayManager);
|
|
49
|
+
await gatewayManager.detectAllOnStartup();
|
|
50
|
+
await gatewayManager.startAll();
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as roomsDb from './rooms-db';
|
|
2
|
+
export type AgentStatus = 'thinking' | 'responding' | 'done' | 'error';
|
|
3
|
+
export interface CoordinatorCallbacks {
|
|
4
|
+
onDelta: (roomId: string, agentName: string, text: string) => void;
|
|
5
|
+
onAgentStatus: (roomId: string, agentName: string, status: AgentStatus) => void;
|
|
6
|
+
onAgentComplete: (roomId: string, agentName: string, message: roomsDb.RoomMessage) => void;
|
|
7
|
+
onAgentError: (roomId: string, agentName: string, error: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseMentions(text: string): string[];
|
|
10
|
+
export declare function buildAgentContext(roomId: string, agentName: string, agents: roomsDb.RoomAgent[], maxMessages?: number): Array<{
|
|
11
|
+
role: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function processMessage(roomId: string, content: string, callbacks: CoordinatorCallbacks): Promise<void>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseMentions = parseMentions;
|
|
37
|
+
exports.buildAgentContext = buildAgentContext;
|
|
38
|
+
exports.processMessage = processMessage;
|
|
39
|
+
const roomsDb = __importStar(require("./rooms-db"));
|
|
40
|
+
const gateways_1 = require("../../routes/hermes/gateways");
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// @mention parsing
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
function parseMentions(text) {
|
|
45
|
+
const pattern = /@(\w+)/g;
|
|
46
|
+
const mentions = [];
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
49
|
+
mentions.push(match[1]);
|
|
50
|
+
}
|
|
51
|
+
return [...new Set(mentions)];
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Context building
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
function buildAgentContext(roomId, agentName, agents, maxMessages = 20) {
|
|
57
|
+
const recentMessages = roomsDb.getRecentContext(roomId, agentName, maxMessages);
|
|
58
|
+
const context = [];
|
|
59
|
+
// Add role prompt
|
|
60
|
+
const agent = agents.find((a) => a.agent_name === agentName);
|
|
61
|
+
if (agent?.role_prompt) {
|
|
62
|
+
context.push({ role: 'system', content: agent.role_prompt });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
context.push({
|
|
66
|
+
role: 'system',
|
|
67
|
+
content: `You are ${agentName}, participating in a group chat. Respond concisely. You can see messages where you were @mentioned and public messages (no @mentions).`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Convert recent messages (DESC from DB → reverse to ASC chronological)
|
|
71
|
+
for (const msg of recentMessages.reverse()) {
|
|
72
|
+
if (msg.role === 'user') {
|
|
73
|
+
context.push({ role: 'user', content: msg.content });
|
|
74
|
+
}
|
|
75
|
+
else if (msg.role === 'assistant' && msg.agent_name) {
|
|
76
|
+
context.push({ role: 'assistant', content: `[${msg.agent_name}] ${msg.content}` });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return context;
|
|
80
|
+
}
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Main: process a user message
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
async function processMessage(roomId, content, callbacks) {
|
|
85
|
+
const mentions = parseMentions(content);
|
|
86
|
+
const agents = roomsDb.getRoomAgents(roomId);
|
|
87
|
+
if (agents.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
// Determine responding agents
|
|
90
|
+
const respondingAgents = mentions.length > 0
|
|
91
|
+
? agents.filter((a) => mentions.includes(a.agent_name))
|
|
92
|
+
: agents;
|
|
93
|
+
if (respondingAgents.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
// Store user message
|
|
96
|
+
const round = roomsDb.getLatestRound(roomId);
|
|
97
|
+
roomsDb.addMessage({
|
|
98
|
+
room_id: roomId,
|
|
99
|
+
role: 'user',
|
|
100
|
+
content,
|
|
101
|
+
mentions,
|
|
102
|
+
round,
|
|
103
|
+
});
|
|
104
|
+
// Fan out to agents in parallel
|
|
105
|
+
const tasks = respondingAgents.map((agent) => invokeAgent(roomId, agent, round, content, callbacks).catch(() => { }));
|
|
106
|
+
await Promise.allSettled(tasks);
|
|
107
|
+
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Invoke a single agent
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
async function invokeAgent(roomId, agent, round, userMessage, callbacks) {
|
|
112
|
+
const agentName = agent.agent_name;
|
|
113
|
+
let upstream = '';
|
|
114
|
+
let apiKey = null;
|
|
115
|
+
try {
|
|
116
|
+
callbacks.onAgentStatus(roomId, agentName, 'thinking');
|
|
117
|
+
// Resolve gateway URL and API key for this agent's profile
|
|
118
|
+
const manager = (0, gateways_1.getGatewayManager)();
|
|
119
|
+
if (!manager)
|
|
120
|
+
throw new Error('GatewayManager not initialized');
|
|
121
|
+
upstream = manager.getUpstream(agent.profile);
|
|
122
|
+
apiKey = manager.getApiKey(agent.profile);
|
|
123
|
+
// Build conversation context
|
|
124
|
+
const agents = roomsDb.getRoomAgents(roomId);
|
|
125
|
+
const context = buildAgentContext(roomId, agentName, agents);
|
|
126
|
+
// Start run via Hermes API Server
|
|
127
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
128
|
+
if (apiKey)
|
|
129
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
130
|
+
const body = { input: userMessage };
|
|
131
|
+
if (context.length > 0)
|
|
132
|
+
body.conversation_history = context;
|
|
133
|
+
if (agent.session_id)
|
|
134
|
+
body.session_id = agent.session_id;
|
|
135
|
+
const runRes = await fetch(`${upstream}/v1/runs`, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers,
|
|
138
|
+
body: JSON.stringify(body),
|
|
139
|
+
signal: AbortSignal.timeout(120_000),
|
|
140
|
+
});
|
|
141
|
+
if (!runRes.ok) {
|
|
142
|
+
const errText = await runRes.text();
|
|
143
|
+
throw new Error(`Hermes API error ${runRes.status}: ${errText.slice(0, 200)}`);
|
|
144
|
+
}
|
|
145
|
+
const runData = await runRes.json();
|
|
146
|
+
const runId = runData.run_id || runData.id;
|
|
147
|
+
if (!runId)
|
|
148
|
+
throw new Error('No run_id returned from Hermes API');
|
|
149
|
+
// Persist session_id for subsequent messages
|
|
150
|
+
if (runData.session_id && runData.session_id !== agent.session_id) {
|
|
151
|
+
roomsDb.updateRoomAgentSession(roomId, agentName, runData.session_id);
|
|
152
|
+
}
|
|
153
|
+
callbacks.onAgentStatus(roomId, agentName, 'responding');
|
|
154
|
+
// Stream SSE events
|
|
155
|
+
const sseHeaders = {};
|
|
156
|
+
if (apiKey)
|
|
157
|
+
sseHeaders['Authorization'] = `Bearer ${apiKey}`;
|
|
158
|
+
const sseRes = await fetch(`${upstream}/v1/runs/${runId}/events`, {
|
|
159
|
+
headers: sseHeaders,
|
|
160
|
+
signal: AbortSignal.timeout(300_000),
|
|
161
|
+
});
|
|
162
|
+
if (!sseRes.ok || !sseRes.body) {
|
|
163
|
+
throw new Error(`Failed to stream events: ${sseRes.status}`);
|
|
164
|
+
}
|
|
165
|
+
let fullContent = '';
|
|
166
|
+
const reader = sseRes.body.getReader();
|
|
167
|
+
const decoder = new TextDecoder();
|
|
168
|
+
let buffer = '';
|
|
169
|
+
try {
|
|
170
|
+
while (true) {
|
|
171
|
+
const { done, value } = await reader.read();
|
|
172
|
+
if (done)
|
|
173
|
+
break;
|
|
174
|
+
buffer += decoder.decode(value, { stream: true });
|
|
175
|
+
const lines = buffer.split('\n');
|
|
176
|
+
buffer = lines.pop() || '';
|
|
177
|
+
for (const line of lines) {
|
|
178
|
+
if (!line.startsWith('data: '))
|
|
179
|
+
continue;
|
|
180
|
+
const data = line.slice(6).trim();
|
|
181
|
+
if (data === '[DONE]')
|
|
182
|
+
continue;
|
|
183
|
+
try {
|
|
184
|
+
const event = JSON.parse(data);
|
|
185
|
+
if (event.event === 'message.delta' && event.delta) {
|
|
186
|
+
fullContent += event.delta;
|
|
187
|
+
callbacks.onDelta(roomId, agentName, event.delta);
|
|
188
|
+
}
|
|
189
|
+
if (event.event === 'run.completed' || event.event === 'run.failed') {
|
|
190
|
+
// If delta contained in completed event
|
|
191
|
+
if (event.event === 'run.completed' && event.response) {
|
|
192
|
+
fullContent = event.response;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// skip malformed JSON
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
reader.releaseLock();
|
|
204
|
+
}
|
|
205
|
+
if (!fullContent)
|
|
206
|
+
fullContent = '(no response)';
|
|
207
|
+
// Store completed response
|
|
208
|
+
const msg = roomsDb.addMessage({
|
|
209
|
+
room_id: roomId,
|
|
210
|
+
role: 'assistant',
|
|
211
|
+
agent_name: agentName,
|
|
212
|
+
content: fullContent,
|
|
213
|
+
round,
|
|
214
|
+
});
|
|
215
|
+
callbacks.onAgentStatus(roomId, agentName, 'done');
|
|
216
|
+
callbacks.onAgentComplete(roomId, agentName, msg);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
const errorMsg = err?.message || 'Unknown error';
|
|
220
|
+
callbacks.onAgentStatus(roomId, agentName, 'error');
|
|
221
|
+
callbacks.onAgentError(roomId, agentName, errorMsg);
|
|
222
|
+
roomsDb.addMessage({
|
|
223
|
+
room_id: roomId,
|
|
224
|
+
role: 'system',
|
|
225
|
+
agent_name: agentName,
|
|
226
|
+
content: `Error: ${errorMsg}`,
|
|
227
|
+
round,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Server as SocketIOServer } from 'socket.io';
|
|
2
|
+
import type { Server as HttpServer } from 'http';
|
|
3
|
+
export declare function setupGroupChatSocketIO(httpServer: HttpServer): SocketIOServer;
|
|
4
|
+
export declare function getGroupChatIO(): SocketIOServer | null;
|
|
5
|
+
export declare function disconnectGroupChatIO(): void;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.setupGroupChatSocketIO = setupGroupChatSocketIO;
|
|
37
|
+
exports.getGroupChatIO = getGroupChatIO;
|
|
38
|
+
exports.disconnectGroupChatIO = disconnectGroupChatIO;
|
|
39
|
+
const socket_io_1 = require("socket.io");
|
|
40
|
+
const auth_1 = require("../auth");
|
|
41
|
+
const roomsDb = __importStar(require("./rooms-db"));
|
|
42
|
+
const coordinator = __importStar(require("./coordinator"));
|
|
43
|
+
let io = null;
|
|
44
|
+
function setupGroupChatSocketIO(httpServer) {
|
|
45
|
+
io = new socket_io_1.Server(httpServer, {
|
|
46
|
+
path: '/socket.io/group-chat',
|
|
47
|
+
cors: { origin: '*' },
|
|
48
|
+
});
|
|
49
|
+
// Auth middleware
|
|
50
|
+
io.use(async (socket, next) => {
|
|
51
|
+
const authToken = await (0, auth_1.getToken)();
|
|
52
|
+
if (authToken) {
|
|
53
|
+
const token = socket.handshake.auth.token || socket.handshake.query.token;
|
|
54
|
+
if (token !== authToken) {
|
|
55
|
+
return next(new Error('Unauthorized'));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
next();
|
|
59
|
+
});
|
|
60
|
+
io.on('connection', (socket) => {
|
|
61
|
+
let currentRoom = null;
|
|
62
|
+
socket.on('room:join', async (roomId) => {
|
|
63
|
+
if (currentRoom) {
|
|
64
|
+
socket.leave(currentRoom);
|
|
65
|
+
}
|
|
66
|
+
currentRoom = roomId;
|
|
67
|
+
socket.join(roomId);
|
|
68
|
+
const messages = roomsDb.getRoomMessages(roomId, 100);
|
|
69
|
+
const agents = roomsDb.getRoomAgents(roomId);
|
|
70
|
+
socket.emit('room:history', { roomId, messages, agents });
|
|
71
|
+
});
|
|
72
|
+
socket.on('room:leave', (roomId) => {
|
|
73
|
+
socket.leave(roomId);
|
|
74
|
+
if (currentRoom === roomId)
|
|
75
|
+
currentRoom = null;
|
|
76
|
+
});
|
|
77
|
+
socket.on('room:message', async (data) => {
|
|
78
|
+
if (!currentRoom || currentRoom !== data.roomId)
|
|
79
|
+
return;
|
|
80
|
+
if (!data.content?.trim())
|
|
81
|
+
return;
|
|
82
|
+
const { roomId, content } = data;
|
|
83
|
+
// Process via coordinator (stores user message internally)
|
|
84
|
+
await coordinator.processMessage(roomId, content, {
|
|
85
|
+
onDelta(rId, agentName, text) {
|
|
86
|
+
io.to(rId).emit('message:delta', { roomId: rId, agentName, text });
|
|
87
|
+
},
|
|
88
|
+
onAgentStatus(rId, agentName, status) {
|
|
89
|
+
io.to(rId).emit('agent:status', { roomId: rId, agentName, status });
|
|
90
|
+
},
|
|
91
|
+
onAgentComplete(rId, agentName, message) {
|
|
92
|
+
io.to(rId).emit('room:message', { roomId: rId, message });
|
|
93
|
+
},
|
|
94
|
+
onAgentError(rId, agentName, error) {
|
|
95
|
+
io.to(rId).emit('agent:status', { roomId: rId, agentName, status: 'error', error });
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
socket.on('disconnect', () => {
|
|
100
|
+
if (currentRoom) {
|
|
101
|
+
socket.leave(currentRoom);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
return io;
|
|
106
|
+
}
|
|
107
|
+
function getGroupChatIO() {
|
|
108
|
+
return io;
|
|
109
|
+
}
|
|
110
|
+
function disconnectGroupChatIO() {
|
|
111
|
+
if (io) {
|
|
112
|
+
io.disconnectSockets();
|
|
113
|
+
io = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface Room {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
settings: string;
|
|
5
|
+
created_at: number;
|
|
6
|
+
updated_at: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RoomAgent {
|
|
9
|
+
room_id: string;
|
|
10
|
+
agent_name: string;
|
|
11
|
+
profile: string;
|
|
12
|
+
session_id: string;
|
|
13
|
+
role_prompt: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RoomMessage {
|
|
16
|
+
id: string;
|
|
17
|
+
room_id: string;
|
|
18
|
+
role: 'user' | 'assistant' | 'system';
|
|
19
|
+
agent_name: string;
|
|
20
|
+
content: string;
|
|
21
|
+
mentions: string;
|
|
22
|
+
round: number;
|
|
23
|
+
created_at: number;
|
|
24
|
+
}
|
|
25
|
+
export interface NewRoomMessage {
|
|
26
|
+
room_id: string;
|
|
27
|
+
role: string;
|
|
28
|
+
agent_name?: string;
|
|
29
|
+
content: string;
|
|
30
|
+
mentions?: string[];
|
|
31
|
+
round?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function createRoom(name: string): Room;
|
|
34
|
+
export declare function listRooms(): Room[];
|
|
35
|
+
export declare function getRoom(roomId: string): Room | null;
|
|
36
|
+
export declare function updateRoom(roomId: string, updates: Partial<Pick<Room, 'name' | 'settings'>>): boolean;
|
|
37
|
+
export declare function deleteRoom(roomId: string): boolean;
|
|
38
|
+
export declare function addRoomAgent(roomId: string, agent: {
|
|
39
|
+
agent_name: string;
|
|
40
|
+
profile: string;
|
|
41
|
+
role_prompt?: string;
|
|
42
|
+
}): RoomAgent | null;
|
|
43
|
+
export declare function removeRoomAgent(roomId: string, agentName: string): boolean;
|
|
44
|
+
export declare function getRoomAgents(roomId: string): RoomAgent[];
|
|
45
|
+
export declare function updateRoomAgentSession(roomId: string, agentName: string, sessionId: string): void;
|
|
46
|
+
export declare function addMessage(msg: NewRoomMessage): RoomMessage;
|
|
47
|
+
export declare function getRoomMessages(roomId: string, limit?: number, offset?: number): RoomMessage[];
|
|
48
|
+
export declare function getLatestRound(roomId: string): number;
|
|
49
|
+
/**
|
|
50
|
+
* Get recent context messages relevant to a specific agent.
|
|
51
|
+
* Returns messages where:
|
|
52
|
+
* - mentions is empty (public message, no @mentions)
|
|
53
|
+
* - mentions includes agentName
|
|
54
|
+
* - message is FROM this agent
|
|
55
|
+
*/
|
|
56
|
+
export declare function getRecentContext(roomId: string, agentName: string, limit?: number): RoomMessage[];
|