agentgui 1.0.930 → 1.0.932

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.
Files changed (128) hide show
  1. package/AGENTS.md +35 -12
  2. package/database.js +31 -2
  3. package/lib/http-handler.js +11 -25
  4. package/lib/routes-registry.js +4 -48
  5. package/lib/server-startup.js +3 -11
  6. package/lib/ws-setup.js +2 -1
  7. package/package.json +3 -3
  8. package/server.js +7 -1
  9. package/site/app/js/app.js +64 -68
  10. package/site/app/js/backend.js +1 -1
  11. package/static/lib/xstate.umd.min.js +1 -1
  12. package/lib/db-queries-chunks.js +0 -195
  13. package/lib/db-queries-chunks2.js +0 -82
  14. package/lib/db-queries-cleanup.js +0 -74
  15. package/lib/db-queries-del.js +0 -141
  16. package/lib/db-queries-events.js +0 -68
  17. package/lib/db-queries-import.js +0 -133
  18. package/lib/db-queries-messages.js +0 -102
  19. package/lib/db-queries-sessions.js +0 -112
  20. package/lib/db-queries-streams.js +0 -100
  21. package/lib/db-queries.js +0 -89
  22. package/lib/jsonl-parser.js +0 -190
  23. package/lib/jsonl-watcher.js +0 -64
  24. package/lib/routes-agent-actions.js +0 -61
  25. package/lib/routes-auth-config.js +0 -30
  26. package/lib/routes-conversations.js +0 -96
  27. package/lib/routes-debug.js +0 -119
  28. package/lib/routes-messages.js +0 -139
  29. package/lib/routes-runs.js +0 -156
  30. package/lib/routes-scripts.js +0 -135
  31. package/lib/routes-sessions.js +0 -144
  32. package/lib/routes-threads.js +0 -100
  33. package/lib/routes-util.js +0 -110
  34. package/lib/ws-handlers-conv.js +0 -138
  35. package/lib/ws-handlers-conv2.js +0 -169
  36. package/lib/ws-handlers-msg.js +0 -121
  37. package/lib/ws-handlers-queue.js +0 -56
  38. package/lib/ws-handlers-run.js +0 -182
  39. package/lib/ws-handlers-scripts.js +0 -66
  40. package/lib/ws-handlers-session.js +0 -105
  41. package/lib/ws-handlers-session2.js +0 -85
  42. package/lib/ws-legacy-handlers.js +0 -51
  43. package/static/app.js +0 -261
  44. package/static/css/app-shell.css +0 -419
  45. package/static/css/brand-bible.css +0 -591
  46. package/static/css/colors_and_type.css +0 -568
  47. package/static/css/gmail-skin.css +0 -663
  48. package/static/css/main.css +0 -4015
  49. package/static/css/tools-popup.css +0 -472
  50. package/static/index.html +0 -418
  51. package/static/js/agent-auth.js +0 -146
  52. package/static/js/app-shortcuts.js +0 -30
  53. package/static/js/audio-recorder-processor.js +0 -18
  54. package/static/js/client-agents.js +0 -155
  55. package/static/js/client-cache.js +0 -171
  56. package/static/js/client-conv.js +0 -198
  57. package/static/js/client-events.js +0 -164
  58. package/static/js/client-exec.js +0 -160
  59. package/static/js/client-helpers.js +0 -199
  60. package/static/js/client-load.js +0 -175
  61. package/static/js/client-render.js +0 -132
  62. package/static/js/client-scroll.js +0 -178
  63. package/static/js/client-status.js +0 -167
  64. package/static/js/client-streaming.js +0 -117
  65. package/static/js/client-streaming2.js +0 -116
  66. package/static/js/client-streaming3.js +0 -153
  67. package/static/js/client-streaming4.js +0 -194
  68. package/static/js/client-ui-controls.js +0 -170
  69. package/static/js/client-ui.js +0 -128
  70. package/static/js/client-ui2.js +0 -160
  71. package/static/js/client-url.js +0 -93
  72. package/static/js/client-utils.js +0 -174
  73. package/static/js/client-ws-msg.js +0 -88
  74. package/static/js/client-ws.js +0 -161
  75. package/static/js/client.js +0 -145
  76. package/static/js/codec.js +0 -4
  77. package/static/js/conv-list-machine.js +0 -145
  78. package/static/js/conv-list-renderer.js +0 -198
  79. package/static/js/conv-machine.js +0 -110
  80. package/static/js/conv-sidebar-actions.js +0 -188
  81. package/static/js/conv-sidebar-clone.js +0 -91
  82. package/static/js/conversations.js +0 -116
  83. package/static/js/dialogs-types.js +0 -111
  84. package/static/js/dialogs.js +0 -53
  85. package/static/js/event-filter-config.js +0 -36
  86. package/static/js/event-processor.js +0 -181
  87. package/static/js/features.js +0 -187
  88. package/static/js/image-loader-element.js +0 -76
  89. package/static/js/image-loader.js +0 -146
  90. package/static/js/prompt-machine.js +0 -108
  91. package/static/js/recording-machine.js +0 -49
  92. package/static/js/script-runner.js +0 -192
  93. package/static/js/state-barrier.js +0 -105
  94. package/static/js/streaming-renderer-dispatch.js +0 -144
  95. package/static/js/streaming-renderer-events.js +0 -163
  96. package/static/js/streaming-renderer-events2.js +0 -125
  97. package/static/js/streaming-renderer-params.js +0 -38
  98. package/static/js/streaming-renderer-render-misc.js +0 -107
  99. package/static/js/streaming-renderer-render.js +0 -181
  100. package/static/js/streaming-renderer-render2.js +0 -149
  101. package/static/js/streaming-renderer-render3.js +0 -142
  102. package/static/js/streaming-renderer-static.js +0 -181
  103. package/static/js/streaming-renderer-static2.js +0 -140
  104. package/static/js/streaming-renderer-stream.js +0 -170
  105. package/static/js/streaming-renderer-text.js +0 -185
  106. package/static/js/streaming-renderer-tools.js +0 -189
  107. package/static/js/streaming-renderer-tools2.js +0 -92
  108. package/static/js/streaming-renderer.js +0 -200
  109. package/static/js/syntax-highlighter-render.js +0 -72
  110. package/static/js/syntax-highlighter.js +0 -131
  111. package/static/js/terminal-machine.js +0 -51
  112. package/static/js/terminal.js +0 -178
  113. package/static/js/ui-components-rendering.js +0 -62
  114. package/static/js/ui-components.js +0 -88
  115. package/static/js/websocket-manager.js +0 -107
  116. package/static/js/ws-client.js +0 -87
  117. package/static/js/ws-core.js +0 -162
  118. package/static/js/ws-latency.js +0 -88
  119. package/static/js/ws-machine.js +0 -68
  120. package/static/lib/msgpackr.min.js +0 -2
  121. package/static/theme.js +0 -74
  122. package/static/vendor/highlight-js.css +0 -10
  123. package/static/vendor/highlight.min.js +0 -1244
  124. package/static/vendor/prism-dark.css +0 -129
  125. package/static/vendor/rippleui.css +0 -35
  126. package/static/vendor/xterm-addon-fit.min.js +0 -8
  127. package/static/vendor/xterm.css +0 -8
  128. package/static/vendor/xterm.min.js +0 -8
