iobroker.assistant 1.1.0 → 1.2.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.
Files changed (71) hide show
  1. package/README.md +63 -1
  2. package/admin/custom/.vite/manifest.json +62 -62
  3. package/admin/custom/assets/Components-BSewCBfS.js +2 -0
  4. package/admin/custom/assets/{_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomAssistant__mf_owner__1-A4gakomR.js → _virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomAssistant__mf_owner__1-C5NPM0Of.js} +1 -1
  5. package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__201375093093590__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-CwqmfMvn.js → _virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__163539842885036__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-IaosaymG.js} +1 -1
  6. package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__201375093093590__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BMuLYe6f.js → _virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__163539842885036__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-Cdyjt5KT.js} +39 -39
  7. package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__201375093093590__loadShare__shared-BxjZmnpc.js → _virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__163539842885036__loadShare__shared-Dsw5zWS8.js} +1 -1
  8. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__163539842885036__prebuild___mf_0_iobroker_mf_1_json_mf_2_config__prebuild__-SgSagTOv.js +122 -0
  9. package/admin/custom/assets/{hostInit-4roC4hEj.js → hostInit-jJe8bIfV.js} +1 -1
  10. package/admin/custom/assets/{lib-CchzgTUs.js → lib-CL8mXvI6.js} +1 -1
  11. package/admin/custom/assets/{mf-entry-bootstrap-0-4806bbe4.js → mf-entry-bootstrap-0-ab227aa6.js} +2 -2
  12. package/admin/custom/assets/{pendingShares-DLlv8yFY.js → pendingShares-wrGtfliQ.js} +1 -1
  13. package/admin/custom/customComponents.js +2 -2
  14. package/admin/custom/i18n/de.json +40 -0
  15. package/admin/custom/i18n/en.json +40 -0
  16. package/admin/custom/i18n/es.json +40 -0
  17. package/admin/custom/i18n/fr.json +40 -0
  18. package/admin/custom/i18n/it.json +40 -0
  19. package/admin/custom/i18n/nl.json +40 -0
  20. package/admin/custom/i18n/pl.json +40 -0
  21. package/admin/custom/i18n/pt.json +40 -0
  22. package/admin/custom/i18n/ru.json +40 -0
  23. package/admin/custom/i18n/uk.json +40 -0
  24. package/admin/custom/i18n/zh-cn.json +40 -0
  25. package/admin/custom/index.html +2 -2
  26. package/admin/custom/mf-manifest.json +1 -1
  27. package/admin/custom/mf-stats.json +1 -1
  28. package/admin/i18n/de.json +6 -2
  29. package/admin/i18n/en.json +6 -2
  30. package/admin/i18n/es.json +6 -2
  31. package/admin/i18n/fr.json +6 -2
  32. package/admin/i18n/it.json +6 -2
  33. package/admin/i18n/nl.json +6 -2
  34. package/admin/i18n/pl.json +6 -2
  35. package/admin/i18n/pt.json +6 -2
  36. package/admin/i18n/ru.json +6 -2
  37. package/admin/i18n/uk.json +6 -2
  38. package/admin/i18n/zh-cn.json +6 -2
  39. package/admin/jsonConfig.json +29 -2
  40. package/build/lib/journal.js +298 -2
  41. package/build/lib/journal.js.map +1 -1
  42. package/build/lib/llm.js +105 -4
  43. package/build/lib/llm.js.map +1 -1
  44. package/build/lib/localLlm.js +47 -10
  45. package/build/lib/localLlm.js.map +1 -1
  46. package/build/lib/nlu.js +124 -5
  47. package/build/lib/nlu.js.map +1 -1
  48. package/build/lib/tools.js +3 -0
  49. package/build/lib/tools.js.map +1 -1
  50. package/build/lib/voice/esphome.js +81 -7
  51. package/build/lib/voice/esphome.js.map +1 -1
  52. package/build/lib/voice/esphomeProto.js.map +1 -1
  53. package/build/lib/voice/mediaServer.js +29 -1
  54. package/build/lib/voice/mediaServer.js.map +1 -1
  55. package/build/lib/voice/protocol.js.map +1 -1
  56. package/build/lib/voice/voiceServer.js +81 -7
  57. package/build/lib/voice/voiceServer.js.map +1 -1
  58. package/build/lib/voice/wakeWords.js +284 -0
  59. package/build/lib/voice/wakeWords.js.map +1 -0
  60. package/build/lib/voice/wyoming.js +12 -4
  61. package/build/lib/voice/wyoming.js.map +1 -1
  62. package/build/lib/weather.js +160 -0
  63. package/build/lib/weather.js.map +1 -1
  64. package/build/main.js +716 -74
  65. package/build/main.js.map +1 -1
  66. package/io-package.json +27 -25
  67. package/package.json +5 -5
  68. package/admin/custom/assets/Components-C6qcpaRC.js +0 -1
  69. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__201375093093590__prebuild___mf_0_iobroker_mf_1_json_mf_2_config__prebuild__-TM4l0OYm.js +0 -122
  70. /package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__201375093093590__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-BkH7fzDw.js → _virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__163539842885036__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-BkH7fzDw.js} +0 -0
  71. /package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__201375093093590__prebuild__react_mf_1_jsx_mf_2_runtime__prebuild__-M61rjtmp.js → _virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__163539842885036__prebuild__react_mf_1_jsx_mf_2_runtime__prebuild__-M61rjtmp.js} +0 -0
