iobroker.eos-admin 7.9.93 → 7.9.96

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 (55) hide show
  1. package/INSTALL_TEST_V7.9.96.md +11 -0
  2. package/MERGE_UPDATE_README_V7.9.96.md +5 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +19 -19
  4. package/NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md +44 -0
  5. package/PUBLISH_STABLE_V7.9.96.md +10 -0
  6. package/RC73_EOS_ADMIN_MERGE_VALIDATION.md +78 -0
  7. package/README_EMS_OVERVIEW_MERGE.md +45 -0
  8. package/README_STABILITY_V7.9.96.md +11 -0
  9. package/RELEASE_ACCEPTANCE_V7.9.96.md +10 -0
  10. package/RELEASE_NOTES_V7.9.96.md +12 -0
  11. package/VALIDATION_EMS_OVERVIEW_MERGE.md +35 -0
  12. package/adminWww/assets/Adapters-B5_jQ7DE-v84.js +1 -1
  13. package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
  14. package/adminWww/assets/index-D2ymscJA-v84.js +1 -1
  15. package/adminWww/css/eos-ems-overview.css +62 -0
  16. package/adminWww/css/nexowatt-native-shell.css +104 -158
  17. package/adminWww/index.html +15 -11
  18. package/adminWww/js/eos-account-management.js +1 -1
  19. package/adminWww/js/eos-assistant.js +5 -556
  20. package/adminWww/js/eos-basic-settings.js +1 -1
  21. package/adminWww/js/eos-branding-sanitizer.js +1 -3
  22. package/adminWww/js/eos-ems-overview.js +140 -0
  23. package/adminWww/js/eos-native-security.js +1 -1
  24. package/adminWww/js/eos-policy-client.js +1 -1
  25. package/adminWww/js/eos-role-bootstrap.js +4 -4
  26. package/adminWww/js/eos-role-ui.js +34 -38
  27. package/adminWww/js/nexowatt-native-shell.js +11 -6
  28. package/adminWww/static/js/nexowatt-stable-login.js +79 -0
  29. package/adminWww/static/js/nexowatt-stable-v96.js +82 -0
  30. package/build/lib/chat/assistantKnowledge.js +6 -21
  31. package/build/lib/chat/chatOrchestrator.js +38 -33
  32. package/build/lib/chat/mcpClientManager.js +1 -1
  33. package/build/lib/nexowattStableAuth.js +74 -0
  34. package/build/lib/web.js +24 -7
  35. package/build/main.js +123 -308
  36. package/io-package.json +25 -16
  37. package/package.json +32 -15
  38. package/tools/nexowatt-account-management-selftest.cjs +2 -3
  39. package/tools/nexowatt-apply-ems-overview.cjs +115 -0
  40. package/tools/nexowatt-clean-legacy-runtime.cjs +4 -0
  41. package/tools/nexowatt-ems-overview-selftest.cjs +35 -0
  42. package/tools/nexowatt-internal-reserve-selftest.cjs +1 -3
  43. package/tools/nexowatt-login-layout-selftest.cjs +43 -22
  44. package/tools/nexowatt-merge-update-selftest.cjs +69 -0
  45. package/tools/nexowatt-modern-ui-selftest.cjs +4 -5
  46. package/tools/nexowatt-stable-v96-selftest.cjs +13 -0
  47. package/tools/nexowatt-sync-release-version.cjs +181 -0
  48. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +16 -0
  49. package/tools/nexowatt-validate-package.cjs +41 -10
  50. package/tools/nexowatt-version-sync-selftest.cjs +86 -0
  51. package/INSTALL_TEST_V7.9.93.md +0 -12
  52. package/PUBLISH_STABLE_V7.9.93.md +0 -19
  53. package/README_STABILITY_V7.9.93.md +0 -15
  54. package/RELEASE_ACCEPTANCE_V7.9.93.md +0 -15
  55. package/RELEASE_NOTES_V7.9.93.md +0 -10
@@ -76,48 +76,53 @@ class ChatOrchestrator {
76
76
  this.mcp = mcp;
77
77
  this.adapter = adapter;
78
78
  }
