mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
feat(control-ui): confirm dreaming restart changes
Require explicit confirmation before applying restart-impacting Dreaming mode changes in the Control UI. - Add pending/confirm/loading state for the Dreaming toggle path - Render a restart confirmation dialog before sending the config patch - Sync Control UI locale metadata and cover the confirmation flow in browser tests Fixes #63804
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 待機中",
|
||||
|
||||
@@ -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: "드리밍 유휴",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "การฝันไม่ได้ทำงาน",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "Сновидіння неактивне",
|
||||
|
||||
@@ -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 空闲",
|
||||
|
||||
@@ -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 閒置中",
|
||||
|
||||
@@ -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}
|
||||
</main>
|
||||
${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}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -46,6 +46,257 @@ describe("control UI routing", () => {
|
||||
|
||||
const dreamsLink = app.querySelector<HTMLAnchorElement>('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",
|
||||
"",
|
||||
"<!-- openclaw:dreaming:diary:start -->",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"*January 1, 2026*",
|
||||
"",
|
||||
"What Happened",
|
||||
"1. Stable operator rule surfaced.",
|
||||
"",
|
||||
"<!-- openclaw:dreaming:diary:end -->",
|
||||
].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<typeof app.client>;
|
||||
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<HTMLButtonElement>(".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<HTMLButtonElement>("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();
|
||||
|
||||
45
ui/src/ui/views/dreaming-restart-confirmation.ts
Normal file
45
ui/src/ui/views/dreaming-restart-confirmation.ts
Normal file
@@ -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`
|
||||
<div class="exec-approval-overlay" role="dialog" aria-modal="true" aria-live="polite">
|
||||
<div class="exec-approval-card">
|
||||
<div class="exec-approval-header">
|
||||
<div>
|
||||
<div class="exec-approval-title">${t("dreaming.restartConfirmation.title")}</div>
|
||||
<div class="exec-approval-sub">${t("dreaming.restartConfirmation.subtitle")}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="callout danger" style="margin-top: 12px;">
|
||||
${t("dreaming.restartConfirmation.warning")}
|
||||
</div>
|
||||
${props.hasError
|
||||
? html`<div class="exec-approval-error">${t("dreaming.restartConfirmation.failed")}</div>`
|
||||
: nothing}
|
||||
<div class="exec-approval-actions">
|
||||
<button class="btn danger" ?disabled=${props.loading} @click=${props.onConfirm}>
|
||||
${props.loading
|
||||
? t("dreaming.restartConfirmation.restarting")
|
||||
: t("dreaming.restartConfirmation.confirm")}
|
||||
</button>
|
||||
<button class="btn" ?disabled=${props.loading} @click=${props.onCancel}>
|
||||
${t("common.cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user