@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.

Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/bin/uplink.js +279 -0
  4. package/middleware/error-handler.js +69 -0
  5. package/package.json +93 -0
  6. package/public/css/agents.36b98c0f.css +1469 -0
  7. package/public/css/agents.css +1469 -0
  8. package/public/css/app.a6a7f8f5.css +2731 -0
  9. package/public/css/app.css +2731 -0
  10. package/public/css/artifacts.css +444 -0
  11. package/public/css/commands.css +55 -0
  12. package/public/css/connection.css +131 -0
  13. package/public/css/dashboard.css +233 -0
  14. package/public/css/developer.css +328 -0
  15. package/public/css/files.css +123 -0
  16. package/public/css/markdown.css +156 -0
  17. package/public/css/message-actions.css +278 -0
  18. package/public/css/mobile.css +614 -0
  19. package/public/css/panels-unified.css +483 -0
  20. package/public/css/premium.css +415 -0
  21. package/public/css/realtime.css +189 -0
  22. package/public/css/satellites.css +401 -0
  23. package/public/css/shortcuts.css +185 -0
  24. package/public/css/split-view.4def0262.css +673 -0
  25. package/public/css/split-view.css +673 -0
  26. package/public/css/theme-generator.css +391 -0
  27. package/public/css/themes.css +387 -0
  28. package/public/css/timestamps.css +54 -0
  29. package/public/css/variables.css +78 -0
  30. package/public/dist/bundle.b55050c4.js +15757 -0
  31. package/public/favicon.svg +24 -0
  32. package/public/img/agents/ada.png +0 -0
  33. package/public/img/agents/clarice.png +0 -0
  34. package/public/img/agents/dennis-nedry.png +0 -0
  35. package/public/img/agents/elliot-alderson.png +0 -0
  36. package/public/img/agents/main.png +0 -0
  37. package/public/img/agents/scotty.png +0 -0
  38. package/public/img/agents/top-flight-security.png +0 -0
  39. package/public/index.html +1083 -0
  40. package/public/js/agents-data.js +234 -0
  41. package/public/js/agents-ui.js +72 -0
  42. package/public/js/agents.js +1525 -0
  43. package/public/js/app.js +79 -0
  44. package/public/js/appearance-settings.js +111 -0
  45. package/public/js/artifacts.js +432 -0
  46. package/public/js/audio-queue.js +168 -0
  47. package/public/js/bootstrap.js +54 -0
  48. package/public/js/chat.js +1211 -0
  49. package/public/js/commands.js +581 -0
  50. package/public/js/connection-api.js +121 -0
  51. package/public/js/connection.js +1231 -0
  52. package/public/js/context-tracker.js +271 -0
  53. package/public/js/core.js +172 -0
  54. package/public/js/dashboard.js +452 -0
  55. package/public/js/developer.js +432 -0
  56. package/public/js/encryption.js +124 -0
  57. package/public/js/errors.js +122 -0
  58. package/public/js/event-bus.js +77 -0
  59. package/public/js/fetch-utils.js +171 -0
  60. package/public/js/file-handler.js +229 -0
  61. package/public/js/files.js +352 -0
  62. package/public/js/gateway-chat.js +538 -0
  63. package/public/js/logger.js +112 -0
  64. package/public/js/markdown.js +190 -0
  65. package/public/js/message-actions.js +431 -0
  66. package/public/js/message-renderer.js +288 -0
  67. package/public/js/missed-messages.js +235 -0
  68. package/public/js/mobile-debug.js +95 -0
  69. package/public/js/notifications.js +367 -0
  70. package/public/js/offline-queue.js +178 -0
  71. package/public/js/onboarding.js +543 -0
  72. package/public/js/panels.js +156 -0
  73. package/public/js/premium.js +412 -0
  74. package/public/js/realtime-voice.js +844 -0
  75. package/public/js/satellite-sync.js +256 -0
  76. package/public/js/satellite-ui.js +175 -0
  77. package/public/js/satellites.js +1516 -0
  78. package/public/js/settings.js +1087 -0
  79. package/public/js/shortcuts.js +381 -0
  80. package/public/js/split-chat.js +1234 -0
  81. package/public/js/split-resize.js +211 -0
  82. package/public/js/splitview.js +340 -0
  83. package/public/js/storage.js +408 -0
  84. package/public/js/streaming-handler.js +324 -0
  85. package/public/js/stt-settings.js +316 -0
  86. package/public/js/theme-generator.js +661 -0
  87. package/public/js/themes.js +164 -0
  88. package/public/js/timestamps.js +198 -0
  89. package/public/js/tts-settings.js +575 -0
  90. package/public/js/ui.js +267 -0
  91. package/public/js/update-notifier.js +143 -0
  92. package/public/js/utils/constants.js +165 -0
  93. package/public/js/utils/sanitize.js +93 -0
  94. package/public/js/utils/sse-parser.js +195 -0
  95. package/public/js/voice.js +883 -0
  96. package/public/manifest.json +58 -0
  97. package/public/moon_texture.jpg +0 -0
  98. package/public/sw.js +221 -0
  99. package/public/three.min.js +6 -0
  100. package/server/channel.js +529 -0
  101. package/server/chat.js +270 -0
  102. package/server/config-store.js +362 -0
  103. package/server/config.js +159 -0
  104. package/server/context.js +131 -0
  105. package/server/gateway-commands.js +211 -0
  106. package/server/gateway-proxy.js +318 -0
  107. package/server/index.js +22 -0
  108. package/server/logger.js +89 -0
  109. package/server/middleware/auth.js +188 -0
  110. package/server/middleware.js +218 -0
  111. package/server/openclaw-discover.js +308 -0
  112. package/server/premium/index.js +156 -0
  113. package/server/premium/license.js +140 -0
  114. package/server/realtime/bridge.js +837 -0
  115. package/server/realtime/index.js +349 -0
  116. package/server/realtime/tts-stream.js +446 -0
  117. package/server/routes/agents.js +564 -0
  118. package/server/routes/artifacts.js +174 -0
  119. package/server/routes/chat.js +311 -0
  120. package/server/routes/config-settings.js +345 -0
  121. package/server/routes/config.js +603 -0
  122. package/server/routes/files.js +307 -0
  123. package/server/routes/index.js +18 -0
  124. package/server/routes/media.js +451 -0
  125. package/server/routes/missed-messages.js +107 -0
  126. package/server/routes/premium.js +75 -0
  127. package/server/routes/push.js +156 -0
  128. package/server/routes/satellite.js +406 -0
  129. package/server/routes/status.js +251 -0
  130. package/server/routes/stt.js +35 -0
  131. package/server/routes/voice.js +260 -0
  132. package/server/routes/webhooks.js +203 -0
  133. package/server/routes.js +206 -0
  134. package/server/runtime-config.js +336 -0
  135. package/server/share.js +305 -0
  136. package/server/stt/faster-whisper.js +72 -0
  137. package/server/stt/groq.js +51 -0
  138. package/server/stt/index.js +196 -0
  139. package/server/stt/openai.js +49 -0
  140. package/server/sync.js +244 -0
  141. package/server/tailscale-https.js +175 -0
  142. package/server/tts.js +646 -0
  143. package/server/update-checker.js +172 -0
  144. package/server/utils/filename.js +129 -0
  145. package/server/utils.js +147 -0
  146. package/server/watchdog.js +318 -0
  147. package/server/websocket/broadcast.js +359 -0
  148. package/server/websocket/connections.js +339 -0
  149. package/server/websocket/index.js +215 -0
  150. package/server/websocket/routing.js +277 -0
  151. package/server/websocket/sync.js +102 -0
  152. package/server.js +404 -0
  153. package/utils/detect-tool-usage.js +93 -0
  154. package/utils/errors.js +158 -0
  155. package/utils/html-escape.js +84 -0
  156. package/utils/id-sanitize.js +94 -0
  157. package/utils/response.js +130 -0
  158. package/utils/with-retry.js +105 -0
