diff --git a/CHANGELOG.md b/CHANGELOG.md index bf63c53511c..6b02b963df3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai ### Fixes - CLI/plugins: stop security-blocked plugin installs from retrying as hook packs, so normal plugin packages report the scanner failure without a misleading "not a valid hook pack" follow-up. Fixes #61175; supersedes #64102. Thanks @KonsultDigital and @ziyincody. +- Control UI/Dreaming: require explicit confirmation before applying restart-impacting Dreaming mode changes, with restart warning copy and loading feedback. Fixes #63804. (#63807) Thanks @bbddbb1. - CLI/update: keep the automatic post-update completion refresh on the core-command tree so it no longer stages bundled plugin runtime deps before the Gateway restart path, avoiding `.24` update hangs and 1006 disconnect cascades. Fixes #72665. Thanks @sakalaboator and @He-Pin. - Agents/Bedrock: stop heartbeat runs from persisting blank user transcript turns and repair existing blank user text messages before replay, preventing AWS Bedrock `ContentBlock` blank-text validation failures. Fixes #72640 and #72622. Thanks @goldzulu. - Agents/LM Studio: promote standalone bracketed local-model tool requests into registered tool calls and hide unsupported bracket blocks from visible replies, so MemPalace MCP lookups do not print raw `[tool]` JSON scaffolding in chat. Fixes #66178. Thanks @detroit357. diff --git a/ui/src/i18n/.i18n/de.meta.json b/ui/src/i18n/.i18n/de.meta.json index c57012c4a9a..864c0e47248 100644 --- a/ui/src/i18n/.i18n/de.meta.json +++ b/ui/src/i18n/.i18n/de.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:11.631Z", + "generatedAt": "2026-04-27T07:37:20.795Z", "locale": "de", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/es.meta.json b/ui/src/i18n/.i18n/es.meta.json index 6dff35f4b68..83d1ff4e5bd 100644 --- a/ui/src/i18n/.i18n/es.meta.json +++ b/ui/src/i18n/.i18n/es.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:11.941Z", + "generatedAt": "2026-04-27T07:37:21.116Z", "locale": "es", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fr.meta.json b/ui/src/i18n/.i18n/fr.meta.json index 249647f03dc..569edcd9f77 100644 --- a/ui/src/i18n/.i18n/fr.meta.json +++ b/ui/src/i18n/.i18n/fr.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:12.883Z", + "generatedAt": "2026-04-27T07:37:22.097Z", "locale": "fr", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/id.meta.json b/ui/src/i18n/.i18n/id.meta.json index 12dad3e096e..1d7583896bf 100644 --- a/ui/src/i18n/.i18n/id.meta.json +++ b/ui/src/i18n/.i18n/id.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:13.865Z", + "generatedAt": "2026-04-27T07:37:23.072Z", "locale": "id", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ja-JP.meta.json b/ui/src/i18n/.i18n/ja-JP.meta.json index 3a1243bc1a4..deff76e3889 100644 --- a/ui/src/i18n/.i18n/ja-JP.meta.json +++ b/ui/src/i18n/.i18n/ja-JP.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:12.252Z", + "generatedAt": "2026-04-27T07:37:21.442Z", "locale": "ja-JP", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ko.meta.json b/ui/src/i18n/.i18n/ko.meta.json index 7d85db0dea6..f8f0c882cfb 100644 --- a/ui/src/i18n/.i18n/ko.meta.json +++ b/ui/src/i18n/.i18n/ko.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:12.563Z", + "generatedAt": "2026-04-27T07:37:21.771Z", "locale": "ko", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pl.meta.json b/ui/src/i18n/.i18n/pl.meta.json index 6f6c1806ed3..d642430ac85 100644 --- a/ui/src/i18n/.i18n/pl.meta.json +++ b/ui/src/i18n/.i18n/pl.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:14.204Z", + "generatedAt": "2026-04-27T07:37:23.392Z", "locale": "pl", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pt-BR.meta.json b/ui/src/i18n/.i18n/pt-BR.meta.json index 548782bdfc8..4c08f8ef3bd 100644 --- a/ui/src/i18n/.i18n/pt-BR.meta.json +++ b/ui/src/i18n/.i18n/pt-BR.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:11.305Z", + "generatedAt": "2026-04-27T07:37:20.461Z", "locale": "pt-BR", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/th.meta.json b/ui/src/i18n/.i18n/th.meta.json index b319d6dfebb..c83861422ac 100644 --- a/ui/src/i18n/.i18n/th.meta.json +++ b/ui/src/i18n/.i18n/th.meta.json @@ -1,11 +1,18 @@ { - "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:14.524Z", + "fallbackKeys": [ + "dreaming.restartConfirmation.confirm", + "dreaming.restartConfirmation.failed", + "dreaming.restartConfirmation.restarting", + "dreaming.restartConfirmation.subtitle", + "dreaming.restartConfirmation.title", + "dreaming.restartConfirmation.warning" + ], + "generatedAt": "2026-04-27T07:37:23.707Z", "locale": "th", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, "translatedKeys": 752, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/tr.meta.json b/ui/src/i18n/.i18n/tr.meta.json index a62cf41c7a2..2869dc75d54 100644 --- a/ui/src/i18n/.i18n/tr.meta.json +++ b/ui/src/i18n/.i18n/tr.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:13.204Z", + "generatedAt": "2026-04-27T07:37:22.430Z", "locale": "tr", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/uk.meta.json b/ui/src/i18n/.i18n/uk.meta.json index 2ee549e5df4..cbb871683cb 100644 --- a/ui/src/i18n/.i18n/uk.meta.json +++ b/ui/src/i18n/.i18n/uk.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:13.531Z", + "generatedAt": "2026-04-27T07:37:22.755Z", "locale": "uk", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-CN.meta.json b/ui/src/i18n/.i18n/zh-CN.meta.json index bdbc7466567..40374f8ed77 100644 --- a/ui/src/i18n/.i18n/zh-CN.meta.json +++ b/ui/src/i18n/.i18n/zh-CN.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:10.673Z", + "generatedAt": "2026-04-27T07:37:19.772Z", "locale": "zh-CN", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-TW.meta.json b/ui/src/i18n/.i18n/zh-TW.meta.json index 0e712cf7b9d..fbbdee1d348 100644 --- a/ui/src/i18n/.i18n/zh-TW.meta.json +++ b/ui/src/i18n/.i18n/zh-TW.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-04-26T21:47:10.990Z", + "generatedAt": "2026-04-27T07:37:20.133Z", "locale": "zh-TW", "model": "gpt-5.5", "provider": "openai", - "sourceHash": "0b1690213c6431759bd87ed8a231c4f523c79bac42dfac74028698fb18e7ebba", - "totalKeys": 752, - "translatedKeys": 752, + "sourceHash": "802e1bbb6a0e64584ec06cab4c61b65808c23669206a3a216646cf1a558ba657", + "totalKeys": 758, + "translatedKeys": 758, "workflow": 1 } diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts index 91304601f0c..0a07bbce576 100644 --- a/ui/src/i18n/locales/de.ts +++ b/ui/src/i18n/locales/de.ts @@ -356,6 +356,15 @@ export const de: TranslationMap = { on: "Träumen aktiviert", off: "Träumen deaktiviert", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Träumen aktiv", idle: "Träumen im Leerlauf", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index e8bbfc6b848..c2686407661 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -346,6 +346,15 @@ export const en: TranslationMap = { on: "Dreaming On", off: "Dreaming Off", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming Active", idle: "Dreaming Idle", diff --git a/ui/src/i18n/locales/es.ts b/ui/src/i18n/locales/es.ts index 92d8d922d1b..313c4c9238b 100644 --- a/ui/src/i18n/locales/es.ts +++ b/ui/src/i18n/locales/es.ts @@ -350,6 +350,15 @@ export const es: TranslationMap = { on: "Sueño activado", off: "Sueño desactivado", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Sueño activo", idle: "Sueño inactivo", diff --git a/ui/src/i18n/locales/fr.ts b/ui/src/i18n/locales/fr.ts index 960f6acfb68..5a4579f6042 100644 --- a/ui/src/i18n/locales/fr.ts +++ b/ui/src/i18n/locales/fr.ts @@ -354,6 +354,15 @@ export const fr: TranslationMap = { on: "Rêverie activée", off: "Rêverie désactivée", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Rêverie active", idle: "Rêverie inactive", diff --git a/ui/src/i18n/locales/id.ts b/ui/src/i18n/locales/id.ts index 119db021594..ad4739dd5d1 100644 --- a/ui/src/i18n/locales/id.ts +++ b/ui/src/i18n/locales/id.ts @@ -350,6 +350,15 @@ export const id: TranslationMap = { on: "Dreaming Aktif", off: "Dreaming Nonaktif", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming Aktif", idle: "Dreaming Idle", diff --git a/ui/src/i18n/locales/ja-JP.ts b/ui/src/i18n/locales/ja-JP.ts index 026b116042e..3a4d4f4d9d9 100644 --- a/ui/src/i18n/locales/ja-JP.ts +++ b/ui/src/i18n/locales/ja-JP.ts @@ -354,6 +354,15 @@ export const ja_JP: TranslationMap = { on: "Dreaming オン", off: "Dreaming オフ", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming 有効", idle: "Dreaming 待機中", diff --git a/ui/src/i18n/locales/ko.ts b/ui/src/i18n/locales/ko.ts index 693be5d6ce6..bf17e006a8b 100644 --- a/ui/src/i18n/locales/ko.ts +++ b/ui/src/i18n/locales/ko.ts @@ -349,6 +349,15 @@ export const ko: TranslationMap = { on: "드리밍 켜짐", off: "드리밍 꺼짐", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "드리밍 활성", idle: "드리밍 유휴", diff --git a/ui/src/i18n/locales/pl.ts b/ui/src/i18n/locales/pl.ts index 575fbf05284..73ec6205e6d 100644 --- a/ui/src/i18n/locales/pl.ts +++ b/ui/src/i18n/locales/pl.ts @@ -352,6 +352,15 @@ export const pl: TranslationMap = { on: "Dreaming włączone", off: "Dreaming wyłączone", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming aktywne", idle: "Dreaming bezczynne", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index 5297af310a7..ef8261a35bf 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -350,6 +350,15 @@ export const pt_BR: TranslationMap = { on: "Dreaming ativado", off: "Dreaming desativado", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming ativo", idle: "Dreaming inativo", diff --git a/ui/src/i18n/locales/th.ts b/ui/src/i18n/locales/th.ts index 6541c3d9391..832d3b95331 100644 --- a/ui/src/i18n/locales/th.ts +++ b/ui/src/i18n/locales/th.ts @@ -343,6 +343,15 @@ export const th: TranslationMap = { on: "เปิดการฝัน", off: "ปิดการฝัน", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "การฝันกำลังทำงาน", idle: "การฝันไม่ได้ทำงาน", diff --git a/ui/src/i18n/locales/tr.ts b/ui/src/i18n/locales/tr.ts index 81a46c5a6ac..96c4812729a 100644 --- a/ui/src/i18n/locales/tr.ts +++ b/ui/src/i18n/locales/tr.ts @@ -355,6 +355,15 @@ export const tr: TranslationMap = { on: "Dreaming Açık", off: "Dreaming Kapalı", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming Etkin", idle: "Dreaming Boşta", diff --git a/ui/src/i18n/locales/uk.ts b/ui/src/i18n/locales/uk.ts index c26e3626d2e..118825359a3 100644 --- a/ui/src/i18n/locales/uk.ts +++ b/ui/src/i18n/locales/uk.ts @@ -353,6 +353,15 @@ export const uk: TranslationMap = { on: "Сновидіння увімкнено", off: "Сновидіння вимкнено", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Сновидіння активне", idle: "Сновидіння неактивне", diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index b64c90a71ef..f12156e0db7 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -343,6 +343,15 @@ export const zh_CN: TranslationMap = { on: "Dreaming 已开启", off: "Dreaming 已关闭", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming 运行中", idle: "Dreaming 空闲", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 64b3c404b23..eaea1e1b744 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -343,6 +343,15 @@ export const zh_TW: TranslationMap = { on: "Dreaming 已開啟", off: "Dreaming 已關閉", }, + restartConfirmation: { + title: "Restart Gateway to Apply Change", + subtitle: "Changing Dreaming mode restarts the gateway.", + warning: + "This action will restart the Gateway and may temporarily interrupt chats, automations, and connected channels.", + confirm: "Confirm Restart", + restarting: "Restarting…", + failed: "Could not apply change. Check your connection and try again.", + }, status: { active: "Dreaming 進行中", idle: "Dreaming 閒置中", diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index e5c559239a3..b8af6974b24 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -143,6 +143,7 @@ import { createDefaultDraft, draftToCronFormPatch, } from "./views/cron-quick-create.ts"; +import { renderDreamingRestartConfirmation } from "./views/dreaming-restart-confirmation.ts"; import { renderDreaming } from "./views/dreaming.ts"; import { renderExecApprovalPrompt } from "./views/exec-approval.ts"; import { renderGatewayUrlConfirmation } from "./views/gateway-url-confirmation.ts"; @@ -738,16 +739,49 @@ export function renderApp(state: AppViewState) { }; }; const applyDreamingEnabled = (enabled: boolean) => { - if (state.dreamingModeSaving || dreamingOn === enabled) { + if ( + state.dreamingModeSaving || + state.dreamingRestartConfirmLoading || + state.dreamingRestartConfirmOpen || + dreamingOn === enabled + ) { + return; + } + state.dreamingPendingEnabled = enabled; + state.dreamingRestartConfirmOpen = true; + state.dreamingStatusError = null; + }; + const cancelDreamingRestart = () => { + if (state.dreamingRestartConfirmLoading) { + return; + } + state.dreamingRestartConfirmOpen = false; + state.dreamingPendingEnabled = null; + state.dreamingStatusError = null; + }; + const confirmDreamingRestart = () => { + const enabled = state.dreamingPendingEnabled; + if (enabled == null || state.dreamingRestartConfirmLoading) { return; } void (async () => { - const updated = await updateDreamingEnabled(state, enabled); - if (!updated) { - return; + state.dreamingRestartConfirmLoading = true; + state.dreamingStatusError = null; + try { + const updated = await updateDreamingEnabled(state, enabled); + if (!updated) { + if (!state.dreamingStatusError) { + state.dreamingStatusError = t("dreaming.restartConfirmation.failed"); + } + return; + } + await loadConfig(state); + await loadDreamingStatus(state); + state.dreamingRestartConfirmOpen = false; + state.dreamingPendingEnabled = null; + } finally { + state.dreamingRestartConfirmLoading = false; } - await loadConfig(state); - await loadDreamingStatus(state); })(); }; const basePath = normalizeBasePath(state.basePath ?? ""); @@ -2491,7 +2525,15 @@ export function renderApp(state: AppViewState) { }) : nothing} - ${renderExecApprovalPrompt(state)} ${renderGatewayUrlConfirmation(state)} ${nothing} + ${renderExecApprovalPrompt(state)} ${renderGatewayUrlConfirmation(state)} + ${renderDreamingRestartConfirmation({ + open: state.dreamingRestartConfirmOpen, + loading: state.dreamingRestartConfirmLoading, + onConfirm: confirmDreamingRestart, + onCancel: cancelDreamingRestart, + hasError: Boolean(state.dreamingStatusError), + })} + ${nothing} `; } diff --git a/ui/src/ui/app-view-state.ts b/ui/src/ui/app-view-state.ts index a2b2e3a4bff..ca7d09b05bf 100644 --- a/ui/src/ui/app-view-state.ts +++ b/ui/src/ui/app-view-state.ts @@ -155,6 +155,9 @@ export type AppViewState = { dreamingStatusError: string | null; dreamingStatus: import("./controllers/dreaming.js").DreamingStatus | null; dreamingModeSaving: boolean; + dreamingRestartConfirmOpen: boolean; + dreamingRestartConfirmLoading: boolean; + dreamingPendingEnabled: boolean | null; dreamDiaryLoading: boolean; dreamDiaryActionLoading: boolean; dreamDiaryActionMessage: { kind: "success" | "error"; text: string } | null; diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index f0c6a25500a..a0cb27fbe27 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -262,6 +262,9 @@ export class OpenClawApp extends LitElement { @state() dreamingStatusError: string | null = null; @state() dreamingStatus: DreamingStatus | null = null; @state() dreamingModeSaving = false; + @state() dreamingRestartConfirmOpen = false; + @state() dreamingRestartConfirmLoading = false; + @state() dreamingPendingEnabled: boolean | null = null; @state() dreamDiaryLoading = false; @state() dreamDiaryActionLoading = false; @state() dreamDiaryActionMessage: { kind: "success" | "error"; text: string } | null = null; diff --git a/ui/src/ui/navigation.browser.test.ts b/ui/src/ui/navigation.browser.test.ts index 00bd09c3315..6ca02d7785b 100644 --- a/ui/src/ui/navigation.browser.test.ts +++ b/ui/src/ui/navigation.browser.test.ts @@ -46,6 +46,257 @@ describe("control UI routing", () => { const dreamsLink = app.querySelector('a.nav-item[href="/dreaming"]'); expect(dreamsLink).not.toBeNull(); + }); + + it("renders the dreaming view on the /dreaming route", async () => { + const app = mountApp("/dreaming"); + app.dreamingStatus = { + enabled: true, + timezone: "Europe/Madrid", + verboseLogging: false, + storageMode: "inline", + separateReports: false, + shortTermCount: 2, + recallSignalCount: 1, + dailySignalCount: 1, + groundedSignalCount: 0, + totalSignalCount: 2, + phaseSignalCount: 0, + lightPhaseHitCount: 0, + remPhaseHitCount: 0, + promotedTotal: 1, + promotedToday: 1, + shortTermEntries: [], + signalEntries: [], + promotedEntries: [], + phases: { + light: { enabled: true, cron: "", managedCronPresent: false, lookbackDays: 7, limit: 20 }, + deep: { + enabled: true, + cron: "", + managedCronPresent: false, + limit: 20, + minScore: 0.75, + minRecallCount: 3, + minUniqueQueries: 2, + recencyHalfLifeDays: 7, + }, + rem: { + enabled: true, + cron: "", + managedCronPresent: false, + lookbackDays: 7, + limit: 20, + minPatternStrength: 0.6, + }, + }, + }; + app.dreamDiaryPath = "DREAMS.md"; + app.dreamDiaryContent = [ + "# Dream Diary", + "", + "", + "", + "---", + "", + "*January 1, 2026*", + "", + "What Happened", + "1. Stable operator rule surfaced.", + "", + "", + ].join("\n"); + app.requestUpdate(); + await app.updateComplete; + + expect(app.tab).toBe("dreams"); + expect(app.querySelector(".dreams__tab")).not.toBeNull(); + expect(app.querySelector(".dreams__lobster")).not.toBeNull(); + }); + + it("requires confirmation before sending dreaming restart patch", async () => { + const app = mountApp("/dreaming"); + const request = vi.fn(async (method: string) => { + if (method === "config.schema.lookup") { + return { + schema: { + additionalProperties: true, + }, + children: [{ key: "dreaming" }], + }; + } + if (method === "config.patch") { + return { ok: true }; + } + if (method === "config.get") { + return { + hash: "hash-2", + config: { + plugins: { + slots: { + memory: "memory-core", + }, + entries: { + "memory-core": { + config: { + dreaming: { + enabled: true, + }, + }, + }, + }, + }, + }, + }; + } + if (method === "doctor.memory.status") { + return { + dreaming: { + enabled: true, + timezone: "UTC", + verboseLogging: false, + storageMode: "inline", + separateReports: false, + shortTermCount: 0, + recallSignalCount: 0, + dailySignalCount: 0, + groundedSignalCount: 0, + totalSignalCount: 0, + phaseSignalCount: 0, + lightPhaseHitCount: 0, + remPhaseHitCount: 0, + promotedTotal: 0, + promotedToday: 0, + shortTermEntries: [], + signalEntries: [], + promotedEntries: [], + phases: { + light: { + enabled: true, + cron: "", + managedCronPresent: false, + lookbackDays: 7, + limit: 20, + }, + deep: { + enabled: true, + cron: "", + managedCronPresent: false, + limit: 20, + minScore: 0.75, + minRecallCount: 3, + minUniqueQueries: 2, + recencyHalfLifeDays: 7, + }, + rem: { + enabled: true, + cron: "", + managedCronPresent: false, + lookbackDays: 7, + limit: 20, + minPatternStrength: 0.6, + }, + }, + }, + }; + } + return {}; + }); + + app.client = { + request, + stop: vi.fn(), + } as unknown as NonNullable; + app.connected = true; + app.configSnapshot = { + hash: "hash-1", + config: { + plugins: { + slots: { + memory: "memory-core", + }, + entries: { + "memory-core": { + config: { + dreaming: { + enabled: true, + }, + }, + }, + }, + }, + }, + }; + app.dreamingStatus = { + enabled: true, + timezone: "UTC", + verboseLogging: false, + storageMode: "inline", + separateReports: false, + shortTermCount: 0, + recallSignalCount: 0, + dailySignalCount: 0, + groundedSignalCount: 0, + totalSignalCount: 0, + phaseSignalCount: 0, + lightPhaseHitCount: 0, + remPhaseHitCount: 0, + promotedTotal: 0, + promotedToday: 0, + shortTermEntries: [], + signalEntries: [], + promotedEntries: [], + phases: { + light: { enabled: true, cron: "", managedCronPresent: false, lookbackDays: 7, limit: 20 }, + deep: { + enabled: true, + cron: "", + managedCronPresent: false, + limit: 20, + minScore: 0.75, + minRecallCount: 3, + minUniqueQueries: 2, + recencyHalfLifeDays: 7, + }, + rem: { + enabled: true, + cron: "", + managedCronPresent: false, + lookbackDays: 7, + limit: 20, + minPatternStrength: 0.6, + }, + }, + }; + app.requestUpdate(); + await app.updateComplete; + + const toggle = app.querySelector(".dreams__phase-toggle--on"); + expect(toggle).not.toBeNull(); + toggle?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); + await app.updateComplete; + + expect(request).not.toHaveBeenCalledWith("config.patch", expect.anything()); + const confirmRestart = Array.from(app.querySelectorAll("button")).find( + (button) => button.textContent?.trim() === "Confirm Restart", + ); + expect(confirmRestart).not.toBeUndefined(); + confirmRestart?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); + + await nextFrame(); + await app.updateComplete; + + expect(request).toHaveBeenCalledWith( + "config.patch", + expect.objectContaining({ + baseHash: "hash-1", + }), + ); + }); + + it("renders the refreshed top navigation shell", async () => { + const app = mountApp("/chat"); + await app.updateComplete; expect(app.querySelector(".topnav-shell")).not.toBeNull(); expect(app.querySelector(".topnav-shell__content")).not.toBeNull(); diff --git a/ui/src/ui/views/dreaming-restart-confirmation.ts b/ui/src/ui/views/dreaming-restart-confirmation.ts new file mode 100644 index 00000000000..ec1da319a18 --- /dev/null +++ b/ui/src/ui/views/dreaming-restart-confirmation.ts @@ -0,0 +1,45 @@ +import { html, nothing } from "lit"; +import { t } from "../../i18n/index.ts"; + +type DreamingRestartConfirmationProps = { + open: boolean; + loading: boolean; + onConfirm: () => void; + onCancel: () => void; + hasError: boolean; +}; + +export function renderDreamingRestartConfirmation(props: DreamingRestartConfirmationProps) { + if (!props.open) { + return nothing; + } + + return html` + + `; +}