@@ -128,7 +128,7 @@
128
128
  "Room": "Stanza",
129
129
  "Routines": "Routine",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "Avviare il server vocale UDP (satelliti ESP/Hannah)",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "Esegue un piccolo modello locale (node-llama-cpp) per domande generiche; passa al modello LLM cloud quando necessita di dati in tempo reale dal dispositivo. Installalo tramite il pulsante qui sotto.",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "Esegue un piccolo modello locale (node-llama-cpp) per le domande generali. Con un provider cloud risponde solo quando il cloud non funziona, a meno che «Chiedi prima al modello locale» sia attivo. Installalo con il pulsante qui sotto.",
132
132
  "Said or typed": "Detto o digitato",
133
133
  "Satellites": "Satelliti",
134
134
  "Satellites, rooms or devices (comma separated)": "Satelliti, stanze o dispositivi (separati da virgole)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "Sono preinstallati due suoni predefiniti (timer.wav, alarm.wav): scegline uno qui sotto oppure carica il tuo file mp3/wav. Il suono selezionato verrà riprodotto prima dell'annuncio vocale. Richiede ffmpeg sul computer host e su almeno un satellite.",
161
161
  "UDP port": "porta UDP",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "Carica i jingle in formato mp3/wav qui sotto; il suono selezionato verrà riprodotto prima dell'annuncio vocale. È necessario che ffmpeg sia installato sul computer host e su almeno un satellite.",
163
- "Use a local LLM before the cloud (installed on demand)": "Utilizzare un LLM locale prima del cloud (installato su richiesta).",
163
+ "Use a local LLM (installed on demand)": "Usa un LLM locale (installato su richiesta)",
164
+ "Ask the local model first": "Chiedi prima al modello locale",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "Ogni domanda che le regole non capiscono va al modello locale prima che al cloud. Risparmia chiamate al cloud, ma su un Raspberry Pi aggiunge 10–25 secondi a queste domande.",
166
+ "Local model timeout (s)": "Timeout del modello locale (s)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "Trascorso questo tempo il modello locale viene interrotto e la domanda passa al cloud.",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "Usato quando il provider sopra non funziona (interruzione, chiave scaduta, credito esaurito). Un motore locale mantiene attivo il controllo vocale senza internet.",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "Usato quando il provider sopra non funziona. Piper fa parlare l'assistente anche senza internet.",
166
170
  "Verify device feedback": "Verifica il riscontro del dispositivo",
@@ -128,7 +128,7 @@
128
128
  "Room": "Ruimte",
129
129
  "Routines": "Routines",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "Start de UDP-spraakserver (ESP/Hannah-satellieten).",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "Voert een klein lokaal model (node-llama-cpp) uit voor algemene vragen; schakelt over naar het cloudgebaseerde LLM-model wanneer live apparaatgegevens nodig zijn. Installeer het via de onderstaande knop.",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "Draait een klein lokaal model (node-llama-cpp) voor algemene vragen. Met een cloudprovider antwoordt het alleen als de cloud uitvalt, tenzij \"Eerst het lokale model vragen\" aan staat. Installeer het met de knop hieronder.",
132
132
  "Said or typed": "Gezegd of getypt",
133
133
  "Satellites": "Satellieten",
134
134
  "Satellites, rooms or devices (comma separated)": "Satellieten, kamers of apparaten (gescheiden door komma's)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "Er zijn twee standaardgeluiden (timer.wav, alarm.wav) vooraf geïnstalleerd. Kies er hieronder een of upload uw eigen mp3/wav-bestand. Het geselecteerde geluid wordt afgespeeld vóór de gesproken aankondiging. FFmpeg is vereist op de host en op ten minste één satelliet.",
161
161
  "UDP port": "UDP-poort",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "Upload hieronder mp3/wav-jingles; het geselecteerde geluid wordt afgespeeld vóór de gesproken aankondiging. FFmpeg moet geïnstalleerd zijn op de host en op ten minste één satelliet.",
163
- "Use a local LLM before the cloud (installed on demand)": "Gebruik een lokale LLM vóór de cloud (installatie op aanvraag).",
163
+ "Use a local LLM (installed on demand)": "Een lokaal LLM gebruiken (op aanvraag geïnstalleerd)",
164
+ "Ask the local model first": "Eerst het lokale model vragen",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "Elke vraag die de regels niet begrijpen gaat eerst naar het lokale model en dan naar de cloud. Bespaart cloudaanroepen, maar kost op een Raspberry Pi 10–25 seconden extra per zo’n vraag.",
166
+ "Local model timeout (s)": "Time-out lokaal model (s)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "Na deze tijd wordt het lokale model gestopt en gaat de vraag door naar de cloud.",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "Wordt gebruikt als de bovenstaande provider faalt (storing, verlopen sleutel, tegoed op). Een lokale engine houdt spraakbediening werkend zonder internet.",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "Wordt gebruikt als de bovenstaande provider faalt. Met Piper blijft de assistent spreken zonder internet.",
166
170
  "Verify device feedback": "Terugmelding van apparaat controleren",
