node-red-contrib-knx-ultimate 6.3.19 → 6.3.22

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/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 6.3.22** - August 2026<br/>
10
+
11
+ - **KNX AI — visible context size and real prompt weight**: the “Chat context overview” card now shows the current operational context limit and the actual UTF-8 size of the last operational chat prompt. Ollama, Bionic, OpenAI-compatible and Claude token-usage fields are captured when returned by the provider; otherwise the UI clearly labels the token count as an estimate. Local limits reflect the active context without exceeding KNX AI's 16K cap.<br/>
12
+ - **KNX AI — documentation-free operational chat**: Telegram, RedBot and custom CHAT adapters no longer inject packaged help, README, changelog, wiki or example snippets into model prompts. They retain live and archived KNX data, ETS semantics, Node-RED inventory, conversation and home memory, AI Education, cameras and TTS; the web Assistant keeps documentation access for package-support questions.<br/>
13
+ - **KNX AI — tolerant safe-read normalization**: state queries from small local models no longer become rejected writes when the model returns exact ETS destinations but omits the operation discriminator and payload. Payload-free operations are safely inferred as `GroupValue_Read`; payload-bearing operations remain writes and retain every ETS-role, DPT, payload and confirmation safeguard.<br/>
14
+ - **KNX AI — fixed 16K Ollama runtime and prompt budget**: Ollama's advertised model maximum is now informational instead of being sent back as the requested `num_ctx`. KNX AI caps `num_ctx` and its relevance-selected semantic prompt at 16K, preventing 131K KV-cache allocation and full-context prefill. An audit confirmed that OpenAI and Claude receive only output limits, not a client-selected maximum context window.<br/>
15
+ - **KNX AI — fixed 16K Bionic prompt budget**: Bionic's reported active or maximum context can no longer promote conversations to the full 131K data view. KNX AI always builds a relevance-selected 16K semantic prompt for Bionic while preserving the provider's loaded instance, open-ended reasoning, routines, KNX control, cameras and TTS capabilities.<br/>
16
+ - **KNX AI — preserve Bionic LM Studio context**: model discovery now keeps the context length of an already loaded Bionic instance instead of confusing the model's maximum capability with the desired runtime setting. KNX AI no longer loads inactive models through the management API: the first chat request leaves JIT loading and saved per-model defaults to Bionic, while KNX AI uses a conservative 16K prompt budget until the active configuration can be inspected.<br/>
17
+ - **KNX AI — neutral structured-response example**: the conversational contract now demonstrates empty action arrays instead of a fabricated group address, DPT, camera and payload. This prevents small local models from copying placeholder operations into unrelated replies such as greetings while preserving open-ended reasoning and every existing KNX, camera, TTS and routine capability.<br/>
18
+
19
+ **Version 6.3.21** - August 2026<br/>
20
+
21
+ - **KNX AI — persistent adapter-event history**: every event published by an automatically detected adapter is now normalized into vendor-neutral metadata and appended to a node-specific daily JSONL archive. The archive follows the 10-day KNX retention, guaranteeing at least 24 hours of camera and future adapter events across Node-RED restarts without storing snapshot image data. The editor context card exposes the actual adapter-history directories.<br/>
22
+ - **KNX AI — authoritative historical queries**: web Assistant, Telegram, RedBot and custom CHAT channels now query both the adapter-event archive and the existing KNX daily telegram files. Historical prompts include totals calculated across every stored row in the requested interval plus relevance-selected detail samples, preventing sample size from being reported as the total. Natural-language ranges now include multilingual “last N hours” requests and are bounded by available retention.<br/>
23
+
9
24
  **Version 6.3.19** - August 2026<br/>
10
25
 
11
26
  - **KNX AI — conversational multi-step routines**: added coordinated routines such as leaving home, bedtime and cinema mode. KNX AI can now perform a first pass with up to 20 fresh KNX state reads, use the authoritative bus results to prepare an ordered plan of up to 12 validated writes, request one confirmation and then execute the complete routine. After confirmation it waits up to four seconds for immediate matching bus feedback, reports verified and unverified operations without treating missing immediate feedback as a device failure, and dispatches any explicitly requested TTS Ultimate announcement only after execution. Routine details, preliminary readings and execution results are exposed as structured chat metadata. The importable confirmation example, editor help and wiki documentation were updated in EN, IT, DE, FR, ES and zh-CN.<br/>
