agent-comms 1.4.3 → 1.5.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/.claude-plugin/plugin.json +1 -1
- package/dist/bridges/user/cli.d.ts +12 -0
- package/dist/bridges/user/cli.d.ts.map +1 -0
- package/dist/bridges/user/cli.js +77 -0
- package/dist/bridges/user/cli.js.map +1 -0
- package/dist/bridges/user/controller.d.ts +39 -0
- package/dist/bridges/user/controller.d.ts.map +1 -0
- package/dist/bridges/user/controller.js +185 -0
- package/dist/bridges/user/controller.js.map +1 -0
- package/dist/bridges/user/tui.d.ts +21 -0
- package/dist/bridges/user/tui.d.ts.map +1 -0
- package/dist/bridges/user/tui.js +237 -0
- package/dist/bridges/user/tui.js.map +1 -0
- package/dist/bridges/user/web/index.html.d.ts +8 -0
- package/dist/bridges/user/web/index.html.d.ts.map +1 -0
- package/dist/bridges/user/web/index.html.js +386 -0
- package/dist/bridges/user/web/index.html.js.map +1 -0
- package/dist/bridges/user/web/server.d.ts +19 -0
- package/dist/bridges/user/web/server.d.ts.map +1 -0
- package/dist/bridges/user/web/server.js +277 -0
- package/dist/bridges/user/web/server.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +63 -1
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend HTML — single-page chat UI served as a string constant.
|
|
3
|
+
*
|
|
4
|
+
* Vanilla JS + CSS, no build step, no framework. Communicates via
|
|
5
|
+
* REST API + WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FRONTEND_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Agent Comms</title>\n<style>\n :root {\n --bg: #1a1a2e;\n --surface: #16213e;\n --border: #0f3460;\n --text: #e4e4e4;\n --dim: #888;\n --accent: #00b4d8;\n --green: #06d6a0;\n --red: #ef476f;\n --yellow: #ffd166;\n --purple: #b5838d;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n background: var(--bg);\n color: var(--text);\n height: 100vh;\n display: flex;\n }\n #sidebar {\n width: 260px;\n background: var(--surface);\n border-right: 1px solid var(--border);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n #sidebar h2 {\n padding: 12px 16px;\n font-size: 14px;\n color: var(--accent);\n border-bottom: 1px solid var(--border);\n }\n .sidebar-section {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n }\n .sidebar-section h3 {\n font-size: 11px;\n text-transform: uppercase;\n color: var(--dim);\n padding: 8px 8px 4px;\n }\n .room-item, .agent-item {\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .room-item:hover, .agent-item:hover { background: rgba(255,255,255,0.05); }\n .room-item.active { background: var(--border); }\n .status-dot {\n width: 8px; height: 8px;\n border-radius: 50%;\n display: inline-block;\n }\n .status-dot.active { background: var(--green); }\n .status-dot.idle { background: var(--yellow); }\n .status-dot.busy { background: var(--red); }\n .status-dot.offline { background: var(--dim); }\n #main {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n #header {\n padding: 12px 20px;\n border-bottom: 1px solid var(--border);\n font-size: 15px;\n font-weight: 600;\n background: var(--surface);\n }\n #messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .msg {\n font-size: 13px;\n line-height: 1.5;\n }\n .msg .sender { font-weight: 600; color: var(--accent); }\n .msg .time { color: var(--dim); font-size: 11px; margin-left: 8px; }\n .msg.system { color: var(--dim); font-style: italic; }\n .msg.status { color: var(--yellow); font-size: 12px; }\n .msg.dm { color: var(--purple); }\n .msg .dm-badge {\n background: var(--purple);\n color: #fff;\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 3px;\n font-weight: 600;\n }\n #input-bar {\n display: flex;\n padding: 12px 20px;\n gap: 8px;\n background: var(--surface);\n border-top: 1px solid var(--border);\n }\n #input {\n flex: 1;\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 8px 12px;\n color: var(--text);\n font-size: 13px;\n outline: none;\n }\n #input:focus { border-color: var(--accent); }\n #send-btn {\n background: var(--accent);\n border: none;\n border-radius: 6px;\n padding: 8px 16px;\n color: #fff;\n font-weight: 600;\n cursor: pointer;\n }\n #send-btn:hover { opacity: 0.9; }\n #empty-state {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--dim);\n font-size: 14px;\n }\n</style>\n</head>\n<body>\n\n<div id=\"sidebar\">\n <h2>Agent Comms</h2>\n <div class=\"sidebar-section\">\n <h3>Rooms</h3>\n <div id=\"room-list\"></div>\n <h3>Agents</h3>\n <div id=\"agent-list\"></div>\n </div>\n</div>\n\n<div id=\"main\">\n <div id=\"header\">Select a room</div>\n <div id=\"messages\"></div>\n <div id=\"input-bar\">\n <input id=\"input\" type=\"text\" placeholder=\"Type a message or /command...\" autocomplete=\"off\" />\n <button id=\"send-btn\">Send</button>\n </div>\n</div>\n\n<script>\nconst $ = (s) => document.querySelector(s);\nconst messagesEl = $('#messages');\nconst inputEl = $('#input');\nconst headerEl = $('#header');\nconst roomListEl = $('#room-list');\nconst agentListEl = $('#agent-list');\n\nlet currentRoom = null;\nlet ws = null;\n\n// Connect WebSocket\nfunction connect() {\n const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';\n ws = new WebSocket(proto + '//' + location.host);\n\n ws.onopen = () => { addSystem('Connected to mesh'); };\n ws.onclose = () => { addSystem('Disconnected \u2014 reconnecting...'); setTimeout(connect, 3000); };\n ws.onerror = () => {};\n\n ws.onmessage = (e) => {\n const frame = JSON.parse(e.data);\n if (frame.type === 'delivery') handleDelivery(frame.event);\n if (frame.type === 'result') addSystem(frame.result.content);\n if (frame.type === 'error') addSystem('Error: ' + frame.message);\n if (frame.type === 'state') {\n renderAgents(frame.agents);\n renderRooms(frame.rooms);\n }\n };\n}\n\nfunction sendAction(params) {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(params));\n }\n}\n\n// Delivery event handling\nfunction handleDelivery(event) {\n switch (event.type) {\n case 'room_message':\n if (!currentRoom || event.message.room !== currentRoom) return;\n addMessage(event.message.from, event.message.content, event.message.timestamp);\n break;\n case 'dm':\n addDm(event.message.from, event.message.content, event.message.timestamp);\n break;\n case 'member_joined':\n addSystem(event.agent + ' joined ' + event.room);\n refreshState();\n break;\n case 'member_left':\n addSystem(event.agent + ' left ' + event.room);\n refreshState();\n break;\n case 'member_status':\n addStatus(event.agent + ' is now ' + event.status + ' in ' + event.room);\n refreshState();\n break;\n case 'delivery_status':\n addStatus('Message ' + event.messageId + ' ' + event.status + ' by ' + event.agent);\n break;\n case 'room_members':\n if (currentRoom === event.room) {\n addSystem('Members: ' + event.members.map(m => m.name + ' (' + m.status + ')').join(', '));\n }\n break;\n case 'room_invite':\n addSystem('Invited to ' + event.room + ' by ' + event.from);\n break;\n }\n}\n\n// UI rendering\nfunction addMessage(sender, content, timestamp) {\n const time = (timestamp || new Date().toISOString()).slice(11, 19);\n const div = document.createElement('div');\n div.className = 'msg';\n div.innerHTML = '<span class=\"sender\">' + esc(sender) + '</span><span class=\"time\">' + time + '</span>: ' + esc(content);\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction addDm(sender, content, timestamp) {\n const time = (timestamp || new Date().toISOString()).slice(11, 19);\n const div = document.createElement('div');\n div.className = 'msg dm';\n div.innerHTML = '<span class=\"dm-badge\">DM</span> <span class=\"sender\">' + esc(sender) + '</span><span class=\"time\">' + time + '</span>: ' + esc(content);\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction addSystem(text) {\n const div = document.createElement('div');\n div.className = 'msg system';\n div.textContent = text;\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction addStatus(text) {\n const div = document.createElement('div');\n div.className = 'msg status';\n div.textContent = text;\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction clearMessages() {\n messagesEl.innerHTML = '';\n}\n\nfunction esc(s) {\n const d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n}\n\n// State refresh\nasync function refreshState() {\n const [agentsRes, roomsRes] = await Promise.all([\n fetch('/api/agents'), fetch('/api/rooms')\n ]);\n renderAgents(await agentsRes.json());\n renderRooms(await roomsRes.json());\n}\n\nfunction renderAgents(agents) {\n agentListEl.innerHTML = '';\n for (const a of agents) {\n const div = document.createElement('div');\n div.className = 'agent-item';\n const dot = a.status || 'offline';\n div.innerHTML = '<span class=\"status-dot ' + dot + '\"></span> ' + esc(a.name);\n agentListEl.appendChild(div);\n }\n}\n\nfunction renderRooms(rooms) {\n roomListEl.innerHTML = '';\n for (const r of rooms) {\n const div = document.createElement('div');\n div.className = 'room-item' + (currentRoom === r.id ? ' active' : '');\n const joined = r.members.length;\n div.innerHTML = r.type.charAt(0).toUpperCase() + ' ' + esc(r.name) + ' <span style=\"color:var(--dim)\">(' + joined + ')</span>';\n div.onclick = () => joinRoom(r.id);\n roomListEl.appendChild(div);\n }\n}\n\nasync function joinRoom(roomId) {\n currentRoom = roomId;\n headerEl.textContent = roomId;\n clearMessages();\n\n // Highlight in sidebar\n document.querySelectorAll('.room-item').forEach(el => el.classList.remove('active'));\n sendAction({ action: 'join_room', room: roomId });\n\n // Load history\n const res = await fetch('/api/rooms/' + encodeURIComponent(roomId) + '/messages');\n const messages = await res.json();\n for (const m of messages) {\n addMessage(m.from, m.content, m.timestamp);\n }\n addSystem('Joined ' + roomId);\n inputEl.focus();\n}\n\n// Input handling\nfunction handleInput() {\n const text = inputEl.value.trim();\n if (!text) return;\n inputEl.value = '';\n\n if (text.startsWith('/')) {\n const parts = text.slice(1).split(/\\s+/);\n const cmd = parts[0].toLowerCase();\n switch (cmd) {\n case 'join': sendAction({ action: 'join_room', room: parts[1] }); break;\n case 'leave': sendAction({ action: 'leave_room', room: parts[1] || currentRoom }); break;\n case 'rooms': refreshState(); break;\n case 'agents': refreshState(); break;\n case 'dm': sendAction({ action: 'dm', target: parts[1], content: parts.slice(2).join(' ') }); break;\n case 'create': sendAction({ action: 'create_room', name: parts[1], type: 'public' }); break;\n case 'destroy': sendAction({ action: 'destroy_room', room: parts[1] }); break;\n case 'help':\n addSystem('Commands: /join, /leave, /rooms, /agents, /dm, /create, /destroy, /help');\n break;\n default:\n addSystem('Unknown command: /' + cmd);\n }\n } else if (currentRoom) {\n sendAction({ action: 'send', target: currentRoom, content: text });\n } else {\n addSystem('Join a room first (click one in the sidebar)');\n }\n}\n\n$('#send-btn').onclick = handleInput;\ninputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleInput(); });\n\n// Boot\nconnect();\nrefreshState();\ninputEl.focus();\n</script>\n</body>\n</html>";
|
|
8
|
+
//# sourceMappingURL=index.html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.html.d.ts","sourceRoot":"","sources":["../../../../src/bridges/user/web/index.html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,aAAa,2xVA0XlB,CAAC"}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend HTML — single-page chat UI served as a string constant.
|
|
3
|
+
*
|
|
4
|
+
* Vanilla JS + CSS, no build step, no framework. Communicates via
|
|
5
|
+
* REST API + WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export const FRONTEND_HTML = `<!DOCTYPE html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="utf-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<title>Agent Comms</title>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #1a1a2e;
|
|
16
|
+
--surface: #16213e;
|
|
17
|
+
--border: #0f3460;
|
|
18
|
+
--text: #e4e4e4;
|
|
19
|
+
--dim: #888;
|
|
20
|
+
--accent: #00b4d8;
|
|
21
|
+
--green: #06d6a0;
|
|
22
|
+
--red: #ef476f;
|
|
23
|
+
--yellow: #ffd166;
|
|
24
|
+
--purple: #b5838d;
|
|
25
|
+
}
|
|
26
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
27
|
+
body {
|
|
28
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
29
|
+
background: var(--bg);
|
|
30
|
+
color: var(--text);
|
|
31
|
+
height: 100vh;
|
|
32
|
+
display: flex;
|
|
33
|
+
}
|
|
34
|
+
#sidebar {
|
|
35
|
+
width: 260px;
|
|
36
|
+
background: var(--surface);
|
|
37
|
+
border-right: 1px solid var(--border);
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
}
|
|
42
|
+
#sidebar h2 {
|
|
43
|
+
padding: 12px 16px;
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
color: var(--accent);
|
|
46
|
+
border-bottom: 1px solid var(--border);
|
|
47
|
+
}
|
|
48
|
+
.sidebar-section {
|
|
49
|
+
flex: 1;
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
padding: 8px;
|
|
52
|
+
}
|
|
53
|
+
.sidebar-section h3 {
|
|
54
|
+
font-size: 11px;
|
|
55
|
+
text-transform: uppercase;
|
|
56
|
+
color: var(--dim);
|
|
57
|
+
padding: 8px 8px 4px;
|
|
58
|
+
}
|
|
59
|
+
.room-item, .agent-item {
|
|
60
|
+
padding: 6px 10px;
|
|
61
|
+
border-radius: 6px;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
font-size: 13px;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 6px;
|
|
67
|
+
}
|
|
68
|
+
.room-item:hover, .agent-item:hover { background: rgba(255,255,255,0.05); }
|
|
69
|
+
.room-item.active { background: var(--border); }
|
|
70
|
+
.status-dot {
|
|
71
|
+
width: 8px; height: 8px;
|
|
72
|
+
border-radius: 50%;
|
|
73
|
+
display: inline-block;
|
|
74
|
+
}
|
|
75
|
+
.status-dot.active { background: var(--green); }
|
|
76
|
+
.status-dot.idle { background: var(--yellow); }
|
|
77
|
+
.status-dot.busy { background: var(--red); }
|
|
78
|
+
.status-dot.offline { background: var(--dim); }
|
|
79
|
+
#main {
|
|
80
|
+
flex: 1;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
}
|
|
85
|
+
#header {
|
|
86
|
+
padding: 12px 20px;
|
|
87
|
+
border-bottom: 1px solid var(--border);
|
|
88
|
+
font-size: 15px;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
background: var(--surface);
|
|
91
|
+
}
|
|
92
|
+
#messages {
|
|
93
|
+
flex: 1;
|
|
94
|
+
overflow-y: auto;
|
|
95
|
+
padding: 16px 20px;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: 4px;
|
|
99
|
+
}
|
|
100
|
+
.msg {
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
line-height: 1.5;
|
|
103
|
+
}
|
|
104
|
+
.msg .sender { font-weight: 600; color: var(--accent); }
|
|
105
|
+
.msg .time { color: var(--dim); font-size: 11px; margin-left: 8px; }
|
|
106
|
+
.msg.system { color: var(--dim); font-style: italic; }
|
|
107
|
+
.msg.status { color: var(--yellow); font-size: 12px; }
|
|
108
|
+
.msg.dm { color: var(--purple); }
|
|
109
|
+
.msg .dm-badge {
|
|
110
|
+
background: var(--purple);
|
|
111
|
+
color: #fff;
|
|
112
|
+
font-size: 10px;
|
|
113
|
+
padding: 1px 5px;
|
|
114
|
+
border-radius: 3px;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
}
|
|
117
|
+
#input-bar {
|
|
118
|
+
display: flex;
|
|
119
|
+
padding: 12px 20px;
|
|
120
|
+
gap: 8px;
|
|
121
|
+
background: var(--surface);
|
|
122
|
+
border-top: 1px solid var(--border);
|
|
123
|
+
}
|
|
124
|
+
#input {
|
|
125
|
+
flex: 1;
|
|
126
|
+
background: var(--bg);
|
|
127
|
+
border: 1px solid var(--border);
|
|
128
|
+
border-radius: 6px;
|
|
129
|
+
padding: 8px 12px;
|
|
130
|
+
color: var(--text);
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
outline: none;
|
|
133
|
+
}
|
|
134
|
+
#input:focus { border-color: var(--accent); }
|
|
135
|
+
#send-btn {
|
|
136
|
+
background: var(--accent);
|
|
137
|
+
border: none;
|
|
138
|
+
border-radius: 6px;
|
|
139
|
+
padding: 8px 16px;
|
|
140
|
+
color: #fff;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
#send-btn:hover { opacity: 0.9; }
|
|
145
|
+
#empty-state {
|
|
146
|
+
flex: 1;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
color: var(--dim);
|
|
151
|
+
font-size: 14px;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
154
|
+
</head>
|
|
155
|
+
<body>
|
|
156
|
+
|
|
157
|
+
<div id="sidebar">
|
|
158
|
+
<h2>Agent Comms</h2>
|
|
159
|
+
<div class="sidebar-section">
|
|
160
|
+
<h3>Rooms</h3>
|
|
161
|
+
<div id="room-list"></div>
|
|
162
|
+
<h3>Agents</h3>
|
|
163
|
+
<div id="agent-list"></div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div id="main">
|
|
168
|
+
<div id="header">Select a room</div>
|
|
169
|
+
<div id="messages"></div>
|
|
170
|
+
<div id="input-bar">
|
|
171
|
+
<input id="input" type="text" placeholder="Type a message or /command..." autocomplete="off" />
|
|
172
|
+
<button id="send-btn">Send</button>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<script>
|
|
177
|
+
const $ = (s) => document.querySelector(s);
|
|
178
|
+
const messagesEl = $('#messages');
|
|
179
|
+
const inputEl = $('#input');
|
|
180
|
+
const headerEl = $('#header');
|
|
181
|
+
const roomListEl = $('#room-list');
|
|
182
|
+
const agentListEl = $('#agent-list');
|
|
183
|
+
|
|
184
|
+
let currentRoom = null;
|
|
185
|
+
let ws = null;
|
|
186
|
+
|
|
187
|
+
// Connect WebSocket
|
|
188
|
+
function connect() {
|
|
189
|
+
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
190
|
+
ws = new WebSocket(proto + '//' + location.host);
|
|
191
|
+
|
|
192
|
+
ws.onopen = () => { addSystem('Connected to mesh'); };
|
|
193
|
+
ws.onclose = () => { addSystem('Disconnected — reconnecting...'); setTimeout(connect, 3000); };
|
|
194
|
+
ws.onerror = () => {};
|
|
195
|
+
|
|
196
|
+
ws.onmessage = (e) => {
|
|
197
|
+
const frame = JSON.parse(e.data);
|
|
198
|
+
if (frame.type === 'delivery') handleDelivery(frame.event);
|
|
199
|
+
if (frame.type === 'result') addSystem(frame.result.content);
|
|
200
|
+
if (frame.type === 'error') addSystem('Error: ' + frame.message);
|
|
201
|
+
if (frame.type === 'state') {
|
|
202
|
+
renderAgents(frame.agents);
|
|
203
|
+
renderRooms(frame.rooms);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function sendAction(params) {
|
|
209
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
210
|
+
ws.send(JSON.stringify(params));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Delivery event handling
|
|
215
|
+
function handleDelivery(event) {
|
|
216
|
+
switch (event.type) {
|
|
217
|
+
case 'room_message':
|
|
218
|
+
if (!currentRoom || event.message.room !== currentRoom) return;
|
|
219
|
+
addMessage(event.message.from, event.message.content, event.message.timestamp);
|
|
220
|
+
break;
|
|
221
|
+
case 'dm':
|
|
222
|
+
addDm(event.message.from, event.message.content, event.message.timestamp);
|
|
223
|
+
break;
|
|
224
|
+
case 'member_joined':
|
|
225
|
+
addSystem(event.agent + ' joined ' + event.room);
|
|
226
|
+
refreshState();
|
|
227
|
+
break;
|
|
228
|
+
case 'member_left':
|
|
229
|
+
addSystem(event.agent + ' left ' + event.room);
|
|
230
|
+
refreshState();
|
|
231
|
+
break;
|
|
232
|
+
case 'member_status':
|
|
233
|
+
addStatus(event.agent + ' is now ' + event.status + ' in ' + event.room);
|
|
234
|
+
refreshState();
|
|
235
|
+
break;
|
|
236
|
+
case 'delivery_status':
|
|
237
|
+
addStatus('Message ' + event.messageId + ' ' + event.status + ' by ' + event.agent);
|
|
238
|
+
break;
|
|
239
|
+
case 'room_members':
|
|
240
|
+
if (currentRoom === event.room) {
|
|
241
|
+
addSystem('Members: ' + event.members.map(m => m.name + ' (' + m.status + ')').join(', '));
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case 'room_invite':
|
|
245
|
+
addSystem('Invited to ' + event.room + ' by ' + event.from);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// UI rendering
|
|
251
|
+
function addMessage(sender, content, timestamp) {
|
|
252
|
+
const time = (timestamp || new Date().toISOString()).slice(11, 19);
|
|
253
|
+
const div = document.createElement('div');
|
|
254
|
+
div.className = 'msg';
|
|
255
|
+
div.innerHTML = '<span class="sender">' + esc(sender) + '</span><span class="time">' + time + '</span>: ' + esc(content);
|
|
256
|
+
messagesEl.appendChild(div);
|
|
257
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function addDm(sender, content, timestamp) {
|
|
261
|
+
const time = (timestamp || new Date().toISOString()).slice(11, 19);
|
|
262
|
+
const div = document.createElement('div');
|
|
263
|
+
div.className = 'msg dm';
|
|
264
|
+
div.innerHTML = '<span class="dm-badge">DM</span> <span class="sender">' + esc(sender) + '</span><span class="time">' + time + '</span>: ' + esc(content);
|
|
265
|
+
messagesEl.appendChild(div);
|
|
266
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function addSystem(text) {
|
|
270
|
+
const div = document.createElement('div');
|
|
271
|
+
div.className = 'msg system';
|
|
272
|
+
div.textContent = text;
|
|
273
|
+
messagesEl.appendChild(div);
|
|
274
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function addStatus(text) {
|
|
278
|
+
const div = document.createElement('div');
|
|
279
|
+
div.className = 'msg status';
|
|
280
|
+
div.textContent = text;
|
|
281
|
+
messagesEl.appendChild(div);
|
|
282
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function clearMessages() {
|
|
286
|
+
messagesEl.innerHTML = '';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function esc(s) {
|
|
290
|
+
const d = document.createElement('div');
|
|
291
|
+
d.textContent = s;
|
|
292
|
+
return d.innerHTML;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// State refresh
|
|
296
|
+
async function refreshState() {
|
|
297
|
+
const [agentsRes, roomsRes] = await Promise.all([
|
|
298
|
+
fetch('/api/agents'), fetch('/api/rooms')
|
|
299
|
+
]);
|
|
300
|
+
renderAgents(await agentsRes.json());
|
|
301
|
+
renderRooms(await roomsRes.json());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function renderAgents(agents) {
|
|
305
|
+
agentListEl.innerHTML = '';
|
|
306
|
+
for (const a of agents) {
|
|
307
|
+
const div = document.createElement('div');
|
|
308
|
+
div.className = 'agent-item';
|
|
309
|
+
const dot = a.status || 'offline';
|
|
310
|
+
div.innerHTML = '<span class="status-dot ' + dot + '"></span> ' + esc(a.name);
|
|
311
|
+
agentListEl.appendChild(div);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function renderRooms(rooms) {
|
|
316
|
+
roomListEl.innerHTML = '';
|
|
317
|
+
for (const r of rooms) {
|
|
318
|
+
const div = document.createElement('div');
|
|
319
|
+
div.className = 'room-item' + (currentRoom === r.id ? ' active' : '');
|
|
320
|
+
const joined = r.members.length;
|
|
321
|
+
div.innerHTML = r.type.charAt(0).toUpperCase() + ' ' + esc(r.name) + ' <span style="color:var(--dim)">(' + joined + ')</span>';
|
|
322
|
+
div.onclick = () => joinRoom(r.id);
|
|
323
|
+
roomListEl.appendChild(div);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function joinRoom(roomId) {
|
|
328
|
+
currentRoom = roomId;
|
|
329
|
+
headerEl.textContent = roomId;
|
|
330
|
+
clearMessages();
|
|
331
|
+
|
|
332
|
+
// Highlight in sidebar
|
|
333
|
+
document.querySelectorAll('.room-item').forEach(el => el.classList.remove('active'));
|
|
334
|
+
sendAction({ action: 'join_room', room: roomId });
|
|
335
|
+
|
|
336
|
+
// Load history
|
|
337
|
+
const res = await fetch('/api/rooms/' + encodeURIComponent(roomId) + '/messages');
|
|
338
|
+
const messages = await res.json();
|
|
339
|
+
for (const m of messages) {
|
|
340
|
+
addMessage(m.from, m.content, m.timestamp);
|
|
341
|
+
}
|
|
342
|
+
addSystem('Joined ' + roomId);
|
|
343
|
+
inputEl.focus();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Input handling
|
|
347
|
+
function handleInput() {
|
|
348
|
+
const text = inputEl.value.trim();
|
|
349
|
+
if (!text) return;
|
|
350
|
+
inputEl.value = '';
|
|
351
|
+
|
|
352
|
+
if (text.startsWith('/')) {
|
|
353
|
+
const parts = text.slice(1).split(/\\s+/);
|
|
354
|
+
const cmd = parts[0].toLowerCase();
|
|
355
|
+
switch (cmd) {
|
|
356
|
+
case 'join': sendAction({ action: 'join_room', room: parts[1] }); break;
|
|
357
|
+
case 'leave': sendAction({ action: 'leave_room', room: parts[1] || currentRoom }); break;
|
|
358
|
+
case 'rooms': refreshState(); break;
|
|
359
|
+
case 'agents': refreshState(); break;
|
|
360
|
+
case 'dm': sendAction({ action: 'dm', target: parts[1], content: parts.slice(2).join(' ') }); break;
|
|
361
|
+
case 'create': sendAction({ action: 'create_room', name: parts[1], type: 'public' }); break;
|
|
362
|
+
case 'destroy': sendAction({ action: 'destroy_room', room: parts[1] }); break;
|
|
363
|
+
case 'help':
|
|
364
|
+
addSystem('Commands: /join, /leave, /rooms, /agents, /dm, /create, /destroy, /help');
|
|
365
|
+
break;
|
|
366
|
+
default:
|
|
367
|
+
addSystem('Unknown command: /' + cmd);
|
|
368
|
+
}
|
|
369
|
+
} else if (currentRoom) {
|
|
370
|
+
sendAction({ action: 'send', target: currentRoom, content: text });
|
|
371
|
+
} else {
|
|
372
|
+
addSystem('Join a room first (click one in the sidebar)');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
$('#send-btn').onclick = handleInput;
|
|
377
|
+
inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleInput(); });
|
|
378
|
+
|
|
379
|
+
// Boot
|
|
380
|
+
connect();
|
|
381
|
+
refreshState();
|
|
382
|
+
inputEl.focus();
|
|
383
|
+
</script>
|
|
384
|
+
</body>
|
|
385
|
+
</html>`;
|
|
386
|
+
//# sourceMappingURL=index.html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.html.js","sourceRoot":"","sources":["../../../../src/bridges/user/web/index.html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA0XrB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web UI server — HTTP + WebSocket for browser-based chat.
|
|
3
|
+
*
|
|
4
|
+
* Serves a single-page frontend and exposes a JSON API over HTTP
|
|
5
|
+
* with real-time delivery events over WebSocket.
|
|
6
|
+
*
|
|
7
|
+
* REST endpoints:
|
|
8
|
+
* GET / → frontend HTML
|
|
9
|
+
* GET /api/agents → list agents
|
|
10
|
+
* GET /api/rooms → list rooms
|
|
11
|
+
* GET /api/rooms/:id/messages → read room messages
|
|
12
|
+
* POST /api/action → execute any CommsAction
|
|
13
|
+
*
|
|
14
|
+
* WebSocket:
|
|
15
|
+
* Server pushes delivery events as JSON frames.
|
|
16
|
+
* Client sends action objects.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runWeb(userName: string, port?: number): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/bridges/user/web/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,wBAAsB,MAAM,CAC1B,QAAQ,EAAE,MAAM,EAChB,IAAI,SAAe,GAClB,OAAO,CAAC,IAAI,CAAC,CAiCf"}
|