@@ -128,7 +128,7 @@
128
128
  "Room": "Pokój",
129
129
  "Routines": "Rutyny",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "Uruchom serwer głosowy UDP (satelity ESP/Hannah)",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "Uruchamia mały model lokalny (node-llama-cpp) w przypadku pytań ogólnych; eskaluje do chmurowego LLM, gdy potrzebuje danych z urządzenia na żywo. Zainstaluj go, klikając poniższy przycisk.",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "Uruchamia mały lokalny model (node-llama-cpp) do ogólnych pytań. Przy dostawcy chmurowym odpowiada tylko, gdy chmura zawiedzie, chyba że włączono „Najpierw pytaj model lokalny”. Zainstaluj go przyciskiem poniżej.",
132
132
  "Said or typed": "Powiedziałeś lub napisałeś",
133
133
  "Satellites": "Satelity",
134
134
  "Satellites, rooms or devices (comma separated)": "Satelity, pomieszczenia lub urządzenia (oddzielone przecinkami)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "Dwa domyślne dźwięki (timer.wav, alarm.wav) są preinstalowane — wybierz jeden z poniższych lub prześlij własny plik mp3/wav. Wybrany dźwięk odtwarza się przed komunikatem głosowym. Wymagany jest ffmpeg na hoście i co najmniej jeden satelita.",
161
161
  "UDP port": "Port UDP",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "Prześlij poniżej dżingle w formacie mp3/wav; wybrany dźwięk będzie odtwarzany przed komunikatem głosowym. Wymagany jest zainstalowany ffmpeg na hoście i co najmniej jednym satelicie.",
163
- "Use a local LLM before the cloud (installed on demand)": "Użyj lokalnego LLM przed chmurą (instalowanego na żądanie)",
163
+ "Use a local LLM (installed on demand)": "Użyj lokalnego LLM (instalowanego na żądanie)",
164
+ "Ask the local model first": "Najpierw pytaj model lokalny",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "Każde pytanie, którego nie rozumieją reguły, trafia do modelu lokalnego przed chmurą. Oszczędza wywołania chmury, ale na Raspberry Pi wydłuża takie pytania o 10–25 sekund.",
166
+ "Local model timeout (s)": "Limit czasu modelu lokalnego (s)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "Po tym czasie model lokalny jest zatrzymywany, a pytanie trafia do chmury.",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "Używane, gdy powyższy dostawca zawiedzie (awaria, wygasły klucz, wyczerpany limit). Lokalny silnik utrzymuje sterowanie głosowe bez internetu.",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "Używane, gdy powyższy dostawca zawiedzie. Piper pozwala asystentowi mówić bez internetu.",
166
170
  "Verify device feedback": "Sprawdzaj potwierdzenie z urządzenia",
@@ -128,7 +128,7 @@
128
128
  "Room": "Divisão",
129
129
  "Routines": "Rotinas",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "Execute o servidor de voz UDP (ESP / satélites Hannah)",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "Executa um pequeno modelo local (node-llama-cpp) para perguntas gerais; recorre ao LLM na nuvem quando precisa de dados de dispositivos em tempo real. Instale-o com o botão abaixo.",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "Executa um pequeno modelo local (node-llama-cpp) para perguntas gerais. Com um provedor de nuvem, só responde quando a nuvem falha, a menos que \"Perguntar primeiro ao modelo local\" esteja ativo. Instale-o com o botão abaixo.",
132
132
  "Said or typed": "Dito ou digitado",
133
133
  "Satellites": "Satélites",
134
134
  "Satellites, rooms or devices (comma separated)": "Satélites, cômodos ou dispositivos (separados por vírgulas)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "Dois sons padrão (timer.wav, alarm.wav) estão pré-instalados — escolha um abaixo ou carregue seu próprio arquivo mp3/wav. O som selecionado é reproduzido antes do anúncio falado. Requer o ffmpeg instalado no host e em pelo menos um satélite.",
161
161
  "UDP port": "porta UDP",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "Faça o upload de jingles em MP3/WAV abaixo; o som selecionado será reproduzido antes do anúncio falado. É necessário ter o ffmpeg instalado no servidor e em pelo menos um satélite.",
163
- "Use a local LLM before the cloud (installed on demand)": "Utilize um LLM local antes da nuvem (instalado sob demanda).",
163
+ "Use a local LLM (installed on demand)": "Usar um LLM local (instalado sob demanda)",
164
+ "Ask the local model first": "Perguntar primeiro ao modelo local",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "Cada pergunta que as regras não entendem vai ao modelo local antes da nuvem. Poupa chamadas à nuvem, mas num Raspberry Pi acrescenta 10–25 segundos a essas perguntas.",
166
+ "Local model timeout (s)": "Tempo limite do modelo local (s)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "Após este tempo o modelo local é interrompido e a pergunta segue para a nuvem.",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "Usado quando o provedor acima falha (interrupção, chave expirada, cota esgotada). Um motor local mantém o controle por voz funcionando sem internet.",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "Usado quando o provedor acima falha. O Piper mantém o assistente falando sem internet.",
166
170
  "Verify device feedback": "Verificar a resposta do dispositivo",
@@ -128,7 +128,7 @@
128
128
  "Room": "Комната",
