iobroker.eos-admin 7.9.93 → 7.9.95

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 (45) hide show
  1. package/INSTALL_TEST_V7.9.95.md +15 -0
  2. package/NEXOWATT_EOS_BUILD_INFO.json +23 -18
  3. package/NEXOWATT_EOS_STABLE_7.9.94.md +16 -0
  4. package/{PUBLISH_STABLE_V7.9.93.md → PUBLISH_STABLE_V7.9.95.md} +2 -5
  5. package/README_STABILITY_V7.9.95.md +12 -0
  6. package/RELEASE_ACCEPTANCE_V7.9.95.md +11 -0
  7. package/RELEASE_NOTES_V7.9.95.md +12 -0
  8. package/adminWww/assets/Adapters-B5_jQ7DE-v84.js +1 -1
  9. package/adminWww/css/nexowatt-native-shell.css +4 -156
  10. package/adminWww/index.html +13 -11
  11. package/adminWww/js/eos-account-management.js +1 -1
  12. package/adminWww/js/eos-assistant.js +5 -556
  13. package/adminWww/js/eos-basic-settings.js +1 -1
  14. package/adminWww/js/eos-branding-sanitizer.js +1 -3
  15. package/adminWww/js/eos-native-security.js +1 -1
  16. package/adminWww/js/eos-policy-client.js +1 -1
  17. package/adminWww/js/eos-role-bootstrap.js +2 -2
  18. package/adminWww/js/eos-role-ui.js +1 -1
  19. package/adminWww/js/nexowatt-native-shell.js +1 -1
  20. package/adminWww/static/js/nexowatt-stable-login.js +79 -0
  21. package/build/lib/chat/assistantKnowledge.js +6 -21
  22. package/build/lib/chat/chatOrchestrator.js +38 -33
  23. package/build/lib/chat/mcpClientManager.js +1 -1
  24. package/build/lib/nexowattStableAuth.js +74 -0
  25. package/build/lib/web.js +24 -7
  26. package/build/main.js +123 -308
  27. package/io-package.json +20 -16
  28. package/package.json +23 -32
  29. package/INSTALL_TEST_V7.9.93.md +0 -12
  30. package/README_STABILITY_V7.9.93.md +0 -15
  31. package/RELEASE_ACCEPTANCE_V7.9.93.md +0 -15
  32. package/RELEASE_NOTES_V7.9.93.md +0 -10
  33. package/tools/nexowatt-account-management-selftest.cjs +0 -61
  34. package/tools/nexowatt-clean-legacy-runtime.cjs +0 -147
  35. package/tools/nexowatt-ensure-release-defaults.cjs +0 -72
  36. package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +0 -37
  37. package/tools/nexowatt-generate-repo-entry.cjs +0 -3
  38. package/tools/nexowatt-internal-reserve-selftest.cjs +0 -57
  39. package/tools/nexowatt-login-layout-selftest.cjs +0 -62
  40. package/tools/nexowatt-modern-ui-selftest.cjs +0 -50
  41. package/tools/nexowatt-patch-repo.cjs +0 -54
  42. package/tools/nexowatt-publish-channel-guard.cjs +0 -58
  43. package/tools/nexowatt-release-defaults-selftest.cjs +0 -29
  44. package/tools/nexowatt-runtime-cleanup-selftest.cjs +0 -47
  45. package/tools/nexowatt-validate-package.cjs +0 -246