79
- /** System prompt describing EOS Assist, tool usage and the authenticated role. */
80
- buildSystemPrompt(language, mode, uiContext, accessRole = 'enduser') {
81
- const roleText = accessRole === 'admin'
82
- ? 'Admin / NexoWatt Service with full service visibility'
83
- : accessRole === 'installer'
84
- ? 'Installer with commissioning and troubleshooting visibility'
85
- : 'End user with only explicitly permitted Smart Home and operating visibility';
79
+ /** System prompt describing the assistant's role, tool usage and the current permission mode. */
80
+ buildSystemPrompt(language, mode, uiContext) {
86
81
  const lines = [
87
- 'You are EOS Assist inside NexoWatt EOS.',
88
- 'Your job is to answer questions about the user\'s ACTUAL EOS system and guide them through the',
89
- 'NexoWatt EOS interface. You cover all settings and product areas: Cockpit/Overview, Modules,',
90
- 'Services, Datapoints, Structure (rooms/functions), System logs, Access & Rights, EOS applications,',
91
- 'System hosts, Files, NexoWatt Backup, licenses, devices, communication protocols and diagnostics.',
82
+ 'You are the ioBroker Admin Assistant, an in-app helper inside the ioBroker admin web UI.',
83
+ 'You help the user operate their ioBroker installation: explain how to do things in the UI,',
84
+ 'recommend which adapter to use for a device or service, and answer questions about the',
85
+ "user's actual system (their states, objects, devices grouped by room/function, installed",
86
+ 'adapters, running instances, logs and historical values).',
92
87
  '',
93
- `Authenticated role: ${roleText}.`,
94
- 'Never reveal data or instructions outside that role. The tool layer enforces ACLs; if data is not',
95
- 'available, state that it is not available to the current account instead of guessing.',
88
+ 'KEY CAPABILITY: you can navigate the admin UI yourself. When the user asks you to OPEN or GO to',
89
+ 'something, call the navigate_admin_ui tool with the hash route — it opens the tab OR the specific',
90
+ 'dialog immediately (no confirmation, it only changes the view), e.g.',
91
+ '{"hash":"#tab-hosts/base-settings/system.host.MSI"}. Otherwise (just pointing the user somewhere)',
92
+ 'offer a clickable Markdown hash link, e.g. [Base settings of MSI](#tab-hosts/base-settings/system.host.MSI).',
93
+ 'NEVER say a dialog can only be opened manually or that deep-linking a dialog is not possible.',
96
94
  '',
97
- 'Rules:',
98
- '- For every system-specific question, use the available read tools and base the answer on their results.',
99
- '- Never invent installed modules, running services, settings, datapoint values, devices or errors.',
100
- '- Use NexoWatt EOS product terminology in normal answers. Do not present the underlying platform name',
101
- ' as the product. Technical package IDs or command names may appear only in `code` when strictly needed.',
102
- '- Explain results clearly and practically. Use short lists or tables for multiple devices or values.',
103
- '- Object and state IDs are case-sensitive; show necessary IDs in `monospace`.',
104
- '- You may navigate the EOS interface with navigate_admin_ui when the user explicitly asks to open a',
105
- ' page. Use these EOS routes: Cockpit `#tab-intro`, Modules `#tab-adapters`, Services `#tab-instances`,',
106
- ' Datapoints `#tab-objects`, Structure `#tab-enums`, System logs `#tab-logs`, Access & Rights',
107
- ' `#tab-users`, and instance configuration `#tab-instances/config/<module>.<instance>`.',
108
- '- Do not claim an action was completed unless a tool result confirms it.',
109
- `- Answer in the user\'s language. The current EOS language is "${language}".`,
95
+ 'Guidelines:',
96
+ '- Use the available tools to look up real data from THIS ioBroker system instead of guessing.',
97
+ ' E.g. to list devices in a room call list_devices with the room name; to read a value call',
98
+ ' get_states; to find objects call search_objects.',
99
+ '- Base your answer on the tool results. If a tool returns an error or nothing, say so briefly',
100
+ ' and suggest what the user could check.',
101
+ '- Be concise and practical. Use short lists or tables when listing devices or states.',
102
+ '- Object IDs are case-sensitive (e.g. "hue.0.lights.1.on"); show them in `monospace`.',
103
+ '- IMPORTANT — this admin version SUPPORTS deep links that open specific DIALOGS directly,',
104
+ ' not just tabs. It is FALSE to say a dialog "can only be opened with the gear/cog icon" or',
105
+ ' that deep-linking a dialog is "technically not possible" — never say that. Instead always',
106
+ ' offer a clickable Markdown hash link and let the USER click it; do NOT claim you opened or',
107
+ ' navigated anywhere yourself. Examples: [Instances](#tab-instances), [Objects](#tab-objects),',
108
+ ' [hue.0 config](#tab-instances/config/hue.0), [edit this object](#tab-objects/edit/<id>),',
109
+ " and a host's base-settings dialog, e.g. MSI:",
110
+ ' [Base settings of MSI](#tab-hosts/base-settings/system.host.MSI). The exact dialog routes are',
111
+ ' listed in the knowledge below. Prefer these links over the navigate_admin_ui tool.',
112
+ `- Answer in the user's language. The admin UI language is currently "${language}".`,
110
113
  ];
111
114
  if (mode === 'act') {
112
- lines.push('- Changes are possible only through explicit write/action tools and confirmation. Propose the', ' smallest safe action and respect all EOS safety and role restrictions.');
115
+ lines.push('- You may change things using the write/action tools (set_state, create_user_state,', ' extend_object, assign_to_enums, install_adapter, run_command, run_node_script). Every such', ' call is shown to the user for explicit confirmation before it runs, so propose the smallest', ' sensible action.', '- create_user_state only works in "0_userdata.0." or "javascript.<n>." — never elsewhere.', '- extend_object merges common/native into an existing object — use it to start/stop an', ' instance (common.enabled), change instance settings (native) or set enum members.', '- assign_to_enums adds objects to room/function enums (add-only) — use it to sort devices.', '- run_command runs an ioBroker CLI command and streams its output to the user; use it for', ' maintenance actions (restart, upload, fix, ...), not to fetch data.', '- run_node_script executes a short Node.js script on the host to CHECK things (network, files,', ' environment). Prefer read-only checks, keep it short, and put results in console.log. Never', ' use it for destructive operations.', '- run_javascript runs a one-off JS/TS script INSIDE the javascript adapter (full ioBroker API:', ' on/getState/setState/schedule/sendTo). Use it to test automation logic or do an API-based', ' check/action; it is not saved. Use run_node_script instead for plain OS checks (no ioBroker API).');
113
116
  }
114
117
  else {
115
- lines.push('- This header assistant is READ-ONLY. It may inspect and explain the system, but it must not', ' change states, objects, settings, services, modules, passwords or backups.');
118
+ lines.push('- You currently have READ-ONLY access: you cannot change states, create objects or install', ' adapters. When a change is needed, explain the exact steps the user should take in the UI.');
116
119
  }
117
120
  if (uiContext?.hash) {
118
- lines.push('', `The user is currently on EOS route \`${uiContext.hash}\`. Consider this context.`);
121
+ lines.push('', `The user is currently in the admin UI at the route \`${uiContext.hash}\`. Take this into ` +
122
+ "account — don't tell them to open a tab/page they are already on, and refer to where " +
123
+ 'they are when helpful.');
119
124
  }
120
- lines.push('', 'NexoWatt EOS knowledge:', (0, assistantKnowledge_1.renderAssistantKnowledge)());
125
+ lines.push('', 'ioBroker how-to knowledge (use this to guide the user precisely):', (0, assistantKnowledge_1.renderAssistantKnowledge)());
121
126
  return { role: 'system', content: lines.join('\n') };
122
127
  }
123
128
  /** Assemble the tool list offered to the model for the given mode. */
@@ -149,7 +154,7 @@ class ChatOrchestrator {
149
154
  ? { ...message, content: parseTextToolCalls(message.content).content }
150
155
  : message);
151
156
  if (!messages.some(message => message.role === 'system')) {
152
- messages.unshift(this.buildSystemPrompt(language, mode, params.uiContext, params.accessRole));
157
+ messages.unshift(this.buildSystemPrompt(language, mode, params.uiContext));
153
158
  }
154
159
  const newMessages = [];
155
160
  const steps = [];
@@ -44,7 +44,7 @@ class McpClientManager {
44
44
  language: this.options.language,
45
45
  allowSetState: this.options.allowSetState ?? false,
46
46
  allowObjectChange: this.options.allowObjectChange ?? false,
47
- clientName: 'NexoWatt EOS Assist',
47
+ clientName: 'ioBroker.admin chat helper',
48
48
  clientVersion: '1.0.0',
49
49
  })