129
129
  "Routines": "Рутины",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "Запустите UDP-сервер голосовой связи (спутники ESP/Hannah).",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "Запускает небольшую локальную модель (node-llama-cpp) для решения общих вопросов; при необходимости передает данные с устройства в режиме реального времени в облачную LLM. Установите ее с помощью кнопки ниже.",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "Запускает небольшую локальную модель (node-llama-cpp) для общих вопросов. При наличии облачного провайдера она отвечает только при сбое облака, если не включено «Сначала спрашивать локальную модель». Установка кнопкой ниже.",
132
132
  "Said or typed": "Сказано или напечатано",
133
133
  "Satellites": "Спутники",
134
134
  "Satellites, rooms or devices (comma separated)": "Сателлиты, комнаты или устройства (через запятую)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "По умолчанию предустановлены два звука (timer.wav, alarm.wav) — выберите один из них ниже или загрузите свой собственный mp3/wav. Выбранный звук воспроизводится перед голосовым сообщением. Требуется ffmpeg на хосте и как минимум один сателлит.",
161
161
  "UDP port": "UDP-порт",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "Загрузите джинглы в формате mp3/wav ниже; выбранный звук воспроизводится перед голосовым объявлением. Необходимо установить ffmpeg на хост-системе и иметь как минимум один спутник.",
163
- "Use a local LLM before the cloud (installed on demand)": "Используйте локальный LLM перед облаком (устанавливается по запросу).",
163
+ "Use a local LLM (installed on demand)": "Использовать локальную LLM (устанавливается по запросу)",
164
+ "Ask the local model first": "Сначала спрашивать локальную модель",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "Каждый вопрос, который не поняли правила, сначала идёт к локальной модели, потом в облако. Экономит облачные запросы, но на Raspberry Pi добавляет к таким вопросам 10–25 секунд.",
166
+ "Local model timeout (s)": "Тайм-аут локальной модели (с)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "По истечении этого времени локальная модель останавливается, и вопрос передаётся в облако.",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "Используется, если указанный выше провайдер не работает (сбой, истёкший ключ, исчерпанная квота). Локальный движок сохраняет голосовое управление без интернета.",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "Используется, если указанный выше провайдер не работает. Piper позволяет ассистенту говорить без интернета.",
166
170
  "Verify device feedback": "Проверять ответ устройства",
@@ -128,7 +128,7 @@
128
128
  "Room": "Кімната",
129
129
  "Routines": "Рутини",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "Запуск голосового сервера UDP (супутники ESP / Hannah)",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "Запускає невелику локальну модель (node-llama-cpp) для загальних питань; переходить до хмарного LLM, коли потрібні дані з пристрою в реальному часі. Встановіть її за допомогою кнопки нижче.",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "Запускає невелику локальну модель (node-llama-cpp) для загальних запитань. З хмарним провайдером вона відповідає лише у разі збою хмари, якщо не ввімкнено «Спочатку питати локальну модель». Встановлення кнопкою нижче.",
132
132
  "Said or typed": "Сказав або надрукував",
133
133
  "Satellites": "Супутники",
134
134
  "Satellites, rooms or devices (comma separated)": "Сателіти, кімнати або пристрої (через кому)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "Попередньо встановлено два звуки за замовчуванням (timer.wav, alarm.wav) — виберіть один із наведених нижче або завантажте власний mp3/wav. Вибраний звук відтворюється перед голосовим оголошенням. Потрібен ffmpeg на хості та принаймні один супутник.",
161
161
  "UDP port": "UDP-порт",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "Завантажте mp3/wav мелодії нижче; вибраний звук відтворюється перед голосовим оголошенням. Потрібно встановити ffmpeg на хості та принаймні одному супутнику.",
163
- "Use a local LLM before the cloud (installed on demand)": "Використовуйте локальний LLM перед хмарою (встановлюється на вимогу)",
163
+ "Use a local LLM (installed on demand)": "Використовувати локальну LLM (встановлюється на вимогу)",
164
+ "Ask the local model first": "Спочатку питати локальну модель",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "Кожне запитання, яке не зрозуміли правила, спочатку йде до локальної моделі, потім у хмару. Заощаджує хмарні запити, але на Raspberry Pi додає до таких запитань 10–25 секунд.",
166
+ "Local model timeout (s)": "Тайм-аут локальної моделі (с)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "Після цього часу локальна модель зупиняється, і запитання передається в хмару.",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "Використовується, якщо вказаний вище провайдер не працює (збій, прострочений ключ, вичерпана квота). Локальний рушій зберігає голосове керування без інтернету.",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "Використовується, якщо вказаний вище провайдер не працює. Piper дає асистентові змогу говорити без інтернету.",
166
170
  "Verify device feedback": "Перевіряти відповідь пристрою",
@@ -128,7 +128,7 @@
128
128
  "Room": "房间",
129
129
  "Routines": "例程",
130
130
  "Run the UDP voice server (ESP / Hannah satellites)": "运行UDP语音服务器(ESP/Hannah卫星)",
131
- "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.": "用于处理一般性问题的本地小型模型(node-llama-cpp);当需要实时设备数据时,会升级到云端LLM。点击下方按钮即可安装。",
131
+ "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.": "运行一个小型本地模型(node-llama-cpp)回答一般问题。配置了云服务商时,只有在云端失败时才由它回答,除非启用了“先询问本地模型”。请用下方按钮安装。",
132
132
  "Said or typed": "口头或书面",