@@ -0,0 +1,271 @@
1
+ // ============================================
2
+ // CONTEXT TRACKER MODULE
3
+ // Shows context window usage in the header
4
+ // Fetches real context data from gateway session store
5
+ // ============================================
6
+
7
+ import { UplinkCore } from './core.js';
8
+
9
+ // Default context window size (tokens)
10
+ const DEFAULT_CONTEXT_WINDOW = 200000;
11
+
12
+ // Poll interval (fetch after each message, plus periodic refresh)
13
+ const REFRESH_INTERVAL_MS = 60000; // 1 minute periodic refresh
14
+
15
+ // State
16
+ let contextUsed = 0;
17
+ let contextMax = DEFAULT_CONTEXT_WINDOW;
18
+ let displayedTokens = 0; // Animated display value
19
+ let animationFrame = null;
20
+ let refreshTimer = null;
21
+ let currentSatelliteId = 'main';
22
+
23
+ /**
24
+ * Format token count to human-readable string
25
+ * @param {number} tokens - Token count
26
+ * @returns {string} - Formatted string (e.g., "22k", "1.2k", "200k")
27
+ */
28
+ function formatTokens(tokens) {
29
+ if (tokens >= 1000000) {
30
+ return (tokens / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
31
+ }
32
+ if (tokens >= 1000) {
33
+ return (tokens / 1000).toFixed(tokens >= 10000 ? 0 : 1).replace(/\.0$/, '') + 'k';
34
+ }
35
+ return String(tokens);
36
+ }
37
+
38
+ /**
39
+ * Get color based on usage percentage
40
+ * @param {number} pct - Usage percentage (0-100)
41
+ * @returns {string} - CSS color value
42
+ */
43
+ function getUsageColor(pct) {
44
+ if (pct >= 90) return 'var(--error)'; // Red — critical
45
+ if (pct >= 75) return 'var(--warning)'; // Yellow — getting full
46
+ return 'var(--success)'; // Green — healthy
47
+ }
48
+
49
+ /**
50
+ * Animate the token count text smoothly toward a target value
51
+ * Uses eased lerp over ~600ms to match the CSS bar transition
52
+ */
53
+ function animateTokenCount(target, max, textEl) {
54
+ if (animationFrame) cancelAnimationFrame(animationFrame);
55
+
56
+ const start = displayedTokens;
57
+ const diff = target - start;
58
+ if (diff === 0) return;
59
+
60
+ const duration = 600; // ms, matches CSS transition
61
+ const startTime = performance.now();
62
+
63
+ function tick(now) {
64
+ const elapsed = now - startTime;
65
+ const progress = Math.min(elapsed / duration, 1);
66
+ // Ease-out cubic
67
+ const eased = 1 - Math.pow(1 - progress, 3);
68
+
69
+ displayedTokens = Math.round(start + diff * eased);
70
+ const isMobile = window.innerWidth <= 768;
71
+ const pct = Math.min(100, Math.round((displayedTokens / max) * 100));
72
+ textEl.textContent = isMobile ? `${pct}%` : `${formatTokens(displayedTokens)}/${formatTokens(max)}`;
73
+
74
+ if (progress < 1) {
75
+ animationFrame = requestAnimationFrame(tick);
76
+ } else {
77
+ displayedTokens = target;
78
+ animationFrame = null;
79
+ }
80
+ }
81
+
82
+ animationFrame = requestAnimationFrame(tick);
83
+ }
84
+
85
+ /**
86
+ * Update the context tracker display
87
+ */
88
+ function updateDisplay() {
89
+ const badge = document.getElementById('contextBadge');
90
+ const barFill = document.getElementById('contextBarFill');
91
+ const text = document.getElementById('contextText');
92
+
93
+ if (!badge) return;
94
+
95
+ if (contextUsed <= 0) {
96
+ badge.style.display = 'none';
97
+ return;
98
+ }
99
+
100
+ badge.style.display = 'flex';
101
+
102
+ const pct = Math.min(100, Math.round((contextUsed / contextMax) * 100));
103
+ const color = getUsageColor(pct);
104
+
105
+ // Update bar fill
106
+ if (barFill) {
107
+ barFill.style.width = pct + '%';
108
+ barFill.style.background = color;
109
+ }
110
+
111
+ // Animate text — smooth count toward target value
112
+ if (text) {
113
+ animateTokenCount(contextUsed, contextMax, text);
114
+ }
115
+
116
+ // Update tooltip — full details on hover
117
+ badge.title = `Context: ${contextUsed.toLocaleString()} / ${contextMax.toLocaleString()} tokens (${pct}%)`;
118
+
119
+ // Update ARIA
120
+ badge.setAttribute('aria-label', `Context window usage: ${pct}% — ${formatTokens(contextUsed)} of ${formatTokens(contextMax)} tokens used`);
121
+ }
122
+
123
+ /**
124
+ * Fetch context data from the server
125
+ */
126
+ async function fetchContext() {
127
+ try {
128
+ const satelliteId = window.UplinkSatellites?.getCurrentSatellite() || 'main';
129
+ currentSatelliteId = satelliteId;
130
+
131
+ const agentId = window.UplinkSatellites?.getCurrentAgentId?.() || 'main';
132
+ const response = await fetch(`/api/session/context?satelliteId=${encodeURIComponent(satelliteId)}&agentId=${encodeURIComponent(agentId)}`);
133
+ if (!response.ok) return;
134
+
135
+ const data = await response.json();
136
+ if (!data.ok && data.error) {
137
+ if (window.logger) logger.warn('ContextTracker: Fetch error', data.error);
138
+ return;
139
+ }
140
+
141
+ contextUsed = data.totalTokens || 0;
142
+ contextMax = data.contextTokens || DEFAULT_CONTEXT_WINDOW;
143
+ updateDisplay();
144
+ } catch (e) {
145
+ if (window.logger) logger.warn('ContextTracker: Fetch failed', e.message);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Called after a message completes — refresh context data
151
+ * Multiple reads: immediate-ish for responsiveness, then again after
152
+ * pruning has had time to settle the token count
153
+ */
154
+ function refresh() {
155
+ setTimeout(fetchContext, 2000); // Quick read
156
+ setTimeout(fetchContext, 8000); // Post-pruning settle
157
+ setTimeout(fetchContext, 20000); // Final settle (catches slower prune cycles)
158
+ }
159
+
160
+ /**
161
+ * Reset the tracker (e.g., on clear chat)
162
+ */
163
+ function reset() {
164
+ contextUsed = 0;
165
+ displayedTokens = 0;
166
+ updateDisplay();
167
+ // Fetch fresh data after reset
168
+ setTimeout(fetchContext, 2000);
169
+ }
170
+
171
+ /**
172
+ * Get current state
173
+ * @returns {Object} - Current context tracker state
174
+ */
175
+ function getState() {
176
+ return {
177
+ contextUsed,
178
+ contextMax,
179
+ percentage: contextMax > 0 ? Math.round((contextUsed / contextMax) * 100) : 0,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Initialize the module
185
+ */
186
+ function init() {
187
+ // Initial fetch
188
+ fetchContext();
189
+
190
+ // Periodic refresh
191
+ refreshTimer = setInterval(fetchContext, REFRESH_INTERVAL_MS);
192
+
193
+ // Re-render on resize (switches between token count and percentage)
194
+ // Debounced to avoid excessive reflows during resize
195
+ window.addEventListener('resize', UplinkCore.debounce(updateDisplay, 150));
196
+
197
+ // Refresh on tab resume — but only if already connected (avoid fetch errors during reconnect)
198
+ document.addEventListener('visibilitychange', () => {
199
+ if (document.visibilityState === 'visible' && window.UplinkConnection?.isConnected?.()) {
200
+ fetchContext();
201
+ }
202
+ });
203
+
204
+ // Refresh on reconnect (session may have changed while disconnected)
205
+ if (window.UplinkConnection?.onConnection) {
206
+ window.UplinkConnection.onConnection((event) => {
207
+ if (event === 'connected') {
208
+ setTimeout(fetchContext, 1000);
209
+ }
210
+ });
211
+ }
212
+
213
+ // Watch for system messages mentioning compaction
214
+ const observer = new MutationObserver((mutations) => {
215
+ for (const mutation of mutations) {
216
+ for (const node of mutation.addedNodes) {
217
+ if (node.nodeType !== 1) continue;
218
+ // Check if it's a system message about compaction
219
+ const text = node.textContent || '';
220
+ if (node.classList?.contains('system') && /compact/i.test(text)) {
221
+ setTimeout(fetchContext, 3000); // Delay for store to update
222
+ }
223
+ }
224
+ }
225
+ });
226
+ const messagesEl = document.getElementById('messages');
227
+ if (messagesEl) {
228
+ observer.observe(messagesEl, { childList: true });
229
+ }
230
+
231
+ // Listen for chat clear events to reset
232
+ const settingsClearBtn = document.getElementById('settingsClearBtn');
233
+ if (settingsClearBtn) {
234
+ settingsClearBtn.addEventListener('click', () => {
235
+ setTimeout(reset, 500);
236
+ });
237
+ }
238
+
239
+ if (window.logger) {
240
+ logger.debug('ContextTracker: Initialized');
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Cleanup function - stop timers
246
+ */
247
+ function cleanup() {
248
+ if (refreshTimer) {
249
+ clearInterval(refreshTimer);
250
+ refreshTimer = null;
251
+ }
252
+ }
253
+
254
+ // Expose API
255
+ export const UplinkContextTracker = {
256
+ init,
257
+ refresh, // Call after message completes
258
+ fetchContext, // Manual fetch
259
+ reset,
260
+ getState,
261
+ cleanup,
262
+ };
263
+
264
+ // Backward compat: assign to window
265
+ window.UplinkContextTracker = UplinkContextTracker;
266
+
267
+ // Register with core for coordinated initialization
268
+ UplinkCore.registerModule('context-tracker', init);
269
+
270
+ // Cleanup on page unload
271
+ window.addEventListener('beforeunload', cleanup);
@@ -0,0 +1,172 @@
1
+ // ============================================
2
+ // CORE MODULE
3
+ // Shared state, config, initialization coordinator
4
+ // ============================================
5
+
6
+ import { UplinkLogger } from './logger.js';
7
+
8
+ // Global state - other modules read/write via UplinkCore
9
+ const state = {
10
+ agentName: 'Assistant',
11
+ gatewayUrl: '',
12
+ audioResponses: true,
13
+ encryptionEnabled: false,
14
+ currentPassword: null,
15
+ mode: 'text', // 'text' or 'voice'
16
+ chatState: 'idle' // 'idle', 'processing', 'recording'
17
+ };
18
+
19
+ // Module registry - tracks which modules are loaded
20
+ const modules = {};
21
+
22
+ // Initialization queue - modules register init functions
23
+ const initQueue = [];
24
+ let initialized = false;
25
+
26
+ // Load config from localStorage
27
+ function loadConfig() {
28
+ try {
29
+ const config = JSON.parse(localStorage.getItem('uplink-config') || 'null');
30
+ if (config) {
31
+ state.agentName = config.agentName || 'Assistant';
32
+ state.gatewayUrl = config.gatewayUrl || '';
33
+ state.audioResponses = config.audioResponses !== undefined ? config.audioResponses : true;
34
+ state.encryptionEnabled = config.encryptionEnabled || false;
35
+ }
36
+ return config;
37
+ } catch (e) {
38
+ UplinkLogger.error('Core: Failed to load config', e);
39
+ return null;
40
+ }
41
+ }
42
+
43
+ // Save config to localStorage
44
+ function saveConfig() {
45
+ try {
46
+ localStorage.setItem('uplink-config', JSON.stringify({
47
+ agentName: state.agentName,
48
+ gatewayUrl: state.gatewayUrl,
49
+ audioResponses: state.audioResponses,
50
+ encryptionEnabled: state.encryptionEnabled
51
+ }));
52
+ } catch (e) {
53
+ UplinkLogger.error('Core: Failed to save config', e);
54
+ }
55
+ }
56
+
57
+ // Register a module
58
+ function registerModule(name, initFn) {
59
+ modules[name] = { loaded: true, initialized: false };
60
+ if (initFn) {
61
+ if (initialized) {
62
+ // Already initialized, run immediately
63
+ try {
64
+ initFn();
65
+ modules[name].initialized = true;
66
+ } catch (e) {
67
+ console.error(`Core: Module ${name} failed to initialize`, e);
68
+ }
69
+ } else {
70
+ initQueue.push({ name, fn: initFn });
71
+ }
72
+ }
73
+ }
74
+
75
+ // Run initialization
76
+ function init() {
77
+ if (initialized) return;
78
+
79
+ UplinkLogger.debug('Core: Initializing...');
80
+ loadConfig();
81
+
82
+ // Run all queued init functions
83
+ initQueue.forEach(({ name, fn }) => {
84
+ try {
85
+ fn();
86
+ modules[name].initialized = true;
87
+ UplinkLogger.debug(`Core: Module ${name} initialized`);
88
+ } catch (e) {
89
+ UplinkLogger.error(`Core: Module ${name} failed to initialize`, e);
90
+ }
91
+ });
92
+
93
+ initialized = true;
94
+ UplinkLogger.debug('Core: Ready');
95
+
96
+ // Register service worker for PWA + force update if stale
97
+ if ('serviceWorker' in navigator) {
98
+ navigator.serviceWorker.register('/sw.js')
99
+ .then(reg => {
100
+ UplinkLogger.debug('Core: Service Worker registered', reg.scope);
101
+ // Check for updates immediately, then every 5 minutes
102
+ reg.update();
103
+ setInterval(() => reg.update(), 5 * 60 * 1000);
104
+ })
105
+ .catch(err => {
106
+ UplinkLogger.warn('Core: Service Worker registration failed', err);
107
+ });
108
+ }
109
+
110
+ // Dispatch ready event
111
+ window.dispatchEvent(new CustomEvent('uplink:ready'));
112
+ }
113
+
114
+ // Check if a module is available
115
+ function hasModule(name) {
116
+ return modules[name]?.initialized === true;
117
+ }
118
+
119
+ // Utility: Debounce function for performance optimization
120
+ function debounce(fn, delay) {
121
+ let timeoutId;
122
+ return function debounced(...args) {
123
+ clearTimeout(timeoutId);
124
+ timeoutId = setTimeout(() => fn.apply(this, args), delay);
125
+ };
126
+ }
127
+
128
+ // Expose API
129
+ export const UplinkCore = {
130
+ state,
131
+ loadConfig,
132
+ saveConfig,
133
+ registerModule,
134
+ hasModule,
135
+ init,
136
+ debounce,
137
+
138
+ // State shortcuts
139
+ get agentName() { return state.agentName; },
140
+ set agentName(v) { state.agentName = v; saveConfig(); },
141
+ get gatewayUrl() { return state.gatewayUrl; },
142
+ set gatewayUrl(v) { state.gatewayUrl = v; saveConfig(); },
143
+ get audioResponses() { return state.audioResponses; },
144
+ set audioResponses(v) { state.audioResponses = v; saveConfig(); },
145
+ get encryptionEnabled() { return state.encryptionEnabled; },
146
+ set encryptionEnabled(v) { state.encryptionEnabled = v; saveConfig(); },
147
+ get mode() { return state.mode; },
148
+ set mode(v) { state.mode = v; },
149
+ get chatState() { return state.chatState; },
150
+ set chatState(v) { state.chatState = v; }
151
+ };
152
+
153
+ export {
154
+ state,
155
+ loadConfig,
156
+ saveConfig,
157
+ registerModule,
158
+ hasModule,
159
+ init,
160
+ debounce
161
+ };
162
+
163
+ // Backward compat: assign to window
164
+ window.UplinkCore = UplinkCore;
165
+
166
+ // Auto-init when DOM ready
167
+ if (document.readyState === 'loading') {
168
+ document.addEventListener('DOMContentLoaded', init);
169
+ } else {
170
+ // Small delay to let other modules register
171
+ setTimeout(init, 10);
172
+ }