@mooncompany/uplink-chat 0.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.
Potentially problematic release.
This version of @mooncompany/uplink-chat might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/bin/uplink.js +279 -0
- package/middleware/error-handler.js +69 -0
- package/package.json +93 -0
- package/public/css/agents.36b98c0f.css +1469 -0
- package/public/css/agents.css +1469 -0
- package/public/css/app.a6a7f8f5.css +2731 -0
- package/public/css/app.css +2731 -0
- package/public/css/artifacts.css +444 -0
- package/public/css/commands.css +55 -0
- package/public/css/connection.css +131 -0
- package/public/css/dashboard.css +233 -0
- package/public/css/developer.css +328 -0
- package/public/css/files.css +123 -0
- package/public/css/markdown.css +156 -0
- package/public/css/message-actions.css +278 -0
- package/public/css/mobile.css +614 -0
- package/public/css/panels-unified.css +483 -0
- package/public/css/premium.css +415 -0
- package/public/css/realtime.css +189 -0
- package/public/css/satellites.css +401 -0
- package/public/css/shortcuts.css +185 -0
- package/public/css/split-view.4def0262.css +673 -0
- package/public/css/split-view.css +673 -0
- package/public/css/theme-generator.css +391 -0
- package/public/css/themes.css +387 -0
- package/public/css/timestamps.css +54 -0
- package/public/css/variables.css +78 -0
- package/public/dist/bundle.b55050c4.js +15757 -0
- package/public/favicon.svg +24 -0
- package/public/img/agents/ada.png +0 -0
- package/public/img/agents/clarice.png +0 -0
- package/public/img/agents/dennis-nedry.png +0 -0
- package/public/img/agents/elliot-alderson.png +0 -0
- package/public/img/agents/main.png +0 -0
- package/public/img/agents/scotty.png +0 -0
- package/public/img/agents/top-flight-security.png +0 -0
- package/public/index.html +1083 -0
- package/public/js/agents-data.js +234 -0
- package/public/js/agents-ui.js +72 -0
- package/public/js/agents.js +1525 -0
- package/public/js/app.js +79 -0
- package/public/js/appearance-settings.js +111 -0
- package/public/js/artifacts.js +432 -0
- package/public/js/audio-queue.js +168 -0
- package/public/js/bootstrap.js +54 -0
- package/public/js/chat.js +1211 -0
- package/public/js/commands.js +581 -0
- package/public/js/connection-api.js +121 -0
- package/public/js/connection.js +1231 -0
- package/public/js/context-tracker.js +271 -0
- package/public/js/core.js +172 -0
- package/public/js/dashboard.js +452 -0
- package/public/js/developer.js +432 -0
- package/public/js/encryption.js +124 -0
- package/public/js/errors.js +122 -0
- package/public/js/event-bus.js +77 -0
- package/public/js/fetch-utils.js +171 -0
- package/public/js/file-handler.js +229 -0
- package/public/js/files.js +352 -0
- package/public/js/gateway-chat.js +538 -0
- package/public/js/logger.js +112 -0
- package/public/js/markdown.js +190 -0
- package/public/js/message-actions.js +431 -0
- package/public/js/message-renderer.js +288 -0
- package/public/js/missed-messages.js +235 -0
- package/public/js/mobile-debug.js +95 -0
- package/public/js/notifications.js +367 -0
- package/public/js/offline-queue.js +178 -0
- package/public/js/onboarding.js +543 -0
- package/public/js/panels.js +156 -0
- package/public/js/premium.js +412 -0
- package/public/js/realtime-voice.js +844 -0
- package/public/js/satellite-sync.js +256 -0
- package/public/js/satellite-ui.js +175 -0
- package/public/js/satellites.js +1516 -0
- package/public/js/settings.js +1087 -0
- package/public/js/shortcuts.js +381 -0
- package/public/js/split-chat.js +1234 -0
- package/public/js/split-resize.js +211 -0
- package/public/js/splitview.js +340 -0
- package/public/js/storage.js +408 -0
- package/public/js/streaming-handler.js +324 -0
- package/public/js/stt-settings.js +316 -0
- package/public/js/theme-generator.js +661 -0
- package/public/js/themes.js +164 -0
- package/public/js/timestamps.js +198 -0
- package/public/js/tts-settings.js +575 -0
- package/public/js/ui.js +267 -0
- package/public/js/update-notifier.js +143 -0
- package/public/js/utils/constants.js +165 -0
- package/public/js/utils/sanitize.js +93 -0
- package/public/js/utils/sse-parser.js +195 -0
- package/public/js/voice.js +883 -0
- package/public/manifest.json +58 -0
- package/public/moon_texture.jpg +0 -0
- package/public/sw.js +221 -0
- package/public/three.min.js +6 -0
- package/server/channel.js +529 -0
- package/server/chat.js +270 -0
- package/server/config-store.js +362 -0
- package/server/config.js +159 -0
- package/server/context.js +131 -0
- package/server/gateway-commands.js +211 -0
- package/server/gateway-proxy.js +318 -0
- package/server/index.js +22 -0
- package/server/logger.js +89 -0
- package/server/middleware/auth.js +188 -0
- package/server/middleware.js +218 -0
- package/server/openclaw-discover.js +308 -0
- package/server/premium/index.js +156 -0
- package/server/premium/license.js +140 -0
- package/server/realtime/bridge.js +837 -0
- package/server/realtime/index.js +349 -0
- package/server/realtime/tts-stream.js +446 -0
- package/server/routes/agents.js +564 -0
- package/server/routes/artifacts.js +174 -0
- package/server/routes/chat.js +311 -0
- package/server/routes/config-settings.js +345 -0
- package/server/routes/config.js +603 -0
- package/server/routes/files.js +307 -0
- package/server/routes/index.js +18 -0
- package/server/routes/media.js +451 -0
- package/server/routes/missed-messages.js +107 -0
- package/server/routes/premium.js +75 -0
- package/server/routes/push.js +156 -0
- package/server/routes/satellite.js +406 -0
- package/server/routes/status.js +251 -0
- package/server/routes/stt.js +35 -0
- package/server/routes/voice.js +260 -0
- package/server/routes/webhooks.js +203 -0
- package/server/routes.js +206 -0
- package/server/runtime-config.js +336 -0
- package/server/share.js +305 -0
- package/server/stt/faster-whisper.js +72 -0
- package/server/stt/groq.js +51 -0
- package/server/stt/index.js +196 -0
- package/server/stt/openai.js +49 -0
- package/server/sync.js +244 -0
- package/server/tailscale-https.js +175 -0
- package/server/tts.js +646 -0
- package/server/update-checker.js +172 -0
- package/server/utils/filename.js +129 -0
- package/server/utils.js +147 -0
- package/server/watchdog.js +318 -0
- package/server/websocket/broadcast.js +359 -0
- package/server/websocket/connections.js +339 -0
- package/server/websocket/index.js +215 -0
- package/server/websocket/routing.js +277 -0
- package/server/websocket/sync.js +102 -0
- package/server.js +404 -0
- package/utils/detect-tool-usage.js +93 -0
- package/utils/errors.js +158 -0
- package/utils/html-escape.js +84 -0
- package/utils/id-sanitize.js +94 -0
- package/utils/response.js +130 -0
- package/utils/with-retry.js +105 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// GATEWAY CHAT MODULE
|
|
3
|
+
// Handles Gateway WebSocket events with streaming fix
|
|
4
|
+
// Subscribes to agent events for deltas, chat events for final state
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
import { UplinkCore } from './core.js';
|
|
8
|
+
|
|
9
|
+
// WebSocket reference (from connection.js)
|
|
10
|
+
let ws = null;
|
|
11
|
+
|
|
12
|
+
// Message listeners for external consumers
|
|
13
|
+
let messageListeners = [];
|
|
14
|
+
|
|
15
|
+
// Track streaming message state
|
|
16
|
+
let currentStreamingMessage = null;
|
|
17
|
+
let currentMessageId = null;
|
|
18
|
+
|
|
19
|
+
// History state
|
|
20
|
+
let historyLoaded = false;
|
|
21
|
+
|
|
22
|
+
// Gateway API endpoint
|
|
23
|
+
const GATEWAY_API_BASE = '/api/gateway';
|
|
24
|
+
|
|
25
|
+
// Module init retry state
|
|
26
|
+
let initRetryCount = 0;
|
|
27
|
+
const MAX_INIT_RETRIES = 10;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the gateway chat module
|
|
31
|
+
* Sets up WebSocket listeners using the shared connection
|
|
32
|
+
*/
|
|
33
|
+
function init() {
|
|
34
|
+
// Wait for connection module to be ready
|
|
35
|
+
if (!window.UplinkConnection) {
|
|
36
|
+
if (initRetryCount >= MAX_INIT_RETRIES) {
|
|
37
|
+
logger.error('GatewayChat: UplinkConnection not found after max retries, giving up');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
initRetryCount++;
|
|
41
|
+
const delay = Math.min(100 * Math.pow(2, initRetryCount), 5000);
|
|
42
|
+
logger.debug(`GatewayChat: Waiting for UplinkConnection (${initRetryCount}/${MAX_INIT_RETRIES})...`);
|
|
43
|
+
setTimeout(init, delay);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Subscribe to connection events
|
|
48
|
+
window.UplinkConnection.onConnection((event, data) => {
|
|
49
|
+
if (event === 'connected') {
|
|
50
|
+
onConnected();
|
|
51
|
+
} else if (event === 'disconnected') {
|
|
52
|
+
onDisconnected();
|
|
53
|
+
} else if (event === 'message') {
|
|
54
|
+
handleWebSocketMessage(data);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Get initial WebSocket if already connected
|
|
59
|
+
ws = window.UplinkConnection.getWebSocket();
|
|
60
|
+
if (ws && window.UplinkConnection.isConnected()) {
|
|
61
|
+
onConnected();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
logger.debug('GatewayChat: Initialized');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Called when WebSocket connects
|
|
69
|
+
*/
|
|
70
|
+
function onConnected() {
|
|
71
|
+
ws = window.UplinkConnection.getWebSocket();
|
|
72
|
+
logger.debug('GatewayChat: Connected to Gateway');
|
|
73
|
+
|
|
74
|
+
// Load history on connect if not already loaded
|
|
75
|
+
if (!historyLoaded) {
|
|
76
|
+
loadHistory();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Called when WebSocket disconnects
|
|
82
|
+
*/
|
|
83
|
+
function onDisconnected() {
|
|
84
|
+
ws = null;
|
|
85
|
+
logger.debug('GatewayChat: Disconnected from Gateway');
|
|
86
|
+
|
|
87
|
+
// Finalize any streaming message
|
|
88
|
+
if (currentStreamingMessage) {
|
|
89
|
+
finalizeStreamingMessage();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Track malformed message count for rate limiting error display
|
|
94
|
+
let malformedMessageCount = 0;
|
|
95
|
+
let lastMalformedErrorShown = 0;
|
|
96
|
+
const MALFORMED_ERROR_COOLDOWN_MS = 10000; // Only show error every 10 seconds
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handle incoming WebSocket messages
|
|
100
|
+
* @param {string} data - Raw message data
|
|
101
|
+
*/
|
|
102
|
+
function handleWebSocketMessage(data) {
|
|
103
|
+
try {
|
|
104
|
+
const message = JSON.parse(data);
|
|
105
|
+
|
|
106
|
+
// Reset malformed count on successful parse
|
|
107
|
+
malformedMessageCount = 0;
|
|
108
|
+
|
|
109
|
+
// Handle agent events - streaming deltas
|
|
110
|
+
// Gateway sends streaming deltas on agent events at payload.data.delta
|
|
111
|
+
if (message.type === 'agent' || message.event === 'agent') {
|
|
112
|
+
handleAgentEvent(message);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle chat events - final message state
|
|
117
|
+
if (message.type === 'chat' || message.event === 'chat') {
|
|
118
|
+
handleChatEvent(message);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Handle pong/heartbeat
|
|
123
|
+
if (message.type === 'pong') {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Notify listeners of other message types
|
|
128
|
+
notifyListeners('message', message);
|
|
129
|
+
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// Log malformed message with details for debugging
|
|
132
|
+
malformedMessageCount++;
|
|
133
|
+
const truncatedData = data && data.length > 200 ? data.substring(0, 200) + '...' : data;
|
|
134
|
+
logger.warn('GatewayChat: Malformed message received', {
|
|
135
|
+
error: e.message,
|
|
136
|
+
dataPreview: truncatedData,
|
|
137
|
+
dataLength: data ? data.length : 0,
|
|
138
|
+
count: malformedMessageCount
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Notify listeners about the malformed message
|
|
142
|
+
notifyListeners('malformed-message', {
|
|
143
|
+
error: e.message,
|
|
144
|
+
dataPreview: truncatedData,
|
|
145
|
+
count: malformedMessageCount
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Show user-friendly error if we're getting repeated malformed messages
|
|
149
|
+
// This suggests a critical protocol issue
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
if (malformedMessageCount >= 5 && (now - lastMalformedErrorShown) > MALFORMED_ERROR_COOLDOWN_MS) {
|
|
152
|
+
lastMalformedErrorShown = now;
|
|
153
|
+
showMalformedMessageError();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Show user-friendly error for repeated malformed messages
|
|
160
|
+
*/
|
|
161
|
+
function showMalformedMessageError() {
|
|
162
|
+
const errorMsg = 'Communication issue detected. Some messages may not display correctly.';
|
|
163
|
+
|
|
164
|
+
// Try to use UplinkChat to show the error
|
|
165
|
+
if (window.UplinkChat?.addMessage) {
|
|
166
|
+
window.UplinkChat.addMessage(errorMsg, 'system', null, false);
|
|
167
|
+
} else if (window.addMessage) {
|
|
168
|
+
window.addMessage(errorMsg, 'system', null, false);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Also notify listeners
|
|
172
|
+
notifyListeners('critical-error', {
|
|
173
|
+
type: 'malformed-messages',
|
|
174
|
+
message: errorMsg,
|
|
175
|
+
count: malformedMessageCount
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
logger.error('GatewayChat: Critical - repeated malformed messages detected', {
|
|
179
|
+
count: malformedMessageCount
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Handle agent events - streaming deltas from Gateway
|
|
185
|
+
* @param {object} message - The agent event message
|
|
186
|
+
*/
|
|
187
|
+
function handleAgentEvent(message) {
|
|
188
|
+
// Extract delta from payload.data.delta (per Gateway spec)
|
|
189
|
+
const delta = message.payload?.data?.delta ||
|
|
190
|
+
message.data?.delta ||
|
|
191
|
+
message.delta;
|
|
192
|
+
|
|
193
|
+
if (!delta) {
|
|
194
|
+
// No delta in this agent event, might be status update
|
|
195
|
+
if (message.payload?.status || message.status) {
|
|
196
|
+
const status = message.payload?.status || message.status;
|
|
197
|
+
notifyListeners('status', { status, type: 'agent' });
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check if this is tool output that should be filtered
|
|
203
|
+
if (isToolOutput(delta)) {
|
|
204
|
+
// Route to activity panel instead of chat
|
|
205
|
+
routeToActivityPanel(delta, 'tool');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Start or continue streaming message
|
|
210
|
+
if (!currentStreamingMessage) {
|
|
211
|
+
currentStreamingMessage = delta;
|
|
212
|
+
currentMessageId = message.messageId || message.id || generateId();
|
|
213
|
+
|
|
214
|
+
notifyListeners('stream-start', {
|
|
215
|
+
messageId: currentMessageId,
|
|
216
|
+
content: delta
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
currentStreamingMessage += delta;
|
|
220
|
+
|
|
221
|
+
notifyListeners('stream-delta', {
|
|
222
|
+
messageId: currentMessageId,
|
|
223
|
+
delta: delta,
|
|
224
|
+
content: currentStreamingMessage
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Handle chat events - final message state from Gateway
|
|
231
|
+
* @param {object} message - The chat event message
|
|
232
|
+
*/
|
|
233
|
+
function handleChatEvent(message) {
|
|
234
|
+
const content = message.payload?.content ||
|
|
235
|
+
message.payload?.text ||
|
|
236
|
+
message.content ||
|
|
237
|
+
message.text;
|
|
238
|
+
|
|
239
|
+
const messageId = message.messageId || message.id || generateId();
|
|
240
|
+
|
|
241
|
+
// Finalize any ongoing streaming
|
|
242
|
+
if (currentStreamingMessage) {
|
|
243
|
+
finalizeStreamingMessage();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Handle tool output filtering for final messages too
|
|
247
|
+
if (content && isToolOutput(content)) {
|
|
248
|
+
routeToActivityPanel(content, 'tool');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Notify listeners of final message
|
|
253
|
+
notifyListeners('message-final', {
|
|
254
|
+
messageId: messageId,
|
|
255
|
+
content: content,
|
|
256
|
+
role: message.payload?.role || message.role || 'assistant',
|
|
257
|
+
timestamp: message.timestamp || Date.now()
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Finalize the current streaming message
|
|
263
|
+
*/
|
|
264
|
+
function finalizeStreamingMessage() {
|
|
265
|
+
if (!currentStreamingMessage) return;
|
|
266
|
+
|
|
267
|
+
notifyListeners('stream-end', {
|
|
268
|
+
messageId: currentMessageId,
|
|
269
|
+
content: currentStreamingMessage
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
currentStreamingMessage = null;
|
|
273
|
+
currentMessageId = null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Send a message to the Gateway
|
|
278
|
+
* @param {string} text - The message text to send
|
|
279
|
+
* @returns {Promise<boolean>} - Success status
|
|
280
|
+
*/
|
|
281
|
+
function sendMessage(text) {
|
|
282
|
+
if (!text || !text.trim()) {
|
|
283
|
+
logger.warn('GatewayChat: Cannot send empty message');
|
|
284
|
+
return Promise.resolve(false);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check connection
|
|
288
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
289
|
+
logger.error('GatewayChat: Cannot send message - not connected');
|
|
290
|
+
return Promise.resolve(false);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const message = {
|
|
294
|
+
type: 'chat',
|
|
295
|
+
content: text.trim(),
|
|
296
|
+
timestamp: Date.now(),
|
|
297
|
+
messageId: generateId()
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
ws.send(JSON.stringify(message));
|
|
302
|
+
|
|
303
|
+
notifyListeners('message-sent', {
|
|
304
|
+
messageId: message.messageId,
|
|
305
|
+
content: message.content
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
logger.debug('GatewayChat: Message sent', message.messageId);
|
|
309
|
+
return Promise.resolve(true);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
logger.error('GatewayChat: Failed to send message', e);
|
|
312
|
+
return Promise.resolve(false);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Load chat history from Gateway
|
|
318
|
+
* @returns {Promise<Array>} - Array of history messages
|
|
319
|
+
*/
|
|
320
|
+
async function loadHistory() {
|
|
321
|
+
try {
|
|
322
|
+
const response = await fetch(`${GATEWAY_API_BASE}/history`);
|
|
323
|
+
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
throw new Error(`HTTP ${response.status}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const data = await response.json();
|
|
329
|
+
const messages = data.messages || data.history || data || [];
|
|
330
|
+
|
|
331
|
+
historyLoaded = true;
|
|
332
|
+
|
|
333
|
+
notifyListeners('history-loaded', { messages });
|
|
334
|
+
|
|
335
|
+
logger.debug('GatewayChat: History loaded', messages.length, 'messages');
|
|
336
|
+
return messages;
|
|
337
|
+
} catch (e) {
|
|
338
|
+
logger.error('GatewayChat: Failed to load history', e);
|
|
339
|
+
notifyListeners('history-error', { error: e.message });
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Register a callback for message events
|
|
346
|
+
* @param {Function} callback - Function to call with (eventType, data)
|
|
347
|
+
* @returns {Function} - Unsubscribe function
|
|
348
|
+
*/
|
|
349
|
+
function onMessage(callback) {
|
|
350
|
+
if (typeof callback !== 'function') {
|
|
351
|
+
logger.warn('GatewayChat: onMessage requires a function');
|
|
352
|
+
return () => {};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
messageListeners.push(callback);
|
|
356
|
+
|
|
357
|
+
// Return unsubscribe function
|
|
358
|
+
return () => {
|
|
359
|
+
messageListeners = messageListeners.filter(cb => cb !== callback);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Notify all registered listeners
|
|
365
|
+
* @param {string} eventType - Type of event
|
|
366
|
+
* @param {object} data - Event data
|
|
367
|
+
*/
|
|
368
|
+
function notifyListeners(eventType, data) {
|
|
369
|
+
messageListeners.forEach(callback => {
|
|
370
|
+
try {
|
|
371
|
+
callback(eventType, data);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
logger.error('GatewayChat: Listener error', e);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Check if text is tool output that should be filtered from chat
|
|
380
|
+
* @param {string} text - Text to check
|
|
381
|
+
* @returns {boolean} - True if this is tool output
|
|
382
|
+
*/
|
|
383
|
+
function isToolOutput(text) {
|
|
384
|
+
if (!text || typeof text !== 'string') return false;
|
|
385
|
+
|
|
386
|
+
const lines = text.split('\n');
|
|
387
|
+
|
|
388
|
+
// Check each line for tool output patterns
|
|
389
|
+
for (const line of lines) {
|
|
390
|
+
const trimmed = line.trim();
|
|
391
|
+
|
|
392
|
+
// Lines starting with "SUCCESS:"
|
|
393
|
+
if (trimmed.startsWith('SUCCESS:')) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Lines starting with "FAILED:" or "ERROR:"
|
|
398
|
+
if (trimmed.startsWith('FAILED:') || trimmed.startsWith('ERROR:')) {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// File paths with line numbers (e.g., path/file.js:123)
|
|
403
|
+
if (/^[\w\/\\.-]+\.[\w]+:\d+/.test(trimmed)) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// TCP connection output patterns
|
|
408
|
+
if (/TCP\s+(connection|socket|listen|accept)/i.test(trimmed)) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
if (/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+/.test(trimmed)) {
|
|
412
|
+
return true; // IP:port pattern
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Command output indicators
|
|
416
|
+
if (trimmed.startsWith('$ ') || trimmed.startsWith('> ')) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Process output patterns
|
|
421
|
+
if (/^\[.*\]\s*(stdout|stderr|exit|pid)/i.test(trimmed)) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Check for command output blocks (multiple lines that look like terminal output)
|
|
427
|
+
const codeBlockPatterns = [
|
|
428
|
+
/```(?:bash|sh|shell|powershell|cmd)/i,
|
|
429
|
+
/```\n\$/,
|
|
430
|
+
/```\n>/
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
for (const pattern of codeBlockPatterns) {
|
|
434
|
+
if (pattern.test(text)) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Route tool output to the activity panel instead of chat
|
|
444
|
+
* @param {string} content - The content to route
|
|
445
|
+
* @param {string} type - Type of content (tool, command, etc.)
|
|
446
|
+
*/
|
|
447
|
+
function routeToActivityPanel(content, type) {
|
|
448
|
+
logger.debug('GatewayChat: Routing to activity panel', type);
|
|
449
|
+
|
|
450
|
+
// Try to use the developer/activity panel
|
|
451
|
+
if (window.UplinkDeveloper) {
|
|
452
|
+
// Log as a tool call
|
|
453
|
+
if (type === 'tool') {
|
|
454
|
+
window.UplinkDeveloper.logTool('Gateway Tool Output');
|
|
455
|
+
window.UplinkDeveloper.logToolCall('output', { content: truncate(content, 500) }, null);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Also notify listeners so they can handle it
|
|
460
|
+
notifyListeners('tool-output', {
|
|
461
|
+
content: content,
|
|
462
|
+
type: type,
|
|
463
|
+
timestamp: Date.now()
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Generate a unique message ID
|
|
469
|
+
* @returns {string} - Unique ID
|
|
470
|
+
*/
|
|
471
|
+
function generateId() {
|
|
472
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Truncate string to max length
|
|
477
|
+
* @param {string} str - String to truncate
|
|
478
|
+
* @param {number} maxLen - Maximum length
|
|
479
|
+
* @returns {string} - Truncated string
|
|
480
|
+
*/
|
|
481
|
+
function truncate(str, maxLen) {
|
|
482
|
+
if (!str || str.length <= maxLen) return str;
|
|
483
|
+
return str.slice(0, maxLen) + '... [truncated]';
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get current connection state
|
|
488
|
+
* @returns {object} - Connection state
|
|
489
|
+
*/
|
|
490
|
+
function getState() {
|
|
491
|
+
return {
|
|
492
|
+
connected: ws !== null && ws.readyState === WebSocket.OPEN,
|
|
493
|
+
streaming: currentStreamingMessage !== null,
|
|
494
|
+
historyLoaded: historyLoaded,
|
|
495
|
+
messageId: currentMessageId
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Clear/reset the module state
|
|
501
|
+
*/
|
|
502
|
+
function clear() {
|
|
503
|
+
currentStreamingMessage = null;
|
|
504
|
+
currentMessageId = null;
|
|
505
|
+
historyLoaded = false;
|
|
506
|
+
messageListeners = [];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Export API
|
|
510
|
+
export const gatewayChat = {
|
|
511
|
+
init,
|
|
512
|
+
sendMessage,
|
|
513
|
+
loadHistory,
|
|
514
|
+
onMessage,
|
|
515
|
+
isToolOutput,
|
|
516
|
+
getState,
|
|
517
|
+
clear
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// Backward compat: assign to window
|
|
521
|
+
window.gatewayChat = gatewayChat;
|
|
522
|
+
window.gatewayChat.send = sendMessage;
|
|
523
|
+
|
|
524
|
+
// Helper for init retry with exponential backoff
|
|
525
|
+
function scheduleInitRetry() {
|
|
526
|
+
if (initRetryCount >= MAX_INIT_RETRIES) {
|
|
527
|
+
logger.warn('GatewayChat: Max init retries reached, giving up');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
initRetryCount++;
|
|
531
|
+
const delay = Math.min(50 * Math.pow(2, initRetryCount), 5000);
|
|
532
|
+
setTimeout(init, delay);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Register with core for coordinated initialization
|
|
536
|
+
UplinkCore.registerModule('gateway-chat', init);
|
|
537
|
+
|
|
538
|
+
logger.debug('GatewayChat: Module loaded');
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// LOGGER MODULE
|
|
3
|
+
// Centralized logging with level control
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
// Log levels
|
|
7
|
+
const LEVELS = {
|
|
8
|
+
debug: 0,
|
|
9
|
+
info: 1,
|
|
10
|
+
warn: 2,
|
|
11
|
+
error: 3,
|
|
12
|
+
none: 4
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Default level (production = warn, can be overridden)
|
|
16
|
+
let currentLevel = LEVELS.warn;
|
|
17
|
+
|
|
18
|
+
// Load level from settings if available
|
|
19
|
+
function loadLevel() {
|
|
20
|
+
try {
|
|
21
|
+
const settings = JSON.parse(localStorage.getItem('uplink-settings') || '{}');
|
|
22
|
+
if (settings.logLevel && LEVELS[settings.logLevel] !== undefined) {
|
|
23
|
+
currentLevel = LEVELS[settings.logLevel];
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Ignore
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Set log level
|
|
31
|
+
function setLevel(level) {
|
|
32
|
+
if (LEVELS[level] !== undefined) {
|
|
33
|
+
currentLevel = LEVELS[level];
|
|
34
|
+
try {
|
|
35
|
+
const settings = JSON.parse(localStorage.getItem('uplink-settings') || '{}');
|
|
36
|
+
settings.logLevel = level;
|
|
37
|
+
localStorage.setItem('uplink-settings', JSON.stringify(settings));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// Ignore
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get current level name
|
|
45
|
+
function getLevel() {
|
|
46
|
+
return Object.keys(LEVELS).find(k => LEVELS[k] === currentLevel) || 'warn';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Logging functions
|
|
50
|
+
function debug(...args) {
|
|
51
|
+
if (currentLevel <= LEVELS.debug) {
|
|
52
|
+
console.log('[DEBUG]', ...args);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function info(...args) {
|
|
57
|
+
if (currentLevel <= LEVELS.info) {
|
|
58
|
+
console.log('[INFO]', ...args);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function warn(...args) {
|
|
63
|
+
if (currentLevel <= LEVELS.warn) {
|
|
64
|
+
console.warn('[WARN]', ...args);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function error(...args) {
|
|
69
|
+
if (currentLevel <= LEVELS.error) {
|
|
70
|
+
console.error('[ERROR]', ...args);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Group logging (for structured output)
|
|
75
|
+
function group(label, fn) {
|
|
76
|
+
if (currentLevel <= LEVELS.debug) {
|
|
77
|
+
console.group(label);
|
|
78
|
+
try {
|
|
79
|
+
fn();
|
|
80
|
+
} finally {
|
|
81
|
+
console.groupEnd();
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
fn();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Initialize
|
|
89
|
+
loadLevel();
|
|
90
|
+
|
|
91
|
+
// Export API
|
|
92
|
+
export const UplinkLogger = {
|
|
93
|
+
debug,
|
|
94
|
+
info,
|
|
95
|
+
warn,
|
|
96
|
+
error,
|
|
97
|
+
group,
|
|
98
|
+
setLevel,
|
|
99
|
+
getLevel,
|
|
100
|
+
LEVELS
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export { debug, info, warn, error, group, setLevel, getLevel, LEVELS };
|
|
104
|
+
|
|
105
|
+
// Backward compat: assign to window
|
|
106
|
+
if (typeof window !== 'undefined') {
|
|
107
|
+
window.UplinkLogger = UplinkLogger;
|
|
108
|
+
window.logger = UplinkLogger;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Logger initialized - use debug for init message (only shown if level is debug)
|
|
112
|
+
debug('Logger: Initialized (level=' + getLevel() + ')');
|