iobroker.javascript 9.1.1 → 9.2.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.
Files changed (91) hide show
  1. package/README.md +32 -16
  2. package/admin/assets/AiChatPanel-Cc1KscBJ.js +315 -0
  3. package/admin/assets/AiDatapointProvider-B0i3I5Fm.js +2 -0
  4. package/admin/assets/AiDiffView-RPozjjHf.js +1 -0
  5. package/admin/assets/AiInlineProvider-DkgjVSFQ.js +7 -0
  6. package/admin/assets/{Save-Cld9qv9i.js → Save-CkuNSiWL.js} +1 -1
  7. package/admin/assets/ScriptEditor-6qrtvwPL.js +2 -0
  8. package/admin/assets/ScriptEditorVanillaMonaco-bJua4sm1.js +3 -0
  9. package/admin/assets/applyCodeEdit-BdRnbz2g.js +1 -0
  10. package/admin/assets/{createSvgIcon-DPvF8xqu.js → createSvgIcon-DYs_tNrE.js} +1 -1
  11. package/admin/assets/docs-compact-CKNGiYh2.js +336 -0
  12. package/admin/assets/{index-BVPl8hv0.js → index-BZxCaQhO.js} +5 -5
  13. package/admin/assets/index-CYbVtccs.js +1267 -0
  14. package/admin/assets/index-CexrwGGC.js +986 -0
  15. package/admin/assets/{index-DFZipply.js → index-DsW7vBcg.js} +1 -1
  16. package/admin/assets/index-SHs7_wDh.js +1 -0
  17. package/admin/assets/{index-DlDUIfpA.js → index-dN73sbyQ.js} +1 -1
  18. package/admin/assets/index-wHNMOf-c.js +5 -0
  19. package/admin/assets/inheritsLoose-CIXm_zPk.js +29 -0
  20. package/admin/assets/inlineChatWidget-Cu6rb4Ar.js +2 -0
  21. package/admin/assets/inlineDiffController-DBLpPOe2.js +16 -0
  22. package/admin/assets/{iobroker_javascript__loadShare___mf_0_mui_mf_1_material__loadShare__.mjs-QWG5Arqe.js → iobroker_javascript__loadShare___mf_0_mui_mf_1_material__loadShare__.mjs-CV1_X-Lf.js} +1 -1
  23. package/admin/assets/{localSharedImportMap-xmSu9Qkg.js → localSharedImportMap-BCwqnmn4.js} +1 -1
  24. package/admin/assets/stateHoverProvider-CGChzV5W.js +3 -0
  25. package/admin/assets/useTheme-DuTJsNQ-.js +1 -0
  26. package/admin/assets/{virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-D-eF6r8M.js → virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-DNrEsMrg.js} +2 -2
  27. package/admin/custom/assets/{Components-B8x6wOMD.js → Components-B9ZyjeRI.js} +1 -1
  28. package/admin/custom/assets/{index-N2n19KrF.js → index-DNKzDki_.js} +152 -152
  29. package/admin/custom/assets/{index-DbXK0odl.js → index-NItS-TWp.js} +1 -1
  30. package/admin/custom/assets/index-sozs10Ij.js +960 -0
  31. package/admin/custom/assets/{localSharedImportMap-2Enh8EZe.js → localSharedImportMap-BxT0VwTO.js} +1 -1
  32. package/admin/custom/assets/{virtualExposes-CqrW7o8v.js → virtualExposes-jSvr-kK3.js} +1 -1
  33. package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-BZZsMeVW.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-Br3IlnqO.js} +2 -2
  34. package/admin/custom/customComponents.js +1 -1
  35. package/admin/google-blockly/own/blocks_words.js +7 -0
  36. package/admin/google-blockly/own/field_oid.js +311 -37
  37. package/admin/i18n/de.json +8 -0
  38. package/admin/i18n/en.json +8 -0
  39. package/admin/i18n/es.json +8 -0
  40. package/admin/i18n/flat.txt +427 -0
  41. package/admin/i18n/fr.json +8 -0
  42. package/admin/i18n/it.json +8 -0
  43. package/admin/i18n/nl.json +8 -0
  44. package/admin/i18n/pl.json +8 -0
  45. package/admin/i18n/pt.json +8 -0
  46. package/admin/i18n/ru.json +8 -0
  47. package/admin/i18n/uk.json +8 -0
  48. package/admin/i18n/zh-cn.json +8 -0
  49. package/admin/img/anthropic.svg +1 -0
  50. package/admin/img/custom.svg +1 -0
  51. package/admin/img/deepseek.svg +1 -0
  52. package/admin/img/gemini.svg +1 -0
  53. package/admin/img/openai.svg +1 -0
  54. package/admin/jsonConfig.json +80 -18
  55. package/admin/mf-manifest.json +1 -1
  56. package/admin/remoteEntry.js +1 -1
  57. package/admin/tab.html +6 -6
  58. package/build/lib/aiProviderResolver.js +80 -0
  59. package/build/lib/aiProviderResolver.js.map +1 -0
  60. package/build/lib/anthropicAdapter.js +175 -0
  61. package/build/lib/anthropicAdapter.js.map +1 -0
  62. package/build/lib/debugger.js +2 -2
  63. package/build/lib/debugger.js.map +1 -1
  64. package/build/lib/inspect.js.map +1 -1
  65. package/build/lib/nodeModulesManagement.js +2 -2
  66. package/build/lib/nodeModulesManagement.js.map +1 -1
  67. package/build/lib/protectFs.js +5 -0
  68. package/build/lib/protectFs.js.map +1 -1
  69. package/build/lib/sandbox.js +9 -4
  70. package/build/lib/sandbox.js.map +1 -1
  71. package/build/main.js +190 -101
  72. package/build/main.js.map +1 -1
  73. package/build/types.d.ts +3 -0
  74. package/docs/de/README.md +81 -10
  75. package/docs/de/blockly.md +34 -0
  76. package/docs/en/README.md +81 -10
  77. package/docs/en/blockly.md +34 -0
  78. package/install/installTypings.js +1 -1
  79. package/io-package.json +82 -54
  80. package/package.json +7 -6
  81. package/admin/assets/OpenAiDialog-rhLuRLIM.js +0 -13
  82. package/admin/assets/ScriptEditor-C-fO5h68.js +0 -2
  83. package/admin/assets/ScriptEditorVanillaMonaco-DSYvOslZ.js +0 -1
  84. package/admin/assets/docs-BrInh2Pn.js +0 -2036
  85. package/admin/assets/index-C49cpVll.js +0 -5
  86. package/admin/assets/index-XSrHkgLE.js +0 -1230
  87. package/admin/assets/index-gIA8x-8f.js +0 -1
  88. package/admin/assets/index-q6iFaoFb.js +0 -986
  89. package/admin/assets/inheritsLoose-B0Ba5G9z.js +0 -29
  90. package/admin/assets/useTheme-hy6f3gra.js +0 -1
  91. package/admin/custom/assets/index-CYc31Gwg.js +0 -960