@@ -1,88 +0,0 @@
1
- class UIComponents {
2
- static createModal(config = {}) {
3
- const { title = 'Dialog', content = '', buttons = [], onClose = null, size = 'medium' } = config;
4
- const modal = document.createElement('div');
5
- modal.className = 'modal-overlay';
6
- modal.dataset.modal = 'true';
7
- const sizeClasses = { 'small': 'max-w-sm', 'medium': 'max-w-md', 'large': 'max-w-2xl' };
8
- modal.innerHTML = `<div class="modal-content card ${sizeClasses[size] || sizeClasses['medium']}"><div class="card-header flex justify-between items-center"><h2 class="text-xl font-bold">${UIComponents.escapeHtml(title)}</h2><button class="btn btn-ghost btn-sm modal-close">&times;</button></div><div class="card-body modal-body">${typeof content === 'string' ? UIComponents.escapeHtml(content) : ''}</div><div class="card-footer flex gap-2 justify-end">${buttons.map(btn => `<button class="btn btn-${btn.variant || 'secondary'}" data-action="${btn.action || 'close'}">${UIComponents.escapeHtml(btn.label)}</button>`).join('')}</div></div>`;
9
- const closeBtn = modal.querySelector('.modal-close');
10
- closeBtn.addEventListener('click', () => { modal.remove(); if (onClose) onClose(); });
11
- modal.querySelectorAll('[data-action]').forEach(btn => {
12
- btn.addEventListener('click', (e) => { const action = e.target.dataset.action; if (action === 'close') { modal.remove(); if (onClose) onClose(); } });
13
- });
14
- modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); if (onClose) onClose(); } });
15
- return modal;
16
- }
17
-
18
- static createTabs(config = {}) {
19
- const { tabs = [], activeTab = 0, onChange = null } = config;
20
- const container = document.createElement('div');
21
- container.className = 'tabs';
22
- const tabButtons = document.createElement('div');
23
- tabButtons.className = 'tab-buttons flex border-b';
24
- const tabContent = document.createElement('div');
25
- tabContent.className = 'tab-content mt-4';
26
- tabs.forEach((tab, index) => {
27
- const btn = document.createElement('button');
28
- btn.className = `tab tab-underline tab-button ${index === activeTab ? 'tab-active' : ''}`;
29
- btn.textContent = tab.label;
30
- btn.dataset.tabIndex = index;
31
- btn.addEventListener('click', () => {
32
- tabButtons.querySelectorAll('.tab-button').forEach((b, i) => b.classList.toggle('tab-active', i === index));
33
- tabContent.querySelectorAll('.tab-pane').forEach((pane, i) => { pane.style.display = i === index ? 'block' : 'none'; });
34
- if (onChange) onChange(index);
35
- });
36
- tabButtons.appendChild(btn);
37
- const pane = document.createElement('div');
38
- pane.className = 'tab-pane';
39
- pane.style.display = index === activeTab ? 'block' : 'none';
40
- pane.innerHTML = typeof tab.content === 'string' ? tab.content : '';
41
- tabContent.appendChild(pane);
42
- });
43
- container.appendChild(tabButtons);
44
- container.appendChild(tabContent);
45
- return container;
46
- }
47
-
48
- static createAlert(config = {}) {
49
- const { message = '', type = 'info', duration = 5000, dismissible = true } = config;
50
- const alert = document.createElement('div');
51
- const typeClasses = { 'info': 'alert-info', 'success': 'alert-success', 'warning': 'alert-warning', 'error': 'alert-error' };
52
- alert.className = `alert ${typeClasses[type] || typeClasses['info']}`;
53
- alert.innerHTML = `<div class="flex justify-between items-center"><span>${UIComponents.escapeHtml(message)}</span>${dismissible ? '<button class="text-current hover:opacity-75">&times;</button>' : ''}</div>`;
54
- if (dismissible) alert.querySelector('button').addEventListener('click', () => alert.remove());
55
- if (duration > 0) setTimeout(() => alert.remove(), duration);
56
- return alert;
57
- }
58
-
59
- static createSpinner(config = {}) {
60
- const { size = 'medium', text = 'Loading...' } = config;
61
- const sizeClasses = { 'small': 'spinner-xs', 'medium': 'spinner-sm', 'large': 'spinner-md' };
62
- const container = document.createElement('div');
63
- container.className = 'flex items-center gap-3 justify-center p-4';
64
- container.innerHTML = `<div class="spinner-simple spinner-primary ${sizeClasses[size] || sizeClasses['medium']}"></div><span class="text-base-content">${UIComponents.escapeHtml(text)}</span>`;
65
- return container;
66
- }
67
-
68
- static createProgressBar(config = {}) {
69
- const { percentage = 0, label = '', showLabel = true } = config;
70
- const container = document.createElement('div');
71
- container.className = 'progress-container';
72
- let html = '';
73
- if (label && showLabel) html += `<div class="flex justify-between mb-2 text-sm"><span>${UIComponents.escapeHtml(label)}</span><span>${Math.round(percentage)}%</span></div>`;
74
- html += `<progress class="progress progress-primary progress-xs w-full" value="${Math.min(100, Math.max(0, percentage))}" max="100"></progress>`;
75
- container.innerHTML = html;
76
- return container;
77
- }
78
-
79
- static createCollapsible(config = {}) {
80
- const { title = 'Details', content = '', isOpen = false } = config;
81
- const container = document.createElement('div');
82
- container.className = 'collapsible';
83
- container.innerHTML = `<details ${isOpen ? 'open' : ''}><summary class="cursor-pointer font-semibold hover:bg-base-200 px-2 py-1 rounded transition-colors">${UIComponents.escapeHtml(title)}</summary><div class="content mt-2 ml-4">${typeof content === 'string' ? content : ''}</div></details>`;
84
- return container;
85
- }
86
- }
87
-
88
- if (typeof module !== 'undefined' && module.exports) module.exports = UIComponents;
@@ -1,107 +0,0 @@
1
- Object.assign(WebSocketManager.prototype, {
2
- sendMessage(data) {
3
- if (!data || typeof data !== 'object') throw new Error('Invalid message data');
4
- if (data.type === 'subscribe') {
5
- const key = data.sessionId ? 'session:' + data.sessionId : 'conv:' + data.conversationId;
6
- this.activeSubscriptions.add(key);
7
- } else if (data.type === 'unsubscribe') {
8
- const key = data.sessionId ? 'session:' + data.sessionId : 'conv:' + data.conversationId;
9
- this.activeSubscriptions.delete(key);
10
- }
11
- if (!this.isConnected) { this.bufferMessage(data); return false; }
12
- try {
13
- this.ws.send(window._codec ? window._codec.encode(data) : msgpackr.pack(data));
14
- this.stats.totalMessagesSent++;
15
- return true;
16
- } catch (error) { this.stats.totalErrors++; this.bufferMessage(data); return false; }
17
- },
18
-
19
- bufferMessage(data) {
20
- if (this.messageBuffer.length >= this.config.maxBufferedMessages) this.messageBuffer.shift();
21
- this.messageBuffer.push(data);
22
- this.emit('message_buffered', { bufferLength: this.messageBuffer.length });
23
- },
24
-
25
- flushMessageBuffer() {
26
- if (this.messageBuffer.length === 0) return;
27
- const messages = [...this.messageBuffer];
28
- this.messageBuffer = [];
29
- for (const message of messages) {
30
- try {
31
- this.ws.send(window._codec ? window._codec.encode(message) : msgpackr.pack(message));
32
- this.stats.totalMessagesSent++;
33
- } catch (error) { this.bufferMessage(message); }
34
- }
35
- this.emit('buffer_flushed', { count: messages.length });
36
- },
37
-
38
- subscribeToSession(sessionId) { return this.sendMessage({ type: 'subscribe', sessionId, timestamp: Date.now() }); },
39
- subscribeToConversation(conversationId) { return this.sendMessage({ type: 'subscribe', conversationId, timestamp: Date.now() }); },
40
-
41
- resubscribeAll() {
42
- for (const key of this.activeSubscriptions) {
43
- const colonIdx = key.indexOf(':');
44
- const type = key.substring(0, colonIdx);
45
- const id = key.substring(colonIdx + 1);
46
- const msg = { type: 'subscribe', timestamp: Date.now() };
47
- if (type === 'session') msg.sessionId = id; else msg.conversationId = id;
48
- try { this.ws.send(window._codec ? window._codec.encode(msg) : msgpackr.pack(msg)); this.stats.totalMessagesSent++; } catch (_) {}
49
- }
50
- },
51
-
52
- unsubscribeFromSession(sessionId) { return this.sendMessage({ type: 'unsubscribe', sessionId, timestamp: Date.now() }); },
53
-
54
- requestSessionHistory(sessionId, limit = 1000, offset = 0) {
55
- return new Promise((resolve, reject) => {
56
- const requestId = 'history-' + Date.now() + '-' + Math.random();
57
- const timeout = setTimeout(() => { this.requestMap.delete(requestId); this.stats.totalTimeouts++; reject(new Error('History request timeout')); }, this.config.messageTimeout);
58
- this.requestMap.set(requestId, { type: 'history', resolve: (d) => { clearTimeout(timeout); resolve(d); }, reject });
59
- this.sendMessage({ type: 'request_history', requestId, sessionId, limit, offset, timestamp: Date.now() });
60
- });
61
- },
62
-
63
- getLastSeq(sessionId) { return this.lastSeqBySession[sessionId] || -1; },
64
-
65
- setConnectionState(state) {
66
- this.connectionState = state;
67
- this.emit('state_change', { state, timestamp: Date.now() });
68
- },
69
-
70
- disconnect() {
71
- this.isManuallyDisconnected = true;
72
- this.reconnectCount = 0;
73
- this.stopHeartbeat();
74
- if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
75
- if (this.ws) this.ws.close();
76
- this.messageBuffer = [];
77
- this.requestMap.clear();
78
- this.setConnectionState('disconnected');
79
- },
80
-
81
- getStatus() {
82
- return { isConnected: this.isConnected, isConnecting: this.isConnecting, connectionState: this.connectionState, reconnectCount: this.reconnectCount, bufferLength: this.messageBuffer.length, latency: { ...this.latency, samples: undefined }, stats: { ...this.stats } };
83
- },
84
-
85
- on(event, callback) { if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(callback); },
86
- off(event, callback) { if (!this.listeners[event]) return; const index = this.listeners[event].indexOf(callback); if (index > -1) this.listeners[event].splice(index, 1); },
87
- emit(event, data) { if (!this.listeners[event]) return; this.listeners[event].forEach((cb) => { try { cb(data); } catch (error) {} }); },
88
-
89
- destroy() {
90
- if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', this._onVisibilityChange);
91
- if (typeof window !== 'undefined') window.removeEventListener('online', this._onOnline);
92
- this.disconnect();
93
- this.listeners = {};
94
- },
95
-
96
- subscribeToVoiceList(callback) {
97
- if (!this.voiceListListeners.includes(callback)) this.voiceListListeners.push(callback);
98
- if (this.cachedVoiceList !== null) callback(this.cachedVoiceList);
99
- },
100
-
101
- unsubscribeFromVoiceList(callback) {
102
- const idx = this.voiceListListeners.indexOf(callback);
103
- if (idx > -1) this.voiceListListeners.splice(idx, 1);
104
- }
105
- });
106
-
107
- if (typeof module !== 'undefined' && module.exports) module.exports = WebSocketManager;
@@ -1,87 +0,0 @@
1
- class WsClient {
2
- constructor(wsManager) {
3
- this._ws = wsManager;
4
- this._pending = new Map();
5
- this._installed = false;
6
- this._connectPromise = null;
7
- this._install();
8
- }
9
-
10
- _install() {
11
- if (this._installed) return;
12
- this._installed = true;
13
- this._ws.on('message', (data) => {
14
- if (data.r && this._pending.has(data.r)) {
15
- const p = this._pending.get(data.r);
16
- this._pending.delete(data.r);
17
- clearTimeout(p.timer);
18
- if (data.e) {
19
- p.reject(Object.assign(new Error(data.e.m || 'RPC error'), { code: data.e.c }));
20
- } else {
21
- p.resolve(data.d);
22
- }
23
- return; // consumed — don't re-emit
24
- }
25
- });
26
- this._ws.on('disconnected', () => this.cancelAll());
27
- }
28
-
29
- _ensureConnected() {
30
- if (this._ws.isConnected) return Promise.resolve();
31
- if (this._connectPromise) return this._connectPromise;
32
- this._connectPromise = this._ws.connect().then(() => {
33
- this._connectPromise = null;
34
- }).catch(() => {
35
- this._connectPromise = null;
36
- });
37
- return this._connectPromise;
38
- }
39
-
40
- _id() {
41
- let id = '';
42
- for (let i = 0; i < 8; i++) id += ((Math.random() * 16) | 0).toString(16);
43
- return id;
44
- }
45
-
46
- request(method, params = {}, timeout = 30000) {
47
- return this._ensureConnected().then(() => {
48
- return new Promise((resolve, reject) => {
49
- const r = this._id();
50
- const timer = setTimeout(() => {
51
- this._pending.delete(r);
52
- reject(new Error(`RPC timeout: ${method}`));
53
- }, timeout);
54
- this._pending.set(r, { resolve, reject, timer });
55
- this._ws.sendMessage({ r, m: method, p: params });
56
- });
57
- });
58
- }
59
-
60
- rpc(method, params) {
61
- return this.request(method, params);
62
- }
63
-
64
- cancelAll() {
65
- for (const [, p] of this._pending) {
66
- clearTimeout(p.timer);
67
- p.reject(new Error('Connection lost'));
68
- }
69
- this._pending.clear();
70
- }
71
-
72
- get pendingCount() {
73
- return this._pending.size;
74
- }
75
- }
76
-
77
- window.WsClient = WsClient;
78
-
79
- window.wsManager = new WebSocketManager();
80
- window.wsClient = new WsClient(window.wsManager);
81
- window.wsManager.connect().catch(function() {});
82
-
83
- import('./codec.js').then(codec => {
84
- window._codec = codec;
85
- }).catch(e => {
86
- console.error('[ws-client] Failed to load codec, using msgpackr fallback:', e);
87
- });
@@ -1,162 +0,0 @@
1
- class WebSocketManager {
2
- constructor(config = {}) {
3
- this.config = {
4
- url: config.url || this.getWebSocketURL(),
5
- reconnectDelays: config.reconnectDelays || [500, 1000, 2000, 4000, 8000, 15000, 30000],
6
- maxReconnectDelay: config.maxReconnectDelay || 30000,
7
- heartbeatInterval: config.heartbeatInterval || 15000,
8
- messageTimeout: config.messageTimeout || 60000,
9
- maxBufferedMessages: config.maxBufferedMessages || 1000,
10
- pongTimeout: config.pongTimeout || 5000,
11
- latencyWindowSize: config.latencyWindowSize || 10,
12
- ...config
13
- };
14
- this.ws = null;
15
- this._isConnected = false;
16
- this._isConnecting = false;
17
- this._isManuallyDisconnected = false;
18
- this._reconnectCount = 0;
19
- this.isManuallyDisconnected = false;
20
- this.reconnectCount = 0;
21
- this.reconnectTimer = null;
22
- this.messageBuffer = [];
23
- this.requestMap = new Map();
24
- this.heartbeatTimer = null;
25
- this._connectionState = 'disconnected';
26
- this.activeSubscriptions = new Set();
27
- this._wsActor = typeof createWsActor === 'function' ? createWsActor() : null;
28
- this.connectionEstablishedAt = 0;
29
- this.cachedVoiceList = null;
30
- this.voiceListListeners = [];
31
- this.latency = { samples: [], current: 0, avg: 0, jitter: 0, quality: 'unknown', predicted: 0, predictedNext: 0, trend: 'stable', missedPongs: 0, pingCounter: 0 };
32
- this._latencyEma = null;
33
- this._trendHistory = [];
34
- this._trendCount = 0;
35
- this._reconnectedAt = 0;
36
- this.stats = { totalConnections: 0, totalReconnects: 0, totalMessagesSent: 0, totalMessagesReceived: 0, totalErrors: 0, totalTimeouts: 0, avgLatency: 0, lastConnectedTime: null, connectionDuration: 0 };
37
- this.lastSeqBySession = {};
38
- this.listeners = {};
39
- this._onVisibilityChange = this._handleVisibilityChange.bind(this);
40
- this._onOnline = this._handleOnline.bind(this);
41
- if (typeof document !== 'undefined') document.addEventListener('visibilitychange', this._onVisibilityChange);
42
- if (typeof window !== 'undefined') window.addEventListener('online', this._onOnline);
43
- }
44
-
45
- get isConnected() { return this._wsActor ? this._wsActor.getSnapshot().value === 'connected' : this._isConnected; }
46
- set isConnected(v) { this._isConnected = v; }
47
- get isConnecting() { return this._wsActor ? this._wsActor.getSnapshot().value === 'connecting' : this._isConnecting; }
48
- set isConnecting(v) { this._isConnecting = v; }
49
- get connectionState() { return this._wsActor ? this._wsActor.getSnapshot().value : this._connectionState; }
50
- set connectionState(v) { this._connectionState = v; }
51
- get isManuallyDisconnected() { return this._wsActor ? !!this._wsActor.getSnapshot().context.manualDisconnect : this._isManuallyDisconnected; }
52
- set isManuallyDisconnected(v) { this._isManuallyDisconnected = v; if (this._wsActor && v) this._wsActor.send({ type: 'MANUAL_DISCONNECT' }); }
53
- get reconnectCount() { return this._wsActor ? this._wsActor.getSnapshot().context.reconnectCount : this._reconnectCount; }
54
- set reconnectCount(v) { this._reconnectCount = v; }
55
-
56
- getWebSocketURL() {
57
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
58
- const baseURL = window.__BASE_URL || '/gm';
59
- let url = `${protocol}//${window.location.host}${baseURL}/sync`;
60
- if (window.__WS_TOKEN) url += `?token=${encodeURIComponent(window.__WS_TOKEN)}`;
61
- return url;
62
- }
63
-
64
- async connect() {
65
- if (this.isConnected || this.isConnecting) return this.ws;
66
- this.isManuallyDisconnected = false;
67
- this._isConnecting = true;
68
- if (this._wsActor) this._wsActor.send({ type: 'CONNECT' });
69
- this.setConnectionState('connecting');
70
- try {
71
- this.ws = new WebSocket(this.config.url);
72
- this.ws.binaryType = 'arraybuffer';
73
- this.ws.onopen = () => this.onOpen();
74
- this.ws.onmessage = (event) => this.onMessage(event);
75
- this.ws.onerror = (error) => this.onError(error);
76
- this.ws.onclose = () => this.onClose();
77
- return await this.waitForConnection(this.config.messageTimeout);
78
- } catch (error) {
79
- this.isConnecting = false;
80
- this.stats.totalErrors++;
81
- this.scheduleReconnect();
82
- throw error;
83
- }
84
- }
85
-
86
- waitForConnection(timeout = 5000) {
87
- return new Promise((resolve, reject) => {
88
- const timer = setTimeout(() => reject(new Error('WebSocket connection timeout')), timeout);
89
- const check = () => {
90
- if (this.isConnected || this.ws?.readyState === WebSocket.OPEN) { clearTimeout(timer); resolve(this.ws); }
91
- else setTimeout(check, 50);
92
- };
93
- check();
94
- });
95
- }
96
-
97
- onOpen() {
98
- this._isConnected = true;
99
- this._isConnecting = false;
100
- if (this._wsActor) this._wsActor.send({ type: 'OPEN' });
101
- this.connectionEstablishedAt = Date.now();
102
- this._reconnectedAt = this.stats.totalConnections > 0 ? Date.now() : 0;
103
- this.stats.totalConnections++;
104
- this.stats.lastConnectedTime = Date.now();
105
- this.latency.missedPongs = 0;
106
- this.setConnectionState('connected');
107
- this.flushMessageBuffer();
108
- this.resubscribeAll();
109
- this.startHeartbeat();
110
- this.emit('connected', { timestamp: Date.now() });
111
- }
112
-
113
- async onMessage(event) {
114
- try {
115
- const buf = event.data instanceof Blob ? await event.data.arrayBuffer() : event.data;
116
- const parsed = window._codec ? window._codec.decode(buf) : msgpackr.unpack(new Uint8Array(buf));
117
- const messages = Array.isArray(parsed) ? parsed : [parsed];
118
- this.stats.totalMessagesReceived += messages.length;
119
- for (const data of messages) {
120
- if (data.type === 'pong') { this._handlePong(data); continue; }
121
- if (data.type === 'voice_list') {
122
- this.cachedVoiceList = data.voices || [];
123
- for (const listener of this.voiceListListeners) { try { listener(this.cachedVoiceList); } catch (_) {} }
124
- }
125
- if (data.seq !== undefined && data.sessionId) {
126
- this.lastSeqBySession[data.sessionId] = Math.max(this.lastSeqBySession[data.sessionId] || -1, data.seq);
127
- }
128
- this.emit('message', data);
129
- if (data.type) this.emit('message:' + data.type, data);
130
- }
131
- } catch (error) { this.stats.totalErrors++; }
132
- }
133
-
134
- onError(error) {
135
- this.stats.totalErrors++;
136
- if (this._wsActor) this._wsActor.send({ type: 'ERROR' });
137
- this.emit('error', { error, timestamp: Date.now() });
138
- }
139
-
140
- onClose() {
141
- this._isConnected = false;
142
- this._isConnecting = false;
143
- if (this._wsActor) this._wsActor.send({ type: 'CLOSE' });
144
- this.setConnectionState('disconnected');
145
- this.stopHeartbeat();
146
- if (this.stats.lastConnectedTime) this.stats.connectionDuration = Date.now() - this.stats.lastConnectedTime;
147
- this.emit('disconnected', { timestamp: Date.now() });
148
- if (!this.isManuallyDisconnected) this.scheduleReconnect();
149
- }
150
-
151
- scheduleReconnect() {
152
- if (this.isManuallyDisconnected || this.reconnectTimer) return;
153
- const delays = this.config.reconnectDelays;
154
- const baseDelay = this.reconnectCount < delays.length ? delays[this.reconnectCount] : this.config.maxReconnectDelay;
155
- const delay = Math.round(baseDelay + Math.random() * 0.3 * baseDelay);
156
- this.reconnectCount++;
157
- this.stats.totalReconnects++;
158
- this.setConnectionState('reconnecting');
159
- this.emit('reconnecting', { delay, attempt: this.reconnectCount, nextAttemptAt: Date.now() + delay });
160
- this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.connect().catch(() => {}); }, delay);
161
- }
162
- }
@@ -1,88 +0,0 @@
1
- Object.assign(WebSocketManager.prototype, {
2
- startHeartbeat() {
3
- this.stopHeartbeat();
4
- this.heartbeatTimer = setInterval(() => { this.ping(); }, this.config.heartbeatInterval);
5
- },
6
-
7
- stopHeartbeat() {
8
- if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; }
9
- },
10
-
11
- _reportLatency() {
12
- this.emit('latency', { current: this.latency.current, avg: this.latency.avg, jitter: this.latency.jitter, quality: this.latency.quality, trend: this.latency.trend });
13
- },
14
-
15
- _handleVisibilityChange() {
16
- if (document.visibilityState === 'visible' && !this.isConnected && !this.isManuallyDisconnected) this.connect().catch(() => {});
17
- },
18
-
19
- _handleOnline() {
20
- if (!this.isConnected && !this.isManuallyDisconnected) this.connect().catch(() => {});
21
- },
22
-
23
- ping() {
24
- if (!this.isConnected) return;
25
- this.latency.pingCounter++;
26
- const id = this.latency.pingCounter;
27
- const pongTimer = setTimeout(() => {
28
- this.latency.missedPongs++;
29
- this.emit('latency', { current: null, avg: this.latency.avg, jitter: this.latency.jitter, quality: 'poor', missed: true });
30
- if (this.latency.missedPongs >= 3) { this.onClose(); }
31
- }, this.config.pongTimeout);
32
- this.requestMap.set('ping:' + id, { type: 'ping', sentAt: Date.now(), pongTimer });
33
- try {
34
- this.ws.send(window._codec ? window._codec.encode({ type: 'ping', id }) : msgpackr.pack({ type: 'ping', id }));
35
- } catch (_) { clearTimeout(pongTimer); this.requestMap.delete('ping:' + id); }
36
- },
37
-
38
- _handlePong(data) {
39
- const key = 'ping:' + data.id;
40
- const pending = this.requestMap.get(key);
41
- if (!pending) return;
42
- clearTimeout(pending.pongTimer);
43
- this.requestMap.delete(key);
44
- this.latency.missedPongs = 0;
45
- const rtt = Date.now() - pending.sentAt;
46
- this._recordLatency(rtt);
47
- this._checkDegradation();
48
- this._reportLatency();
49
- },
50
-
51
- _recordLatency(rtt) {
52
- const samples = this.latency.samples;
53
- samples.push(rtt);
54
- if (samples.length > this.config.latencyWindowSize) samples.shift();
55
- this.latency.current = rtt;
56
- this.latency.avg = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);
57
- const jitterSamples = samples.slice(1).map((v, i) => Math.abs(v - samples[i]));
58
- this.latency.jitter = jitterSamples.length ? Math.round(jitterSamples.reduce((a, b) => a + b, 0) / jitterSamples.length) : 0;
59
- this.latency.quality = this._qualityTier(this.latency.avg);
60
- if (this._latencyEma === null) this._latencyEma = rtt;
61
- else this._latencyEma = 0.3 * rtt + 0.7 * this._latencyEma;
62
- this.latency.predicted = Math.round(this._latencyEma);
63
- this.stats.avgLatency = this.latency.avg;
64
- this._trendHistory.push(rtt);
65
- if (this._trendHistory.length > 5) this._trendHistory.shift();
66
- if (this._trendHistory.length >= 3) {
67
- const h = this._trendHistory;
68
- const slope = (h[h.length - 1] - h[0]) / h.length;
69
- this.latency.trend = slope > 5 ? 'rising' : slope < -5 ? 'falling' : 'stable';
70
- }
71
- },
72
-
73
- _qualityTier(avg) {
74
- if (avg < 50) return 'excellent';
75
- if (avg < 150) return 'good';
76
- if (avg < 300) return 'fair';
77
- return 'poor';
78
- },
79
-
80
- _checkDegradation() {
81
- const tier = this.latency.quality;
82
- const prev = this._lastQualityTier;
83
- if (tier !== prev) {
84
- this._lastQualityTier = tier;
85
- this.emit('quality_change', { quality: tier, latency: this.latency.avg });
86
- }
87
- }
88
- });
@@ -1,68 +0,0 @@
1
-
2
- const { createMachine, createActor, assign } = XState;
3
-
4
- const wsMachine = createMachine({
5
- id: 'websocket',
6
- initial: 'disconnected',
7
- context: {
8
- reconnectCount: 0,
9
- lastConnectedAt: null,
10
- lastDisconnectedAt: null,
11
- manualDisconnect: false,
12
- },
13
- states: {
14
- disconnected: {
15
- entry: assign({ lastDisconnectedAt: () => Date.now() }),
16
- on: {
17
- CONNECT: { target: 'connecting', guard: ({ context }) => !context.manualDisconnect },
18
- MANUAL_DISCONNECT: { actions: assign({ manualDisconnect: true }) },
19
- },
20
- },
21
- connecting: {
22
- on: {
23
- OPEN: {
24
- target: 'connected',
25
- actions: assign({ reconnectCount: 0, lastConnectedAt: () => Date.now() }),
26
- },
27
- ERROR: 'reconnecting',
28
- CLOSE: 'reconnecting',
29
- MANUAL_DISCONNECT: {
30
- target: 'disconnected',
31
- actions: assign({ manualDisconnect: true }),
32
- },
33
- },
34
- },
35
- connected: {
36
- entry: assign({ lastConnectedAt: () => Date.now() }),
37
- on: {
38
- CLOSE: 'reconnecting',
39
- ERROR: 'reconnecting',
40
- MANUAL_DISCONNECT: {
41
- target: 'disconnected',
42
- actions: assign({ manualDisconnect: true, reconnectCount: 0 }),
43
- },
44
- },
45
- },
46
- reconnecting: {
47
- entry: assign(({ context }) => ({ reconnectCount: context.reconnectCount + 1 })),
48
- on: {
49
- CONNECT: 'connecting',
50
- MANUAL_DISCONNECT: {
51
- target: 'disconnected',
52
- actions: assign({ manualDisconnect: true, reconnectCount: 0 }),
53
- },
54
- },
55
- },
56
- },
57
- });
58
-
59
- function createWsActor() {
60
- const actor = createActor(wsMachine);
61
- actor.start();
62
- return actor;
63
- }
64
-
65
- if (typeof window !== 'undefined') {
66
- window.createWsActor = createWsActor;
67
- window.wsMachine = wsMachine;
68
- }