50
50
  .then(mcp => {
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startNexowattStableAuth = startNexowattStableAuth;
4
+ const START_PASSWORD = 'nexowatt';
5
+ const MARKER_KEYS = ["eosFirstLoginPending", "eosMustChangePassword", "eosPasswordSetupRequired", "firstLoginRequired", "mustChangePassword", "nexowattFirstLoginPending", "nexowattMustChangePassword", "nexowattPasswordSetupRequired", "passwordChangeRequired"];
6
+ const timers = new WeakMap();
7
+ function pendingByNative(native) {
8
+ if (!native || typeof native !== 'object') return false;
9
+ return MARKER_KEYS.some(key => native[key] === true || native[key] === 'pending' || native[key] === 'required' || native[key] === 'starter');
10
+ }
11
+ async function setPassword(adapter, user, password) {
12
+ if (typeof adapter.setPasswordAsync === 'function') return adapter.setPasswordAsync(user, password, { user: 'system.user.admin' });
13
+ return new Promise((resolve, reject) => adapter.setPassword(user, password, { user: 'system.user.admin' }, err => err ? reject(err) : resolve()));
14
+ }
15
+ async function ensureGroupMembership(adapter, userId, groupId) {
16
+ const group = await adapter.getForeignObjectAsync(groupId);
17
+ if (!group || group.type !== 'group') return;
18
+ group.common = group.common || { name: groupId.split('.').pop(), members: [] };
19
+ group.common.members = Array.isArray(group.common.members) ? group.common.members : [];
20
+ if (!group.common.members.includes(userId)) {
21
+ group.common.members.push(userId);
22
+ await adapter.setForeignObjectAsync(groupId, group, { user: 'system.user.admin' });
23
+ }
24
+ }
25
+ async function createUser(adapter, name) {
26
+ const id = `system.user.${name}`;
27
+ await adapter.setForeignObjectAsync(id, {
28
+ _id: id, type: 'user',
29
+ common: { name, enabled: true, password: '' },
30
+ native: { nexowattManagedAccount: true, nexowattMustChangePassword: true, nexowattPasswordState: 'starter' }
31
+ }, { user: 'system.user.admin' });
32
+ return adapter.getForeignObjectAsync(id);
33
+ }
34
+ async function prepareOne(adapter, name, group, createMissing) {
35
+ const id = `system.user.${name}`;
36
+ let obj = await adapter.getForeignObjectAsync(id);
37
+ let created = false;
38
+ if (!obj && createMissing) { obj = await createUser(adapter, name); created = true; }
39
+ if (!obj || obj.type !== 'user') return;
40
+ obj.native = obj.native || {};
41
+ const empty = !obj.common || !obj.common.password;
42
+ const pending = created || empty || pendingByNative(obj.native) || obj.native.nexowattPasswordState === 'starter';
43
+ if (pending) {
44
+ await setPassword(adapter, name, START_PASSWORD);
45
+ obj = await adapter.getForeignObjectAsync(id) || obj;
46
+ obj.native = obj.native || {};
47
+ obj.native.nexowattManagedAccount = true;
48
+ obj.native.nexowattStarterPasswordActive = true;
49
+ obj.native.nexowattMustChangePassword = true;
50
+ obj.native.nexowattPasswordState = 'starter';
51
+ obj.native.nexowattStarterPasswordUpdatedAt = new Date().toISOString();
52
+ for (const key of MARKER_KEYS) {
53
+ if (/required|must|pending|first/i.test(key)) obj.native[key] = true;
54
+ }
55
+ await adapter.setForeignObjectAsync(id, obj, { user: 'system.user.admin' });
56
+ }
57
+ await ensureGroupMembership(adapter, id, group);
58
+ }
59
+ async function reconcile(adapter) {
60
+ try {
61
+ await prepareOne(adapter, 'installer', 'system.group.installer', true);
62
+ const guest = await adapter.getForeignObjectAsync('system.user.guest');
63
+ const user = await adapter.getForeignObjectAsync('system.user.user');
64
+ if (guest || !user) await prepareOne(adapter, 'guest', 'system.group.endkunden', true);
65
+ if (user) await prepareOne(adapter, 'user', 'system.group.endkunden', false);
66
+ } catch (e) { adapter.log.warn(`[NexoWatt EOS login] Starter-account reconciliation failed: ${e && e.message || e}`); }
67
+ }
68
+ function startNexowattStableAuth(adapter) {
69
+ if (timers.has(adapter)) return;
70
+ void reconcile(adapter);
71
+ const timer = setInterval(() => void reconcile(adapter), 5000);
72
+ if (timer.unref) timer.unref();
73
+ timers.set(adapter, timer);
74
+ }
package/build/lib/web.js CHANGED
@@ -675,7 +675,11 @@ class Web {
675
675
  const account = (native.nexowattEosAccount || {});
676
676
  const initialized = account.passwordInitialized === true
677
677
  || Number(account.passwordSetupVersion || account.passwordInitializationVersion || 0) >= 1;
678
- const forced = account.forcePasswordChange === true;
678
+ const stableForced = native.nexowattPasswordChangeRequired === true
679
+ || native.eosPasswordChangeRequired === true
680
+ || native.nexowattFirstLoginPending === true
681
+ || native.eosFirstLoginRequired === true;
682
+ const forced = account.forcePasswordChange === true || stableForced;
679
683
  return { required: forced || !initialized, initialized: initialized && !forced, minLength, version: 1 };
680
684
  }
681
685
  catch (e) {
@@ -789,6 +793,12 @@ class Web {
789
793
  account.firstLoginCompletedAt = now;
790
794
  account.forcePasswordChange = false;
791
795
  account.passwordlessFirstLoginAllowed = false;
796
+ native.nexowattPasswordChangeRequired = false;
797
+ native.eosPasswordChangeRequired = false;
798
+ native.nexowattFirstLoginPending = false;
799
+ native.eosFirstLoginRequired = false;
800
+ native.nexowattInitialPasswordApplied = false;
801
+ native.nexowattPasswordChangedAt = now;
792
802
  await this.adapter.setForeignObjectAsync(access.userId, userObject);
793
803
  await this.destroyEosRequestSessions(req);
794
804
  for (const cookie of ['access_token', 'refresh_token', 'connect.sid']) {
@@ -1133,8 +1143,7 @@ class Web {
1133
1143
  res.status(409).json({ error: 'accountDisabled' });
1134
1144
  return;
1135
1145
  }
1136
- const bootstrapSecret = `${(0, node_crypto_1.randomBytes)(48).toString('base64url')}!aA1`;
1137
- await this.setEosUserPassword(targetUserId, bootstrapSecret);
1146
+ await this.setEosUserPassword(targetUserId, 'nexowatt');
1138
1147
  const user = (await this.adapter.getForeignObjectAsync(targetUserId));
1139
1148
  if (!user) {
1140
1149
  throw new Error('userObjectUnavailableAfterPasswordReset');
@@ -1146,21 +1155,29 @@ class Web {
1146
1155
  account.passwordSetupVersion = 0;
1147
1156
  account.passwordInitializationVersion = 0;
1148
1157
  account.forcePasswordChange = true;
1149
- account.passwordlessFirstLoginAllowed = true;
1158
+ account.passwordlessFirstLoginAllowed = false;
1150
1159
  account.passwordResetAt = now;
1151
1160
  account.passwordResetBy = access.userId;
1152
- account.passwordResetMode = 'passwordless-first-activation';
1161
+ account.passwordResetMode = 'initial-password';
1162
+ native.nexowattPasswordChangeRequired = true;
1163
+ native.eosPasswordChangeRequired = true;
1164
+ native.nexowattFirstLoginPending = true;
1165
+ native.eosFirstLoginRequired = true;
1166
+ native.nexowattInitialPasswordApplied = true;
1167
+ native.nexowattInitialPasswordVersion = 1;
1168
+ native.nexowattStableInitialCredentialVersion = 1;
1153
1169
  delete account.passwordSetAt;
1154
1170
  delete account.firstLoginCompletedAt;
1155
1171
  await this.adapter.setForeignObjectAsync(targetUserId, user);
1156
1172
  this.invalidateEosPasswordClaimsForUser(targetUserId);
1157
- this.adapter.log.warn(`EOS account ${targetUserId} was reset for passwordless first activation by ${access.userId}`);
1173
+ this.adapter.log.warn(`EOS account ${targetUserId} was reset to the mandatory initial-password flow by ${access.userId}`);
1158
1174
  res.status(200).json({
1159
1175
  success: true,
1160
1176
  user: targetUserId,
1161
1177
  role: explicitlyManagedRole,
1162
1178
  firstLoginRequired: true,
1163
- passwordlessFirstLoginAllowed: true,
1179
+ passwordlessFirstLoginAllowed: false,
1180
+ initialPasswordRequired: true,
1164
1181
  });
1165
1182
  }
1166
1183
  async getEosHistoryInstances() {