133
133
  "Satellites": "卫星",
134
134
  "Satellites, rooms or devices (comma separated)": "卫星设备、房间或设备(逗号分隔)",
@@ -160,7 +160,11 @@
160
160
  "Two default sounds (timer.wav, alarm.wav) are preinstalled — pick one below or upload your own mp3/wav. The selected sound plays before the spoken announcement. Needs ffmpeg on the host and at least one satellite.": "系统预装了两个默认声音(timer.wav 和 alarm.wav),您可以从下方选择一个,或者上传您自己的 mp3/wav 文件。所选声音会在语音提示之前播放。主机上需要安装 ffmpeg,并且至少需要一个卫星设备。",
161
161
  "UDP port": "UDP端口",
162
162
  "Upload mp3/wav jingles below; the selected sound plays before the spoken announcement. Needs ffmpeg installed on the host and at least one satellite.": "请在下方上传mp3/wav格式的铃声;所选铃声会在语音播报前播放。主机上需要安装ffmpeg,并且至少需要一台卫星设备。",
163
- "Use a local LLM before the cloud (installed on demand)": "在云端之前使用本地LLM(按需安装)",
163
+ "Use a local LLM (installed on demand)": "使用本地 LLM(按需安装)",
164
+ "Ask the local model first": "先询问本地模型",
165
+ "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.": "规则无法理解的每个问题都会先交给本地模型,再交给云端。可节省云端调用,但在树莓派上每个此类问题会多花 10–25 秒。",
166
+ "Local model timeout (s)": "本地模型超时(秒)",
167
+ "After this time the local model is stopped and the question goes on to the cloud.": "超过此时间后本地模型将被停止,问题转交给云端。",
164
168
  "Used when the provider above fails (outage, expired key, empty quota). A local engine keeps voice working without the internet.": "当上方的服务商不可用时使用(故障、密钥过期、额度用尽)。本地引擎可在没有互联网时保持语音功能可用。",
165
169
  "Used when the provider above fails. Piper keeps the assistant speaking without the internet.": "当上方的服务商不可用时使用。Piper 可让助手在没有互联网时继续说话。",
166
170
  "Verify device feedback": "验证设备反馈",
@@ -414,8 +414,8 @@
414
414
  "useLocalLlm": {
415
415
  "newLine": true,
416
416
  "type": "checkbox",
417
- "label": "Use a local LLM before the cloud (installed on demand)",
418
- "help": "Runs a small local model (node-llama-cpp) for general questions; escalates to the cloud LLM when it needs live device data. Install it with the button below.",
417
+ "label": "Use a local LLM (installed on demand)",
418
+ "help": "Runs a small local model (node-llama-cpp) for general questions. With a cloud provider it only answers when the cloud fails, unless \"Ask the local model first\" is on. Install it with the button below.",
419
419
  "default": false,
420
420
  "xs": 12,
421
421
  "sm": 12,
@@ -434,6 +434,33 @@
434
434
  "lg": 6,
435
435
  "xl": 6
436
436
  },