@@ -182,12 +182,18 @@
182
182
  #knx-ai-detected-adapters-status,
183
183
  #knx-ai-chat-context-status,
184
184
  .knx-ai-chat-context-intro,
185
+ .knx-ai-chat-context-metric,
185
186
  .knx-ai-chat-context-description,
186
187
  .knx-ai-chat-context-pattern {
187
188
  color: var(--red-ui-secondary-text-color, #777);
188
189
  font-size: 12px;
189
190
  }
190
191
 
192
+ .knx-ai-chat-context-metric {
193
+ margin-top: 6px;
194
+ font-weight: 600;
195
+ }
196
+
191
197
  .knx-ai-chat-context-group-title {
192
198
  margin: 12px 0 2px;
193
199
  font-size: 12px;
@@ -610,6 +616,8 @@
610
616
  const $sources = $("#knx-ai-chat-context-sources-list").empty();
611
617
  const $files = $("#knx-ai-chat-context-files-list").empty();
612
618
  const $directories = $("#knx-ai-chat-context-directories-list").empty();
619
+ const $contextLimit = $("#knx-ai-chat-context-limit");
620
+ const $lastPromptUsage = $("#knx-ai-chat-context-last-prompt");
613
621
  if (!overview || typeof overview !== "object") {
614
622
  $content.hide();
615
623
  $status
@@ -633,11 +641,59 @@
633
641
  $row.appendTo($list);
634
642
  };
635
643
 
644
+ const formatCompactTokens = function (value) {
645
+ const tokens = Math.max(0, Number(value) || 0);
646
+ if (!tokens) return "?";
647
+ if (tokens >= 1024 && tokens % 1024 === 0) return (tokens / 1024) + "K";
648
+ return tokens.toLocaleString();
649
+ };
650
+ const formatBytes = function (value) {
651
+ const bytes = Math.max(0, Number(value) || 0);
652
+ if (bytes >= (1024 * 1024)) return (bytes / (1024 * 1024)).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " MB";
653
+ if (bytes >= 1024) return (bytes / 1024).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " KB";
654
+ return bytes.toLocaleString() + " B";
655
+ };
656
+
657
+ const contextLimit = overview.contextLimit && typeof overview.contextLimit === "object" ? overview.contextLimit : {};
658
+ const currentProvider = String(contextLimit.provider || $("#node-input-llmProvider").val() || "").toLowerCase();
659
+ let contextLimitTokens = Math.max(0, Number(contextLimit.tokens) || 0);
660
+ if (!contextLimitTokens && (currentProvider === "lmstudio" || currentProvider === "ollama")) {
661
+ contextLimitTokens = Math.min(Math.max(0, Number($("#node-input-llmContextLength").val()) || 16384), 16384);
662
+ }
663
+ const contextLimitValue = contextLimitTokens
664
+ ? formatCompactTokens(contextLimitTokens) + " " + t("knxUltimateAI.messages.chatContextTokens", "tokens")
665
+ : t("knxUltimateAI.messages.chatContextProviderManaged", "managed by the selected provider/model");
666
+ $contextLimit
667
+ .text(t("knxUltimateAI.messages.chatContextLimitLabel", "Maximum operational context") + ": " + contextLimitValue)
668
+ .attr("title", contextLimitTokens ? contextLimitTokens.toLocaleString() + " tokens" : contextLimitValue);
669
+
670
+ const promptUsage = overview.lastPromptUsage && typeof overview.lastPromptUsage === "object" ? overview.lastPromptUsage : null;
671
+ if (promptUsage && Number(promptUsage.bytes) > 0) {
672
+ const exactInputTokens = Math.max(0, Number(promptUsage.exactInputTokens) || 0);
673
+ const estimatedInputTokens = Math.max(0, Number(promptUsage.estimatedInputTokens) || 0);
674
+ const tokenText = exactInputTokens
675
+ ? formatCompactTokens(exactInputTokens) + " " + t("knxUltimateAI.messages.chatContextExactInputTokens", "input tokens measured by the provider")
676
+ : "~" + formatCompactTokens(estimatedInputTokens) + " " + t("knxUltimateAI.messages.chatContextEstimatedInputTokens", "estimated input tokens");
677
+ const imageText = Number(promptUsage.imageCount) > 0
678
+ ? " · " + Number(promptUsage.imageCount).toLocaleString() + " " + t("knxUltimateAI.messages.chatContextImages", "images")
679
+ : "";
680
+ $lastPromptUsage.text(
681
+ t("knxUltimateAI.messages.chatContextLastPromptLabel", "Last chat prompt actual size") + ": " +
682
+ formatBytes(promptUsage.bytes) + " · " + tokenText + imageText
683
+ );
684
+ } else {
685
+ $lastPromptUsage.text(
686
+ t("knxUltimateAI.messages.chatContextLastPromptLabel", "Last chat prompt actual size") + ": " +
687
+ t("knxUltimateAI.messages.chatContextLastPromptUnavailable", "not available until the first chat request")
688
+ );
689
+ }
690
+
636
691
  const sourceLabels = {
637
692
  knxTraffic: t("knxUltimateAI.messages.chatContextSourceKnxTraffic", "Live KNX summary, anomalies, topology and selected telegrams."),
693
+ adapterHistory: t("knxUltimateAI.messages.chatContextSourceAdapterHistory", "Persistent history of automatically detected adapter events."),
638
694
  etsProject: t("knxUltimateAI.messages.chatContextSourceEtsProject", "ETS semantics and the full Node-RED project inventory."),
639
695
  memoryEducation: t("knxUltimateAI.messages.chatContextSourceMemoryEducation", "Session context, AI Education and bounded home memory."),
640
- camerasDocs: t("knxUltimateAI.messages.chatContextSourceCamerasDocs", "Detected cameras and relevant help, README and example snippets."),
696
+ cameras: t("knxUltimateAI.messages.chatContextSourceCameras", "Detected cameras and their available capabilities."),
641
697
  ttsUltimate: t("knxUltimateAI.messages.chatContextSourceTtsUltimate", "Selected TTS Ultimate announcement target.")
642
698
  };
643
699
  (Array.isArray(overview.sources) ? overview.sources : []).forEach(function (sourceId) {
@@ -668,7 +724,9 @@
668
724
 
669
725
  const directoryLabels = {
670
726
  archiveRoot: t("knxUltimateAI.messages.chatContextDirectoryRoot", "Telegram archive root"),
671
- nodeArchive: t("knxUltimateAI.messages.chatContextDirectoryNode", "This node's telegram archive")
727
+ nodeArchive: t("knxUltimateAI.messages.chatContextDirectoryNode", "This node's telegram archive"),
728
+ adapterArchiveRoot: t("knxUltimateAI.messages.chatContextDirectoryAdapterRoot", "Adapter event archive root"),
729
+ adapterNodeArchive: t("knxUltimateAI.messages.chatContextDirectoryAdapterNode", "This node's adapter event archive")
672
730
  };
673
731
  (Array.isArray(overview.telegramDirectories) ? overview.telegramDirectories : []).forEach(function (item) {
674
732
  if (!item || !item.path) return;
@@ -677,7 +735,9 @@
677
735
  directoryLabels[item.id] || String(item.id || ""),
678
736
  "",
679
737
  item.path,
680
- t("knxUltimateAI.messages.chatContextDirectoryBadge", "KNX")
738
+ /^adapter/.test(String(item.id || ""))
739
+ ? t("knxUltimateAI.messages.chatContextDirectoryAdapterBadge", "Adapter")
740
+ : t("knxUltimateAI.messages.chatContextDirectoryBadge", "KNX")
681
741
  );
682
742
  });
683
743
 
@@ -747,11 +807,14 @@
747
807
  let text = t("knxUltimateAI.messages.lmStudioContextAvailable", "Maximum model context") + ": " + maxTokens + " token";
748
808
  let color = "#555";
749
809
  if (state === "loading") {
750
- text = t("knxUltimateAI.messages.lmStudioContextLoading", "Loading the model with its maximum context") + ": " + maxTokens + " token…";
810
+ text = t("knxUltimateAI.messages.lmStudioContextLoading", "Checking the active model context") + "…";
751
811
  color = "#1565c0";
812
+ } else if (state === "inactive") {
813
+ text = t("knxUltimateAI.messages.lmStudioContextInactive", "Model inactive; Bionic defaults will be used on the first request");
814
+ color = "#555";
752
815
  } else if (state === "configured") {
753
816
  const configuredTokens = formatContextTokens(detail.configuredContextLength || detail.loadedContextLength || detail.maxContextLength);
754
- text = t("knxUltimateAI.messages.lmStudioContextConfigured", "Maximum model context configured") + ": " + configuredTokens + " token";
817
+ text = t("knxUltimateAI.messages.lmStudioContextConfigured", "Active model context") + ": " + configuredTokens + " token";
755
818
  color = "#2e7d32";
756
819
  } else if (state === "error") {
757
820
  text = String(errorText || t("knxUltimateAI.messages.lmStudioContextFailed", "Unable to configure the model context"));
@@ -759,6 +822,9 @@
759
822
  } else if (detail.loadedContextLength > 0) {
760
823
  text += " · " + t("knxUltimateAI.messages.lmStudioContextCurrentlyLoaded", "currently loaded") + ": " + loadedTokens + " token";
761
824
  }
825
+ if (provider === "lmstudio" || provider === "ollama") {
826
+ text += " · " + t("knxUltimateAI.messages.localContextBudget", "KNX AI context budget") + ": 16K";
827
+ }
762
828
  $status.text(text).css("color", color).show();
763
829
  };
764
830
 
@@ -819,9 +885,13 @@
819
885
  }
820
886
 
821
887
  const requestVersion = ++localContextRequestVersion;
822
- $("#node-input-llmContextLength").val(Number(detail.maxContextLength));
888
+ const activeContextLength = Number(detail.loadedContextLength) > 0
889
+ ? Number(detail.loadedContextLength)
890
+ : Number(detail.maxContextLength);
891
+ $("#node-input-llmContextLength").val(activeContextLength);
823
892
  if (provider === "ollama") {
824
- detail.configuredContextLength = Number(detail.maxContextLength);
893
+ detail.configuredContextLength = Math.min(Number(detail.maxContextLength), 16384);
894
+ $("#node-input-llmContextLength").val(detail.configuredContextLength);
825
895
  setLocalModelContextStatus(detail, "configured");
826
896
  return;
827
897
  }
@@ -843,13 +913,14 @@
843
913
  type: "POST",
844
914
  contentType: "application/json",
845
915
  data: JSON.stringify(payload)
846
- })
916
+ })
847
917
  .done(function (data) {
848
918
  if (requestVersion !== localContextRequestVersion) return;
849
- detail.loadedContextLength = Math.max(0, Number(data && data.contextLength) || Number(detail.maxContextLength));
850
- detail.configuredContextLength = detail.loadedContextLength;
851
- $("#node-input-llmContextLength").val(detail.loadedContextLength);
852
- setLocalModelContextStatus(detail, "configured");
919
+ const resolvedContextLength = Math.max(0, Number(data && data.contextLength) || 0);
920
+ detail.loadedContextLength = data && data.active === true ? resolvedContextLength : 0;
921
+ detail.configuredContextLength = resolvedContextLength;
922
+ $("#node-input-llmContextLength").val(resolvedContextLength);
923
+ setLocalModelContextStatus(detail, data && data.active === true ? "configured" : "inactive");
853
924
  })
854
925
  .fail(function (xhr) {
855
926
  if (requestVersion !== localContextRequestVersion) return;
@@ -1115,6 +1186,8 @@
1115
1186
  <div id="knx-ai-chat-context-status" data-i18n="knxUltimateAI.messages.chatContextLoading"></div>
1116
1187
  <div id="knx-ai-chat-context-content" style="display:none;">
1117
1188
  <div class="knx-ai-chat-context-intro" data-i18n="knxUltimateAI.messages.chatContextIntro"></div>
1189
+ <div id="knx-ai-chat-context-limit" class="knx-ai-chat-context-metric"></div>
1190
+ <div id="knx-ai-chat-context-last-prompt" class="knx-ai-chat-context-metric"></div>
1118
1191
  <div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextSourcesTitle"></div>
1119
1192
  <div id="knx-ai-chat-context-sources-list"></div>
1120
1193
  <div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextFilesTitle"></div>