agentgui 1.0.274 → 1.0.275

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 (69) hide show
  1. package/CLAUDE.md +280 -280
  2. package/IPFS_DOWNLOADER.md +277 -277
  3. package/TASK_2C_COMPLETION.md +334 -334
  4. package/bin/gmgui.cjs +54 -54
  5. package/build-portable.js +3 -42
  6. package/database.js +1422 -1406
  7. package/lib/claude-runner.js +1130 -1130
  8. package/lib/ipfs-downloader.js +459 -459
  9. package/lib/speech.js +152 -152
  10. package/package.json +1 -1
  11. package/readme.md +76 -76
  12. package/server.js +3787 -3794
  13. package/setup-npm-token.sh +68 -68
  14. package/static/app.js +773 -773
  15. package/static/event-rendering-showcase.html +708 -708
  16. package/static/index.html +3178 -3180
  17. package/static/js/agent-auth.js +298 -298
  18. package/static/js/audio-recorder-processor.js +18 -18
  19. package/static/js/client.js +2656 -2656
  20. package/static/js/conversations.js +583 -583
  21. package/static/js/dialogs.js +267 -267
  22. package/static/js/event-consolidator.js +101 -101
  23. package/static/js/event-filter.js +311 -311
  24. package/static/js/event-processor.js +452 -452
  25. package/static/js/features.js +413 -413
  26. package/static/js/kalman-filter.js +67 -67
  27. package/static/js/progress-dialog.js +130 -130
  28. package/static/js/script-runner.js +219 -219
  29. package/static/js/streaming-renderer.js +2123 -2120
  30. package/static/js/syntax-highlighter.js +269 -269
  31. package/static/js/tts-websocket-handler.js +152 -152
  32. package/static/js/ui-components.js +431 -431
  33. package/static/js/voice.js +849 -849
  34. package/static/js/websocket-manager.js +596 -596
  35. package/static/templates/INDEX.html +465 -465
  36. package/static/templates/README.md +190 -190
  37. package/static/templates/agent-capabilities.html +56 -56
  38. package/static/templates/agent-metadata-panel.html +44 -44
  39. package/static/templates/agent-status-badge.html +30 -30
  40. package/static/templates/code-annotation-panel.html +155 -155
  41. package/static/templates/code-suggestion-panel.html +184 -184
  42. package/static/templates/command-header.html +77 -77
  43. package/static/templates/command-output-scrollable.html +118 -118
  44. package/static/templates/elapsed-time.html +54 -54
  45. package/static/templates/error-alert.html +106 -106
  46. package/static/templates/error-history-timeline.html +160 -160
  47. package/static/templates/error-recovery-options.html +109 -109
  48. package/static/templates/error-stack-trace.html +95 -95
  49. package/static/templates/error-summary.html +80 -80
  50. package/static/templates/event-counter.html +48 -48
  51. package/static/templates/execution-actions.html +97 -97
  52. package/static/templates/execution-progress-bar.html +80 -80
  53. package/static/templates/execution-stepper.html +120 -120
  54. package/static/templates/file-breadcrumb.html +118 -118
  55. package/static/templates/file-diff-viewer.html +121 -121
  56. package/static/templates/file-metadata.html +133 -133
  57. package/static/templates/file-read-panel.html +66 -66
  58. package/static/templates/file-write-panel.html +120 -120
  59. package/static/templates/git-branch-remote.html +107 -107
  60. package/static/templates/git-diff-list.html +101 -101
  61. package/static/templates/git-log-visualization.html +153 -153
  62. package/static/templates/git-status-panel.html +115 -115
  63. package/static/templates/quality-metrics-display.html +170 -170
  64. package/static/templates/terminal-output-panel.html +87 -87
  65. package/static/templates/test-results-display.html +144 -144
  66. package/static/theme.js +72 -72
  67. package/test-download-progress.js +223 -223
  68. package/test-websocket-broadcast.js +147 -147
  69. package/tests/ipfs-downloader.test.js +370 -370