437
+ "localLlmFirst": {
438
+ "newLine": true,
439
+ "type": "checkbox",
440
+ "label": "Ask the local model first",
441
+ "help": "Every question the rules do not understand goes to the local model before the cloud. Saves cloud calls, but on a Raspberry Pi it adds 10–25 seconds to such questions.",
442
+ "default": false,
443
+ "hidden": "!data.useLocalLlm || data.provider === 'none'",
444
+ "xs": 12,
445
+ "sm": 12,
446
+ "md": 6,
447
+ "lg": 6,
448
+ "xl": 6
449
+ },
450
+ "localLlmTimeoutSec": {
451
+ "type": "number",
452
+ "label": "Local model timeout (s)",
453
+ "help": "After this time the local model is stopped and the question goes on to the cloud.",
454
+ "min": 1,
455
+ "max": 300,
456
+ "default": 15,
457
+ "hidden": "!data.useLocalLlm",
458
+ "xs": 12,
459
+ "sm": 6,
460
+ "md": 3,
461
+ "lg": 3,
462
+ "xl": 3
463
+ },
437
464
  "_installLocalLlm": {
438
465
  "newLine": true,
439
466
  "type": "sendTo",
@@ -33,7 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.Journal = void 0;
36
+ exports.Journal = exports.MAX_TRACES = void 0;
37
+ exports.traceDirFor = traceDirFor;
38
+ exports.snapshot = snapshot;
39
+ exports.errorInfo = errorInfo;
37
40
  exports.transportForSource = transportForSource;
38
41
  /**
39
42
  * Conversation journal: one entry per request the assistant answered — where it came from (chat, script,
@@ -46,6 +49,12 @@ exports.transportForSource = transportForSource;
46
49
  * {@link AsyncLocalStorage}: {@link Journal.run} opens it, and {@link Journal.note}/{@link Journal.tool}/
47
50
  * {@link Journal.stateAccess} write to whatever entry the current call chain belongs to — and do nothing
48
51
  * outside of one (a trigger writing a state is not a conversation).
52
+ *
53
+ * Besides the entry, every request keeps a **trace**: the steps of its processing with their timing and
54
+ * whatever explains them — the NLU's parse, the full prompt and every round of the model, tool arguments
55
+ * and results in full, speech recognition and synthesis. Traces are too big to ride along in `journal.last`
56
+ * and in `journal.json`, so the chat fetches one when a request is opened ({@link Journal.trace}); each is
57
+ * kept in a file of its own and only for the newest {@link MAX_TRACES} requests.
49
58
  */
50
59
  const node_async_hooks_1 = require("node:async_hooks");
51
60
  const node_fs_1 = require("node:fs");
@@ -54,6 +63,68 @@ const path = __importStar(require("node:path"));
54
63
  const MAX_ENTRIES = 3000;
55
64
  const SAVE_DELAY_MS = 5000;
56
65
  const RESULT_CHARS = 300;
66
+ /** Requests whose trace is kept on disk. */
67
+ exports.MAX_TRACES = 200;
68
+ /** Traces held in memory (the newest ones — the ones somebody is about to open). */
69
+ const MEMORY_TRACES = 30;
70
+ /** Steps per request; a tool loop gone wild must not grow one trace without end. */
71
+ const MAX_STEPS = 400;
72
+ /** Longer strings are cut — enough for a system prompt with a few hundred devices. */
73
+ const STEP_STRING_CHARS = 100_000;
74
+ /** A reply is matched to its synthesis only this long after the answer. */
75
+ const SPEECH_WINDOW_MS = 120_000;
76
+ /** Entry ids as {@link Journal.run} makes them — checked before one becomes a file name. */
77
+ const ID_RE = /^[a-z0-9-]+$/;
78
+ /** Directory the traces of a journal file are kept in. */
79
+ function traceDirFor(file) {
80
+ return path.join(path.dirname(file), 'journal-trace');
81
+ }
82
+ /**
83
+ * JSON snapshot of a step's data — taken when it is recorded, because the objects go on changing (the
84
+ * model's message list grows, cache markers move). Long strings are cut and buffers summarised.
85
+ */
86
+ function snapshot(value) {
87
+ if (value === undefined) {
88
+ return undefined;
89
+ }
90
+ try {
91
+ const text = JSON.stringify(value, (_key, v) => {
92
+ if (typeof v === 'string' && v.length > STEP_STRING_CHARS) {
93
+ return `${v.slice(0, STEP_STRING_CHARS)}… [+${v.length - STEP_STRING_CHARS} chars]`;
94
+ }
95
+ if (typeof v === 'bigint') {
96
+ return v.toString();
97
+ }
98
+ // Buffer.toJSON() has already run when the replacer sees it.
99
+ const b = v;
100
+ if (b && typeof b === 'object' && b.type === 'Buffer' && Array.isArray(b.data)) {
101
+ return `<${b.data.length} bytes>`;
102
+ }
103
+ return v;
104
+ });
105
+ return text === undefined ? undefined : JSON.parse(text);
106
+ }
107
+ catch (e) {
108
+ return `<not serializable: ${e.message}>`;
109
+ }
110
+ }
111
+ /** Everything an error carries that helps — an API error's status and body, the stack. */
112
+ function errorInfo(e) {
113
+ if (!(e instanceof Error)) {
114
+ return { message: String(e) };
115
+ }
116
+ const info = { name: e.name, message: e.message };
117
+ for (const key of ['status', 'code', 'type', 'param', 'error', 'request_id', 'requestID']) {
118
+ const v = e[key];
119
+ if (v !== undefined && v !== null) {
120
+ info[key] = v;
121
+ }
122
+ }
123
+ if (e.stack) {
124
+ info.stack = e.stack;
125
+ }
126
+ return info;
127
+ }
57
128
  /** Derive the transport from a request source when the caller did not say. */
58
129
  function transportForSource(source) {
59
130
  if (source === 'chat') {
@@ -76,8 +147,16 @@ class Journal {
76
147
  store = new node_async_hooks_1.AsyncLocalStorage();
77
148
  saveTimer = null;
78
149
  counter = 0;
150
+ /** Steps of the newest requests; older ones are read back from their file. */
151
+ traces = new Map();
152
+ /** Traces changed since they were last written. */
153
+ dirty = new Set();
154
+ /** LLM tool calls in progress per request: a state access inside one belongs to that tool's step. */
155
+ toolDepth = new WeakMap();
156
+ traceDir;
79
157
  constructor(opts) {
80
158
  this.opts = opts;
159
+ this.traceDir = opts.file ? traceDirFor(opts.file) : '';
81
160
  }
82
161
  get enabled() {
83
162
  return this.opts.hours > 0;
@@ -110,8 +189,34 @@ class Journal {
110
189
  }
111
190
  clear() {
112
191
  this.entries = [];
192
+ this.traces.clear();
193
+ this.dirty.clear();
113
194
  this.scheduleSave();
114
195
  }
196
+ /** The entry of a request (the chat asks for it together with its trace). */
197
+ get(id) {
198
+ return this.entries.find(e => e.id === id);
199
+ }
200
+ /** The processing steps of a request, or null when none were kept (too old, or from an older version). */
201
+ async trace(id) {
202
+ if (!ID_RE.test(id) || !this.get(id)) {
203
+ return null;
204
+ }
205
+ const steps = this.traces.get(id);
206
+ if (steps) {
207
+ return steps;
208
+ }
209
+ if (!this.traceDir) {
210
+ return null;
211
+ }
212
+ try {
213
+ const raw = JSON.parse(await node_fs_1.promises.readFile(path.join(this.traceDir, `${id}.json`), 'utf8'));
214
+ return Array.isArray(raw?.steps) ? raw.steps : null;
215
+ }
216
+ catch {
217
+ return null;
218
+ }
219
+ }
115
220
  /**
116
221
  * Run `fn` as one journalled request. The entry is published once when the request starts (so the
117
222
  * chat shows the question while the answer is still being worked out) and once when it is finished.
@@ -131,7 +236,7 @@ class Journal {
131
236
  read: [],
132
237
  written: [],
133
238
  };
134
- for (const key of ['satellite', 'room', 'wakeWord', 'sttMs']) {
239
+ for (const key of ['satellite', 'room', 'wakeWord', 'sttMs', 'audioMs']) {
135
240
  if (origin[key] !== undefined && origin[key] !== '') {
136
241
  entry[key] = origin[key];
137
242
  }
@@ -141,6 +246,12 @@ class Journal {
141
246
  entry.wakeWord = '';
142
247
  }
143
248
  this.entries.push(entry);
249
+ this.traces.set(entry.id, []);
250
+ this.addStep(entry, {
251
+ stage: 'request',
252
+ title: `Request from ${source || entry.transport} (${entry.transport})`,
253
+ data: { question, source, origin },
254
+ });
144
255
  this.publish(entry);
145
256
  try {
146
257
  const answer = await this.store.run(entry, fn);
@@ -149,16 +260,162 @@ class Journal {
149
260
  }
150
261
  catch (e) {
151
262
  entry.error = e.message;
263
+ this.addStep(entry, { stage: 'error', level: 'error', title: entry.error, data: errorInfo(e) });
152
264
  throw e;
153
265
  }
154
266
  finally {
155
267
  entry.done = true;
156
268
  entry.ms = Date.now() - entry.ts;
269
+ this.dirty.add(entry.id);
157
270
  this.publish(entry);
158
271
  this.prune();
159
272
  this.scheduleSave();
160
273
  }
161
274
  }
275
+ /** Add a step to the current request's trace (nothing outside of one). */
276
+ step(input) {
277
+ const entry = this.store.getStore();
278
+ if (entry) {
279
+ this.addStep(entry, input);
280
+ }
281
+ }
282
+ /**
283
+ * Run an LLM tool as part of the current request: the call lands in the entry's tool list and, with the
284
+ * full arguments and result, in the trace.
285
+ */
286
+ async toolCall(name, args, fn) {
287
+ const entry = this.store.getStore();
288
+ if (!entry) {
289
+ return fn();
290
+ }
291
+ this.toolDepth.set(entry, (this.toolDepth.get(entry) || 0) + 1);
292
+ const start = Date.now();
293
+ try {
294
+ const result = await fn();
295
+ const ms = Date.now() - start;
296
+ this.addStep(entry, { stage: 'tool', title: `Tool ${name}`, ms, data: { args, result } });
297
+ this.tool(name, args, ms, result);
298
+ return result;
299
+ }
300
+ catch (e) {
301
+ const ms = Date.now() - start;
302
+ const message = e.message;
303
+ this.addStep(entry, {
304
+ stage: 'tool',
305
+ level: 'error',
306
+ title: `Tool ${name} failed: ${message}`,
307
+ ms,
308
+ data: { args, error: errorInfo(e) },
309
+ });
310
+ this.tool(name, args, ms, undefined, message);
311
+ throw e;
312
+ }
313
+ finally {
314
+ this.toolDepth.set(entry, (this.toolDepth.get(entry) || 1) - 1);
315
+ }
316
+ }
317
+ /**
318
+ * Run a state call (MCP `get_states`/`set_state`/…) as part of the current request: the ids land in the
319
+ * entry's read/written lists, and — unless an LLM tool made the call, whose step already shows it — the
320
+ * call with its result in the trace.
321
+ */
322
+ async stateCall(name, args, fn) {
323
+ const entry = this.store.getStore();
324
+ if (!entry) {
325
+ return fn();
326
+ }
327
+ this.stateAccess(name, args);
328
+ if (this.toolDepth.get(entry)) {
329
+ return fn();
330
+ }
331
+ const start = Date.now();
332
+ try {
333
+ const result = await fn();
334
+ this.addStep(entry, { stage: 'state', title: name, ms: Date.now() - start, data: { args, result } });
335
+ return result;
336
+ }
337
+ catch (e) {
338
+ this.addStep(entry, {
339
+ stage: 'state',
340
+ level: 'error',
341
+ title: `${name} failed: ${e.message}`,
342
+ ms: Date.now() - start,
343
+ data: { args, error: errorInfo(e) },
344
+ });
345
+ throw e;
346
+ }
347
+ }
348
+ /**
349
+ * A transport synthesised the reply to the latest request of `source`. That happens after the answer,
350
+ * outside of the request, so the entry is found by its source.
351
+ */
352
+ spoken(source, speech) {
353
+ const now = Date.now();
354
+ for (let i = this.entries.length - 1; i >= 0; i--) {
355
+ const entry = this.entries[i];
356
+ if (entry.source !== source) {
357
+ continue;
358
+ }
359
+ if (!entry.done || entry.ttsMs !== undefined || now - entry.ts - (entry.ms || 0) > SPEECH_WINDOW_MS) {
360
+ return;
361
+ }
362
+ entry.ttsMs = speech.ms;
363
+ entry.replyAudioMs = Math.round(speech.audioMs);
364
+ const voice = speech.voice ? ` (${speech.voice})` : '';
365
+ this.addStep(entry, {
366
+ stage: 'tts',
367
+ title: `Speech synthesis: ${speech.provider}${voice}, ${(speech.audioMs / 1000).toFixed(1)} s of audio`,
368
+ ms: speech.ms,
369
+ data: speech,
370
+ });
371
+ this.dirty.add(entry.id);
372
+ this.publish(entry);
373
+ this.scheduleSave();
374
+ return;
375
+ }
376
+ }
377
+ addStep(entry, input) {
378
+ const steps = this.traces.get(entry.id);
379
+ if (!steps || steps.length > MAX_STEPS) {
380
+ return;
381
+ }
382
+ if (steps.length === MAX_STEPS) {
383
+ steps.push({
384
+ t: Date.now() - entry.ts,
385
+ stage: 'error',
386
+ level: 'warn',
387
+ title: `More than ${MAX_STEPS} steps — the rest is not recorded`,
388
+ });
389
+ return;
390
+ }
391
+ const step = {
392
+ t: Date.now() - entry.ts - (input.ms || 0),
393
+ stage: input.stage,
394
+ title: input.title,
395
+ };
396
+ if (input.ms !== undefined) {
397
+ step.ms = input.ms;
398
+ }
399
+ if (input.level) {
400
+ step.level = input.level;
401
+ }
402
+ if (input.tokens) {
403
+ step.tokens = input.tokens;
404
+ const sum = (entry.tokens ||= { input: 0, output: 0 });
405
+ sum.input += input.tokens.input || 0;
406
+ sum.output += input.tokens.output || 0;
407
+ if (input.tokens.cacheRead) {
408
+ sum.cacheRead = (sum.cacheRead || 0) + input.tokens.cacheRead;
409
+ }
410
+ if (input.tokens.cacheWrite) {
411
+ sum.cacheWrite = (sum.cacheWrite || 0) + input.tokens.cacheWrite;
412
+ }
413
+ }
414
+ if (input.data !== undefined) {
415
+ step.data = snapshot(input.data);
416
+ }
417
+ steps.push(step);
418
+ }
162
419
  /** Fill in fields of the current request's entry (tier, detail, followUp …). */
163
420
  note(fields) {
164
421
  const entry = this.store.getStore();
@@ -243,8 +500,23 @@ class Journal {
243
500
  }
244
501
  start = Math.max(start, this.entries.length - MAX_ENTRIES);
245
502
  if (start > 0) {
503
+ for (const e of this.entries.slice(0, start)) {
504
+ this.traces.delete(e.id);
505
+ this.dirty.delete(e.id);
506
+ }
246
507
  this.entries = this.entries.slice(start);
247
508
  }
509
+ // Without a file the memory is all there is: keep as many as would be on disk.
510
+ const keep = this.traceDir ? MEMORY_TRACES : exports.MAX_TRACES;
511
+ for (const id of this.traces.keys()) {
512
+ if (this.traces.size <= keep) {
513
+ break;
514
+ }
515
+ // Not written yet, or still being answered: needed until it is on disk.
516
+ if ((!this.traceDir || !this.dirty.has(id)) && this.get(id)?.done !== false) {
517
+ this.traces.delete(id);
518
+ }
519
+ }
248
520
  }
249
521
  scheduleSave() {
250
522
  if (!this.opts.file || this.saveTimer) {
@@ -267,6 +539,30 @@ class Journal {
267
539
  catch (e) {
268
540
  this.opts.log.warn(`Journal could not be saved: ${e.message}`);
269
541
  }
542
+ await this.saveTraces();
543
+ this.prune();
544
+ }
545
+ /** Write the traces that changed, and delete the files of requests that are no longer kept. */
546
+ async saveTraces() {
547
+ try {
548
+ await node_fs_1.promises.mkdir(this.traceDir, { recursive: true });
549
+ for (const id of [...this.dirty]) {
550
+ const steps = this.traces.get(id);
551
+ this.dirty.delete(id);
552
+ if (steps) {
553
+ await node_fs_1.promises.writeFile(path.join(this.traceDir, `${id}.json`), JSON.stringify({ id, steps }));
554
+ }
555
+ }
556
+ const keep = new Set(this.entries.slice(-exports.MAX_TRACES).map(e => e.id));
557
+ for (const name of await node_fs_1.promises.readdir(this.traceDir)) {
558
+ if (name.endsWith('.json') && !keep.has(name.slice(0, -5))) {
559
+ await node_fs_1.promises.rm(path.join(this.traceDir, name), { force: true });
560
+ }
561
+ }
562
+ }
563
+ catch (e) {
564
+ this.opts.log.warn(`Journal traces could not be saved: ${e.message}`);
565
+ }
270
566
  }
271
567
  }
272
568
  exports.Journal = Journal;