iobroker.javascript 10.2.5 → 10.4.0
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/README.md +26 -23
- package/admin/assets/AiChatPanel-BjRIKaXG.js +27 -0
- package/admin/assets/{AiChatService-BOL3J2Jd.js → AiChatService-DP1EmVk2.js} +3 -3
- package/admin/assets/AiDiffView-DeynDw7L.js +1 -0
- package/admin/assets/{AiInlineProvider-BIlcrTkS.js → AiInlineProvider-jkEIJJJ-.js} +1 -1
- package/admin/assets/BlocklyEditor-DhwThchX.js +301 -0
- package/admin/assets/Compile-B0uHjrBE.css +1 -0
- package/admin/assets/Compile-e8N5zI-J.js +165 -0
- package/admin/assets/Debugger-BDQUKM1K.js +15 -0
- package/admin/assets/Diff--plofAtP.js +1 -0
- package/admin/assets/{Documentation-Bc0RmoEF.js → Documentation-D3E0U4AK.js} +4 -4
- package/admin/assets/ExpandMore-Dio4GeHC.js +1 -0
- package/admin/assets/FbEditor-B8S-mCvc.css +1 -0
- package/admin/assets/FbEditor-BjLPNzpK.js +15 -0
- package/admin/assets/History-Ewi5YEEn.js +1 -0
- package/admin/assets/Import-6Mm2vrM2.js +1 -0
- package/admin/assets/MonacoDiff-BYvG7IqB.js +1 -0
- package/admin/assets/NotificationsActive-CuNfWVJM.js +1 -0
- package/admin/assets/RulesEditor--YX6ETsU.js +1 -0
- package/admin/assets/{ScriptEditor-CEHpEFp7.js → ScriptEditor-BadPC8Mf.js} +2 -2
- package/admin/assets/ScriptEditorVanillaMonaco-BnAmy59d.js +3 -0
- package/admin/assets/Send-Pz1eveU5.js +1 -0
- package/admin/assets/_virtual_mf-localSharedImportMap___mfe_internal__iobroker_javascript__mf_owner__1-Ceh8EDcF.js +1 -0
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__234968779812303__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BRdHvuuG.js +1217 -0
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__234968779812303__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-DLWT3PGo.js +9 -0
- package/admin/assets/aiPromptBuilder-Co6AqZwx.js +1 -0
- package/admin/assets/{blockly-fcfyKYzp.js → blockly-B0t_IRJQ.js} +1 -1
- package/admin/assets/blocklyTheme-iZoge1S9.js +1 -0
- package/admin/assets/blocks_action-Ctn5A1AU.js +1 -0
- package/admin/assets/{blocks_convert-BP2RjhQH.js → blocks_convert-BI0lf0sf.js} +1 -1
- package/admin/assets/blocks_logic-o49klUX_.js +1 -0
- package/admin/assets/blocks_number-DZtyWFyR.js +1 -0
- package/admin/assets/blocks_object-BuzGCxVr.js +1 -0
- package/admin/assets/blocks_procedures-jn9sIYmR.js +3 -0
- package/admin/assets/blocks_sendto-Coui8w1H.js +5 -0
- package/admin/assets/blocks_switch-CeRZizTM.js +2 -0
- package/admin/assets/blocks_system-CaBPIC40.js +1 -0
- package/admin/assets/blocks_text-DeDtPYWf.js +1 -0
- package/admin/assets/blocks_time-CTXQH3Lm.js +1 -0
- package/admin/assets/blocks_timeout-D8IOKOb-.js +1 -0
- package/admin/assets/{blocks_trigger-CnmXLCUE.js → blocks_trigger-zuuXJbhb.js} +2 -2
- package/admin/assets/{cronHoverProvider-CcIFAhVy.js → cronHoverProvider-Curcjz4m.js} +1 -1
- package/admin/assets/dist-CzitsFi7.js +1 -0
- package/admin/assets/field_cron-RgLY53_S.js +1 -0
- package/admin/assets/field_oid-rBhotXJe.js +1 -0
- package/admin/assets/field_script-CU7g3-oB.js +1 -0
- package/admin/assets/helpers-BZgjFQNf.js +1 -0
- package/admin/assets/helpers-BwllSRzJ.js +1 -0
- package/admin/assets/hostInit-CrHjJGJJ.js +1 -0
- package/admin/assets/index-BHhhebRG.css +1 -0
- package/admin/assets/index-Bmmscq2G.js +157 -0
- package/admin/assets/{inlineChatWidget-N8xIkvxX.js → inlineChatWidget-ChchJD9a.js} +2 -2
- package/admin/assets/{inlineDiffController-BuqYsKKq.js → inlineDiffController-ly6qX4jk.js} +2 -2
- package/admin/assets/loadBlocks-BcFtthDb.js +117 -0
- package/admin/assets/loadMonaco-BVOEahNw.js +1 -0
- package/admin/assets/{mf-entry-bootstrap-0-a5a39621.js → mf-entry-bootstrap-0-6db6c4fc.js} +3 -3
- package/admin/assets/pendingShares-mBGPbRwZ.js +2 -0
- package/admin/assets/reactour.esm-Dk3aGxKn.js +137 -0
- package/admin/assets/{stateHoverProvider-BYAbe1Ay.js → stateHoverProvider-wAsP-THx.js} +1 -1
- package/admin/assets/tileFbd-CWAy7R7m.svg +85 -0
- package/admin/assets/utils-DUKzLJUz.js +4 -0
- package/admin/assets/vite-preload-helper-DjNwdemH.js +1 -0
- package/admin/custom/assets/Components-fDcxCyN4.js +1 -0
- package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1-BraOrMSu.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-DbKT6Hsb.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-CVvbIPAy.js +1 -0
- package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DVb0ZZ1Q.js → _virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BhZYie9-.js} +89 -89
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_mui_mf_1_x_mf_2_date_mf_2_pickers__loadShare__.js-CV50dgj4.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare__date_mf_2_fns__loadShare__.js-Djg9OMcQ.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare__leaflet__loadShare__.js-D9EyCd42.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare__shared-Hb6V6BnJ.js +2 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild___mf_0_iobroker_mf_1_json_mf_2_config__prebuild__-BrY-nF2a.js +122 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild__leaflet__prebuild__-CHjWtrkp.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild__react_mf_2_dom_mf_1_client__prebuild__-DOkC-g0X.js +9 -0
- package/admin/custom/assets/dist-CzitsFi7.js +1 -0
- package/admin/custom/assets/helpers-BZgjFQNf.js +1 -0
- package/admin/custom/assets/hostInit-akwaQLq8.js +1 -0
- package/admin/custom/assets/index-BkItz-nL.js +1 -0
- package/admin/custom/assets/{lib-BmDlTzq2.js → lib-CADOx2YN.js} +1 -1
- package/admin/custom/assets/{mf-entry-bootstrap-0-ad1cd7d0.js → mf-entry-bootstrap-0-3c4565e0.js} +3 -3
- package/admin/custom/assets/pendingShares-TCMDgBLn.js +2 -0
- package/admin/custom/assets/utils-DUKzLJUz.js +4 -0
- package/admin/custom/assets/vite-preload-helper-DjNwdemH.js +1 -0
- package/admin/custom/customComponents.js +2 -2
- package/admin/custom/i18n/de.json +19 -1
- package/admin/custom/i18n/en.json +19 -1
- package/admin/custom/i18n/es.json +19 -1
- package/admin/custom/i18n/fr.json +19 -1
- package/admin/custom/i18n/it.json +19 -1
- package/admin/custom/i18n/nl.json +19 -1
- package/admin/custom/i18n/pl.json +19 -1
- package/admin/custom/i18n/pt.json +19 -1
- package/admin/custom/i18n/ru.json +19 -1
- package/admin/custom/i18n/uk.json +19 -1
- package/admin/custom/i18n/zh-cn.json +19 -1
- package/admin/i18n/de.json +13 -1
- package/admin/i18n/en.json +13 -1
- package/admin/i18n/es.json +13 -1
- package/admin/i18n/fr.json +13 -1
- package/admin/i18n/it.json +13 -1
- package/admin/i18n/nl.json +13 -1
- package/admin/i18n/pl.json +13 -1
- package/admin/i18n/pt.json +13 -1
- package/admin/i18n/ru.json +13 -1
- package/admin/i18n/uk.json +13 -1
- package/admin/i18n/zh-cn.json +13 -1
- package/admin/jsonConfig.json +274 -86
- package/admin/mf-manifest.json +1 -1
- package/admin/mf-stats.json +1 -1
- package/admin/remoteEntry.js +2 -2
- package/admin/tab.html +3 -7
- package/build/lib/aiProviderResolver.js +145 -25
- package/build/lib/aiProviderResolver.js.map +1 -1
- package/build/lib/anthropicAdapter.js +37 -0
- package/build/lib/anthropicAdapter.js.map +1 -1
- package/build/lib/fb/analyze.js +288 -0
- package/build/lib/fb/analyze.js.map +1 -0
- package/build/lib/fb/blocks.js +605 -0
- package/build/lib/fb/blocks.js.map +1 -0
- package/build/lib/fb/generate.js +326 -0
- package/build/lib/fb/generate.js.map +1 -0
- package/build/lib/fb/graph.js +240 -0
- package/build/lib/fb/graph.js.map +1 -0
- package/build/lib/fb/index.js +57 -0
- package/build/lib/fb/index.js.map +1 -0
- package/build/lib/fb/js.js +33 -0
- package/build/lib/fb/js.js.map +1 -0
- package/build/lib/fb/layout.js +205 -0
- package/build/lib/fb/layout.js.map +1 -0
- package/build/lib/fb/library.js +657 -0
- package/build/lib/fb/library.js.map +1 -0
- package/build/lib/fb/runtime.js +793 -0
- package/build/lib/fb/runtime.js.map +1 -0
- package/build/lib/fb/time.js +76 -0
- package/build/lib/fb/time.js.map +1 -0
- package/build/lib/fb/types.js +19 -0
- package/build/lib/fb/types.js.map +1 -0
- package/build/lib/fb/user.js +78 -0
- package/build/lib/fb/user.js.map +1 -0
- package/build/lib/inspect.js +66 -10
- package/build/lib/inspect.js.map +1 -1
- package/build/lib/javascript.d.ts +1 -1
- package/build/lib/mirror.js +28 -23
- package/build/lib/mirror.js.map +1 -1
- package/build/lib/sandbox.js +44 -0
- package/build/lib/sandbox.js.map +1 -1
- package/build/lib/scriptHistory.js +245 -0
- package/build/lib/scriptHistory.js.map +1 -0
- package/build/main.js +641 -32
- package/build/main.js.map +1 -1
- package/build/types.d.ts +20 -1
- package/docs/de/fbd.md +274 -0
- package/docs/en/fbd.md +260 -0
- package/io-package.json +55 -29
- package/package.json +7 -7
- package/admin/assets/AiChatPanel-oBeXbUFv.js +0 -27
- package/admin/assets/AiDiffView-C-KKjnJD.js +0 -1
- package/admin/assets/Debugger-DwHHsh9O.js +0 -15
- package/admin/assets/Error-DZ4vCykD.js +0 -1
- package/admin/assets/Import-Cu02ceLW.js +0 -1
- package/admin/assets/RulesEditor-QCbi1lV_.js +0 -1
- package/admin/assets/ScriptEditorVanillaMonaco-ChQmnk0p.js +0 -3
- package/admin/assets/_virtual_mf-localSharedImportMap___mfe_internal__iobroker_javascript__mf_owner__1-CM95r358.js +0 -1
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__180366418666083__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BEpNZh4U.js +0 -1217
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__180366418666083__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-FeM77flT.js +0 -9
- package/admin/assets/aiPromptBuilder-DKler0UK.js +0 -1
- package/admin/assets/blocks_action-DywZVfRY.js +0 -1
- package/admin/assets/blocks_logic-CdTl5YZw.js +0 -1
- package/admin/assets/blocks_number-DXFd9Rva.js +0 -1
- package/admin/assets/blocks_object-by1uBDHi.js +0 -1
- package/admin/assets/blocks_procedures-BcJ7N70D.js +0 -3
- package/admin/assets/blocks_sendto-qIKzOAcV.js +0 -5
- package/admin/assets/blocks_switch-dLF6hSJl.js +0 -2
- package/admin/assets/blocks_system-DmX-SBy8.js +0 -1
- package/admin/assets/blocks_text-BI4zNU3o.js +0 -1
- package/admin/assets/blocks_time-BgYA4Y_4.js +0 -1
- package/admin/assets/blocks_timeout-DUzgMyAu.js +0 -1
- package/admin/assets/dist-D4uJqRBO.js +0 -4
- package/admin/assets/field_cron-Ba46q8Qc.js +0 -1
- package/admin/assets/field_oid-lWCjOzuq.js +0 -1
- package/admin/assets/field_script-BCWZHhnW.js +0 -1
- package/admin/assets/helpers-CnEjYAup.js +0 -1
- package/admin/assets/hostInit-CVybS7BB.js +0 -1
- package/admin/assets/index-B3s0zmWC.js +0 -871
- package/admin/assets/index-CXaeqPRy.css +0 -1
- package/admin/assets/pendingShares-DOAkyFMS.js +0 -1
- package/admin/assets/vite-preload-helper-CaC3yEL3.js +0 -1
- package/admin/custom/assets/Components-FkGDIlId.js +0 -1
- package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1-Cs88NUCd.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-P-8ffWXw.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-ZVuRAEo5.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-Dna3WF04.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_mui_mf_1_x_mf_2_date_mf_2_pickers__loadShare__.js-DcjdCGAx.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare__date_mf_2_fns__loadShare__.js-CnhNjamd.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare__leaflet__loadShare__.js-Dz7BFJqH.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-B0olko2J.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild___mf_0_iobroker_mf_1_json_mf_2_config__prebuild__-CzN4reh_.js +0 -122
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild__leaflet__prebuild__-xPcBaiTQ.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild__react_mf_2_dom_mf_1_client__prebuild__-DuFBE5fh.js +0 -9
- package/admin/custom/assets/dist-D4uJqRBO.js +0 -4
- package/admin/custom/assets/hostInit-6oh4UY2k.js +0 -1
- package/admin/custom/assets/index-BrlyEy6J.js +0 -1
- package/admin/custom/assets/pendingShares-kV2kwejX.js +0 -1
- package/admin/custom/assets/vite-preload-helper-CaC3yEL3.js +0 -1
- /package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild__react_mf_1_jsx_mf_2_runtime__prebuild__-CXpyx8Vy.js → _virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild__react_mf_1_jsx_mf_2_runtime__prebuild__-CXpyx8Vy.js} +0 -0
package/build/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The MIT License (MIT)
|
|
6
6
|
*
|
|
7
|
-
* Copyright (c) 2014-
|
|
7
|
+
* Copyright (c) 2014-2026 bluefox <dogafox@gmail.com>,
|
|
8
8
|
*
|
|
9
9
|
* Copyright (c) 2014 hobbyquaker
|
|
10
10
|
*/
|
|
@@ -81,6 +81,7 @@ const sandbox_1 = require("./lib/sandbox");
|
|
|
81
81
|
const nodeModulesManagement_1 = require("./lib/nodeModulesManagement");
|
|
82
82
|
const secrets_1 = require("./lib/secrets");
|
|
83
83
|
const aiProviderResolver_1 = require("./lib/aiProviderResolver");
|
|
84
|
+
const scriptHistory_1 = require("./lib/scriptHistory");
|
|
84
85
|
const anthropicAdapter_1 = require("./lib/anthropicAdapter");
|
|
85
86
|
const eventObj_1 = require("./lib/eventObj");
|
|
86
87
|
const scheduler_1 = require("./lib/scheduler");
|
|
@@ -107,6 +108,11 @@ const forbiddenMirrorLocations = [
|
|
|
107
108
|
];
|
|
108
109
|
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(`${__dirname}/../package.json`).toString());
|
|
109
110
|
const SCRIPT_CODE_MARKER = 'script.js.';
|
|
111
|
+
/**
|
|
112
|
+
* Instance-message type the script editor subscribes to, and under which finished AI answers are
|
|
113
|
+
* pushed back to it. Shared verbatim with `src-editor/src/AiChat/AiChatService.ts`.
|
|
114
|
+
*/
|
|
115
|
+
const AI_PUSH_MESSAGE_TYPE = 'aiChatAnswer';
|
|
110
116
|
let webstormDebug;
|
|
111
117
|
const isCI = !!process.env.CI;
|
|
112
118
|
// ambient declarations for typescript
|
|
@@ -241,6 +247,8 @@ const TS_MEMORY_RELEASE_DELAY_MS = 60_000;
|
|
|
241
247
|
* gone and the type definitions and compilers are given back.
|
|
242
248
|
*/
|
|
243
249
|
const EDITOR_ONLINE_TIMEOUT_MS = 30_000;
|
|
250
|
+
/** An editor showing a function block diagram online asks every 10 s; after this, it is gone */
|
|
251
|
+
const FBD_WATCH_TIMEOUT_MS = 30_000;
|
|
244
252
|
/** How often it is checked whether the editor has gone away */
|
|
245
253
|
const EDITOR_CHECK_INTERVAL_MS = 10_000;
|
|
246
254
|
// compiler instance for global JS declarations. Built on the first compilation only - see LazyTsServer
|
|
@@ -344,6 +352,13 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
344
352
|
* subscriptions set up in `subscribeAiCredentials`.
|
|
345
353
|
*/
|
|
346
354
|
aiCredentialCache = new Map();
|
|
355
|
+
/**
|
|
356
|
+
* Editor sessions waiting for pushed AI answers: the session token the editor invented when it
|
|
357
|
+
* subscribed, mapped to the client id the messaging controller gave us. A `chatCompletion`
|
|
358
|
+
* message carries the token, which is how a request is matched back to the browser tab that
|
|
359
|
+
* sent it - the message itself has no socket id.
|
|
360
|
+
*/
|
|
361
|
+
aiUiClients = new Map();
|
|
347
362
|
/** Unsubscribe callbacks for the AI credential subscriptions (manager mode). */
|
|
348
363
|
aiCredentialUnsubscribers = [];
|
|
349
364
|
/**
|
|
@@ -368,6 +383,8 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
368
383
|
typingPackageFiles = [];
|
|
369
384
|
/** Until when a script editor counts as open. Refreshed by every `editorPing` */
|
|
370
385
|
editorOnlineUntil = 0;
|
|
386
|
+
/** Counts the editors that started to show a function block diagram online - see `fbdWatch` */
|
|
387
|
+
fbdViewers = 0;
|
|
371
388
|
/** Checks regularly whether the editor is gone and the type definitions can be unloaded */
|
|
372
389
|
editorCheckInterval = null;
|
|
373
390
|
/** Set once the user has been told that TypeScript is compiled without type definitions */
|
|
@@ -387,6 +404,16 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
387
404
|
...options,
|
|
388
405
|
name: 'javascript', // adapter name
|
|
389
406
|
useFormatDate: true,
|
|
407
|
+
/*
|
|
408
|
+
* The script editor subscribes here so that a finished AI answer can be *pushed* to it.
|
|
409
|
+
* An AI request regularly runs longer than 30 s, and `@iobroker/ws` answers every socket
|
|
410
|
+
* callback with the string "timeout" after exactly that (socket.io.js: `Date.now() + 30_000`,
|
|
411
|
+
* not configurable). The answer then arrived in a dead callback and the chat stayed empty
|
|
412
|
+
* with nothing in any log. So the editor gets an immediate acknowledgement and the result
|
|
413
|
+
* later as an instance message.
|
|
414
|
+
*/
|
|
415
|
+
uiClientSubscribe: (info) => this.onUiClientSubscribe(info),
|
|
416
|
+
uiClientUnsubscribe: (info) => this.onUiClientUnsubscribe(info),
|
|
390
417
|
/**
|
|
391
418
|
* If the JS-Controller catches an unhandled error, this will be called,
|
|
392
419
|
* so we have a chance to handle it ourselves.
|
|
@@ -468,6 +495,13 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
468
495
|
}
|
|
469
496
|
}
|
|
470
497
|
}
|
|
498
|
+
if (debugMode) {
|
|
499
|
+
// This process was started by the inspector (see `debugStart`) with `--debug`, so the whole log goes
|
|
500
|
+
// through the pipes of that process. If it is gone, writing to them fails with EPIPE, and an unhandled
|
|
501
|
+
// EPIPE would end the debugged process with an uncaught exception (#2382)
|
|
502
|
+
process.stdout.on('error', () => { });
|
|
503
|
+
process.stderr.on('error', () => { });
|
|
504
|
+
}
|
|
471
505
|
this.context = {
|
|
472
506
|
mods: this.mods,
|
|
473
507
|
objects: this.objects,
|
|
@@ -502,6 +536,9 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
502
536
|
scheduler: null,
|
|
503
537
|
timerId: 0,
|
|
504
538
|
rulesOpened: null, // opened rules
|
|
539
|
+
fbdWatch: new Map(),
|
|
540
|
+
fbdDebug: new Map(),
|
|
541
|
+
fbdDebugSettings: new Map(),
|
|
505
542
|
language: this.language || 'en',
|
|
506
543
|
updateLogSubscriptions: this.updateLogSubscriptions.bind(this),
|
|
507
544
|
convertBackStringifiedValues: this.convertBackStringifiedValues.bind(this),
|
|
@@ -516,6 +553,220 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
516
553
|
};
|
|
517
554
|
this.tsServer = new lazyTsServer_1.LazyTsServer(typescriptSettings_1.tsCompilerOptions, this.tsLog, TS_MEMORY_RELEASE_DELAY_MS);
|
|
518
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Make sure the instance has a file area, which is where the script history lives.
|
|
558
|
+
*
|
|
559
|
+
* The adapter's own namespace needs a `meta` object before anything can be written under it.
|
|
560
|
+
* Deliberately not `javascript.admin`: that is the UI's file area and every
|
|
561
|
+
* `iobroker upload javascript` wipes it.
|
|
562
|
+
*/
|
|
563
|
+
async ensureHistoryStorage() {
|
|
564
|
+
try {
|
|
565
|
+
await this.setForeignObjectNotExistsAsync(this.namespace, {
|
|
566
|
+
type: 'meta',
|
|
567
|
+
common: {
|
|
568
|
+
name: 'javascript files',
|
|
569
|
+
type: 'meta.user',
|
|
570
|
+
},
|
|
571
|
+
native: {},
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
catch (e) {
|
|
575
|
+
this.log.warn(`Cannot create the file storage of the instance: ${e instanceof Error ? e.message : String(e)}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Keep the saved source of a script, so it can be looked at and brought back later.
|
|
580
|
+
*
|
|
581
|
+
* Hooked into the object change and not into the editor on purpose: this way a change from the
|
|
582
|
+
* mirror directory, from the object browser, from another script or from the AI is kept just
|
|
583
|
+
* the same as one typed into the editor.
|
|
584
|
+
*
|
|
585
|
+
* @param id id of the script
|
|
586
|
+
* @param obj the script as it is now
|
|
587
|
+
* @param formerObj the script as it was before this change, if it existed
|
|
588
|
+
*/
|
|
589
|
+
async storeScriptVersion(id, obj, formerObj) {
|
|
590
|
+
const keep = (0, scriptHistory_1.resolveHistoryVersions)(this.config.historyVersions);
|
|
591
|
+
if (!keep) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
// Only the instance that runs the script writes its history, otherwise two instances
|
|
595
|
+
// store every version twice
|
|
596
|
+
if (obj.common?.engine !== `system.adapter.${this.namespace}`) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const source = obj.common.source;
|
|
600
|
+
if (!(0, scriptHistory_1.isWorthStoring)(formerObj?.common?.source, source)) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const ts = typeof obj.ts === 'number' && obj.ts ? obj.ts : Date.now();
|
|
605
|
+
const entry = {
|
|
606
|
+
ts,
|
|
607
|
+
...(obj.from ? { from: obj.from } : {}),
|
|
608
|
+
...(obj.user ? { user: obj.user } : {}),
|
|
609
|
+
// The source of a protected script is encrypted in the object and is kept that
|
|
610
|
+
// way - the history must not be the place where it lies around in plain text
|
|
611
|
+
...(0, scriptHistory_1.describeSource)(source, !!obj.native?.protected),
|
|
612
|
+
};
|
|
613
|
+
await this.writeFileAsync(this.namespace, (0, scriptHistory_1.versionPath)(id, ts), source);
|
|
614
|
+
const raw = await this.readFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id)).catch(() => null);
|
|
615
|
+
const current = (0, scriptHistory_1.parseIndex)((0, scriptHistory_1.fileToString)(raw));
|
|
616
|
+
const { index, obsolete } = (0, scriptHistory_1.addVersion)(current, entry, keep);
|
|
617
|
+
await this.writeFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id), JSON.stringify(index));
|
|
618
|
+
for (const old of obsolete) {
|
|
619
|
+
await this.delFileAsync(this.namespace, (0, scriptHistory_1.versionPath)(id, old)).catch(() => {
|
|
620
|
+
/* a version that is already gone is one we do not have to delete */
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
this.log.debug(`Script history: stored version ${ts} of ${id} (${index.length} kept)`);
|
|
624
|
+
}
|
|
625
|
+
catch (e) {
|
|
626
|
+
// A history is a convenience; it must never get in the way of saving a script
|
|
627
|
+
this.log.warn(`Cannot store the version of ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* The stored versions of a script, newest first, without their sources.
|
|
632
|
+
*
|
|
633
|
+
* @param id id of the script
|
|
634
|
+
*/
|
|
635
|
+
async readScriptVersions(id) {
|
|
636
|
+
try {
|
|
637
|
+
const raw = await this.readFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id));
|
|
638
|
+
return (0, scriptHistory_1.parseIndex)((0, scriptHistory_1.fileToString)(raw));
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
// No history yet - not an error, just nothing to show
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/** What every stored history occupies, biggest first - read from the indexes, never from the sources */
|
|
646
|
+
async collectHistoryUsage() {
|
|
647
|
+
const rows = [];
|
|
648
|
+
let folders = [];
|
|
649
|
+
try {
|
|
650
|
+
folders = await this.readDirAsync(this.namespace, scriptHistory_1.HISTORY_ROOT);
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
// Nothing stored yet
|
|
654
|
+
}
|
|
655
|
+
for (const folder of folders) {
|
|
656
|
+
const id = (0, scriptHistory_1.decodeFolderName)(folder.file);
|
|
657
|
+
const index = await this.readScriptVersions(id);
|
|
658
|
+
if (!index.length) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const { versions, bytes } = (0, scriptHistory_1.sumIndex)(index);
|
|
662
|
+
rows.push({ id, exists: !!this.objects[id], versions, bytes });
|
|
663
|
+
}
|
|
664
|
+
rows.sort((a, b) => b.bytes - a.bytes);
|
|
665
|
+
return {
|
|
666
|
+
rows,
|
|
667
|
+
total: {
|
|
668
|
+
scripts: rows.length,
|
|
669
|
+
versions: rows.reduce((sum, r) => sum + r.versions, 0),
|
|
670
|
+
bytes: rows.reduce((sum, r) => sum + r.bytes, 0),
|
|
671
|
+
},
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Remove stored versions.
|
|
676
|
+
*
|
|
677
|
+
* @param options what to remove
|
|
678
|
+
* @param options.id the script whose versions are meant
|
|
679
|
+
* @param options.ts one single version of that script; without it the whole history goes
|
|
680
|
+
* @param options.orphans every history whose script no longer exists
|
|
681
|
+
* @param options.all every stored version there is - the scripts themselves stay untouched
|
|
682
|
+
*/
|
|
683
|
+
async deleteHistory(options) {
|
|
684
|
+
let deleted = 0;
|
|
685
|
+
let bytes = 0;
|
|
686
|
+
/**
|
|
687
|
+
* Delete versions of one script and write the remaining index back.
|
|
688
|
+
*
|
|
689
|
+
* @param id the script
|
|
690
|
+
* @param keep the versions that stay; an empty list removes the index as well
|
|
691
|
+
* @param drop the versions to remove
|
|
692
|
+
*/
|
|
693
|
+
const apply = async (id, keep, drop) => {
|
|
694
|
+
for (const version of drop) {
|
|
695
|
+
bytes += version.size || 0;
|
|
696
|
+
deleted++;
|
|
697
|
+
await this.delFileAsync(this.namespace, (0, scriptHistory_1.versionPath)(id, version.ts)).catch(() => {
|
|
698
|
+
/* a version that is already gone is one we do not have to delete */
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (keep.length) {
|
|
702
|
+
await this.writeFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id), JSON.stringify(keep));
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
await this.delFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id)).catch(() => {
|
|
706
|
+
/* already gone */
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
if (options.orphans || options.all) {
|
|
711
|
+
const { rows } = await this.collectHistoryUsage();
|
|
712
|
+
// `all` takes everything, `orphans` only what has no script behind it anymore
|
|
713
|
+
for (const row of options.all ? rows : rows.filter(r => !r.exists)) {
|
|
714
|
+
await apply(row.id, [], await this.readScriptVersions(row.id));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else if (options.id) {
|
|
718
|
+
const index = await this.readScriptVersions(options.id);
|
|
719
|
+
if (options.ts) {
|
|
720
|
+
await apply(options.id, index.filter(v => v.ts !== options.ts), index.filter(v => v.ts === options.ts));
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
await apply(options.id, [], index);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
this.log.debug(`Script history: removed ${deleted} versions (${(0, scriptHistory_1.formatBytes)(bytes)})`);
|
|
727
|
+
return { deleted, bytes };
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Cut every history down to the configured number of versions.
|
|
731
|
+
*
|
|
732
|
+
* Without this a smaller limit would only take effect script by script, on the next save of
|
|
733
|
+
* each - and setting it to zero would never free anything at all.
|
|
734
|
+
*/
|
|
735
|
+
async trimAllHistories() {
|
|
736
|
+
const keep = (0, scriptHistory_1.resolveHistoryVersions)(this.config.historyVersions);
|
|
737
|
+
let folders = [];
|
|
738
|
+
try {
|
|
739
|
+
folders = await this.readDirAsync(this.namespace, scriptHistory_1.HISTORY_ROOT);
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
for (const folder of folders) {
|
|
745
|
+
const id = (0, scriptHistory_1.decodeFolderName)(folder.file);
|
|
746
|
+
const index = await this.readScriptVersions(id);
|
|
747
|
+
if (!index.length) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const { index: kept, obsolete } = (0, scriptHistory_1.trimIndex)(index, keep);
|
|
751
|
+
if (!obsolete.length) {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
for (const ts of obsolete) {
|
|
755
|
+
await this.delFileAsync(this.namespace, (0, scriptHistory_1.versionPath)(id, ts)).catch(() => {
|
|
756
|
+
/* already gone */
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
if (kept.length) {
|
|
760
|
+
await this.writeFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id), JSON.stringify(kept));
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
await this.delFileAsync(this.namespace, (0, scriptHistory_1.indexPath)(id)).catch(() => {
|
|
764
|
+
/* already gone */
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
this.log.debug(`Script history: trimmed ${id} to ${kept.length} versions`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
519
770
|
async onObjectChange(id, obj) {
|
|
520
771
|
// Check if we should ignore this change (once!) because we just updated the compiled sources
|
|
521
772
|
if (this.ignoreObjectChange.has(id)) {
|
|
@@ -605,6 +856,10 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
605
856
|
}
|
|
606
857
|
}
|
|
607
858
|
}
|
|
859
|
+
// Keep the saved source before the branches below decide what else to do with it
|
|
860
|
+
if (obj?.type === 'script') {
|
|
861
|
+
await this.storeScriptVersion(id, obj, formerObj);
|
|
862
|
+
}
|
|
608
863
|
// handle Script object updates
|
|
609
864
|
if (!obj && formerObj?.type === 'script') {
|
|
610
865
|
// Object Deleted just now
|
|
@@ -814,7 +1069,9 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
814
1069
|
}
|
|
815
1070
|
async onUnload(callback) {
|
|
816
1071
|
try {
|
|
817
|
-
|
|
1072
|
+
// Do not wait for it: the inspector needs a moment to stop the debugged process, and it does that by
|
|
1073
|
+
// itself as soon as the connection to this process is gone. The shutdown must not be delayed by it.
|
|
1074
|
+
void this.debugStop();
|
|
818
1075
|
this.stopTimeSchedules();
|
|
819
1076
|
if (this.setStateCountCheckInterval) {
|
|
820
1077
|
clearInterval(this.setStateCountCheckInterval);
|
|
@@ -877,6 +1134,79 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
877
1134
|
}
|
|
878
1135
|
await this.main();
|
|
879
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* A script editor registers itself for pushed AI answers.
|
|
1139
|
+
*
|
|
1140
|
+
* The editor sends a token it made up; we remember which messaging-controller client it belongs
|
|
1141
|
+
* to. Only our own message type is accepted - anything else is some other UI asking for
|
|
1142
|
+
* something we do not serve.
|
|
1143
|
+
*
|
|
1144
|
+
* @param info client id and the subscribe message, as the messaging controller hands it over
|
|
1145
|
+
* @param info.clientId the id to address this client with later
|
|
1146
|
+
* @param info.message the subscribe message, carrying the type and the editor's session token
|
|
1147
|
+
*/
|
|
1148
|
+
onUiClientSubscribe(info) {
|
|
1149
|
+
const message = info.message?.message;
|
|
1150
|
+
if (message?.type !== AI_PUSH_MESSAGE_TYPE) {
|
|
1151
|
+
return { accepted: false, error: `Unknown subscription type "${message?.type || ''}"` };
|
|
1152
|
+
}
|
|
1153
|
+
const token = (message.data?.sessionToken || '').trim();
|
|
1154
|
+
if (!token) {
|
|
1155
|
+
return { accepted: false, error: 'No session token provided' };
|
|
1156
|
+
}
|
|
1157
|
+
this.aiUiClients.set(token, info.clientId);
|
|
1158
|
+
this.log.debug(`AI editor session subscribed for pushed answers (${this.aiUiClients.size} open)`);
|
|
1159
|
+
return { accepted: true };
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* A script editor went away - drop every token that pointed at it.
|
|
1163
|
+
*
|
|
1164
|
+
* @param info the client the messaging controller is retiring
|
|
1165
|
+
* @param info.clientId the id that is going away
|
|
1166
|
+
*/
|
|
1167
|
+
onUiClientUnsubscribe(info) {
|
|
1168
|
+
for (const [token, clientId] of this.aiUiClients) {
|
|
1169
|
+
if (clientId === info.clientId) {
|
|
1170
|
+
this.aiUiClients.delete(token);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
this.log.debug(`AI editor session unsubscribed (${this.aiUiClients.size} open)`);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Build the function that delivers the answer of one `chatCompletion` request.
|
|
1177
|
+
*
|
|
1178
|
+
* If the editor registered for pushed answers and named itself in the message, the answer goes
|
|
1179
|
+
* out as an instance message and the socket callback is acknowledged right away. Everything
|
|
1180
|
+
* else - an older editor, the inline completion, a script calling us - keeps the plain
|
|
1181
|
+
* request/response behaviour.
|
|
1182
|
+
*
|
|
1183
|
+
* @param obj the incoming sendTo message
|
|
1184
|
+
*/
|
|
1185
|
+
buildAiResponder(obj) {
|
|
1186
|
+
const token = (obj.message?.uiSession || '').toString().trim();
|
|
1187
|
+
const requestId = (obj.message?.requestId || '').toString().trim();
|
|
1188
|
+
const clientId = token ? this.aiUiClients.get(token) : undefined;
|
|
1189
|
+
if (!clientId || !requestId) {
|
|
1190
|
+
return payload => this.sendTo(obj.from, obj.command, payload, obj.callback);
|
|
1191
|
+
}
|
|
1192
|
+
// Release the socket callback immediately - it has 30 s to live, the request has more.
|
|
1193
|
+
this.sendTo(obj.from, obj.command, { accepted: true, requestId }, obj.callback);
|
|
1194
|
+
let sent = false;
|
|
1195
|
+
return payload => {
|
|
1196
|
+
if (sent) {
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
sent = true;
|
|
1200
|
+
this.sendToUI({
|
|
1201
|
+
clientId,
|
|
1202
|
+
data: { type: AI_PUSH_MESSAGE_TYPE, requestId, ...payload },
|
|
1203
|
+
}).catch(e => {
|
|
1204
|
+
// The tab was closed, or the admin dropped the subscription in the meantime.
|
|
1205
|
+
this.aiUiClients.delete(token);
|
|
1206
|
+
this.log.warn(`Cannot deliver the AI answer to the editor: ${e instanceof Error ? e.message : String(e)}`);
|
|
1207
|
+
});
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
880
1210
|
/** Read and decrypt a single AI credential's key from the central store; returns '' (and logs) on error. */
|
|
881
1211
|
async readAiCredentialKey(id) {
|
|
882
1212
|
if (!adapter_core_1.Credentials?.getCredentials) {
|
|
@@ -885,6 +1215,15 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
885
1215
|
}
|
|
886
1216
|
try {
|
|
887
1217
|
const cred = await adapter_core_1.Credentials.getCredentials(this, id);
|
|
1218
|
+
/*
|
|
1219
|
+
* Only an entry that was stored as an AI credential. The store holds the secrets of the
|
|
1220
|
+
* whole system - a database password, the login of a camera - and nothing but this check
|
|
1221
|
+
* keeps a request for "the key of this provider" from reaching one of them.
|
|
1222
|
+
*/
|
|
1223
|
+
if (cred?.type !== 'ai') {
|
|
1224
|
+
this.log.warn(`Cannot read AI credential "${id}": it is not an AI credential`);
|
|
1225
|
+
return '';
|
|
1226
|
+
}
|
|
888
1227
|
return (cred?.values?.key || '').trim();
|
|
889
1228
|
}
|
|
890
1229
|
catch (e) {
|
|
@@ -904,12 +1243,12 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
904
1243
|
* The settings-dialog Test button may pass form values that are not saved yet
|
|
905
1244
|
* (`messageApiKey` / `messageCredentialId` / `credentialType`); those win over the stored config.
|
|
906
1245
|
*/
|
|
907
|
-
async resolveAiCredentials(provider
|
|
908
|
-
const mode =
|
|
1246
|
+
async resolveAiCredentials(provider) {
|
|
1247
|
+
const mode = this.config.credentialType || 'manual';
|
|
909
1248
|
if (mode === 'manager') {
|
|
910
1249
|
// The base URL is not a secret and is resolved the same way in both modes.
|
|
911
|
-
const { baseUrl } = (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider
|
|
912
|
-
const id = (
|
|
1250
|
+
const { baseUrl } = (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider);
|
|
1251
|
+
const id = (0, aiProviderResolver_1.getProviderCredentialId)(this.config, provider).trim();
|
|
913
1252
|
if (!id) {
|
|
914
1253
|
return { apiKey: '', baseUrl };
|
|
915
1254
|
}
|
|
@@ -918,11 +1257,74 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
918
1257
|
const apiKey = cached !== undefined ? cached : await this.readAiCredentialKey(id);
|
|
919
1258
|
return { apiKey, baseUrl };
|
|
920
1259
|
}
|
|
1260
|
+
return (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Endpoint and key for the Test button of the settings dialog.
|
|
1264
|
+
*
|
|
1265
|
+
* The dialog tries what stands in the form - a key that was typed but not saved, a credential that
|
|
1266
|
+
* was chosen but not saved, an endpoint that was entered but not saved. What the form may decide is
|
|
1267
|
+
* limited by `resolveTestEndpoint`: a secret of this system is never carried to an address that
|
|
1268
|
+
* came with the message. A chat request takes none of this and reads the configuration instead -
|
|
1269
|
+
* see `resolveAiCredentials`.
|
|
1270
|
+
*
|
|
1271
|
+
* @param provider the provider that is being tested
|
|
1272
|
+
* @param opts what the Test button sent
|
|
1273
|
+
* @param opts.messageBaseUrl the address that stands in the form
|
|
1274
|
+
* @param opts.messageApiKey the key that stands in the form
|
|
1275
|
+
* @param opts.messageCredentialId the entry of the credential store that the form names
|
|
1276
|
+
* @param opts.credentialType where the form says the keys are kept
|
|
1277
|
+
*/
|
|
1278
|
+
async resolveAiTestCredentials(provider, opts = {}) {
|
|
1279
|
+
const mode = opts.credentialType || this.config.credentialType || 'manual';
|
|
1280
|
+
if (mode === 'manager') {
|
|
1281
|
+
// A key out of the store is a secret of this system, so the address is the stored one
|
|
1282
|
+
const { baseUrl } = (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider);
|
|
1283
|
+
const id = (opts.messageCredentialId || (0, aiProviderResolver_1.getProviderCredentialId)(this.config, provider)).trim();
|
|
1284
|
+
if (!id) {
|
|
1285
|
+
return { apiKey: '', baseUrl };
|
|
1286
|
+
}
|
|
1287
|
+
const cached = this.aiCredentialCache.get(id);
|
|
1288
|
+
const apiKey = cached !== undefined ? cached : await this.readAiCredentialKey(id);
|
|
1289
|
+
return { apiKey, baseUrl };
|
|
1290
|
+
}
|
|
921
1291
|
// Manual mode. The Test button sends the current form key (maybe empty) — let it win.
|
|
922
1292
|
if (opts.messageApiKey !== undefined) {
|
|
923
1293
|
return (0, aiProviderResolver_1.resolveTestCredentials)(this.config, provider, opts.messageApiKey, opts.messageBaseUrl);
|
|
924
1294
|
}
|
|
925
|
-
return (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider
|
|
1295
|
+
return (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider);
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Whether the user a message was sent on behalf of may have code executed here.
|
|
1299
|
+
*
|
|
1300
|
+
* `obj.user` arrives with js-controller 7.2.5 and newer: a socket server such as `admin` or `web`
|
|
1301
|
+
* puts the authenticated user of the connection there, and a socket client cannot set it itself.
|
|
1302
|
+
* Where it is missing - an older controller, a script, an adapter that names nobody - nothing is
|
|
1303
|
+
* checked and everything stays as it was.
|
|
1304
|
+
*
|
|
1305
|
+
* The right asked for is the one `cmdExec` is checked against: running a script here is running
|
|
1306
|
+
* code on this host, whatever the ACL of the single objects it then touches says.
|
|
1307
|
+
*
|
|
1308
|
+
* @param obj the incoming message
|
|
1309
|
+
*/
|
|
1310
|
+
async mayExecuteFor(obj) {
|
|
1311
|
+
// `user` is not in the older `@iobroker/types` this adapter builds against
|
|
1312
|
+
const user = obj.user;
|
|
1313
|
+
if (!user) {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
try {
|
|
1317
|
+
const permissions = await this.calculatePermissionsAsync(user, {
|
|
1318
|
+
cmdExec: { type: 'other', operation: 'execute' },
|
|
1319
|
+
});
|
|
1320
|
+
if (permissions?.other?.execute) {
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
return `User "${user}" has no permission to execute scripts`;
|
|
1324
|
+
}
|
|
1325
|
+
catch (e) {
|
|
1326
|
+
return `Cannot check the permissions of "${user}": ${e instanceof Error ? e.message : String(e)}`;
|
|
1327
|
+
}
|
|
926
1328
|
}
|
|
927
1329
|
/**
|
|
928
1330
|
* Warns about the one setup that the fix for #2369 stops working.
|
|
@@ -1199,12 +1601,154 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1199
1601
|
console.log(`Enable messaging for ${this.context.rulesOpened}`);
|
|
1200
1602
|
break;
|
|
1201
1603
|
}
|
|
1604
|
+
case 'fbdWatch': {
|
|
1605
|
+
// An editor shows a function block diagram online. While it keeps asking, the runtime
|
|
1606
|
+
// of the script sends snapshots to `debug.fbd`; a window that just goes away is
|
|
1607
|
+
// forgotten after FBD_WATCH_TIMEOUT_MS. `full`: a new viewer, who needs everything once.
|
|
1608
|
+
// `path`: the instance of a user block whose inside the editor shows, like `b3/b7`.
|
|
1609
|
+
const { script, watch, full, path } = (obj.message || {});
|
|
1610
|
+
if (typeof script === 'string') {
|
|
1611
|
+
if (watch) {
|
|
1612
|
+
const current = this.context.fbdWatch.get(script);
|
|
1613
|
+
const until = Date.now() + FBD_WATCH_TIMEOUT_MS;
|
|
1614
|
+
// several editors may look into several instances
|
|
1615
|
+
const paths = current?.paths || new Map();
|
|
1616
|
+
if (typeof path === 'string' && path) {
|
|
1617
|
+
paths.set(path, until);
|
|
1618
|
+
}
|
|
1619
|
+
this.context.fbdWatch.set(script, {
|
|
1620
|
+
until,
|
|
1621
|
+
viewer: !current || full ? ++this.fbdViewers : current.viewer,
|
|
1622
|
+
paths,
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
this.context.fbdWatch.delete(script);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (obj.callback) {
|
|
1630
|
+
this.sendTo(obj.from, obj.command, { ok: true }, obj.callback);
|
|
1631
|
+
}
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
case 'fbdDebug': {
|
|
1635
|
+
// The online view forces values, sets breakpoints or steps through a diagram. The
|
|
1636
|
+
// runtime of the script does it and answers with how it stands now.
|
|
1637
|
+
const { script, ...command } = (obj.message || {});
|
|
1638
|
+
const handler = typeof script === 'string' ? this.context.fbdDebug.get(script) : undefined;
|
|
1639
|
+
let result;
|
|
1640
|
+
if (!handler) {
|
|
1641
|
+
result = { error: 'The diagram does not run' };
|
|
1642
|
+
}
|
|
1643
|
+
else if (!this.context.fbdWatch.has(script)) {
|
|
1644
|
+
// everything the online view sets ends with it
|
|
1645
|
+
result = { error: 'The diagram is not shown online' };
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
try {
|
|
1649
|
+
result = { status: handler(command) };
|
|
1650
|
+
}
|
|
1651
|
+
catch (error) {
|
|
1652
|
+
result = { error: error.message };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (obj.callback) {
|
|
1656
|
+
this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
1657
|
+
}
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1202
1660
|
case 'rulesOff': {
|
|
1203
1661
|
// maybe if (context.rulesOpened === obj.message)
|
|
1204
1662
|
console.log(`Disable messaging for ${this.context.rulesOpened}`);
|
|
1205
1663
|
this.context.rulesOpened = null;
|
|
1206
1664
|
break;
|
|
1207
1665
|
}
|
|
1666
|
+
case 'getScriptVersions': {
|
|
1667
|
+
// The stored versions of one script, without their sources - the list must stay
|
|
1668
|
+
// cheap, a history of 30 Blockly versions is megabytes
|
|
1669
|
+
void (async () => {
|
|
1670
|
+
if (!obj.callback) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const scriptId = (obj.message?.id || '').toString();
|
|
1674
|
+
if (!scriptId) {
|
|
1675
|
+
this.sendTo(obj.from, obj.command, { error: 'No script ID provided' }, obj.callback);
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const versions = await this.readScriptVersions(scriptId);
|
|
1679
|
+
this.sendTo(obj.from, obj.command, { versions }, obj.callback);
|
|
1680
|
+
})();
|
|
1681
|
+
break;
|
|
1682
|
+
}
|
|
1683
|
+
case 'getScriptVersion': {
|
|
1684
|
+
// The source of one stored version, fetched when it is actually looked at
|
|
1685
|
+
void (async () => {
|
|
1686
|
+
if (!obj.callback) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
const scriptId = (obj.message?.id || '').toString();
|
|
1690
|
+
const ts = parseInt(obj.message?.ts, 10);
|
|
1691
|
+
if (!scriptId || isNaN(ts)) {
|
|
1692
|
+
this.sendTo(obj.from, obj.command, { error: 'No script ID or timestamp' }, obj.callback);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
try {
|
|
1696
|
+
const raw = await this.readFileAsync(this.namespace, (0, scriptHistory_1.versionPath)(scriptId, ts));
|
|
1697
|
+
const source = (0, scriptHistory_1.fileToString)(raw);
|
|
1698
|
+
this.sendTo(obj.from, obj.command, { source }, obj.callback);
|
|
1699
|
+
}
|
|
1700
|
+
catch (e) {
|
|
1701
|
+
this.sendTo(obj.from, obj.command, { error: `Cannot read the version: ${e instanceof Error ? e.message : String(e)}` }, obj.callback);
|
|
1702
|
+
}
|
|
1703
|
+
})();
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
case 'getHistoryUsage': {
|
|
1707
|
+
// Read by the overview in the adapter settings (`textSendTo` with container html)
|
|
1708
|
+
void (async () => {
|
|
1709
|
+
if (!obj.callback) {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
try {
|
|
1713
|
+
this.sendTo(obj.from, obj.command, await this.collectHistoryUsage(), obj.callback);
|
|
1714
|
+
}
|
|
1715
|
+
catch (e) {
|
|
1716
|
+
this.sendTo(obj.from, obj.command, { error: `Cannot read the history: ${e instanceof Error ? e.message : String(e)}` }, obj.callback);
|
|
1717
|
+
}
|
|
1718
|
+
})();
|
|
1719
|
+
break;
|
|
1720
|
+
}
|
|
1721
|
+
case 'deleteHistory': {
|
|
1722
|
+
// One version, the history of one script, or every history without a script
|
|
1723
|
+
void (async () => {
|
|
1724
|
+
if (!obj.callback) {
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const scriptId = (obj.message?.id || '').toString();
|
|
1728
|
+
const orphans = !!obj.message?.orphans;
|
|
1729
|
+
// Wiping every history is a deliberate request, never the default of a
|
|
1730
|
+
// message that forgot to say what it meant
|
|
1731
|
+
const all = !!obj.message?.all;
|
|
1732
|
+
if (!scriptId && !orphans && !all) {
|
|
1733
|
+
this.sendTo(obj.from, obj.command, { error: 'No script ID provided' }, obj.callback);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
try {
|
|
1737
|
+
const ts = parseInt(obj.message?.ts, 10);
|
|
1738
|
+
const result = await this.deleteHistory({
|
|
1739
|
+
...(scriptId ? { id: scriptId } : {}),
|
|
1740
|
+
...(isNaN(ts) ? {} : { ts }),
|
|
1741
|
+
...(orphans ? { orphans: true } : {}),
|
|
1742
|
+
...(all ? { all: true } : {}),
|
|
1743
|
+
});
|
|
1744
|
+
this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
1745
|
+
}
|
|
1746
|
+
catch (e) {
|
|
1747
|
+
this.sendTo(obj.from, obj.command, { error: `Cannot delete: ${e instanceof Error ? e.message : String(e)}` }, obj.callback);
|
|
1748
|
+
}
|
|
1749
|
+
})();
|
|
1750
|
+
break;
|
|
1751
|
+
}
|
|
1208
1752
|
case 'getIoBrokerDataDir': {
|
|
1209
1753
|
if (obj.callback) {
|
|
1210
1754
|
this.sendTo(obj.from, obj.command, {
|
|
@@ -1220,25 +1764,33 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1220
1764
|
// credentials manager — they never leave the adapter (frontend only sends `provider`).
|
|
1221
1765
|
void (async () => {
|
|
1222
1766
|
if (!obj.callback) {
|
|
1767
|
+
this.log.warn(`chatCompletion from ${obj.from} came without a callback - nobody can receive the answer`);
|
|
1223
1768
|
return;
|
|
1224
1769
|
}
|
|
1225
1770
|
const chatModel = (obj.message?.model || '').trim();
|
|
1226
1771
|
const messages = obj.message?.messages;
|
|
1227
1772
|
const tools = obj.message?.tools;
|
|
1228
1773
|
const provider = (obj.message?.provider || 'openai').trim();
|
|
1774
|
+
// One line per request, so an empty chat panel can be told apart from a request
|
|
1775
|
+
// that never arrived here. Without it the log stays silent either way.
|
|
1776
|
+
this.log.debug(`chatCompletion from ${obj.from}: ${provider}/${chatModel}, ${Array.isArray(messages) ? messages.length : 0} messages, ${Array.isArray(tools) ? tools.length : 0} tools`);
|
|
1777
|
+
// Answers go through this from here on: either straight back through the socket
|
|
1778
|
+
// callback, or - for an editor that registered for it - pushed as an instance
|
|
1779
|
+
// message once the endpoint is done. See `buildAiResponder`.
|
|
1780
|
+
const respond = this.buildAiResponder(obj);
|
|
1229
1781
|
// How long the caller is willing to wait - see `resolveRequestTimeout`
|
|
1230
1782
|
const requestTimeout = (0, aiProviderResolver_1.resolveRequestTimeout)(obj.message?.timeout);
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
});
|
|
1783
|
+
const maxTokens = (0, aiProviderResolver_1.resolveMaxTokens)(this.config.aiMaxTokens);
|
|
1784
|
+
// Endpoint and key come from the configuration; a request cannot name either
|
|
1785
|
+
const { apiKey, baseUrl } = await this.resolveAiCredentials(provider);
|
|
1234
1786
|
// Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
|
|
1235
1787
|
if (!apiKey &&
|
|
1236
1788
|
(provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
|
|
1237
|
-
|
|
1789
|
+
respond({ error: 'No API key provided' });
|
|
1238
1790
|
return;
|
|
1239
1791
|
}
|
|
1240
1792
|
if (!chatModel || !messages) {
|
|
1241
|
-
|
|
1793
|
+
respond({ error: 'Model and messages are required' });
|
|
1242
1794
|
return;
|
|
1243
1795
|
}
|
|
1244
1796
|
let url;
|
|
@@ -1255,7 +1807,15 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1255
1807
|
const anthropicTools = tools?.length ? (0, anthropicAdapter_1.translateToolsToAnthropic)(tools) : [];
|
|
1256
1808
|
bodyObj = {
|
|
1257
1809
|
model: chatModel,
|
|
1258
|
-
|
|
1810
|
+
/*
|
|
1811
|
+
* Anthropic requires `max_tokens`, so unlike the other providers it
|
|
1812
|
+
* cannot be left to the endpoint. 8192 is the default because it is
|
|
1813
|
+
* what every Anthropic model accepts - the older small ones cap out
|
|
1814
|
+
* below that and answer 400 to anything higher. The newer models write
|
|
1815
|
+
* far longer answers than that, and a script that runs into the limit
|
|
1816
|
+
* is simply cut off mid-line, so the value is a setting.
|
|
1817
|
+
*/
|
|
1818
|
+
max_tokens: maxTokens,
|
|
1259
1819
|
stream: false,
|
|
1260
1820
|
...(systemText ? { system: systemText } : {}),
|
|
1261
1821
|
messages: anthropicMessages,
|
|
@@ -1301,7 +1861,7 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1301
1861
|
chatHeaders['Content-Length'] = bodyBuffer.length;
|
|
1302
1862
|
const resolved = resolveRequestModule(url);
|
|
1303
1863
|
if (!resolved) {
|
|
1304
|
-
|
|
1864
|
+
respond({ error: `Invalid API URL: ${url}` });
|
|
1305
1865
|
return;
|
|
1306
1866
|
}
|
|
1307
1867
|
const { module: requestModule, isHttps } = resolved;
|
|
@@ -1332,19 +1892,39 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1332
1892
|
content = message?.content || '';
|
|
1333
1893
|
tool_calls = message?.tool_calls;
|
|
1334
1894
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1895
|
+
// How the endpoint says it finished. Passed on in both
|
|
1896
|
+
// cases, so the editor can tell a truncated answer from
|
|
1897
|
+
// a complete one instead of just showing what arrived.
|
|
1898
|
+
const info = (0, aiProviderResolver_1.extractAiResponseInfo)(parsed);
|
|
1899
|
+
// `.trim()`, because a whitespace-only answer is just as
|
|
1900
|
+
// empty to the user but used to slip past this check and
|
|
1901
|
+
// arrive in the editor as a blank chat bubble with no
|
|
1902
|
+
// reason attached - the stop reason below is the whole point.
|
|
1903
|
+
if (!content.trim() && !tool_calls?.length) {
|
|
1904
|
+
const error = (0, aiProviderResolver_1.describeEmptyAiResponse)(info, data);
|
|
1905
|
+
// What arrived instead of an answer. Without this the
|
|
1906
|
+
// log only repeats what is already known - that there
|
|
1907
|
+
// is nothing - instead of naming the blocks that came
|
|
1908
|
+
const shape = provider === 'anthropic'
|
|
1909
|
+
? (0, anthropicAdapter_1.describeAnthropicContent)(parsed)
|
|
1910
|
+
: `choices: ${parsed.choices?.length ?? 0}, content: ${typeof parsed
|
|
1911
|
+
.choices?.[0]?.message?.content}`;
|
|
1912
|
+
this.log.warn(`chatCompletion (${provider}/${chatModel}): ${error}. Answer contained: ${shape}`);
|
|
1913
|
+
respond({ error, ...info });
|
|
1337
1914
|
}
|
|
1338
1915
|
else {
|
|
1339
|
-
this.
|
|
1916
|
+
this.log.debug(`chatCompletion (${provider}/${chatModel}): answered with ${content.length} characters, ${tool_calls?.length || 0} tool calls, stop reason ${info.finishReason || '-'}`);
|
|
1917
|
+
respond({
|
|
1340
1918
|
success: true,
|
|
1341
1919
|
content,
|
|
1342
1920
|
...(tool_calls ? { tool_calls } : {}),
|
|
1343
|
-
|
|
1921
|
+
...info,
|
|
1922
|
+
});
|
|
1344
1923
|
}
|
|
1345
1924
|
}
|
|
1346
|
-
catch {
|
|
1347
|
-
this.
|
|
1925
|
+
catch (e) {
|
|
1926
|
+
this.log.warn(`chatCompletion (${provider}/${chatModel}): cannot read the answer of ${url}: ${e instanceof Error ? e.message : String(e)}. Response: ${data.substring(0, 200)}`);
|
|
1927
|
+
respond({ error: 'Invalid JSON response from API' });
|
|
1348
1928
|
}
|
|
1349
1929
|
}
|
|
1350
1930
|
else {
|
|
@@ -1356,24 +1936,28 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1356
1936
|
catch {
|
|
1357
1937
|
detail = data.substring(0, 200);
|
|
1358
1938
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1939
|
+
const error = `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`;
|
|
1940
|
+
this.log.warn(`chatCompletion (${provider}/${chatModel}): ${error}`);
|
|
1941
|
+
respond({ error });
|
|
1362
1942
|
}
|
|
1363
1943
|
});
|
|
1364
1944
|
});
|
|
1365
1945
|
req.on('error', (err) => {
|
|
1366
|
-
this.
|
|
1946
|
+
this.log.warn(`chatCompletion (${provider}/${chatModel}): cannot reach ${url}: ${err.message}`);
|
|
1947
|
+
respond({ error: `Connection failed: ${err.message}` });
|
|
1367
1948
|
});
|
|
1368
1949
|
req.on('timeout', () => {
|
|
1369
1950
|
req.destroy();
|
|
1370
|
-
|
|
1951
|
+
const error = `Connection timeout (${Math.round(requestTimeout / 1000)}s)`;
|
|
1952
|
+
this.log.warn(`chatCompletion (${provider}/${chatModel}): ${url} - ${error}`);
|
|
1953
|
+
respond({ error });
|
|
1371
1954
|
});
|
|
1372
1955
|
req.write(bodyBuffer);
|
|
1373
1956
|
req.end();
|
|
1374
1957
|
}
|
|
1375
1958
|
catch (error) {
|
|
1376
|
-
this.
|
|
1959
|
+
this.log.warn(`chatCompletion (${provider}/${chatModel}): ${url} - ${error.toString()}`);
|
|
1960
|
+
respond({ error: `Connection failed: ${error.toString()}` });
|
|
1377
1961
|
}
|
|
1378
1962
|
})();
|
|
1379
1963
|
break;
|
|
@@ -1387,7 +1971,7 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1387
1971
|
return;
|
|
1388
1972
|
}
|
|
1389
1973
|
const provider = (obj.message?.provider || 'openai').trim();
|
|
1390
|
-
const { apiKey, baseUrl } = await this.
|
|
1974
|
+
const { apiKey, baseUrl } = await this.resolveAiTestCredentials(provider, {
|
|
1391
1975
|
messageApiKey: obj.message?.apiKey,
|
|
1392
1976
|
messageBaseUrl: obj.message?.baseUrl,
|
|
1393
1977
|
messageCredentialId: obj.message?.credentialId,
|
|
@@ -1396,7 +1980,17 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1396
1980
|
// Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
|
|
1397
1981
|
if (!apiKey &&
|
|
1398
1982
|
(provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
|
|
1399
|
-
|
|
1983
|
+
// Say which of the two setups came up empty - "No API key provided" sent the user
|
|
1984
|
+
// looking for a key field that `manager` mode does not even show.
|
|
1985
|
+
const mode = obj.message?.credentialType || this.config.credentialType || 'manual';
|
|
1986
|
+
let error = 'No API key provided';
|
|
1987
|
+
if (mode === 'manager') {
|
|
1988
|
+
const id = (obj.message?.credentialId || (0, aiProviderResolver_1.getProviderCredentialId)(this.config, provider)).trim();
|
|
1989
|
+
error = id
|
|
1990
|
+
? `Credential "${id}" contains no API key`
|
|
1991
|
+
: `No credential selected for "${provider}"`;
|
|
1992
|
+
}
|
|
1993
|
+
this.sendTo(obj.from, obj.command, { error }, obj.callback);
|
|
1400
1994
|
return;
|
|
1401
1995
|
}
|
|
1402
1996
|
let url;
|
|
@@ -1555,7 +2149,15 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1555
2149
|
}
|
|
1556
2150
|
case 'execute': {
|
|
1557
2151
|
if (obj.callback) {
|
|
1558
|
-
|
|
2152
|
+
/*
|
|
2153
|
+
* This runs code on this host with the whole ioBroker API behind it. Anybody who
|
|
2154
|
+
* may send a message here could ask for it, and the message never said on whose
|
|
2155
|
+
* behalf - so there was nothing to check it against. Where the controller names
|
|
2156
|
+
* the user (js-controller 7.2.5 and newer), that user needs the right a command on
|
|
2157
|
+
* the host needs; where it does not, nothing changes.
|
|
2158
|
+
*/
|
|
2159
|
+
void this.mayExecuteFor(obj)
|
|
2160
|
+
.then(async (error) => error ? { ok: false, error, logs: [], output: '' } : await this.executeScript(obj.message))
|
|
1559
2161
|
.then(result => this.sendTo(obj.from, obj.command, result, obj.callback))
|
|
1560
2162
|
.catch(err => this.sendTo(obj.from, obj.command, {
|
|
1561
2163
|
ok: false,
|
|
@@ -1627,6 +2229,9 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
1627
2229
|
}
|
|
1628
2230
|
}
|
|
1629
2231
|
async main() {
|
|
2232
|
+
await this.ensureHistoryStorage();
|
|
2233
|
+
// Makes a changed limit take effect at once instead of script by script
|
|
2234
|
+
await this.trimAllHistories();
|
|
1630
2235
|
// Patch the font as it sometimes is wrong
|
|
1631
2236
|
if (!this.context.debugMode) {
|
|
1632
2237
|
if (await this.patchFont()) {
|
|
@@ -3057,7 +3662,9 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
3057
3662
|
obj.common.engineType ||= '';
|
|
3058
3663
|
if (obj.common.engineType.toLowerCase().startsWith('javascript') ||
|
|
3059
3664
|
obj.common.engineType === 'Blockly' ||
|
|
3060
|
-
obj.common.engineType === 'Rules'
|
|
3665
|
+
obj.common.engineType === 'Rules' ||
|
|
3666
|
+
// function block diagram: generated JavaScript, like Blockly and Rules (not known to @iobroker/types yet)
|
|
3667
|
+
obj.common.engineType === 'FBD') {
|
|
3061
3668
|
// Javascript
|
|
3062
3669
|
this.log.info(`${name}: start JavaScript (${obj.common.engineType})`);
|
|
3063
3670
|
let sourceFn = name;
|
|
@@ -3488,10 +4095,12 @@ class JavaScript extends adapter_core_1.Adapter {
|
|
|
3488
4095
|
async debugStop() {
|
|
3489
4096
|
if (this.debugState.child) {
|
|
3490
4097
|
this.debugSendToInspector({ cmd: 'end' });
|
|
4098
|
+
// The inspector stops the debugged process first and ends itself only then, because that process writes
|
|
4099
|
+
// its log through its pipes (#2382). Kill it only if it does not manage that in time.
|
|
3491
4100
|
this.debugState.endTimeout = setTimeout(() => {
|
|
3492
4101
|
this.debugState.endTimeout = null;
|
|
3493
|
-
this.debugState.child?.kill('
|
|
3494
|
-
},
|
|
4102
|
+
this.debugState.child?.kill('SIGKILL');
|
|
4103
|
+
}, 5_000);
|
|
3495
4104
|
this.debugState.promiseOnEnd ||= Promise.resolve(0);
|
|
3496
4105
|
}
|
|
3497
4106
|
else {
|