@@ -1,298 +1,298 @@
1
- (function() {
2
- var BASE = window.__BASE_URL || '';
3
- var btn = document.getElementById('agentAuthBtn');
4
- var dropdown = document.getElementById('agentAuthDropdown');
5
- var agents = [], providers = {}, authRunning = false, editingProvider = null;
6
- var AUTH_CONV_ID = '__agent_auth__';
7
-
8
- function init() {
9
- if (!btn || !dropdown) return;
10
- btn.style.display = 'flex';
11
- btn.addEventListener('click', toggleDropdown);
12
- document.addEventListener('click', function(e) {
13
- if (!btn.contains(e.target) && !dropdown.contains(e.target)) closeDropdown();
14
- });
15
- window.addEventListener('conversation-selected', function() { refresh(); });
16
- window.addEventListener('ws-message', onWsMessage);
17
- refresh();
18
- }
19
-
20
- function refresh() { fetchAuthStatus(); fetchProviderConfigs(); }
21
-
22
- function fetchAuthStatus() {
23
- fetch(BASE + '/api/agents/auth-status').then(function(r) { return r.json(); })
24
- .then(function(data) { agents = data.agents || []; updateButton(); renderDropdown(); })
25
- .catch(function() {});
26
- }
27
-
28
- function fetchProviderConfigs() {
29
- fetch(BASE + '/api/auth/configs').then(function(r) { return r.json(); })
30
- .then(function(data) { providers = data || {}; updateButton(); renderDropdown(); })
31
- .catch(function() {});
32
- }
33
-
34
- function updateButton() {
35
- btn.style.display = 'flex';
36
- var agentOk = agents.length === 0 || agents.every(function(a) { return a.authenticated; });
37
- var pkeys = Object.keys(providers);
38
- var provOk = pkeys.length === 0 || pkeys.some(function(k) { return providers[k].hasKey; });
39
- var anyWarn = agents.some(function(a) { return !a.authenticated; }) ||
40
- pkeys.some(function(k) { return !providers[k].hasKey; });
41
- btn.classList.toggle('auth-ok', agentOk && provOk && (agents.length > 0 || pkeys.length > 0));
42
- btn.classList.toggle('auth-warn', anyWarn);
43
- }
44
-
45
- function renderDropdown() {
46
- dropdown.innerHTML = '';
47
- if (agents.length > 0) {
48
- appendHeader('Agent CLI Auth');
49
- agents.forEach(function(agent) {
50
- var dotClass = agent.authenticated ? 'ok' : (agent.detail === 'unknown' ? 'unknown' : 'missing');
51
- var item = makeItem(dotClass, agent.name, agent.detail);
52
- item.addEventListener('click', function(e) { e.stopPropagation(); closeDropdown(); triggerAuth(agent.id); });
53
- dropdown.appendChild(item);
54
- });
55
- }
56
- var pkeys = Object.keys(providers);
57
- if (pkeys.length > 0) {
58
- if (agents.length > 0) appendSep();
59
- appendHeader('Provider Keys');
60
- pkeys.forEach(function(pid) {
61
- var p = providers[pid];
62
- var item = makeItem(p.hasKey ? 'ok' : 'missing', p.name || pid, p.hasKey ? p.apiKey : 'not set');
63
- item.style.flexWrap = 'wrap';
64
- item.addEventListener('click', function(e) { e.stopPropagation(); toggleEdit(pid); });
65
- dropdown.appendChild(item);
66
- if (editingProvider === pid) dropdown.appendChild(makeEditForm(pid));
67
- });
68
- }
69
- }
70
-
71
- function appendHeader(text) {
72
- var h = document.createElement('div');
73
- h.className = 'agent-auth-section-header';
74
- h.textContent = text;
75
- dropdown.appendChild(h);
76
- }
77
-
78
- function appendSep() {
79
- var s = document.createElement('div');
80
- s.style.cssText = 'height:1px;background:var(--color-border);margin:0.25rem 0;';
81
- dropdown.appendChild(s);
82
- }
83
-
84
- function makeItem(dotClass, name, detail) {
85
- var el = document.createElement('button');
86
- el.className = 'agent-auth-item';
87
- el.innerHTML = '<span class="agent-auth-dot ' + dotClass + '"></span><span>' + esc(name) +
88
- '</span><span style="margin-left:auto;font-size:0.7rem;color:var(--color-text-secondary)">' + esc(detail) + '</span>';
89
- return el;
90
- }
91
-
92
- function makeEditForm(pid) {
93
- var form = document.createElement('div');
94
- form.style.cssText = 'width:100%;padding:0.375rem 0.75rem;display:flex;gap:0.375rem;';
95
- var input = document.createElement('input');
96
- input.type = 'password'; input.placeholder = 'API key';
97
- input.style.cssText = 'flex:1;min-width:0;padding:0.25rem 0.5rem;font-size:0.75rem;border:1px solid var(--color-border);border-radius:0.25rem;background:var(--color-bg-primary);color:var(--color-text-primary);outline:none;';
98
- input.addEventListener('click', function(e) { e.stopPropagation(); });
99
- var saveBtn = document.createElement('button');
100
- saveBtn.textContent = 'Save';
101
- saveBtn.style.cssText = 'padding:0.25rem 0.5rem;font-size:0.7rem;font-weight:600;background:var(--color-primary);color:white;border:none;border-radius:0.25rem;cursor:pointer;flex-shrink:0;';
102
- saveBtn.addEventListener('click', function(e) {
103
- e.stopPropagation();
104
- var key = input.value.trim();
105
- if (!key) return;
106
- saveBtn.disabled = true; saveBtn.textContent = '...';
107
- saveProviderKey(pid, key);
108
- });
109
- form.appendChild(input); form.appendChild(saveBtn);
110
- setTimeout(function() { input.focus(); }, 50);
111
- return form;
112
- }
113
-
114
- function toggleEdit(pid) { editingProvider = editingProvider === pid ? null : pid; renderDropdown(); }
115
-
116
- function saveProviderKey(providerId, apiKey) {
117
- fetch(BASE + '/api/auth/save-config', {
118
- method: 'POST', headers: { 'Content-Type': 'application/json' },
119
- body: JSON.stringify({ providerId: providerId, apiKey: apiKey, defaultModel: '' })
120
- }).then(function(r) { return r.json(); }).then(function(data) {
121
- if (data.success) { editingProvider = null; fetchProviderConfigs(); }
122
- }).catch(function() { editingProvider = null; renderDropdown(); });
123
- }
124
-
125
- function toggleDropdown(e) {
126
- e.stopPropagation();
127
- if (!dropdown.classList.contains('open')) { editingProvider = null; refresh(); }
128
- dropdown.classList.toggle('open');
129
- }
130
-
131
- function closeDropdown() { dropdown.classList.remove('open'); editingProvider = null; }
132
-
133
- var oauthPollInterval = null, oauthPollTimeout = null, oauthFallbackTimer = null;
134
-
135
- function cleanupOAuthPolling() {
136
- if (oauthPollInterval) { clearInterval(oauthPollInterval); oauthPollInterval = null; }
137
- if (oauthPollTimeout) { clearTimeout(oauthPollTimeout); oauthPollTimeout = null; }
138
- if (oauthFallbackTimer) { clearTimeout(oauthFallbackTimer); oauthFallbackTimer = null; }
139
- }
140
-
141
- function showOAuthWaitingModal() {
142
- removeOAuthModal();
143
- var overlay = document.createElement('div');
144
- overlay.id = 'oauthWaitingModal';
145
- overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:9999;';
146
- overlay.innerHTML = '<div style="background:var(--color-bg-secondary,#1f2937);border-radius:1rem;padding:2rem;max-width:28rem;width:calc(100% - 2rem);box-shadow:0 25px 50px rgba(0,0,0,0.5);color:var(--color-text-primary,white);font-family:system-ui,sans-serif;" onclick="event.stopPropagation()">' +
147
- '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">' +
148
- '<h2 style="font-size:1.125rem;font-weight:700;margin:0;">Google Sign-In</h2>' +
149
- '<button id="oauthWaitingClose" style="background:none;border:none;color:var(--color-text-secondary,#9ca3af);font-size:1.5rem;cursor:pointer;padding:0;line-height:1;">\u00d7</button></div>' +
150
- '<div id="oauthWaitingContent" style="text-align:center;padding:1.5rem 0;">' +
151
- '<div style="font-size:2rem;margin-bottom:1rem;animation:pulse 2s infinite;">&#9203;</div>' +
152
- '<p style="font-size:0.85rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">Waiting for Google sign-in to complete...</p>' +
153
- '<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0;">Complete the sign-in in the tab that just opened.</p>' +
154
- '<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0.25rem 0 0;">This dialog will close automatically when done.</p></div>' +
155
- '<div id="oauthPasteFallback" style="display:none;">' +
156
- '<div style="margin-bottom:1rem;padding:1rem;background:var(--color-bg-tertiary,rgba(255,255,255,0.05));border-radius:0.5rem;">' +
157
- '<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">The automatic relay did not complete. This can happen when accessing the server remotely.</p>' +
158
- '<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0;">Copy the <span style="color:white;font-weight:600;">entire URL</span> from the sign-in tab and paste it below.</p></div>' +
159
- '<input type="text" id="oauthPasteInput" placeholder="http://localhost:3000/gm/oauth2callback?code=..." style="width:100%;box-sizing:border-box;padding:0.75rem 1rem;background:var(--color-bg-primary,#374151);border:1px solid var(--color-border,#4b5563);border-radius:0.5rem;color:var(--color-text-primary,white);font-size:0.8rem;font-family:monospace;outline:none;" />' +
160
- '<p id="oauthPasteError" style="font-size:0.75rem;color:#ef4444;margin:0.5rem 0 0;display:none;"></p></div>' +
161
- '<div style="display:flex;gap:0.75rem;margin-top:1.25rem;">' +
162
- '<button id="oauthWaitingCancel" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:1px solid var(--color-border,#4b5563);background:transparent;color:var(--color-text-primary,white);font-size:0.8rem;cursor:pointer;font-weight:600;">Cancel</button>' +
163
- '<button id="oauthPasteSubmit" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:none;background:var(--color-primary,#3b82f6);color:white;font-size:0.8rem;cursor:pointer;font-weight:600;display:none;">Complete Sign-In</button></div>' +
164
- '<style>@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}</style></div>';
165
- document.body.appendChild(overlay);
166
- var dismiss = function() { cleanupOAuthPolling(); authRunning = false; removeOAuthModal(); };
167
- document.getElementById('oauthWaitingClose').addEventListener('click', dismiss);
168
- document.getElementById('oauthWaitingCancel').addEventListener('click', dismiss);
169
- document.getElementById('oauthPasteSubmit').addEventListener('click', submitOAuthPasteUrl);
170
- }
171
-
172
- function showOAuthPasteFallback() {
173
- var fallback = document.getElementById('oauthPasteFallback');
174
- var waitContent = document.getElementById('oauthWaitingContent');
175
- var submitBtn = document.getElementById('oauthPasteSubmit');
176
- if (fallback) fallback.style.display = 'block';
177
- if (waitContent) waitContent.style.display = 'none';
178
- if (submitBtn) submitBtn.style.display = 'block';
179
- var input = document.getElementById('oauthPasteInput');
180
- if (input) {
181
- input.addEventListener('keydown', function(e) { if (e.key === 'Enter') submitOAuthPasteUrl(); });
182
- setTimeout(function() { input.focus(); }, 100);
183
- }
184
- }
185
-
186
- function removeOAuthModal() {
187
- var el = document.getElementById('oauthWaitingModal');
188
- if (el) el.remove();
189
- }
190
-
191
- function submitOAuthPasteUrl() {
192
- var input = document.getElementById('oauthPasteInput');
193
- var errorEl = document.getElementById('oauthPasteError');
194
- var submitBtn = document.getElementById('oauthPasteSubmit');
195
- if (!input) return;
196
- var url = input.value.trim();
197
- if (!url) {
198
- if (errorEl) { errorEl.textContent = 'Please paste the URL from the redirected page.'; errorEl.style.display = 'block'; }
199
- return;
200
- }
201
- if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Verifying...'; }
202
- if (errorEl) errorEl.style.display = 'none';
203
-
204
- fetch(BASE + '/api/gemini-oauth/complete', {
205
- method: 'POST', headers: { 'Content-Type': 'application/json' },
206
- body: JSON.stringify({ url: url })
207
- }).then(function(r) { return r.json(); }).then(function(data) {
208
- if (data.success) {
209
- cleanupOAuthPolling();
210
- authRunning = false;
211
- removeOAuthModal();
212
- refresh();
213
- } else {
214
- if (errorEl) { errorEl.textContent = data.error || 'Failed to complete authentication.'; errorEl.style.display = 'block'; }
215
- if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
216
- }
217
- }).catch(function(e) {
218
- if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
219
- if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
220
- });
221
- }
222
-
223
- function triggerAuth(agentId) {
224
- if (authRunning) return;
225
- fetch(BASE + '/api/agents/' + agentId + '/auth', {
226
- method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
227
- }).then(function(r) { return r.json(); }).then(function(data) {
228
- if (data.ok) {
229
- authRunning = true; showTerminalTab(); switchToTerminalView();
230
- var term = getTerminal();
231
- if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
232
- if (data.authUrl) {
233
- window.open(data.authUrl, '_blank');
234
- if (agentId === 'gemini') {
235
- showOAuthWaitingModal();
236
- cleanupOAuthPolling();
237
- oauthPollInterval = setInterval(function() {
238
- fetch(BASE + '/api/gemini-oauth/status').then(function(r) { return r.json(); }).then(function(status) {
239
- if (status.status === 'success') {
240
- cleanupOAuthPolling();
241
- authRunning = false;
242
- removeOAuthModal();
243
- refresh();
244
- } else if (status.status === 'error') {
245
- cleanupOAuthPolling();
246
- authRunning = false;
247
- removeOAuthModal();
248
- }
249
- }).catch(function() {});
250
- }, 1500);
251
- oauthFallbackTimer = setTimeout(function() {
252
- if (authRunning) showOAuthPasteFallback();
253
- }, 30000);
254
- oauthPollTimeout = setTimeout(function() {
255
- cleanupOAuthPolling();
256
- if (authRunning) { authRunning = false; removeOAuthModal(); }
257
- }, 5 * 60 * 1000);
258
- }
259
- }
260
- }
261
- }).catch(function() {});
262
- }
263
-
264
- function onWsMessage(e) {
265
- var data = e.detail;
266
- if (!data || data.conversationId !== AUTH_CONV_ID) return;
267
- if (data.type === 'script_started') {
268
- authRunning = true; showTerminalTab(); switchToTerminalView();
269
- var term = getTerminal();
270
- if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n'); }
271
- } else if (data.type === 'script_output') {
272
- showTerminalTab();
273
- var term = getTerminal();
274
- if (term) term.write(data.data);
275
- } else if (data.type === 'script_stopped') {
276
- authRunning = false;
277
- removeOAuthModal();
278
- cleanupOAuthPolling();
279
- var term = getTerminal();
280
- var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
281
- if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
282
- setTimeout(refresh, 1000);
283
- }
284
- }
285
-
286
- function showTerminalTab() { var t = document.getElementById('terminalTabBtn'); if (t) t.style.display = ''; }
287
- function switchToTerminalView() {
288
- var bar = document.getElementById('viewToggleBar');
289
- if (!bar) return;
290
- var t = bar.querySelector('[data-view="terminal"]'); if (t) t.click();
291
- }
292
- function getTerminal() { return window.scriptRunner ? window.scriptRunner.getTerminal() : null; }
293
- function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
294
-
295
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
296
- else init();
297
- window.agentAuth = { refresh: refresh };
298
- })();
1
+ (function() {
2
+ var BASE = window.__BASE_URL || '';
3
+ var btn = document.getElementById('agentAuthBtn');
4
+ var dropdown = document.getElementById('agentAuthDropdown');
5
+ var agents = [], providers = {}, authRunning = false, editingProvider = null;
6
+ var AUTH_CONV_ID = '__agent_auth__';
7
+
8
+ function init() {
9
+ if (!btn || !dropdown) return;
10
+ btn.style.display = 'flex';
11
+ btn.addEventListener('click', toggleDropdown);
12
+ document.addEventListener('click', function(e) {
13
+ if (!btn.contains(e.target) && !dropdown.contains(e.target)) closeDropdown();
14
+ });
15
+ window.addEventListener('conversation-selected', function() { refresh(); });
16
+ window.addEventListener('ws-message', onWsMessage);
17
+ refresh();
18
+ }
19
+
20
+ function refresh() { fetchAuthStatus(); fetchProviderConfigs(); }
21
+
22
+ function fetchAuthStatus() {
23
+ fetch(BASE + '/api/agents/auth-status').then(function(r) { return r.json(); })
24
+ .then(function(data) { agents = data.agents || []; updateButton(); renderDropdown(); })
25
+ .catch(function() {});
26
+ }
27
+
28
+ function fetchProviderConfigs() {
29
+ fetch(BASE + '/api/auth/configs').then(function(r) { return r.json(); })
30
+ .then(function(data) { providers = data || {}; updateButton(); renderDropdown(); })
31
+ .catch(function() {});
32
+ }
33
+
34
+ function updateButton() {
35
+ btn.style.display = 'flex';
36
+ var agentOk = agents.length === 0 || agents.every(function(a) { return a.authenticated; });
37
+ var pkeys = Object.keys(providers);
38
+ var provOk = pkeys.length === 0 || pkeys.some(function(k) { return providers[k].hasKey; });
39
+ var anyWarn = agents.some(function(a) { return !a.authenticated; }) ||
40
+ pkeys.some(function(k) { return !providers[k].hasKey; });
41
+ btn.classList.toggle('auth-ok', agentOk && provOk && (agents.length > 0 || pkeys.length > 0));
42
+ btn.classList.toggle('auth-warn', anyWarn);
43
+ }
44
+
45
+ function renderDropdown() {
46
+ dropdown.innerHTML = '';
47
+ if (agents.length > 0) {
48
+ appendHeader('Agent CLI Auth');
49
+ agents.forEach(function(agent) {
50
+ var dotClass = agent.authenticated ? 'ok' : (agent.detail === 'unknown' ? 'unknown' : 'missing');
51
+ var item = makeItem(dotClass, agent.name, agent.detail);
52
+ item.addEventListener('click', function(e) { e.stopPropagation(); closeDropdown(); triggerAuth(agent.id); });
53
+ dropdown.appendChild(item);
54
+ });
55
+ }
56
+ var pkeys = Object.keys(providers);
57
+ if (pkeys.length > 0) {
58
+ if (agents.length > 0) appendSep();
59
+ appendHeader('Provider Keys');
60
+ pkeys.forEach(function(pid) {
61
+ var p = providers[pid];
62
+ var item = makeItem(p.hasKey ? 'ok' : 'missing', p.name || pid, p.hasKey ? p.apiKey : 'not set');
63
+ item.style.flexWrap = 'wrap';
64
+ item.addEventListener('click', function(e) { e.stopPropagation(); toggleEdit(pid); });
65
+ dropdown.appendChild(item);
66
+ if (editingProvider === pid) dropdown.appendChild(makeEditForm(pid));
67
+ });
68
+ }
69
+ }
70
+
71
+ function appendHeader(text) {
72
+ var h = document.createElement('div');
73
+ h.className = 'agent-auth-section-header';
74
+ h.textContent = text;
75
+ dropdown.appendChild(h);
76
+ }
77
+
78
+ function appendSep() {
79
+ var s = document.createElement('div');
80
+ s.style.cssText = 'height:1px;background:var(--color-border);margin:0.25rem 0;';
81
+ dropdown.appendChild(s);
82
+ }
83
+
84
+ function makeItem(dotClass, name, detail) {
85
+ var el = document.createElement('button');
86
+ el.className = 'agent-auth-item';
87
+ el.innerHTML = '<span class="agent-auth-dot ' + dotClass + '"></span><span>' + esc(name) +
88
+ '</span><span style="margin-left:auto;font-size:0.7rem;color:var(--color-text-secondary)">' + esc(detail) + '</span>';
89
+ return el;
90
+ }
91
+
92
+ function makeEditForm(pid) {
93
+ var form = document.createElement('div');
94
+ form.style.cssText = 'width:100%;padding:0.375rem 0.75rem;display:flex;gap:0.375rem;';
95
+ var input = document.createElement('input');
96
+ input.type = 'password'; input.placeholder = 'API key';
97
+ input.style.cssText = 'flex:1;min-width:0;padding:0.25rem 0.5rem;font-size:0.75rem;border:1px solid var(--color-border);border-radius:0.25rem;background:var(--color-bg-primary);color:var(--color-text-primary);outline:none;';
98
+ input.addEventListener('click', function(e) { e.stopPropagation(); });
99
+ var saveBtn = document.createElement('button');
100
+ saveBtn.textContent = 'Save';
101
+ saveBtn.style.cssText = 'padding:0.25rem 0.5rem;font-size:0.7rem;font-weight:600;background:var(--color-primary);color:white;border:none;border-radius:0.25rem;cursor:pointer;flex-shrink:0;';
102
+ saveBtn.addEventListener('click', function(e) {
103
+ e.stopPropagation();
104
+ var key = input.value.trim();
105
+ if (!key) return;
106
+ saveBtn.disabled = true; saveBtn.textContent = '...';
107
+ saveProviderKey(pid, key);
108
+ });
109
+ form.appendChild(input); form.appendChild(saveBtn);
110
+ setTimeout(function() { input.focus(); }, 50);
111
+ return form;
112
+ }
113
+
114
+ function toggleEdit(pid) { editingProvider = editingProvider === pid ? null : pid; renderDropdown(); }
115
+
116
+ function saveProviderKey(providerId, apiKey) {
117
+ fetch(BASE + '/api/auth/save-config', {
118
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
119
+ body: JSON.stringify({ providerId: providerId, apiKey: apiKey, defaultModel: '' })
120
+ }).then(function(r) { return r.json(); }).then(function(data) {
121
+ if (data.success) { editingProvider = null; fetchProviderConfigs(); }
122
+ }).catch(function() { editingProvider = null; renderDropdown(); });
123
+ }
124
+
125
+ function toggleDropdown(e) {
126
+ e.stopPropagation();
127
+ if (!dropdown.classList.contains('open')) { editingProvider = null; refresh(); }
128
+ dropdown.classList.toggle('open');
129
+ }
130
+
131
+ function closeDropdown() { dropdown.classList.remove('open'); editingProvider = null; }
132
+
133
+ var oauthPollInterval = null, oauthPollTimeout = null, oauthFallbackTimer = null;
134
+
135
+ function cleanupOAuthPolling() {
136
+ if (oauthPollInterval) { clearInterval(oauthPollInterval); oauthPollInterval = null; }
137
+ if (oauthPollTimeout) { clearTimeout(oauthPollTimeout); oauthPollTimeout = null; }
138
+ if (oauthFallbackTimer) { clearTimeout(oauthFallbackTimer); oauthFallbackTimer = null; }
139
+ }
140
+
141
+ function showOAuthWaitingModal() {
142
+ removeOAuthModal();
143
+ var overlay = document.createElement('div');
144
+ overlay.id = 'oauthWaitingModal';
145
+ overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:9999;';
146
+ overlay.innerHTML = '<div style="background:var(--color-bg-secondary,#1f2937);border-radius:1rem;padding:2rem;max-width:28rem;width:calc(100% - 2rem);box-shadow:0 25px 50px rgba(0,0,0,0.5);color:var(--color-text-primary,white);font-family:system-ui,sans-serif;" onclick="event.stopPropagation()">' +
147
+ '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">' +
148
+ '<h2 style="font-size:1.125rem;font-weight:700;margin:0;">Google Sign-In</h2>' +
149
+ '<button id="oauthWaitingClose" style="background:none;border:none;color:var(--color-text-secondary,#9ca3af);font-size:1.5rem;cursor:pointer;padding:0;line-height:1;">\u00d7</button></div>' +
150
+ '<div id="oauthWaitingContent" style="text-align:center;padding:1.5rem 0;">' +
151
+ '<div style="font-size:2rem;margin-bottom:1rem;animation:pulse 2s infinite;">&#9203;</div>' +
152
+ '<p style="font-size:0.85rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">Waiting for Google sign-in to complete...</p>' +
153
+ '<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0;">Complete the sign-in in the tab that just opened.</p>' +
154
+ '<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0.25rem 0 0;">This dialog will close automatically when done.</p></div>' +
155
+ '<div id="oauthPasteFallback" style="display:none;">' +
156
+ '<div style="margin-bottom:1rem;padding:1rem;background:var(--color-bg-tertiary,rgba(255,255,255,0.05));border-radius:0.5rem;">' +
157
+ '<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">The automatic relay did not complete. This can happen when accessing the server remotely.</p>' +
158
+ '<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0;">Copy the <span style="color:white;font-weight:600;">entire URL</span> from the sign-in tab and paste it below.</p></div>' +
159
+ '<input type="text" id="oauthPasteInput" placeholder="http://localhost:3000/gm/oauth2callback?code=..." style="width:100%;box-sizing:border-box;padding:0.75rem 1rem;background:var(--color-bg-primary,#374151);border:1px solid var(--color-border,#4b5563);border-radius:0.5rem;color:var(--color-text-primary,white);font-size:0.8rem;font-family:monospace;outline:none;" />' +
160
+ '<p id="oauthPasteError" style="font-size:0.75rem;color:#ef4444;margin:0.5rem 0 0;display:none;"></p></div>' +
161
+ '<div style="display:flex;gap:0.75rem;margin-top:1.25rem;">' +
162
+ '<button id="oauthWaitingCancel" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:1px solid var(--color-border,#4b5563);background:transparent;color:var(--color-text-primary,white);font-size:0.8rem;cursor:pointer;font-weight:600;">Cancel</button>' +
163
+ '<button id="oauthPasteSubmit" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:none;background:var(--color-primary,#3b82f6);color:white;font-size:0.8rem;cursor:pointer;font-weight:600;display:none;">Complete Sign-In</button></div>' +
164
+ '<style>@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}</style></div>';
165
+ document.body.appendChild(overlay);
166
+ var dismiss = function() { cleanupOAuthPolling(); authRunning = false; removeOAuthModal(); };
167
+ document.getElementById('oauthWaitingClose').addEventListener('click', dismiss);
168
+ document.getElementById('oauthWaitingCancel').addEventListener('click', dismiss);
169
+ document.getElementById('oauthPasteSubmit').addEventListener('click', submitOAuthPasteUrl);
170
+ }
171
+
172
+ function showOAuthPasteFallback() {
173
+ var fallback = document.getElementById('oauthPasteFallback');
174
+ var waitContent = document.getElementById('oauthWaitingContent');
175
+ var submitBtn = document.getElementById('oauthPasteSubmit');
176
+ if (fallback) fallback.style.display = 'block';
177
+ if (waitContent) waitContent.style.display = 'none';
178
+ if (submitBtn) submitBtn.style.display = 'block';
179
+ var input = document.getElementById('oauthPasteInput');
180
+ if (input) {
181
+ input.addEventListener('keydown', function(e) { if (e.key === 'Enter') submitOAuthPasteUrl(); });
182
+ setTimeout(function() { input.focus(); }, 100);
183
+ }
184
+ }
185
+
186
+ function removeOAuthModal() {
187
+ var el = document.getElementById('oauthWaitingModal');
188
+ if (el) el.remove();
189
+ }
190
+
191
+ function submitOAuthPasteUrl() {
192
+ var input = document.getElementById('oauthPasteInput');
193
+ var errorEl = document.getElementById('oauthPasteError');
194
+ var submitBtn = document.getElementById('oauthPasteSubmit');
195
+ if (!input) return;
196
+ var url = input.value.trim();
197
+ if (!url) {
198
+ if (errorEl) { errorEl.textContent = 'Please paste the URL from the redirected page.'; errorEl.style.display = 'block'; }
199
+ return;
200
+ }
201
+ if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Verifying...'; }
202
+ if (errorEl) errorEl.style.display = 'none';
203
+
204
+ fetch(BASE + '/api/gemini-oauth/complete', {
205
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
206
+ body: JSON.stringify({ url: url })
207
+ }).then(function(r) { return r.json(); }).then(function(data) {
208
+ if (data.success) {
209
+ cleanupOAuthPolling();
210
+ authRunning = false;
211
+ removeOAuthModal();
212
+ refresh();
213
+ } else {
214
+ if (errorEl) { errorEl.textContent = data.error || 'Failed to complete authentication.'; errorEl.style.display = 'block'; }
215
+ if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
216
+ }
217
+ }).catch(function(e) {
218
+ if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
219
+ if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
220
+ });
221
+ }
222
+
223
+ function triggerAuth(agentId) {
224
+ if (authRunning) return;
225
+ fetch(BASE + '/api/agents/' + agentId + '/auth', {
226
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
227
+ }).then(function(r) { return r.json(); }).then(function(data) {
228
+ if (data.ok) {
229
+ authRunning = true; showTerminalTab(); switchToTerminalView();
230
+ var term = getTerminal();
231
+ if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
232
+ if (data.authUrl) {
233
+ window.open(data.authUrl, '_blank');
234
+ if (agentId === 'gemini') {
235
+ showOAuthWaitingModal();
236
+ cleanupOAuthPolling();
237
+ oauthPollInterval = setInterval(function() {
238
+ fetch(BASE + '/api/gemini-oauth/status').then(function(r) { return r.json(); }).then(function(status) {
239
+ if (status.status === 'success') {
240
+ cleanupOAuthPolling();
241
+ authRunning = false;
242
+ removeOAuthModal();
243
+ refresh();
244
+ } else if (status.status === 'error') {
245
+ cleanupOAuthPolling();
246
+ authRunning = false;
247
+ removeOAuthModal();
248
+ }
249
+ }).catch(function() {});
250
+ }, 1500);
251
+ oauthFallbackTimer = setTimeout(function() {
252
+ if (authRunning) showOAuthPasteFallback();
253
+ }, 30000);
254
+ oauthPollTimeout = setTimeout(function() {
255
+ cleanupOAuthPolling();
256
+ if (authRunning) { authRunning = false; removeOAuthModal(); }
257
+ }, 5 * 60 * 1000);
258
+ }
259
+ }
260
+ }
261
+ }).catch(function() {});
262
+ }
263
+
264
+ function onWsMessage(e) {
265
+ var data = e.detail;
266
+ if (!data || data.conversationId !== AUTH_CONV_ID) return;
267
+ if (data.type === 'script_started') {
268
+ authRunning = true; showTerminalTab(); switchToTerminalView();
269
+ var term = getTerminal();
270
+ if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n'); }
271
+ } else if (data.type === 'script_output') {
272
+ showTerminalTab();
273
+ var term = getTerminal();
274
+ if (term) term.write(data.data);
275
+ } else if (data.type === 'script_stopped') {
276
+ authRunning = false;
277
+ removeOAuthModal();
278
+ cleanupOAuthPolling();
279
+ var term = getTerminal();
280
+ var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
281
+ if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
282
+ setTimeout(refresh, 1000);
283
+ }
284
+ }
285
+
286
+ function showTerminalTab() { var t = document.getElementById('terminalTabBtn'); if (t) t.style.display = ''; }
287
+ function switchToTerminalView() {
288
+ var bar = document.getElementById('viewToggleBar');
289
+ if (!bar) return;
290
+ var t = bar.querySelector('[data-view="terminal"]'); if (t) t.click();
291
+ }
292
+ function getTerminal() { return window.scriptRunner ? window.scriptRunner.getTerminal() : null; }
293
+ function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
294
+
295
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
296
+ else init();
297
+ window.agentAuth = { refresh: refresh };
298
+ })();
@@ -1,18 +1,18 @@
1
- class RecorderProcessor extends AudioWorkletProcessor {
2
- constructor() {
3
- super();
4
- this._stopped = false;
5
- this.port.onmessage = (e) => {
6
- if (e.data === 'stop') this._stopped = true;
7
- };
8
- }
9
- process(inputs) {
10
- if (this._stopped) return false;
11
- const input = inputs[0];
12
- if (input && input[0] && input[0].length > 0) {
13
- this.port.postMessage(new Float32Array(input[0]));
14
- }
15
- return true;
16
- }
17
- }
18
- registerProcessor('recorder-processor', RecorderProcessor);
1
+ class RecorderProcessor extends AudioWorkletProcessor {
2
+ constructor() {
3
+ super();
4
+ this._stopped = false;
5
+ this.port.onmessage = (e) => {
6
+ if (e.data === 'stop') this._stopped = true;
7
+ };
8
+ }
9
+ process(inputs) {
10
+ if (this._stopped) return false;
11
+ const input = inputs[0];
12
+ if (input && input[0] && input[0].length > 0) {
13
+ this.port.postMessage(new Float32Array(input[0]));
14
+ }
15
+ return true;
16
+ }
17
+ }
18
+ registerProcessor('recorder-processor', RecorderProcessor);