@@ -1,559 +1,8 @@
1
1
  (() => {
2
2
  'use strict';
3
-
4
- const VERSION = 'v93-header-eos-assist-live-system';
5
- const previous = window.NEXOWATT_EOS_ASSIST;
6
- if (previous?.version === VERSION) return;
7
- previous?.destroy?.();
8
-
9
- const scriptUrl =
10
- document.currentScript?.src || document.querySelector('script[src*="eos-assistant.js"]')?.src || location.href;
11
- const logoUrl = new URL('../img/eos/nexowatt-192.png', scriptUrl).href;
12
- const abort = new AbortController();
13
- let unsubscribeDom = null;
14
- let reconnectTimer = 0;
15
- let destroyed = false;
16
- let socket = null;
17
- let status = null;
18
- let settings = {
19
- provider: 'anthropic',
20
- model: '',
21
- credentialId: '',
22
- baseUrl: '',
23
- allowSelfSignedCerts: false,
24
- };
25
- let credentials = [];
26
- let apiMessages = [];
27
- let historyKey = 'NexoWatt.EOS.Assist.anonymous';
28
- let loading = false;
29
-
30
- const currentContext = () => {
31
- const hash = String(location.hash || '').toLowerCase();
32
- if (hash.includes('tab-adapters')) {
33
- return ['Module', 'Fragen zu installierten Modulen, Versionen, Kompatibilität und Einrichtung.'];
34
- }
35
- if (hash.includes('tab-instances')) {
36
- return ['Dienste', 'Fragen zu laufenden Diensten, Status, Einstellungen, Speicherverbrauch und Fehlern.'];
37
- }
38
- if (hash.includes('tab-objects')) {
39
- return ['Datenpunkte', 'Fragen zu Messwerten, Metadaten, Schreibfreigaben und aktuellen Zuständen.'];
40
- }
41
- if (hash.includes('tab-enums')) {
42
- return ['Struktur', 'Fragen zu Räumen, Funktionen, Smart-Home-Zuordnungen und Geräten.'];
43
- }
44
- if (hash.includes('tab-logs')) {
45
- return ['Systemlogs', 'Fragen zu aktuellen Meldungen, Verbindungen und Fehlerursachen.'];
46
- }
47
- if (hash.includes('tab-users')) {
48
- return ['Zugänge & Rechte', 'Fragen zu EOS-Rollen, Zugängen und erlaubten Funktionen.'];
49
- }
50
- if (hash.includes('nexowatt-backup') || hash.includes('tab-backup')) {
51
- return ['NexoWatt Backup', 'Fragen zu Sicherungsstatus, Sicherungszielen und Wiederherstellung.'];
52
- }
53
- if (hash.includes('tab-intro')) {
54
- return ['Übersicht', 'Fragen zum Anlagenstatus und zu allen freigegebenen EOS-Bereichen.'];
55
- }
56
- return ['EOS Assist', 'Fragen zum laufenden System, Einstellungen, Modulen, Geräten und Datenpunkten.'];
57
- };
58
-
59
- const findToolbar = () =>
60
- document.querySelector(
61
- '.eos-top-toolbar, #root > .MuiPaper-root > .MuiAppBar-root .MuiToolbar-root, .MuiAppBar-root .MuiToolbar-root',
62
- );
63
- const findUserAnchor = toolbar =>
64
- Array.from(toolbar?.children || []).find(
65
- child => child.querySelector?.('.MuiAvatar-root') || /\b(admin|installer|guest|endkunde|service)\b/i.test(child.textContent || ''),
66
- ) || null;
67
-
68
- const socketPath = () => {
69
- let parts = location.pathname.split('/');
70
- parts.pop();
71
- if (location.pathname.match(/^\/admin\//)) parts = [];
72
- const value = `${parts.join('/')}/socket.io`.replace(/\/{2,}/g, '/');
73
- return value.startsWith('/') ? value : `/${value}`;
74
- };
75
-
76
- const emit = (event, payload = {}, timeoutMs = 30_000) =>
77
- new Promise((resolve, reject) => {
78
- if (!socket?.connected) {
79
- reject(new Error('Keine Verbindung zum EOS-System.'));
80
- return;
81
- }
82
- let finished = false;
83
- const timer = window.setTimeout(() => {
84
- if (finished) return;
85
- finished = true;
86
- reject(new Error('Zeitüberschreitung bei der EOS-Anfrage.'));
87
- }, timeoutMs);
88
- socket.emit(event, payload, response => {
89
- if (finished) return;
90
- finished = true;
91
- window.clearTimeout(timer);
92
- resolve(response || {});
93
- });
94
- });
95
-
96
- const remove = () => document.getElementById('eos-assist-root')?.remove();
97
- const escapeRole = role => (role === 'admin' ? 'Admin / Service' : role === 'installer' ? 'Installateur' : 'Endkunde');
98
-
99
- const persistHistory = () => {
100
- try {
101
- sessionStorage.setItem(historyKey, JSON.stringify({ apiMessages: apiMessages.slice(-24) }));
102
- } catch {
103
- // A conversation must still work if session storage is unavailable.
104
- }
105
- };
106
-
107
- const loadHistory = () => {
108
- apiMessages = [];
109
- try {
110
- const parsed = JSON.parse(sessionStorage.getItem(historyKey) || '{}');
111
- if (Array.isArray(parsed.apiMessages)) {
112
- apiMessages = parsed.apiMessages
113
- .filter(
114
- message =>
115
- (message?.role === 'user' || message?.role === 'assistant') &&
116
- typeof message.content === 'string',
117
- )
118
- .slice(-24);
119
- }
120
- } catch {
121
- apiMessages = [];
122
- }
123
- };
124
-
125
- const appendLinkAwareText = (container, text) => {
126
- const source = String(text || '');
127
- const regex = /\[([^\]]+)\]\((#[^)]+)\)/g;
128
- let cursor = 0;
129
- let match;
130
- while ((match = regex.exec(source))) {
131
- if (match.index > cursor) container.append(document.createTextNode(source.substring(cursor, match.index)));
132
- const link = document.createElement('a');
133
- link.href = match[2];
134
- link.textContent = match[1];
135
- link.addEventListener(
136
- 'click',
137
- event => {
138
- event.preventDefault();
139
- location.hash = match[2];
140
- },
141
- { signal: abort.signal },
142
- );
143
- container.append(link);
144
- cursor = regex.lastIndex;
145
- }
146
- if (cursor < source.length) container.append(document.createTextNode(source.substring(cursor)));
147
- };
148
-
149
- const appendMessage = (role, text, extraClass = '') => {
150
- const root = document.getElementById('eos-assist-root');
151
- const transcript = root?.querySelector('.eos-assist-transcript');
152
- if (!transcript) return;
153
- transcript.querySelector('.eos-assist-welcome')?.remove();
154
- const article = document.createElement('article');
155
- article.className = `eos-assist-message eos-assist-message-${role}${extraClass ? ` ${extraClass}` : ''}`;
156
- const label = document.createElement('strong');
157
- label.textContent = role === 'user' ? 'Du' : role === 'error' ? 'Hinweis' : 'EOS Assist';
158
- const body = document.createElement('div');
159
- appendLinkAwareText(body, text);
160
- article.append(label, body);
161
- transcript.append(article);
162
- transcript.scrollTop = transcript.scrollHeight;
163
- };
164
-
165
- const renderHistory = () => {
166
- const root = document.getElementById('eos-assist-root');
167
- const transcript = root?.querySelector('.eos-assist-transcript');
168
- if (!transcript) return;
169
- transcript.innerHTML = '';
170
- if (!apiMessages.length) {
171
- const welcome = document.createElement('div');
172
- welcome.className = 'eos-assist-welcome';
173
- welcome.textContent =
174
- 'Frage nach dem aktuellen Anlagenstatus, einer Einstellung, einem Modul, Dienst, Gerät, Datenpunkt oder Fehler.';
175
- transcript.append(welcome);
176
- return;
177
- }
178
- for (const message of apiMessages) appendMessage(message.role, message.content);
179
- };
180
-
181
- const setConnectionText = (text, kind = '') => {
182
- const element = document.querySelector('#eos-assist-root .eos-assist-status');
183
- if (!element) return;
184
- element.textContent = text;
185
- element.dataset.kind = kind;
186
- };
187
-
188
- const updateStatusUi = () => {
189
- const root = document.getElementById('eos-assist-root');
190
- if (!root) return;
191
- const configure = root.querySelector('.eos-assist-configure');
192
- configure?.classList.toggle('eos-assist-config-hidden', !status?.canConfigure);
193
- root.classList.toggle('eos-assist-not-configured', status?.configured === false);
194
- const role = status?.role ? escapeRole(status.role) : '';
195
- if (!socket?.connected) setConnectionText('Verbindung wird hergestellt …', 'waiting');
196
- else if (!status) setConnectionText('Systemstatus wird geladen …', 'waiting');
197
- else if (!status.configured) {
198
- setConnectionText(
199
- status.canConfigure
200
- ? 'EOS Assist muss einmalig konfiguriert werden.'
201
- : 'EOS Assist wurde noch nicht durch Admin / Service konfiguriert.',
202
- 'warning',
203
- );
204
- } else setConnectionText(`Verbunden · ${role} · schreibgeschützt`, 'ready');
205
- };
206
-
207
- const fillSettingsForm = () => {
208
- const root = document.getElementById('eos-assist-root');
209
- if (!root) return;
210
- const provider = root.querySelector('.eos-assist-provider');
211
- const credential = root.querySelector('.eos-assist-credential');
212
- const model = root.querySelector('.eos-assist-model');
213
- const baseUrl = root.querySelector('.eos-assist-base-url');
214
- const selfSigned = root.querySelector('.eos-assist-self-signed');
215
- if (provider) provider.value = settings.provider || 'anthropic';
216
- if (credential) {
217
- credential.innerHTML = '<option value="">Kein Zugang ausgewählt</option>';
218
- for (const entry of credentials) {
219
- const option = document.createElement('option');
220
- option.value = entry.id || '';
221
- option.textContent = entry.name || entry.id || 'KI-Zugang';
222
- credential.append(option);
223
- }
224
- credential.value = settings.credentialId || '';
225
- }
226
- if (model) model.value = settings.model || '';
227
- if (baseUrl) baseUrl.value = settings.baseUrl || '';
228
- if (selfSigned) selfSigned.checked = settings.allowSelfSignedCerts === true;
229
- root.classList.toggle('eos-assist-custom-provider', provider?.value === 'custom');
230
- };
231
-
232
- const readSettingsForm = () => {
233
- const root = document.getElementById('eos-assist-root');
234
- return {
235
- provider: root?.querySelector('.eos-assist-provider')?.value || 'anthropic',
236
- credentialId: root?.querySelector('.eos-assist-credential')?.value || '',
237
- model: root?.querySelector('.eos-assist-model')?.value?.trim() || '',
238
- baseUrl: root?.querySelector('.eos-assist-base-url')?.value?.trim() || '',
239
- allowSelfSignedCerts: root?.querySelector('.eos-assist-self-signed')?.checked === true,
240
- };
241
- };
242
-
243
- const loadStatus = async () => {
244
- try {
245
- const response = await emit('eosAssistStatus');
246
- if (response.error) throw new Error(response.error);
247
- status = response;
248
- settings = { ...settings, ...(response.settings || {}) };
249
- credentials = Array.isArray(response.credentials) ? response.credentials : [];
250
- const nextKey = `NexoWatt.EOS.Assist.${response.user || 'anonymous'}`;
251
- if (historyKey !== nextKey) {
252
- historyKey = nextKey;
253
- loadHistory();
254
- renderHistory();
255
- }
256
- fillSettingsForm();
257
- updateStatusUi();
258
- } catch (error) {
259
- status = null;
260
- setConnectionText(error instanceof Error ? error.message : String(error), 'error');
261
- }
262
- };
263
-
264
- const connectSocket = () => {
265
- if (destroyed || socket?.connected) return;
266
- if (!window.io?.connect) {
267
- reconnectTimer = window.setTimeout(connectSocket, 250);
268
- return;
269
- }
270
- socket = window.io.connect('/', { path: socketPath() });
271
- socket.on('connect', () => {
272
- setConnectionText('Verbunden. Systemstatus wird geladen …', 'waiting');
273
- void loadStatus();
274
- });
275
- socket.on('disconnect', () => {
276
- status = null;
277
- updateStatusUi();
278
- });
279
- socket.on('reauthenticate', () => {
280
- setConnectionText('Die Sitzung ist abgelaufen. Bitte erneut anmelden.', 'error');
281
- });
282
- socket.on('connect_error', error => {
283
- setConnectionText(`Verbindungsfehler: ${error?.message || error}`, 'error');
284
- });
285
- };
286
-
287
- const setBusy = busy => {
288
- loading = busy;
289
- const root = document.getElementById('eos-assist-root');
290
- const input = root?.querySelector('.eos-assist-input');
291
- const send = root?.querySelector('.eos-assist-send');
292
- if (input) input.disabled = busy;
293
- if (send) {
294
- send.disabled = busy;
295
- send.textContent = busy ? 'Prüft …' : 'Fragen';
296
- }
297
- };
298
-
299
- const sendQuestion = async () => {
300
- const root = document.getElementById('eos-assist-root');
301
- const input = root?.querySelector('.eos-assist-input');
302
- const text = String(input?.value || '').trim();
303
- if (!text || loading) return;
304
- if (!status?.configured) {
305
- appendMessage(
306
- 'error',
307
- status?.canConfigure
308
- ? 'Öffne die Einstellungen von EOS Assist und hinterlege Anbieter, Zugang und Modell.'
309
- : 'EOS Assist muss zuerst durch Admin / NexoWatt Service konfiguriert werden.',
310
- );
311
- return;
312
- }
313
- apiMessages.push({ role: 'user', content: text });
314
- apiMessages = apiMessages.slice(-24);
315
- persistHistory();
316
- appendMessage('user', text);
317
- input.value = '';
318
- setBusy(true);
319
- setConnectionText('EOS Assist prüft das aktuelle System und die freigegebenen Daten …', 'waiting');
320
- try {
321
- const response = await emit(
322
- 'eosAssist',
323
- { messages: apiMessages, uiContext: { hash: location.hash } },
324
- 620_000,
325
- );
326
- if (response.error) {
327
- appendMessage('error', response.error);
328
- if (response.code === 'notConfigured') {
329
- status = { ...(status || {}), configured: false, canConfigure: response.canConfigure === true };
330
- }
331
- updateStatusUi();
332
- return;
333
- }
334
- const answer = String(response.content || 'Es wurde keine Antwort erzeugt.');
335
- apiMessages.push({ role: 'assistant', content: answer });
336
- apiMessages = apiMessages.slice(-24);
337
- persistHistory();
338
- appendMessage('assistant', answer);
339
- for (const action of response.clientActions || []) {
340
- if (action?.type === 'navigate' && typeof action.hash === 'string') {
341
- location.hash = action.hash.startsWith('#') ? action.hash : `#${action.hash}`;
342
- }
343
- }
344
- setConnectionText(`Antwort auf Basis des aktuellen EOS-Systems · ${escapeRole(status.role)}`, 'ready');
345
- } catch (error) {
346
- appendMessage('error', error instanceof Error ? error.message : String(error));
347
- setConnectionText('Die Anfrage konnte nicht abgeschlossen werden.', 'error');
348
- } finally {
349
- setBusy(false);
350
- input?.focus();
351
- }
352
- };
353
-
354
- const openSettings = open => {
355
- const root = document.getElementById('eos-assist-root');
356
- if (!root || !status?.canConfigure) return;
357
- root.classList.toggle('eos-assist-settings-open', open);
358
- fillSettingsForm();
359
- };
360
-
361
- const testSettings = async () => {
362
- const payload = readSettingsForm();
363
- setConnectionText('Verbindung und verfügbare Modelle werden geprüft …', 'waiting');
364
- try {
365
- const response = await emit('eosAssistTestSettings', payload, 30_000);
366
- if (response.error) throw new Error(response.error);
367
- const models = Array.isArray(response.models) ? response.models : [];
368
- const list = document.querySelector('#eos-assist-root .eos-assist-model-list');
369
- if (list) {
370
- list.innerHTML = '';
371
- for (const id of models) {
372
- const option = document.createElement('option');
373
- option.value = id;
374
- list.append(option);
375
- }
376
- }
377
- const model = document.querySelector('#eos-assist-root .eos-assist-model');
378
- if (model && !model.value && models[0]) model.value = models[0];
379
- setConnectionText(`${models.length} Modell(e) gefunden.`, 'ready');
380
- } catch (error) {
381
- setConnectionText(error instanceof Error ? error.message : String(error), 'error');
382
- }
383
- };
384
-
385
- const saveSettings = async () => {
386
- const payload = readSettingsForm();
387
- setConnectionText('EOS-Assist-Einstellungen werden gespeichert …', 'waiting');
388
- try {
389
- const response = await emit('eosAssistSaveSettings', payload, 30_000);
390
- if (response.error) throw new Error(response.error);
391
- settings = { ...settings, ...(response.settings || payload) };
392
- status = { ...(status || {}), configured: response.configured === true };
393
- openSettings(false);
394
- updateStatusUi();
395
- setConnectionText('EOS Assist ist konfiguriert und schreibgeschützt bereit.', 'ready');
396
- } catch (error) {
397
- setConnectionText(error instanceof Error ? error.message : String(error), 'error');
398
- }
399
- };
400
-
401
- const newConversation = () => {
402
- apiMessages = [];
403
- persistHistory();
404
- renderHistory();
405
- document.querySelector('#eos-assist-root .eos-assist-input')?.focus();
406
- };
407
-
408
- const ensure = () => {
409
- if (destroyed) return;
410
- const hasApp = !!document.getElementById('app-paper');
411
- const login = document.documentElement.classList.contains('eos-login');
412
- const toolbar = findToolbar();
413
- if (!hasApp || login || !toolbar) {
414
- remove();
415
- return;
416
- }
417
- toolbar.classList.add('eos-top-toolbar');
418
- let root = document.getElementById('eos-assist-root');
419
- if (!root) {
420
- root = document.createElement('section');
421
- root.id = 'eos-assist-root';
422
- root.className = 'eos-assist-root eos-assist-header-root';
423
- root.innerHTML = `
424
- <button class="eos-assist-button" type="button" aria-expanded="false" title="EOS Assist">
425
- <span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>Systemhilfe</small>
426
- </button>
427
- <div class="eos-assist-panel" role="dialog" aria-label="EOS Assist">
428
- <div class="eos-assist-head">
429
- <img src="${logoUrl}" alt="NexoWatt EOS">
430
- <div><strong class="eos-assist-title">EOS Assist</strong><span class="eos-assist-text"></span></div>
431
- <button class="eos-assist-close" type="button" aria-label="Schließen">×</button>
432
- </div>
433
- <div class="eos-assist-toolbar">
434
- <button class="eos-assist-new" type="button">Neue Unterhaltung</button>
435
- <button class="eos-assist-configure eos-assist-config-hidden" type="button">Einstellungen</button>
436
- </div>
437
- <div class="eos-assist-conversation">
438
- <div class="eos-assist-transcript" aria-live="polite"></div>
439
- <div class="eos-assist-input-row">
440
- <textarea class="eos-assist-input" rows="2" maxlength="4000" placeholder="Frage zum System, einer Einstellung, einem Modul oder Datenpunkt"></textarea>
441
- <button class="eos-assist-send" type="button">Fragen</button>
442
- </div>
443
- </div>
444
- <div class="eos-assist-settings" aria-label="EOS Assist Einstellungen">
445
- <label>Anbieter<select class="eos-assist-provider">
446
- <option value="anthropic">Anthropic</option>
447
- <option value="openai">OpenAI</option>
448
- <option value="gemini">Gemini</option>
449
- <option value="deepseek">DeepSeek</option>
450
- <option value="custom">Lokal / individueller Anbieter</option>
451
- </select></label>
452
- <label>Gespeicherter KI-Zugang<select class="eos-assist-credential"></select></label>
453
- <label class="eos-assist-base-url-label">Basis-URL<input class="eos-assist-base-url" placeholder="http://127.0.0.1:11434/v1"></label>
454
- <label>Modell<input class="eos-assist-model" list="eos-assist-model-list" placeholder="Modellbezeichnung"><datalist id="eos-assist-model-list" class="eos-assist-model-list"></datalist></label>
455
- <label class="eos-assist-check"><input class="eos-assist-self-signed" type="checkbox"> Selbstsigniertes Zertifikat zulassen</label>
456
- <div class="eos-assist-settings-actions">
457
- <button class="eos-assist-settings-back" type="button">Zurück</button>
458
- <button class="eos-assist-test" type="button">Verbindung testen</button>
459
- <button class="eos-assist-save" type="button">Speichern</button>
460
- </div>
461
- </div>
462
- <div class="eos-assist-status" data-kind="waiting">Verbindung wird hergestellt …</div>
463
- </div>`;
464
- const userAnchor = findUserAnchor(toolbar);
465
- toolbar.insertBefore(root, userAnchor);
466
- renderHistory();
467
- root.addEventListener(
468
- 'click',
469
- event => {
470
- const button = event.target?.closest?.('button');
471
- if (!button) return;
472
- if (button.classList.contains('eos-assist-button')) {
473
- const open = !root.classList.contains('eos-assist-open');
474
- root.classList.toggle('eos-assist-open', open);
475
- button.setAttribute('aria-expanded', open ? 'true' : 'false');
476
- if (open) {
477
- void loadStatus();
478
- window.setTimeout(() => root.querySelector('.eos-assist-input')?.focus(), 30);
479
- }
480
- } else if (button.classList.contains('eos-assist-close')) {
481
- root.classList.remove('eos-assist-open', 'eos-assist-settings-open');
482
- root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
483
- } else if (button.classList.contains('eos-assist-send')) void sendQuestion();
484
- else if (button.classList.contains('eos-assist-new')) newConversation();
485
- else if (button.classList.contains('eos-assist-configure')) openSettings(true);
486
- else if (button.classList.contains('eos-assist-settings-back')) openSettings(false);
487
- else if (button.classList.contains('eos-assist-test')) void testSettings();
488
- else if (button.classList.contains('eos-assist-save')) void saveSettings();
489
- },
490
- { signal: abort.signal },
491
- );
492
- root.addEventListener(
493
- 'keydown',
494
- event => {
495
- if (
496
- event.key === 'Enter' &&
497
- !event.shiftKey &&
498
- event.target?.classList?.contains('eos-assist-input')
499
- ) {
500
- event.preventDefault();
501
- void sendQuestion();
502
- }
503
- if (event.key === 'Escape') {
504
- if (root.classList.contains('eos-assist-settings-open')) openSettings(false);
505
- else {
506
- root.classList.remove('eos-assist-open');
507
- root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
508
- }
509
- }
510
- },
511
- { signal: abort.signal },
512
- );
513
- root.querySelector('.eos-assist-provider')?.addEventListener(
514
- 'change',
515
- event => root.classList.toggle('eos-assist-custom-provider', event.target.value === 'custom'),
516
- { signal: abort.signal },
517
- );
518
- updateStatusUi();
519
- connectSocket();
520
- } else if (root.parentElement !== toolbar) {
521
- const userAnchor = findUserAnchor(toolbar);
522
- toolbar.insertBefore(root, userAnchor);
523
- }
524
- const [title, text] = currentContext();
525
- root.querySelector('.eos-assist-title').textContent = title;
526
- root.querySelector('.eos-assist-text').textContent = text;
527
- };
528
-
529
- const connectDom = () => {
530
- const coordinator = window.NEXOWATT_EOS_DOM_COORDINATOR;
531
- if (!coordinator?.subscribe) {
532
- reconnectTimer = window.setTimeout(connectDom, 250);
533
- return;
534
- }
535
- unsubscribeDom = coordinator.subscribe(ensure);
536
- };
537
-
538
- window.addEventListener('hashchange', ensure, { signal: abort.signal });
539
- window.addEventListener('resize', ensure, { signal: abort.signal });
540
- if (document.readyState === 'loading') {
541
- document.addEventListener('DOMContentLoaded', ensure, { once: true, signal: abort.signal });
542
- } else ensure();
543
- connectDom();
544
- [250, 800, 1600].forEach(delay => window.setTimeout(ensure, delay));
545
-
546
- window.NEXOWATT_EOS_ASSIST = {
547
- version: VERSION,
548
- refresh: ensure,
549
- destroy() {
550
- destroyed = true;
551
- abort.abort();
552
- unsubscribeDom?.();
553
- if (reconnectTimer) window.clearTimeout(reconnectTimer);
554
- socket?.disconnect?.();
555
- socket = null;
556
- remove();
557
- },
558
- };
3
+ // NexoWatt EOS stable 7.9.95: EOS Assist remains disabled until the
4
+ // read-only system assistant is rebuilt and accepted separately.
5
+ window.NEXOWATT_EOS_ASSIST_DISABLED = true;
6
+ document.documentElement.classList.add('eos-assist-disabled');
7
+ document.querySelectorAll('.eos-assist-root,.eos-assist-header-root,[data-eos-assist-root]').forEach(element => element.remove());
559
8
  })();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v93-basic-settings';
4
+ const VERSION = 'v91-basic-settings';
5
5
  const existing = window.NEXOWATT_EOS_BASIC_SETTINGS;
6
6
  if (existing?.version === VERSION) return;
7
7
  existing?.destroy?.();
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- const VERSION = 'v93-visible-branding-publisher';
3
+ const VERSION = 'v91-visible-branding';
4
4
  const existing = window.NEXOWATT_EOS_BRANDING_SANITIZER;
5
5
  if (existing?.version === VERSION) return;
6
6
  existing?.destroy?.();
@@ -8,8 +8,6 @@
8
8
  let unsubscribe = null;
9
9
  const skip = new Set(['SCRIPT','STYLE','CODE','PRE','TEXTAREA','NOSCRIPT']);
10
10
  const rewrite = value => String(value || '')
11
- .replace(/\[\s*iobroker\.eos-admin@[^\]\r\n]+\]/gi, '[NexoWatt]')
12
- .replace(/\[\s*eos-admin@[^\]\r\n]+\]/gi, '[NexoWatt]')
13
11
  .replace(/ioBroker\.admin/gi, 'NexoWatt EOS Admin')
14
12
  .replace(/ioBroker\s*Admin/gi, 'NexoWatt EOS Admin')
15
13
  .replace(/ioBroker/gi, 'NexoWatt EOS');
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v93-product-role-access';
4
+ const VERSION = 'v91-product-role-access';
5
5
  const protectedAdapters = new Set([
6
6
  'admin', 'eos-admin', 'backitup', 'nexowatt-devices', 'nexowatt-device', 'nexowatt-dev', 'nexowatt-ui',
7
7
  ]);
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v93-role-access';
4
+ const VERSION = 'v91-role-access';
5
5
  const existing = window.NEXOWATT_EOS_POLICY_CLIENT;
6
6
  if (existing?.version === VERSION) return;
7
7
  existing?.destroy?.();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v93-final-first-login-submit-guard';
4
+ const VERSION = 'v91-final-first-login-submit-guard';
5
5
  const script = document.currentScript || document.querySelector('script[src*="eos-role-bootstrap.js"]');
6
6
  const entry = script?.dataset?.eosEntry || '';
7
7
  let launched = false;
@@ -500,7 +500,7 @@
500
500
  const { username, password, card, submit } = loginElements();
501
501
  if (!username || !password || !card) return;
502
502
  card.classList.add('eos-login-card-modern');
503
- // v93 compact normal-login first activation: do not add role buttons or a second panel.
503
+ // v91 compact normal-login first activation: do not add role buttons or a second panel.
504
504
  // The account is entered in the existing Loginname field; a blank password is accepted only
505
505
  // after the backend confirms that this exact managed account is eligible.
506
506
  card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove());
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v93-final-clickable-overview-live-reserve-filter';
4
+ const VERSION = 'v91-final-clickable-overview-live-reserve-filter';
5
5
  window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
6
6
 
7
7
  const state = {
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v93-nexowatt-native-shell-modern-overview';
4
+ const VERSION = 'v91-nexowatt-native-shell-modern-overview';
5
5
  const previous = window.NEXOWATT_NATIVE_SHELL;
6
6
  if (previous?.version === VERSION) return;
7
7
  previous?.destroy?.();