package/build/main.js CHANGED
@@ -78,6 +78,8 @@ const protectFs_1 = __importDefault(require("./lib/protectFs"));
78
78
  const words_1 = require("./lib/words");
79
79
  const sandbox_1 = require("./lib/sandbox");
80
80
  const nodeModulesManagement_1 = require("./lib/nodeModulesManagement");
81
+ const aiProviderResolver_1 = require("./lib/aiProviderResolver");
82
+ const anthropicAdapter_1 = require("./lib/anthropicAdapter");
81
83
  const eventObj_1 = require("./lib/eventObj");
82
84
  const scheduler_1 = require("./lib/scheduler");
83
85
  const typescriptSettings_1 = require("./lib/typescriptSettings");
@@ -225,6 +227,19 @@ const jsDeclarationServer = new virtual_tsc_1.Server(typescriptSettings_1.jsDecl
225
227
  * Stores the IDs of script objects whose change should be ignored because
226
228
  * the compiled source was just updated
227
229
  */
230
+ function httpStatusText(code) {
231
+ const texts = {
232
+ 400: 'Bad Request',
233
+ 401: 'Unauthorized',
234
+ 403: 'Forbidden',
235
+ 404: 'Not Found',
236
+ 429: 'Too Many Requests / Rate Limit',
237
+ 500: 'Internal Server Error',
238
+ 502: 'Bad Gateway',
239
+ 503: 'Service Unavailable',
240
+ };
241
+ return texts[code] || `Error ${code}`;
242
+ }
228
243
  class JavaScript extends adapter_core_1.Adapter {
229
244
  context;
230
245
  errorLogFunction = {
@@ -565,9 +580,9 @@ class JavaScript extends adapter_core_1.Adapter {
565
580
  await this.loadScriptById(id);
566
581
  }
567
582
  }
568
- else {
569
- // if (obj.common.source !== formerObj.common.source) {
570
- // Source changed => restart the script
583
+ else if (obj.common.engine === `system.adapter.${this.namespace}` ||
584
+ formerObj.common.engine === `system.adapter.${this.namespace}`) {
585
+ // Source changed => restart the script (only on the relevant instance)
571
586
  this.stopCounters[id] = this.stopCounters[id] ? this.stopCounters[id] + 1 : 1;
572
587
  void this.stopScript(id).then(() => {
573
588
  // only start again after stop when "last" object change to prevent problems on
@@ -905,13 +920,15 @@ class JavaScript extends adapter_core_1.Adapter {
905
920
  break;
906
921
  }
907
922
  case 'chatCompletion': {
908
- // Proxy chat completion requests to an OpenAI-compatible API endpoint
923
+ // Proxy chat completion requests to an OpenAI-compatible API endpoint.
924
+ // API keys are resolved server-side from encryptedNative config — they never
925
+ // leave the adapter (frontend only sends `provider`, not the key).
909
926
  if (obj.callback) {
910
- const baseUrl = (obj.message?.baseUrl || '').trim();
911
- const apiKey = (obj.message?.apiKey || '').trim();
912
927
  const chatModel = (obj.message?.model || '').trim();
913
928
  const messages = obj.message?.messages;
929
+ const tools = obj.message?.tools;
914
930
  const provider = (obj.message?.provider || 'openai').trim();
931
+ const { apiKey, baseUrl } = (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider, obj.message?.baseUrl);
915
932
  // Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
916
933
  if (!apiKey &&
917
934
  (provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
@@ -931,15 +948,16 @@ class JavaScript extends adapter_core_1.Adapter {
931
948
  url = 'https://api.anthropic.com/v1/messages';
932
949
  chatHeaders['x-api-key'] = apiKey;
933
950
  chatHeaders['anthropic-version'] = '2023-06-01';
934
- const systemMessages = messages.filter((m) => m.role === 'system');
935
- const nonSystemMessages = messages.filter((m) => m.role !== 'system');
936
- const systemText = systemMessages.map((m) => m.content).join('\n\n');
951
+ // Translate OpenAI-format messages/tools into Anthropic's content-block format.
952
+ const { system: systemText, messages: anthropicMessages } = (0, anthropicAdapter_1.translateMessagesToAnthropic)(messages);
953
+ const anthropicTools = tools?.length ? (0, anthropicAdapter_1.translateToolsToAnthropic)(tools) : [];
937
954
  bodyObj = {
938
955
  model: chatModel,
939
956
  max_tokens: 8192,
940
957
  stream: false,
941
958
  ...(systemText ? { system: systemText } : {}),
942
- messages: nonSystemMessages,
959
+ messages: anthropicMessages,
960
+ ...(anthropicTools.length ? { tools: anthropicTools } : {}),
943
961
  };
944
962
  }
945
963
  else if (provider === 'gemini') {
@@ -947,19 +965,26 @@ class JavaScript extends adapter_core_1.Adapter {
947
965
  if (apiKey) {
948
966
  chatHeaders.Authorization = `Bearer ${apiKey}`;
949
967
  }
950
- bodyObj = { model: chatModel, messages, stream: false };
968
+ bodyObj = { model: chatModel, messages, stream: false, ...(tools?.length ? { tools } : {}) };
951
969
  }
952
970
  else if (provider === 'deepseek') {
953
971
  url = 'https://api.deepseek.com/chat/completions';
954
972
  chatHeaders.Authorization = `Bearer ${apiKey}`;
955
- bodyObj = { model: chatModel, messages, stream: false };
973
+ bodyObj = { model: chatModel, messages, stream: false, ...(tools?.length ? { tools } : {}) };
956
974
  }
957
975
  else {
958
976
  url = `${baseUrl || 'https://api.openai.com/v1'}/chat/completions`;
959
977
  if (apiKey) {
960
978
  chatHeaders.Authorization = `Bearer ${apiKey}`;
961
979
  }
962
- bodyObj = { model: chatModel, messages, stream: false };
980
+ bodyObj = {
981
+ model: chatModel,
982
+ messages,
983
+ stream: false,
984
+ ...(tools?.length ? { tools } : {}),
985
+ // Disable thinking/reasoning for local models to save context and speed
986
+ ...(baseUrl ? { reasoning_effort: 'none' } : {}),
987
+ };
963
988
  }
964
989
  const body = JSON.stringify(bodyObj);
965
990
  const bodyBuffer = Buffer.from(body, 'utf8');
@@ -974,67 +999,86 @@ class JavaScript extends adapter_core_1.Adapter {
974
999
  }
975
1000
  const isHttps = urlObj.protocol === 'https:';
976
1001
  const requestModule = isHttps ? https : http;
977
- const req = requestModule.request(url, {
978
- method: 'POST',
979
- headers: chatHeaders,
980
- timeout: 600000,
981
- ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
982
- }, res => {
983
- let data = '';
984
- res.on('data', (chunk) => {
985
- data += chunk.toString();
986
- });
987
- res.on('end', () => {
988
- if (res.statusCode === 200) {
989
- try {
990
- const parsed = JSON.parse(data);
991
- const content = provider === 'anthropic'
992
- ? parsed.content?.[0]?.text || ''
993
- : parsed.choices?.[0]?.message?.content || '';
994
- if (!content) {
995
- this.sendTo(obj.from, obj.command, { error: 'Empty response from API' }, obj.callback);
1002
+ try {
1003
+ const req = requestModule.request(url, {
1004
+ method: 'POST',
1005
+ headers: chatHeaders,
1006
+ timeout: 600000,
1007
+ ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1008
+ }, res => {
1009
+ let data = '';
1010
+ res.on('data', (chunk) => {
1011
+ data += chunk.toString();
1012
+ });
1013
+ res.on('end', () => {
1014
+ if (res.statusCode === 200) {
1015
+ try {
1016
+ const parsed = JSON.parse(data);
1017
+ let content;
1018
+ let tool_calls;
1019
+ if (provider === 'anthropic') {
1020
+ const translated = (0, anthropicAdapter_1.translateAnthropicResponseToOpenAI)(parsed);
1021
+ content = translated.content;
1022
+ tool_calls = translated.tool_calls;
1023
+ }
1024
+ else {
1025
+ const message = parsed.choices?.[0]?.message;
1026
+ content = message?.content || '';
1027
+ tool_calls = message?.tool_calls;
1028
+ }
1029
+ if (!content && !tool_calls?.length) {
1030
+ this.sendTo(obj.from, obj.command, { error: 'Empty response from API' }, obj.callback);
1031
+ }
1032
+ else {
1033
+ this.sendTo(obj.from, obj.command, {
1034
+ success: true,
1035
+ content,
1036
+ ...(tool_calls ? { tool_calls } : {}),
1037
+ }, obj.callback);
1038
+ }
996
1039
  }
997
- else {
998
- this.sendTo(obj.from, obj.command, { success: true, content }, obj.callback);
1040
+ catch {
1041
+ this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
999
1042
  }
1000
1043
  }
1001
- catch {
1002
- this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1003
- }
1004
- }
1005
- else {
1006
- let errorMsg = `API returned status ${res.statusCode}`;
1007
- try {
1008
- const parsed = JSON.parse(data);
1009
- if (parsed.error?.message) {
1010
- errorMsg = parsed.error.message;
1044
+ else {
1045
+ let detail = '';
1046
+ try {
1047
+ const errParsed = JSON.parse(data);
1048
+ detail = errParsed.error?.message || data.substring(0, 200);
1011
1049
  }
1050
+ catch {
1051
+ detail = data.substring(0, 200);
1052
+ }
1053
+ this.sendTo(obj.from, obj.command, {
1054
+ error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1055
+ }, obj.callback);
1012
1056
  }
1013
- catch {
1014
- // ignore parse error
1015
- }
1016
- this.sendTo(obj.from, obj.command, { error: errorMsg }, obj.callback);
1017
- }
1057
+ });
1018
1058
  });
1019
- });
1020
- req.on('error', (err) => {
1021
- this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1022
- });
1023
- req.on('timeout', () => {
1024
- req.destroy();
1025
- this.sendTo(obj.from, obj.command, { error: 'Connection timeout (600s)' }, obj.callback);
1026
- });
1027
- req.write(bodyBuffer);
1028
- req.end();
1059
+ req.on('error', (err) => {
1060
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1061
+ });
1062
+ req.on('timeout', () => {
1063
+ req.destroy();
1064
+ this.sendTo(obj.from, obj.command, { error: 'Connection timeout (600s)' }, obj.callback);
1065
+ });
1066
+ req.write(bodyBuffer);
1067
+ req.end();
1068
+ }
1069
+ catch (error) {
1070
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${error.toString()}` }, obj.callback);
1071
+ }
1029
1072
  }
1030
1073
  break;
1031
1074
  }
1032
1075
  case 'testApiConnection': {
1033
- // Test connection to an OpenAI-compatible API endpoint
1076
+ // Test connection to an OpenAI-compatible API endpoint.
1077
+ // The settings-dialog Test button sends the current form value as `apiKey`
1078
+ // (so users can test before saving); otherwise we fall back to the stored key.
1034
1079
  if (obj.callback) {
1035
- const baseUrl = (obj.message?.baseUrl || '').trim();
1036
- const apiKey = (obj.message?.apiKey || '').trim();
1037
1080
  const provider = (obj.message?.provider || 'openai').trim();
1081
+ const { apiKey, baseUrl } = (0, aiProviderResolver_1.resolveTestCredentials)(this.config, provider, obj.message?.apiKey, obj.message?.baseUrl);
1038
1082
  // Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
1039
1083
  if (!apiKey &&
1040
1084
  (provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
@@ -1076,48 +1120,72 @@ class JavaScript extends adapter_core_1.Adapter {
1076
1120
  }
1077
1121
  const isHttps = urlObj.protocol === 'https:';
1078
1122
  const requestModule = isHttps ? https : http;
1079
- const req = requestModule.request(url, {
1080
- method: 'GET',
1081
- headers: testHeaders,
1082
- timeout: 10000,
1083
- ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1084
- }, res => {
1085
- let data = '';
1086
- res.on('data', (chunk) => {
1087
- data += chunk.toString();
1088
- });
1089
- res.on('end', () => {
1090
- if (res.statusCode === 200) {
1091
- try {
1092
- const parsed = JSON.parse(data);
1093
- const models = (parsed.data || [])
1094
- .map((m) => m.id.startsWith('models/') ? m.id.substring(7) : m.id)
1095
- .sort();
1096
- this.sendTo(obj.from, obj.command, { success: true, models, count: models.length }, obj.callback);
1123
+ try {
1124
+ const req = requestModule.request(url, {
1125
+ method: 'GET',
1126
+ headers: testHeaders,
1127
+ timeout: 10000,
1128
+ ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1129
+ }, res => {
1130
+ let data = '';
1131
+ res.on('data', (chunk) => {
1132
+ data += chunk.toString();
1133
+ });
1134
+ res.on('end', () => {
1135
+ if (res.statusCode === 200) {
1136
+ try {
1137
+ const parsed = JSON.parse(data);
1138
+ const models = (parsed.data || [])
1139
+ .map((m) => m.id.startsWith('models/') ? m.id.substring(7) : m.id)
1140
+ .sort();
1141
+ this.sendTo(obj.from, obj.command, { success: true, models, count: models.length }, obj.callback);
1142
+ }
1143
+ catch {
1144
+ this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1145
+ }
1097
1146
  }
1098
- catch {
1099
- this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1147
+ else if (res.statusCode === 401) {
1148
+ this.sendTo(obj.from, obj.command, { error: 'Invalid API key (401)' }, obj.callback);
1100
1149
  }
1101
- }
1102
- else if (res.statusCode === 401) {
1103
- this.sendTo(obj.from, obj.command, { error: 'Invalid API key (401)' }, obj.callback);
1104
- }
1105
- else if (res.statusCode === 403) {
1106
- this.sendTo(obj.from, obj.command, { error: 'Access denied (403)' }, obj.callback);
1107
- }
1108
- else {
1109
- this.sendTo(obj.from, obj.command, { error: `API returned status ${res.statusCode}` }, obj.callback);
1110
- }
1150
+ else if (res.statusCode === 403) {
1151
+ this.sendTo(obj.from, obj.command, { error: 'Access denied (403)' }, obj.callback);
1152
+ }
1153
+ else {
1154
+ // Include response body for debugging
1155
+ let detail = '';
1156
+ try {
1157
+ const errParsed = JSON.parse(data);
1158
+ detail = errParsed.error?.message || data.substring(0, 200);
1159
+ }
1160
+ catch {
1161
+ detail = data.substring(0, 200);
1162
+ }
1163
+ this.sendTo(obj.from, obj.command, {
1164
+ error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1165
+ }, obj.callback);
1166
+ }
1167
+ });
1111
1168
  });
1112
- });
1113
- req.on('error', (err) => {
1114
- this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1115
- });
1116
- req.on('timeout', () => {
1117
- req.destroy();
1118
- this.sendTo(obj.from, obj.command, { error: 'Connection timeout (10s)' }, obj.callback);
1119
- });
1120
- req.end();
1169
+ req.on('error', (err) => {
1170
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1171
+ });
1172
+ req.on('timeout', () => {
1173
+ req.destroy();
1174
+ this.sendTo(obj.from, obj.command, { error: 'Connection timeout (10s)' }, obj.callback);
1175
+ });
1176
+ req.end();
1177
+ }
1178
+ catch (error) {
1179
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${error.toString()}` }, obj.callback);
1180
+ }
1181
+ }
1182
+ break;
1183
+ }
1184
+ case 'getAvailableAiProviders': {
1185
+ // Reports which AI providers have stored credentials (keys never leave the backend).
1186
+ if (obj.callback) {
1187
+ const providers = (0, aiProviderResolver_1.listAvailableProviders)(this.config);
1188
+ this.sendTo(obj.from, obj.command, { providers }, obj.callback);
1121
1189
  }
1122
1190
  break;
1123
1191
  }
@@ -1281,6 +1349,10 @@ class JavaScript extends adapter_core_1.Adapter {
1281
1349
  this.context.allowSelfSignedCerts = this.config.allowSelfSignedCerts;
1282
1350
  const doc = await this.getObjectViewAsync('script', 'javascript', {});
1283
1351
  if (doc?.rows?.length) {
1352
+ // sort global scripts if configured
1353
+ if (this.config.sortGlobalScriptsAlphabetically) {
1354
+ doc.rows.sort((a, b) => a.value._id.localeCompare(b.value._id));
1355
+ }
1284
1356
  // assemble global script
1285
1357
  for (let g = 0; g < doc.rows.length; g++) {
1286
1358
  const obj = doc.rows[g].value;
@@ -2145,6 +2217,9 @@ class JavaScript extends adapter_core_1.Adapter {
2145
2217
  }
2146
2218
  }
2147
2219
  async stopScript(name) {
2220
+ if (!this.scripts[name]) {
2221
+ return false;
2222
+ }
2148
2223
  this.log.info(`${name}: Stopping script`);
2149
2224
  await this.setState(`scriptEnabled.${name.substring(SCRIPT_CODE_MARKER.length)}`, false, true);
2150
2225
  if (this.messageBusHandlers[name]) {
@@ -2228,6 +2303,20 @@ class JavaScript extends adapter_core_1.Adapter {
2228
2303
  for (let i = 0; i < this.scripts[name].intervals.length; i++) {
2229
2304
  clearInterval(this.scripts[name].intervals[i]);
2230
2305
  }
2306
+ // Stop all delayed states (setStateDelayed timers)
2307
+ for (const stateId of Object.keys(this.timers)) {
2308
+ if (this.timers[stateId]) {
2309
+ for (let i = this.timers[stateId].length - 1; i >= 0; i--) {
2310
+ if (this.timers[stateId][i].scriptName === name) {
2311
+ clearTimeout(this.timers[stateId][i].t);
2312
+ this.timers[stateId].splice(i, 1);
2313
+ }
2314
+ }
2315
+ if (!this.timers[stateId].length) {
2316
+ delete this.timers[stateId];
2317
+ }
2318
+ }
2319
+ }
2231
2320
  // Stop all scheduled jobs
2232
2321
  for (let i = 0; i < this.scripts[name].schedules.length; i++) {
2233
2322
  if (this.scripts[name].schedules[i]) {