clay-server 2.33.0 → 2.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/project-notifications.js +17 -0
- package/lib/project-sessions.js +27 -0
- package/lib/public/app.js +1 -3
- package/lib/public/css/messages.css +0 -63
- package/lib/public/css/notifications-center.css +87 -27
- package/lib/public/css/sidebar.css +24 -0
- package/lib/public/css/user-settings.css +537 -147
- package/lib/public/index.html +52 -11
- package/lib/public/modules/app-connection.js +0 -39
- package/lib/public/modules/app-messages.js +18 -4
- package/lib/public/modules/app-notifications.js +133 -20
- package/lib/public/modules/app-panels.js +1 -1
- package/lib/public/modules/app-rendering.js +0 -75
- package/lib/public/modules/input.js +1 -0
- package/lib/public/modules/profile.js +1 -31
- package/lib/public/modules/stt.js +0 -14
- package/lib/public/modules/theme.js +19 -0
- package/lib/public/modules/user-settings.js +266 -21
- package/lib/sdk-bridge.js +118 -12
- package/lib/sdk-message-processor.js +25 -4
- package/lib/server-settings.js +13 -0
- package/lib/yoke/codex-app-server.js +3 -3
- package/lib/yoke/index.js +81 -11
- package/package.json +1 -1
|
@@ -31,15 +31,6 @@ var LANGUAGES = [
|
|
|
31
31
|
{ code: 'de-DE', name: 'German' },
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
-
// --- Persist language choice ---
|
|
35
|
-
function saveLang(code) {
|
|
36
|
-
try { localStorage.setItem('stt-lang', code); } catch (e) { /* ignore */ }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function loadLang() {
|
|
40
|
-
try { return localStorage.getItem('stt-lang'); } catch (e) { return null; }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
34
|
// --- Check browser support ---
|
|
44
35
|
function getSpeechRecognition() {
|
|
45
36
|
return window.SpeechRecognition || window.webkitSpeechRecognition || null;
|
|
@@ -58,9 +49,6 @@ export function initSTT(_ctx) {
|
|
|
58
49
|
return;
|
|
59
50
|
}
|
|
60
51
|
|
|
61
|
-
// Restore saved language
|
|
62
|
-
selectedLang = loadLang();
|
|
63
|
-
|
|
64
52
|
sttBtn.addEventListener('click', function(e) {
|
|
65
53
|
e.stopPropagation();
|
|
66
54
|
|
|
@@ -135,7 +123,6 @@ function hideLangPopover() {
|
|
|
135
123
|
|
|
136
124
|
function onLangSelected(code) {
|
|
137
125
|
selectedLang = code;
|
|
138
|
-
saveLang(code);
|
|
139
126
|
hideLangPopover();
|
|
140
127
|
startRecording();
|
|
141
128
|
}
|
|
@@ -263,7 +250,6 @@ function stopRecording() {
|
|
|
263
250
|
// --- External lang setter (used by profile module) ---
|
|
264
251
|
export function setSTTLang(code) {
|
|
265
252
|
selectedLang = code;
|
|
266
|
-
saveLang(code);
|
|
267
253
|
}
|
|
268
254
|
|
|
269
255
|
export function getSTTLang() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
1
2
|
import { setTerminalTheme } from './terminal.js';
|
|
2
3
|
import { updateMermaidTheme } from './markdown.js';
|
|
3
4
|
|
|
@@ -537,6 +538,16 @@ function updateToggleIcon() {
|
|
|
537
538
|
// User settings toggle
|
|
538
539
|
var usToggle = document.getElementById("us-theme-toggle");
|
|
539
540
|
if (usToggle) usToggle.checked = isLight;
|
|
541
|
+
// User island toggle
|
|
542
|
+
var islandToggle = document.getElementById("user-theme-toggle-btn");
|
|
543
|
+
if (islandToggle) {
|
|
544
|
+
islandToggle.classList.remove("theme-toggle-light", "theme-toggle-dark");
|
|
545
|
+
islandToggle.classList.add(isLight ? "theme-toggle-light" : "theme-toggle-dark");
|
|
546
|
+
islandToggle.innerHTML = iconHtml(isLight ? "moon" : "sun");
|
|
547
|
+
islandToggle.title = isLight ? "Switch to dark mode" : "Switch to light mode";
|
|
548
|
+
islandToggle.setAttribute("aria-label", islandToggle.title);
|
|
549
|
+
}
|
|
550
|
+
refreshIcons();
|
|
540
551
|
}
|
|
541
552
|
|
|
542
553
|
// --- Theme picker UI ---
|
|
@@ -734,6 +745,14 @@ export function initTheme() {
|
|
|
734
745
|
|
|
735
746
|
// Set initial toggle icon
|
|
736
747
|
updateToggleIcon();
|
|
748
|
+
|
|
749
|
+
var islandToggle = document.getElementById("user-theme-toggle-btn");
|
|
750
|
+
if (islandToggle && !islandToggle._clayThemeBound) {
|
|
751
|
+
islandToggle._clayThemeBound = true;
|
|
752
|
+
islandToggle.addEventListener("click", function () {
|
|
753
|
+
toggleDarkMode();
|
|
754
|
+
});
|
|
755
|
+
}
|
|
737
756
|
}
|
|
738
757
|
|
|
739
758
|
// --- Settings picker (for appearance section in server settings) ---
|
|
@@ -5,6 +5,8 @@ import { refreshIcons } from './icons.js';
|
|
|
5
5
|
import { showToast } from './utils.js';
|
|
6
6
|
import { toggleDarkMode, getCurrentTheme, getChatLayout, setChatLayout } from './theme.js';
|
|
7
7
|
import { showEmailSetupModal, getEmailAccountListCache } from './context-sources.js';
|
|
8
|
+
import { setSTTLang } from './stt.js';
|
|
9
|
+
import { userAvatarUrl } from './avatar.js';
|
|
8
10
|
|
|
9
11
|
var ctx = null;
|
|
10
12
|
var settingsEl = null;
|
|
@@ -13,6 +15,11 @@ var closeBtn = null;
|
|
|
13
15
|
var backdrop = null;
|
|
14
16
|
var navItems = null;
|
|
15
17
|
var sections = null;
|
|
18
|
+
var pinForm = null;
|
|
19
|
+
var pinSetBtn = null;
|
|
20
|
+
var pinCancelBtn = null;
|
|
21
|
+
var pinEnabled = false;
|
|
22
|
+
var currentProfile = null;
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
export function initUserSettings(appCtx) {
|
|
@@ -26,6 +33,7 @@ export function initUserSettings(appCtx) {
|
|
|
26
33
|
|
|
27
34
|
navItems = settingsEl.querySelectorAll('.us-nav-item');
|
|
28
35
|
sections = settingsEl.querySelectorAll('.us-section');
|
|
36
|
+
pinForm = document.getElementById('us-pin-form');
|
|
29
37
|
|
|
30
38
|
openBtn.addEventListener('click', function () {
|
|
31
39
|
openUserSettings();
|
|
@@ -66,19 +74,83 @@ export function initUserSettings(appCtx) {
|
|
|
66
74
|
|
|
67
75
|
// PIN save button
|
|
68
76
|
var pinInput = document.getElementById('us-pin-input');
|
|
77
|
+
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
78
|
+
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
69
79
|
var pinSave = document.getElementById('us-pin-save');
|
|
70
|
-
|
|
80
|
+
pinSetBtn = document.getElementById('us-pin-set-btn');
|
|
81
|
+
pinCancelBtn = document.getElementById('us-pin-cancel');
|
|
82
|
+
if (pinInput && pinConfirmInput && pinSave) {
|
|
71
83
|
function validatePin() {
|
|
72
|
-
|
|
84
|
+
var validNew = /^\d{6}$/.test(pinInput.value);
|
|
85
|
+
var validConfirm = /^\d{6}$/.test(pinConfirmInput.value);
|
|
86
|
+
var validCurrent = !pinEnabled || (pinCurrentInput && /^\d{6}$/.test(pinCurrentInput.value));
|
|
87
|
+
pinSave.disabled = !(validNew && validConfirm && validCurrent && pinInput.value === pinConfirmInput.value);
|
|
73
88
|
}
|
|
74
89
|
pinInput.addEventListener('input', validatePin);
|
|
90
|
+
pinConfirmInput.addEventListener('input', validatePin);
|
|
91
|
+
if (pinCurrentInput) pinCurrentInput.addEventListener('input', validatePin);
|
|
75
92
|
pinInput.addEventListener('keyup', function (e) { e.stopPropagation(); validatePin(); });
|
|
76
|
-
|
|
93
|
+
pinConfirmInput.addEventListener('keyup', function (e) { e.stopPropagation(); validatePin(); });
|
|
94
|
+
if (pinCurrentInput) pinCurrentInput.addEventListener('keyup', function (e) { e.stopPropagation(); validatePin(); });
|
|
95
|
+
pinInput.addEventListener('keydown', function (e) {
|
|
96
|
+
e.stopPropagation();
|
|
97
|
+
if (e.key === 'Enter') {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
savePin(pinInput.value);
|
|
100
|
+
}
|
|
101
|
+
if (e.key === 'Escape') {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
hidePinForm();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
pinConfirmInput.addEventListener('keydown', function (e) {
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
if (e.key === 'Enter') {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
savePin(pinInput.value);
|
|
111
|
+
}
|
|
112
|
+
if (e.key === 'Escape') {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
hidePinForm();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
if (pinCurrentInput) {
|
|
118
|
+
pinCurrentInput.addEventListener('keydown', function (e) {
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
if (e.key === 'Enter') {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
savePin(pinInput.value);
|
|
123
|
+
}
|
|
124
|
+
if (e.key === 'Escape') {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
hidePinForm();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
77
130
|
pinInput.addEventListener('keypress', stopProp);
|
|
131
|
+
pinConfirmInput.addEventListener('keypress', stopProp);
|
|
132
|
+
if (pinCurrentInput) pinCurrentInput.addEventListener('keypress', stopProp);
|
|
78
133
|
pinSave.addEventListener('click', function () {
|
|
79
134
|
savePin(pinInput.value);
|
|
80
135
|
});
|
|
81
136
|
}
|
|
137
|
+
if (pinSetBtn) {
|
|
138
|
+
pinSetBtn.addEventListener('click', function () {
|
|
139
|
+
showPinForm();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (pinCancelBtn) {
|
|
143
|
+
pinCancelBtn.addEventListener('click', function () {
|
|
144
|
+
hidePinForm();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var langSelect = document.getElementById('us-lang-select');
|
|
149
|
+
if (langSelect) {
|
|
150
|
+
langSelect.addEventListener('change', function () {
|
|
151
|
+
saveProfileChange({ lang: this.value });
|
|
152
|
+
});
|
|
153
|
+
}
|
|
82
154
|
|
|
83
155
|
// Auto-continue toggle
|
|
84
156
|
var autoContinueToggle = document.getElementById('us-auto-continue');
|
|
@@ -158,6 +230,7 @@ function openUserSettings() {
|
|
|
158
230
|
openBtn.classList.add('active');
|
|
159
231
|
refreshIcons(settingsEl);
|
|
160
232
|
populateAccount();
|
|
233
|
+
hidePinForm();
|
|
161
234
|
switchSection('us-account');
|
|
162
235
|
}
|
|
163
236
|
|
|
@@ -186,6 +259,63 @@ function stopProp(e) {
|
|
|
186
259
|
e.stopPropagation();
|
|
187
260
|
}
|
|
188
261
|
|
|
262
|
+
function showPinForm() {
|
|
263
|
+
if (!pinForm) return;
|
|
264
|
+
pinForm.classList.remove('hidden');
|
|
265
|
+
var pinCurrentRow = document.getElementById('us-pin-current-row');
|
|
266
|
+
var pinInput = document.getElementById('us-pin-input');
|
|
267
|
+
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
268
|
+
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
269
|
+
var pinSave = document.getElementById('us-pin-save');
|
|
270
|
+
var pinMsg = document.getElementById('us-pin-msg');
|
|
271
|
+
var pinFormTitle = document.getElementById('us-pin-form-title');
|
|
272
|
+
if (pinFormTitle) {
|
|
273
|
+
pinFormTitle.textContent = pinEnabled ? 'Change PIN' : 'Set PIN';
|
|
274
|
+
}
|
|
275
|
+
if (pinCurrentRow) {
|
|
276
|
+
pinCurrentRow.classList.toggle('hidden', !pinEnabled);
|
|
277
|
+
}
|
|
278
|
+
if (pinInput) {
|
|
279
|
+
pinInput.value = '';
|
|
280
|
+
}
|
|
281
|
+
if (pinConfirmInput) pinConfirmInput.value = '';
|
|
282
|
+
if (pinCurrentInput) pinCurrentInput.value = '';
|
|
283
|
+
if (pinSave) pinSave.disabled = true;
|
|
284
|
+
if (pinSave) pinSave.textContent = 'Save';
|
|
285
|
+
if (pinMsg) {
|
|
286
|
+
pinMsg.textContent = '';
|
|
287
|
+
pinMsg.className = 'settings-hint settings-pin-error hidden';
|
|
288
|
+
}
|
|
289
|
+
if (pinEnabled && pinCurrentInput) {
|
|
290
|
+
pinCurrentInput.focus();
|
|
291
|
+
} else if (pinInput) {
|
|
292
|
+
pinInput.focus();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function hidePinForm() {
|
|
297
|
+
if (!pinForm) return;
|
|
298
|
+
pinForm.classList.add('hidden');
|
|
299
|
+
var pinCurrentRow = document.getElementById('us-pin-current-row');
|
|
300
|
+
var pinInput = document.getElementById('us-pin-input');
|
|
301
|
+
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
302
|
+
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
303
|
+
var pinSave = document.getElementById('us-pin-save');
|
|
304
|
+
var pinMsg = document.getElementById('us-pin-msg');
|
|
305
|
+
if (pinCurrentRow) {
|
|
306
|
+
pinCurrentRow.classList.remove('hidden');
|
|
307
|
+
}
|
|
308
|
+
if (pinInput) pinInput.value = '';
|
|
309
|
+
if (pinConfirmInput) pinConfirmInput.value = '';
|
|
310
|
+
if (pinCurrentInput) pinCurrentInput.value = '';
|
|
311
|
+
if (pinSave) pinSave.disabled = true;
|
|
312
|
+
if (pinSave) pinSave.textContent = 'Save';
|
|
313
|
+
if (pinMsg) {
|
|
314
|
+
pinMsg.textContent = '';
|
|
315
|
+
pinMsg.className = 'settings-hint settings-pin-error hidden';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
189
319
|
// --- Account population ---
|
|
190
320
|
|
|
191
321
|
function populateAccount() {
|
|
@@ -194,15 +324,57 @@ function populateAccount() {
|
|
|
194
324
|
return r.json();
|
|
195
325
|
}).then(function (data) {
|
|
196
326
|
if (!data) return;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
327
|
+
currentProfile = data;
|
|
328
|
+
var displayName = data.name || data.displayName || data.username || 'Clay User';
|
|
329
|
+
var avatarImg = document.getElementById('us-account-avatar-img');
|
|
330
|
+
var avatarFallback = document.getElementById('us-account-avatar-fallback');
|
|
331
|
+
var accountName = document.getElementById('us-account-name');
|
|
332
|
+
var accountSubline = document.getElementById('us-account-subline');
|
|
333
|
+
var langSelect = document.getElementById('us-lang-select');
|
|
334
|
+
var pinSetBtnEl = document.getElementById('us-pin-set-btn');
|
|
335
|
+
var pinFormTitle = document.getElementById('us-pin-form-title');
|
|
336
|
+
if (accountName) {
|
|
337
|
+
accountName.textContent = displayName;
|
|
338
|
+
}
|
|
339
|
+
if (accountSubline) {
|
|
340
|
+
var parts = [];
|
|
341
|
+
if (data.username) parts.push('@' + data.username);
|
|
342
|
+
if (data.role) parts.push(data.role.charAt(0).toUpperCase() + data.role.slice(1));
|
|
343
|
+
accountSubline.textContent = parts.length ? parts.join(' · ') : 'Local profile';
|
|
344
|
+
}
|
|
345
|
+
pinEnabled = !!data.pinEnabled;
|
|
346
|
+
if (pinSetBtnEl) {
|
|
347
|
+
pinSetBtnEl.textContent = pinEnabled ? 'Change PIN' : 'Set PIN';
|
|
348
|
+
}
|
|
349
|
+
if (pinFormTitle) {
|
|
350
|
+
pinFormTitle.textContent = pinEnabled ? 'Change PIN' : 'Set PIN';
|
|
351
|
+
}
|
|
352
|
+
if (langSelect) {
|
|
353
|
+
langSelect.value = data.lang || 'en-US';
|
|
354
|
+
}
|
|
355
|
+
if (data.lang) {
|
|
356
|
+
setSTTLang(data.lang);
|
|
357
|
+
}
|
|
358
|
+
if (avatarImg && avatarFallback) {
|
|
359
|
+
var avatarUrl = getCurrentUserAvatarSrc(data);
|
|
360
|
+
avatarImg.alt = displayName;
|
|
361
|
+
avatarImg.classList.remove('hidden');
|
|
362
|
+
avatarFallback.classList.remove('hidden');
|
|
363
|
+
avatarFallback.textContent = avatarInitials(displayName);
|
|
364
|
+
avatarImg.onload = function () {
|
|
365
|
+
avatarFallback.classList.add('hidden');
|
|
366
|
+
};
|
|
367
|
+
avatarImg.onerror = function () {
|
|
368
|
+
avatarFallback.classList.remove('hidden');
|
|
369
|
+
};
|
|
370
|
+
avatarImg.src = avatarUrl;
|
|
371
|
+
if (avatarImg.complete && avatarImg.naturalWidth > 0) {
|
|
372
|
+
avatarFallback.classList.add('hidden');
|
|
373
|
+
}
|
|
200
374
|
}
|
|
201
375
|
// Hide account section in single-user mode (no username)
|
|
202
376
|
var accountNav = settingsEl.querySelector('[data-section="us-account"]');
|
|
203
|
-
if (accountNav
|
|
204
|
-
accountNav.style.display = 'none';
|
|
205
|
-
}
|
|
377
|
+
if (accountNav) accountNav.style.display = data.username ? '' : 'none';
|
|
206
378
|
// Auto-continue toggle
|
|
207
379
|
var acToggle = document.getElementById('us-auto-continue');
|
|
208
380
|
if (acToggle) acToggle.checked = !!data.autoContinueOnRateLimit;
|
|
@@ -234,10 +406,89 @@ function populateAccount() {
|
|
|
234
406
|
}).catch(function () {});
|
|
235
407
|
}
|
|
236
408
|
|
|
409
|
+
function saveProfileChange(updates) {
|
|
410
|
+
if (!currentProfile) return;
|
|
411
|
+
var nextProfile = {};
|
|
412
|
+
var payload = {};
|
|
413
|
+
var key;
|
|
414
|
+
for (key in currentProfile) {
|
|
415
|
+
if (Object.prototype.hasOwnProperty.call(currentProfile, key)) {
|
|
416
|
+
nextProfile[key] = currentProfile[key];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (key in updates) {
|
|
420
|
+
if (Object.prototype.hasOwnProperty.call(updates, key)) {
|
|
421
|
+
nextProfile[key] = updates[key];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
currentProfile = nextProfile;
|
|
425
|
+
if (updates.lang) {
|
|
426
|
+
setSTTLang(updates.lang);
|
|
427
|
+
}
|
|
428
|
+
payload.name = nextProfile.name;
|
|
429
|
+
payload.lang = nextProfile.lang;
|
|
430
|
+
payload.avatarColor = nextProfile.avatarColor;
|
|
431
|
+
payload.avatarStyle = nextProfile.avatarStyle;
|
|
432
|
+
payload.avatarSeed = nextProfile.avatarSeed;
|
|
433
|
+
payload.avatarCustom = nextProfile.avatarCustom;
|
|
434
|
+
fetch('/api/profile', {
|
|
435
|
+
method: 'PUT',
|
|
436
|
+
headers: { 'Content-Type': 'application/json' },
|
|
437
|
+
body: JSON.stringify(payload),
|
|
438
|
+
}).catch(function () {});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function avatarInitials(value) {
|
|
442
|
+
var text = (value || '').trim();
|
|
443
|
+
if (!text) return '?';
|
|
444
|
+
var parts = text.split(/\s+/);
|
|
445
|
+
var first = parts[0].charAt(0);
|
|
446
|
+
var second = parts.length > 1 ? parts[1].charAt(0) : parts[0].charAt(1);
|
|
447
|
+
var out = first + (second || '');
|
|
448
|
+
return out.toUpperCase();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function getCurrentUserAvatarSrc(data) {
|
|
452
|
+
var islandAvatar = document.querySelector('.user-island-avatar img');
|
|
453
|
+
if (islandAvatar && islandAvatar.src) return islandAvatar.src;
|
|
454
|
+
if (data && data.avatarCustom) return data.avatarCustom;
|
|
455
|
+
return userAvatarUrl(data, 96);
|
|
456
|
+
}
|
|
457
|
+
|
|
237
458
|
function savePin(pin) {
|
|
238
459
|
var pinInput = document.getElementById('us-pin-input');
|
|
460
|
+
var pinConfirmInput = document.getElementById('us-pin-confirm-input');
|
|
461
|
+
var pinCurrentInput = document.getElementById('us-pin-current-input');
|
|
239
462
|
var pinSave = document.getElementById('us-pin-save');
|
|
240
463
|
var pinMsg = document.getElementById('us-pin-msg');
|
|
464
|
+
var currentPin = pinCurrentInput ? pinCurrentInput.value.trim() : '';
|
|
465
|
+
var confirmPin = pinConfirmInput ? pinConfirmInput.value.trim() : '';
|
|
466
|
+
var newPin = pinInput ? pinInput.value.trim() : '';
|
|
467
|
+
|
|
468
|
+
if (!/^\d{6}$/.test(newPin)) {
|
|
469
|
+
if (pinMsg) {
|
|
470
|
+
pinMsg.textContent = 'PIN must be exactly 6 digits.';
|
|
471
|
+
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
472
|
+
pinMsg.classList.remove('hidden');
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (confirmPin !== newPin) {
|
|
477
|
+
if (pinMsg) {
|
|
478
|
+
pinMsg.textContent = 'PINs do not match.';
|
|
479
|
+
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
480
|
+
pinMsg.classList.remove('hidden');
|
|
481
|
+
}
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (pinEnabled && !/^\d{6}$/.test(currentPin)) {
|
|
485
|
+
if (pinMsg) {
|
|
486
|
+
pinMsg.textContent = 'Current PIN is required.';
|
|
487
|
+
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
488
|
+
pinMsg.classList.remove('hidden');
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
241
492
|
|
|
242
493
|
pinSave.disabled = true;
|
|
243
494
|
pinSave.textContent = 'Saving\u2026';
|
|
@@ -245,32 +496,26 @@ function savePin(pin) {
|
|
|
245
496
|
fetch('/api/user/pin', {
|
|
246
497
|
method: 'PUT',
|
|
247
498
|
headers: { 'Content-Type': 'application/json' },
|
|
248
|
-
body: JSON.stringify({ newPin:
|
|
499
|
+
body: JSON.stringify({ currentPin: currentPin, newPin: newPin }),
|
|
249
500
|
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
250
501
|
if (data.ok) {
|
|
251
|
-
pinInput.value = '';
|
|
252
|
-
pinSave.textContent = 'Change PIN';
|
|
253
|
-
if (pinMsg) {
|
|
254
|
-
pinMsg.textContent = 'Your PIN has been changed.';
|
|
255
|
-
pinMsg.className = 'us-pin-msg us-pin-msg-ok';
|
|
256
|
-
pinMsg.classList.remove('hidden');
|
|
257
|
-
}
|
|
258
502
|
showToast('PIN changed');
|
|
503
|
+
hidePinForm();
|
|
259
504
|
} else {
|
|
260
505
|
pinSave.disabled = false;
|
|
261
|
-
pinSave.textContent = '
|
|
506
|
+
pinSave.textContent = 'Save';
|
|
262
507
|
if (pinMsg) {
|
|
263
508
|
pinMsg.textContent = data.error || 'Could not change your PIN. Please try again.';
|
|
264
|
-
pinMsg.className = '
|
|
509
|
+
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
265
510
|
pinMsg.classList.remove('hidden');
|
|
266
511
|
}
|
|
267
512
|
}
|
|
268
513
|
}).catch(function () {
|
|
269
514
|
pinSave.disabled = false;
|
|
270
|
-
pinSave.textContent = '
|
|
515
|
+
pinSave.textContent = 'Save';
|
|
271
516
|
if (pinMsg) {
|
|
272
517
|
pinMsg.textContent = 'Connection lost. Check your network and try again.';
|
|
273
|
-
pinMsg.className = '
|
|
518
|
+
pinMsg.className = 'settings-hint settings-pin-error us-pin-msg-err';
|
|
274
519
|
pinMsg.classList.remove('hidden');
|
|
275
520
|
}
|
|
276
521
|
});
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -120,6 +120,61 @@ function createSDKBridge(opts) {
|
|
|
120
120
|
var clayTls = opts.clayTls || false;
|
|
121
121
|
var clayAuthToken = opts.clayAuthToken || null;
|
|
122
122
|
var onProcessingChanged = opts.onProcessingChanged || function () {};
|
|
123
|
+
var _cachedFreshAuthState = null;
|
|
124
|
+
var _cachedFreshAuthAt = 0;
|
|
125
|
+
|
|
126
|
+
function getFreshAuthState(force) {
|
|
127
|
+
var yoke = require("./yoke");
|
|
128
|
+
var now = Date.now();
|
|
129
|
+
if (!force && _cachedFreshAuthState && now - _cachedFreshAuthAt < 15000) {
|
|
130
|
+
return _cachedFreshAuthState;
|
|
131
|
+
}
|
|
132
|
+
if (force) yoke.invalidateAuthCache();
|
|
133
|
+
_cachedFreshAuthState = yoke.checkAuth();
|
|
134
|
+
_cachedFreshAuthAt = now;
|
|
135
|
+
return _cachedFreshAuthState;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isAuthErrorMessage(errDetail) {
|
|
139
|
+
if (!errDetail) return false;
|
|
140
|
+
var errLower = String(errDetail).toLowerCase();
|
|
141
|
+
return errLower.indexOf("not logged in") !== -1
|
|
142
|
+
|| errLower.indexOf("unauthenticated") !== -1
|
|
143
|
+
|| errLower.indexOf("authentication") !== -1
|
|
144
|
+
|| errLower.indexOf("sign in") !== -1
|
|
145
|
+
|| errLower.indexOf("log in") !== -1
|
|
146
|
+
|| errLower.indexOf("please login") !== -1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getLoginCommand(vendor) {
|
|
150
|
+
if (vendor === "codex") return "codex login --device-auth";
|
|
151
|
+
if (vendor === "claude") return "claude login";
|
|
152
|
+
return (vendor || "claude") + " login";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function notifyAuthRequired(session, title, body, authLinuxUser, canAutoLogin, loginCommand) {
|
|
156
|
+
var _nm = getNotificationsModule();
|
|
157
|
+
if (!_nm) return false;
|
|
158
|
+
_nm.notify("auth_required", {
|
|
159
|
+
title: title,
|
|
160
|
+
body: body,
|
|
161
|
+
slug: slug,
|
|
162
|
+
sessionId: session.localId,
|
|
163
|
+
ownerId: session.ownerId || null,
|
|
164
|
+
vendor: session.vendor || (adapter && adapter.vendor) || "claude",
|
|
165
|
+
loginCommand: loginCommand,
|
|
166
|
+
linuxUser: authLinuxUser,
|
|
167
|
+
canAutoLogin: canAutoLogin,
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function logAuthDecision(stage, session, errDetail, authState) {
|
|
173
|
+
var vendor = session && session.vendor ? session.vendor : "(none)";
|
|
174
|
+
var errSnippet = errDetail ? String(errDetail).replace(/\s+/g, " ").slice(0, 180) : "";
|
|
175
|
+
var authSummary = authState ? JSON.stringify(authState) : "(none)";
|
|
176
|
+
console.warn("[sdk-bridge] auth decision [" + stage + "] vendor=" + vendor + " auth=" + authSummary + (errSnippet ? " err=" + errSnippet : ""));
|
|
177
|
+
}
|
|
123
178
|
|
|
124
179
|
function getModelsForVendor(vendor) {
|
|
125
180
|
if (vendor && sm.modelsByVendor && sm.modelsByVendor[vendor]) return sm.modelsByVendor[vendor];
|
|
@@ -737,29 +792,49 @@ function createSDKBridge(opts) {
|
|
|
737
792
|
var isContextOverflow = errLower.indexOf("prompt is too long") !== -1
|
|
738
793
|
|| errLower.indexOf("context_length") !== -1
|
|
739
794
|
|| errLower.indexOf("maximum context length") !== -1;
|
|
740
|
-
var isAuthError =
|
|
741
|
-
|| errLower.indexOf("unauthenticated") !== -1
|
|
742
|
-
|| errLower.indexOf("authentication") !== -1
|
|
743
|
-
|| errLower.indexOf("sign in") !== -1
|
|
744
|
-
|| errLower.indexOf("log in") !== -1
|
|
745
|
-
|| errLower.indexOf("please login") !== -1;
|
|
795
|
+
var isAuthError = isAuthErrorMessage(errDetail);
|
|
746
796
|
if (isContextOverflow) {
|
|
747
797
|
sendAndRecord(session, {
|
|
748
798
|
type: "context_overflow",
|
|
749
799
|
text: "Conversation too long to continue.",
|
|
750
800
|
});
|
|
751
801
|
} else if (isAuthError) {
|
|
802
|
+
var freshAuth = getFreshAuthState();
|
|
803
|
+
logAuthDecision("catch-auth-error", session, errDetail, freshAuth);
|
|
804
|
+
if (freshAuth[session.vendor]) {
|
|
805
|
+
sendAndRecord(session, {
|
|
806
|
+
type: "error",
|
|
807
|
+
text: "Authentication looked fine, but " + (session.vendor || "the vendor") + " returned an auth-like error.",
|
|
808
|
+
});
|
|
809
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
810
|
+
sm.broadcastSessionList();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
752
813
|
var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
|
|
753
814
|
var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
|
|
754
815
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
755
816
|
|| !!authLinuxUser
|
|
756
817
|
|| (authUser && authUser.role === "admin");
|
|
757
|
-
|
|
818
|
+
var authTitle = (session.vendor === "codex" ? "Codex" : "Claude Code") + " is not logged in.";
|
|
819
|
+
var authMsg = {
|
|
758
820
|
type: "auth_required",
|
|
759
|
-
text:
|
|
821
|
+
text: authTitle,
|
|
822
|
+
vendor: session.vendor || (adapter && adapter.vendor) || "claude",
|
|
823
|
+
loginCommand: getLoginCommand(session.vendor || (adapter && adapter.vendor) || "claude"),
|
|
760
824
|
linuxUser: authLinuxUser,
|
|
761
825
|
canAutoLogin: canAutoLogin,
|
|
762
|
-
}
|
|
826
|
+
};
|
|
827
|
+
sendAndRecord(session, authMsg);
|
|
828
|
+
if (!notifyAuthRequired(
|
|
829
|
+
session,
|
|
830
|
+
authTitle,
|
|
831
|
+
"Open a terminal, then click the URL and follow the instructions.",
|
|
832
|
+
authLinuxUser,
|
|
833
|
+
canAutoLogin,
|
|
834
|
+
getLoginCommand(session.vendor || (adapter && adapter.vendor) || "claude")
|
|
835
|
+
)) {
|
|
836
|
+
// chat message already sent above
|
|
837
|
+
}
|
|
763
838
|
} else {
|
|
764
839
|
sendAndRecord(session, { type: "error", text: "Claude process error: " + err.message });
|
|
765
840
|
}
|
|
@@ -968,6 +1043,16 @@ function createSDKBridge(opts) {
|
|
|
968
1043
|
} else if (session.vendor) {
|
|
969
1044
|
await ensureVendorReady(session.vendor);
|
|
970
1045
|
}
|
|
1046
|
+
if (session.vendor && !adapters[session.vendor]) {
|
|
1047
|
+
var freshAuth = getFreshAuthState();
|
|
1048
|
+
logAuthDecision("pre-auth-required", session, null, freshAuth);
|
|
1049
|
+
if (freshAuth[session.vendor]) {
|
|
1050
|
+
var recoveredAdapter = await ensureVendorReady(session.vendor);
|
|
1051
|
+
if (recoveredAdapter) {
|
|
1052
|
+
console.log("[sdk-bridge] Auth recheck recovered adapter for " + session.vendor);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
971
1056
|
// If still not available after lazy check, send auth_required
|
|
972
1057
|
if (session.vendor && !adapters[session.vendor]) {
|
|
973
1058
|
var vendorName = session.vendor.charAt(0).toUpperCase() + session.vendor.slice(1);
|
|
@@ -976,14 +1061,35 @@ function createSDKBridge(opts) {
|
|
|
976
1061
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
977
1062
|
|| !!authLinuxUser
|
|
978
1063
|
|| (authUser && authUser.role === "admin");
|
|
979
|
-
|
|
1064
|
+
var authState = getFreshAuthState();
|
|
1065
|
+
logAuthDecision("emit-auth-required", session, "missing adapter", authState);
|
|
1066
|
+
if (authState[session.vendor]) {
|
|
1067
|
+
sendAndRecord(session, {
|
|
1068
|
+
type: "error",
|
|
1069
|
+
text: vendorName + " auth is available, but the adapter could not be initialized.",
|
|
1070
|
+
});
|
|
1071
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
var authMsg2 = {
|
|
980
1075
|
type: "auth_required",
|
|
981
1076
|
text: vendorName + " is not logged in.",
|
|
982
1077
|
vendor: session.vendor,
|
|
983
|
-
loginCommand: session.vendor
|
|
1078
|
+
loginCommand: getLoginCommand(session.vendor),
|
|
984
1079
|
linuxUser: authLinuxUser,
|
|
985
1080
|
canAutoLogin: canAutoLogin,
|
|
986
|
-
}
|
|
1081
|
+
};
|
|
1082
|
+
sendAndRecord(session, authMsg2);
|
|
1083
|
+
if (!notifyAuthRequired(
|
|
1084
|
+
session,
|
|
1085
|
+
vendorName + " is not logged in.",
|
|
1086
|
+
"Open a terminal, then click the URL and follow the instructions.",
|
|
1087
|
+
authLinuxUser,
|
|
1088
|
+
canAutoLogin,
|
|
1089
|
+
getLoginCommand(session.vendor)
|
|
1090
|
+
)) {
|
|
1091
|
+
// chat message already sent above
|
|
1092
|
+
}
|
|
987
1093
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
988
1094
|
return;
|
|
989
1095
|
}
|
|
@@ -18,7 +18,7 @@ function attachMessageProcessor(ctx) {
|
|
|
18
18
|
var discoverSkillDirs = ctx.discoverSkillDirs;
|
|
19
19
|
var mergeSkills = ctx.mergeSkills;
|
|
20
20
|
|
|
21
|
-
var AUTO_TITLE_TURN_THRESHOLD =
|
|
21
|
+
var AUTO_TITLE_TURN_THRESHOLD = 2;
|
|
22
22
|
|
|
23
23
|
function getMateIdForNotification() {
|
|
24
24
|
if (!isMate) return null;
|
|
@@ -435,12 +435,33 @@ function attachMessageProcessor(ctx) {
|
|
|
435
435
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
436
436
|
|| !!authLinuxUser
|
|
437
437
|
|| (authUser && authUser.role === "admin");
|
|
438
|
-
|
|
438
|
+
var authTitle = session.vendor === "codex" ? "Codex is not logged in." : "Claude Code is not logged in.";
|
|
439
|
+
var loginCommand = session.vendor === "codex"
|
|
440
|
+
? "codex login --device-auth"
|
|
441
|
+
: "claude login";
|
|
442
|
+
var _nmLogin = getNotificationsModule();
|
|
443
|
+
var authMsg = {
|
|
439
444
|
type: "auth_required",
|
|
440
|
-
text:
|
|
445
|
+
text: authTitle,
|
|
446
|
+
vendor: session.vendor || "claude",
|
|
447
|
+
loginCommand: loginCommand,
|
|
441
448
|
linuxUser: authLinuxUser,
|
|
442
449
|
canAutoLogin: canAutoLogin,
|
|
443
|
-
}
|
|
450
|
+
};
|
|
451
|
+
sendAndRecord(session, authMsg);
|
|
452
|
+
if (_nmLogin) {
|
|
453
|
+
_nmLogin.notify("auth_required", {
|
|
454
|
+
title: authTitle,
|
|
455
|
+
body: "Open a terminal, then click the URL and follow the instructions.",
|
|
456
|
+
slug: slug,
|
|
457
|
+
sessionId: session.localId,
|
|
458
|
+
ownerId: session.ownerId || null,
|
|
459
|
+
vendor: session.vendor || "claude",
|
|
460
|
+
loginCommand: loginCommand,
|
|
461
|
+
linuxUser: authLinuxUser,
|
|
462
|
+
canAutoLogin: canAutoLogin,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
444
465
|
// Reset CLI session so next query starts fresh with new auth
|
|
445
466
|
session.cliSessionId = null;
|
|
446
467
|
}
|