From a960417739301be2083a2aa0d4439b7442bf017d Mon Sep 17 00:00:00 2001 From: wenjie Date: Mon, 18 May 2026 17:26:00 +0800 Subject: [PATCH] fix: stabilize PicoClaw session switching - Wait for gateway session release before reconnecting to a new or selected session - Refresh runtime status before deciding whether a selected history session can reconnect - Render structured thought messages from gateway payload metadata - Prevent PicoClaw history rows from overflowing horizontally - Add localized labels for the verifying install stage --- web/src/api/picoclaw.ts | 4 +-- web/src/i18n/locales/ca.ts | 1 + web/src/i18n/locales/cz.ts | 1 + web/src/i18n/locales/da.ts | 1 + web/src/i18n/locales/de.ts | 1 + web/src/i18n/locales/en.ts | 1 + web/src/i18n/locales/es.ts | 1 + web/src/i18n/locales/fr.ts | 1 + web/src/i18n/locales/hu.ts | 1 + web/src/i18n/locales/id.ts | 1 + web/src/i18n/locales/it.ts | 1 + web/src/i18n/locales/ja.ts | 1 + web/src/i18n/locales/ko.ts | 1 + web/src/i18n/locales/nb.ts | 1 + web/src/i18n/locales/nl.ts | 1 + web/src/i18n/locales/pl.ts | 1 + web/src/i18n/locales/pt_br.ts | 1 + web/src/i18n/locales/ru.ts | 1 + web/src/i18n/locales/se.ts | 1 + web/src/i18n/locales/th.ts | 1 + web/src/i18n/locales/tr.ts | 1 + web/src/i18n/locales/uk.ts | 1 + web/src/i18n/locales/vi.ts | 1 + web/src/i18n/locales/zh.ts | 1 + web/src/i18n/locales/zh_tw.ts | 1 + web/src/pages/desktop/picoclaw/message.tsx | 30 ++++++++++++------- .../pages/desktop/picoclaw/sidebar-actions.ts | 4 +-- .../pages/desktop/picoclaw/sidebar-effects.ts | 2 +- .../desktop/picoclaw/sidebar-history.tsx | 8 ++--- .../picoclaw/sidebar-session-actions.ts | 24 ++++++++------- web/src/pages/desktop/picoclaw/use-sidebar.ts | 1 - 31 files changed, 66 insertions(+), 31 deletions(-) diff --git a/web/src/api/picoclaw.ts b/web/src/api/picoclaw.ts index 1c3ccf0..b833a18 100644 --- a/web/src/api/picoclaw.ts +++ b/web/src/api/picoclaw.ts @@ -109,14 +109,14 @@ export function sendStopMessage() { return picoclawGateway.sendStopMessage(); } -export function closeGateway() { +export async function closeGateway() { const activeSessionId = picoclawGateway.getSessionId(); picoclawGateway.close(); if (!activeSessionId) { return; } - void releaseRuntimeSession(activeSessionId).catch(() => undefined); + await releaseRuntimeSession(activeSessionId).catch(() => undefined); } export function onGatewayConnectionState(listener: (state: GatewayTransportState) => void) { diff --git a/web/src/i18n/locales/ca.ts b/web/src/i18n/locales/ca.ts index 0c1ebed..f944a8b 100644 --- a/web/src/i18n/locales/ca.ts +++ b/web/src/i18n/locales/ca.ts @@ -541,6 +541,7 @@ const ca = { preparing: 'Preparant', downloading: "S'està baixant", extracting: 'Extracció', + verifying: 'Verificant', installing: 'Instal·lant', installed: 'Instal·lat', install_timeout: 'Temps esgotat', diff --git a/web/src/i18n/locales/cz.ts b/web/src/i18n/locales/cz.ts index 0564ec2..f004768 100644 --- a/web/src/i18n/locales/cz.ts +++ b/web/src/i18n/locales/cz.ts @@ -546,6 +546,7 @@ const cz = { preparing: 'Příprava', downloading: 'Stahování', extracting: 'Extrakce', + verifying: 'Ověřování', installing: 'Instalace probíhá', installed: 'Instalováno', install_timeout: 'Vypršel časový limit', diff --git a/web/src/i18n/locales/da.ts b/web/src/i18n/locales/da.ts index fe622c1..4ea56c5 100644 --- a/web/src/i18n/locales/da.ts +++ b/web/src/i18n/locales/da.ts @@ -545,6 +545,7 @@ const da = { preparing: 'Forberedelse', downloading: 'Downloader', extracting: 'Udpakning', + verifying: 'Bekræfter', installing: 'Installerer', installed: 'Installeret', install_timeout: 'Timeout', diff --git a/web/src/i18n/locales/de.ts b/web/src/i18n/locales/de.ts index b86e109..5f3ca77 100644 --- a/web/src/i18n/locales/de.ts +++ b/web/src/i18n/locales/de.ts @@ -551,6 +551,7 @@ const de = { preparing: 'Vorbereiten', downloading: 'Wird heruntergeladen', extracting: 'Extrahieren', + verifying: 'Überprüfen', installing: 'Installiere', installed: 'Installiert', install_timeout: 'Zeitüberschreitung', diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 32e72f4..b389b6d 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -543,6 +543,7 @@ const en = { preparing: 'Preparing', downloading: 'Downloading', extracting: 'Extracting', + verifying: 'Verifying', installing: 'Installing', installed: 'Installed', install_timeout: 'Timed Out', diff --git a/web/src/i18n/locales/es.ts b/web/src/i18n/locales/es.ts index ab5b0ae..0e46780 100644 --- a/web/src/i18n/locales/es.ts +++ b/web/src/i18n/locales/es.ts @@ -550,6 +550,7 @@ const es = { preparing: 'Preparando', downloading: 'Descargando', extracting: 'Extrayendo', + verifying: 'Verificando', installing: 'Instalando', installed: 'Instalado', install_timeout: 'Tiempo de espera agotado', diff --git a/web/src/i18n/locales/fr.ts b/web/src/i18n/locales/fr.ts index f805ac2..4f2abf1 100644 --- a/web/src/i18n/locales/fr.ts +++ b/web/src/i18n/locales/fr.ts @@ -551,6 +551,7 @@ const fr = { preparing: 'Préparation', downloading: 'Téléchargement', extracting: 'Extraction', + verifying: 'Vérification', installing: 'Installation', installed: 'Installé', install_timeout: 'Délai expiré', diff --git a/web/src/i18n/locales/hu.ts b/web/src/i18n/locales/hu.ts index 12b2a41..5064aca 100644 --- a/web/src/i18n/locales/hu.ts +++ b/web/src/i18n/locales/hu.ts @@ -549,6 +549,7 @@ const hu = { preparing: 'Felkészülés', downloading: 'Letöltés', extracting: 'Kibontás', + verifying: 'Ellenőrzés', installing: 'Telepítés folyamatban', installed: 'Telepítve', install_timeout: 'Időtúllépés', diff --git a/web/src/i18n/locales/id.ts b/web/src/i18n/locales/id.ts index a0d1f8c..cfa9ae1 100644 --- a/web/src/i18n/locales/id.ts +++ b/web/src/i18n/locales/id.ts @@ -545,6 +545,7 @@ const id = { preparing: 'Mempersiapkan', downloading: 'Mengunduh', extracting: 'Mengekstraksi', + verifying: 'Memverifikasi', installing: 'Memasangkan', installed: 'Terpasang', install_timeout: 'Waktu Habis', diff --git a/web/src/i18n/locales/it.ts b/web/src/i18n/locales/it.ts index ac256db..8fde9e0 100644 --- a/web/src/i18n/locales/it.ts +++ b/web/src/i18n/locales/it.ts @@ -549,6 +549,7 @@ const it = { preparing: 'Preparazione', downloading: 'Download in corso', extracting: 'Estrazione', + verifying: 'Verifica in corso', installing: 'Installazione in corso', installed: 'Installato', install_timeout: 'Timeout', diff --git a/web/src/i18n/locales/ja.ts b/web/src/i18n/locales/ja.ts index 85fbd92..402b8f8 100644 --- a/web/src/i18n/locales/ja.ts +++ b/web/src/i18n/locales/ja.ts @@ -549,6 +549,7 @@ const ja = { preparing: '準備中', downloading: 'ダウンロード中', extracting: '展開中', + verifying: '検証中', installing: 'インストール中', installed: 'インストール完了', install_timeout: 'タイムアウト', diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index 3f81edb..21d38e5 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -539,6 +539,7 @@ const ko = { preparing: '준비 중', downloading: '다운로드 중', extracting: '압축 해제 중', + verifying: '검증 중', installing: '설치 중', installed: '설치 완료', install_timeout: '시간 초과', diff --git a/web/src/i18n/locales/nb.ts b/web/src/i18n/locales/nb.ts index 885c768..f98dd77 100644 --- a/web/src/i18n/locales/nb.ts +++ b/web/src/i18n/locales/nb.ts @@ -545,6 +545,7 @@ const nb = { preparing: 'Forbereder', downloading: 'Laster ned', extracting: 'Pakker ut', + verifying: 'Verifiserer', installing: 'Installerer', installed: 'Installert', install_timeout: 'Tidsavbrudd', diff --git a/web/src/i18n/locales/nl.ts b/web/src/i18n/locales/nl.ts index 87ef333..1dde009 100644 --- a/web/src/i18n/locales/nl.ts +++ b/web/src/i18n/locales/nl.ts @@ -549,6 +549,7 @@ const nl = { preparing: 'Voorbereiden', downloading: 'Downloaden', extracting: 'Uitpakken', + verifying: 'Verifiëren', installing: 'Installeren', installed: 'Geïnstalleerd', install_timeout: 'Time-out', diff --git a/web/src/i18n/locales/pl.ts b/web/src/i18n/locales/pl.ts index 5961108..2fdc765 100644 --- a/web/src/i18n/locales/pl.ts +++ b/web/src/i18n/locales/pl.ts @@ -548,6 +548,7 @@ const pl = { preparing: 'Przygotowanie', downloading: 'Pobieranie', extracting: 'Wypakowywanie', + verifying: 'Weryfikowanie', installing: 'Instalowanie', installed: 'Zainstalowano', install_timeout: 'Upłynął limit czasu', diff --git a/web/src/i18n/locales/pt_br.ts b/web/src/i18n/locales/pt_br.ts index 6541fca..4ba275d 100644 --- a/web/src/i18n/locales/pt_br.ts +++ b/web/src/i18n/locales/pt_br.ts @@ -546,6 +546,7 @@ const pt_br = { preparing: 'Preparando', downloading: 'Baixando', extracting: 'Extraindo', + verifying: 'Verificando', installing: 'Instalando', installed: 'Instalado', install_timeout: 'Tempo limite esgotado', diff --git a/web/src/i18n/locales/ru.ts b/web/src/i18n/locales/ru.ts index 192806a..d08da0a 100644 --- a/web/src/i18n/locales/ru.ts +++ b/web/src/i18n/locales/ru.ts @@ -547,6 +547,7 @@ const ru = { preparing: 'Подготовка', downloading: 'Загрузка', extracting: 'Распаковка', + verifying: 'Проверка', installing: 'Установка', installed: 'Установлено', install_timeout: 'Время ожидания истекло', diff --git a/web/src/i18n/locales/se.ts b/web/src/i18n/locales/se.ts index c1b69c7..888d6ef 100644 --- a/web/src/i18n/locales/se.ts +++ b/web/src/i18n/locales/se.ts @@ -541,6 +541,7 @@ const se = { preparing: 'Förbereder', downloading: 'Laddar ner', extracting: 'Packar upp', + verifying: 'Verifierar', installing: 'Installerar', installed: 'Installerad', install_timeout: 'Timeout', diff --git a/web/src/i18n/locales/th.ts b/web/src/i18n/locales/th.ts index 88be2bc..c6cd03b 100644 --- a/web/src/i18n/locales/th.ts +++ b/web/src/i18n/locales/th.ts @@ -538,6 +538,7 @@ const th = { preparing: 'กำลังเตรียมการ', downloading: 'กำลังดาวน์โหลด', extracting: 'กำลังแตกไฟล์', + verifying: 'กำลังตรวจสอบ', installing: 'กำลังติดตั้ง', installed: 'ติดตั้งแล้ว', install_timeout: 'หมดเวลา', diff --git a/web/src/i18n/locales/tr.ts b/web/src/i18n/locales/tr.ts index 702cd4c..c7db967 100644 --- a/web/src/i18n/locales/tr.ts +++ b/web/src/i18n/locales/tr.ts @@ -545,6 +545,7 @@ const tr = { preparing: 'Hazırlanıyor', downloading: 'İndiriliyor', extracting: 'Çıkarılıyor', + verifying: 'Doğrulanıyor', installing: 'Yükleniyor', installed: 'Yüklendi', install_timeout: 'Zaman Aşımı', diff --git a/web/src/i18n/locales/uk.ts b/web/src/i18n/locales/uk.ts index c5b5211..45667c8 100644 --- a/web/src/i18n/locales/uk.ts +++ b/web/src/i18n/locales/uk.ts @@ -546,6 +546,7 @@ const uk = { preparing: 'Підготовка', downloading: 'Завантаження', extracting: 'Розпакування', + verifying: 'Перевірка', installing: 'Встановлення', installed: 'Встановлено', install_timeout: 'Час очікування минув', diff --git a/web/src/i18n/locales/vi.ts b/web/src/i18n/locales/vi.ts index 76e5142..447a6fa 100644 --- a/web/src/i18n/locales/vi.ts +++ b/web/src/i18n/locales/vi.ts @@ -544,6 +544,7 @@ const vi = { preparing: 'Đang chuẩn bị', downloading: 'Đang tải xuống', extracting: 'Đang giải nén', + verifying: 'Đang xác minh', installing: 'Đang cài đặt', installed: 'Đã cài đặt', install_timeout: 'Đã hết thời gian', diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index cd4ff1d..6dc6862 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -533,6 +533,7 @@ const zh = { preparing: '准备中', downloading: '下载中', extracting: '解压中', + verifying: '验证中', installing: '安装中', installed: '已安装', install_timeout: '已超时', diff --git a/web/src/i18n/locales/zh_tw.ts b/web/src/i18n/locales/zh_tw.ts index c8b2a4c..22babdc 100644 --- a/web/src/i18n/locales/zh_tw.ts +++ b/web/src/i18n/locales/zh_tw.ts @@ -533,6 +533,7 @@ const zh_tw = { preparing: '準備中', downloading: '下載中', extracting: '解壓縮中', + verifying: '驗證中', installing: '安裝中', installed: '已安裝', install_timeout: '超時', diff --git a/web/src/pages/desktop/picoclaw/message.tsx b/web/src/pages/desktop/picoclaw/message.tsx index 01015cd..fb2caf5 100644 --- a/web/src/pages/desktop/picoclaw/message.tsx +++ b/web/src/pages/desktop/picoclaw/message.tsx @@ -10,9 +10,6 @@ type MessageProps = { message: PicoclawChatMessage; }; -const hiddenAssistantMessagePrefix = '🔧 `message`'; -const nullToolFeedbackPattern = /^🔧\s*`[^`]+`\s*```(?:[\w-]+)?\s*null\s*```$/s; - function extractDisplayText(value: unknown): string { if (typeof value === 'string') { const normalized = normalizeLiteralEmptyText(value); @@ -77,15 +74,17 @@ function normalizeLiteralEmptyText(value: string): string { return trimmed; } -function shouldHideAssistantMessage(value?: string): boolean { - const normalized = normalizeLiteralEmptyText(value || ''); - if (!normalized) { +function isThoughtMessage(raw: unknown): boolean { + if (!raw || typeof raw !== 'object') { return false; } - return ( - normalized.startsWith(hiddenAssistantMessagePrefix) || nullToolFeedbackPattern.test(normalized) - ); + const payload = (raw as Record).payload; + if (!payload || typeof payload !== 'object') { + return false; + } + + return (payload as Record).kind === 'thought'; } export const Message = ({ message }: MessageProps) => { @@ -153,8 +152,17 @@ export const Message = ({ message }: MessageProps) => { ); } - if (message.kind === 'assistant' && shouldHideAssistantMessage(message.text)) { - return null; + if (message.kind === 'assistant' && isThoughtMessage(message.raw)) { + const thoughtText = extractDisplayText(message.text); + if (!thoughtText) { + return null; + } + + return ( +
+ +
+ ); } const isUser = message.kind === 'user'; diff --git a/web/src/pages/desktop/picoclaw/sidebar-actions.ts b/web/src/pages/desktop/picoclaw/sidebar-actions.ts index 65df749..f6d8f02 100644 --- a/web/src/pages/desktop/picoclaw/sidebar-actions.ts +++ b/web/src/pages/desktop/picoclaw/sidebar-actions.ts @@ -145,7 +145,7 @@ export function createPicoclawSidebarActions(options: PicoclawSidebarActionOptio ]); if (isRuntimeReady) { - closeGateway(); + void closeGateway(); setTakeover({ active: false, sessionId: picoclawGateway.getSessionId(), @@ -194,7 +194,7 @@ export function createPicoclawSidebarActions(options: PicoclawSidebarActionOptio ]); if (isRuntimeReady) { - closeGateway(); + void closeGateway(); setTakeover({ active: false, sessionId: picoclawGateway.getSessionId(), diff --git a/web/src/pages/desktop/picoclaw/sidebar-effects.ts b/web/src/pages/desktop/picoclaw/sidebar-effects.ts index 9bc4d3b..7d9e54c 100644 --- a/web/src/pages/desktop/picoclaw/sidebar-effects.ts +++ b/web/src/pages/desktop/picoclaw/sidebar-effects.ts @@ -228,7 +228,7 @@ export function usePicoclawSidebarLifecycle({ return () => { cancelled = true; - closeGateway(); + void closeGateway(); setTakeover((current) => ({ ...current, active: false, diff --git a/web/src/pages/desktop/picoclaw/sidebar-history.tsx b/web/src/pages/desktop/picoclaw/sidebar-history.tsx index 5733f3c..470f476 100644 --- a/web/src/pages/desktop/picoclaw/sidebar-history.tsx +++ b/web/src/pages/desktop/picoclaw/sidebar-history.tsx @@ -61,8 +61,8 @@ export const SidebarHistory = ({ } return ( -
-
+
+
{sessions.map((session) => { const isActive = session.id === activeSessionId; return ( @@ -86,7 +86,7 @@ export const SidebarHistory = ({ } }} className={[ - 'group flex w-full items-start gap-3 rounded-2xl border px-3 py-3 text-left transition-colors', + 'group box-border flex w-full items-start gap-3 rounded-2xl border px-3 py-3 text-left transition-colors', isSwitching ? 'pointer-events-none opacity-60' : '', isActive ? 'border-sky-400/30 bg-sky-400/10' @@ -98,7 +98,7 @@ export const SidebarHistory = ({
-
+
{session.title}
diff --git a/web/src/pages/desktop/picoclaw/sidebar-session-actions.ts b/web/src/pages/desktop/picoclaw/sidebar-session-actions.ts index 5768ce1..73bfaba 100644 --- a/web/src/pages/desktop/picoclaw/sidebar-session-actions.ts +++ b/web/src/pages/desktop/picoclaw/sidebar-session-actions.ts @@ -32,7 +32,6 @@ type TakeoverSetter = Dispatch>; type PicoclawSidebarSessionActionOptions = { t: TFunction; - runtimeStatus: PicoclawRuntimeStatus | null; transportState: PicoclawTransportState; runState: PicoclawRunState; config: PicoclawConfigState; @@ -59,7 +58,6 @@ type PicoclawSidebarSessionActionOptions = { export function createPicoclawSidebarSessionActions(options: PicoclawSidebarSessionActionOptions) { const { t, - runtimeStatus, transportState, runState, config, @@ -142,7 +140,7 @@ export function createPicoclawSidebarSessionActions(options: PicoclawSidebarSess return; } - closeGateway(); + const closePromise = closeGateway(); setIsHistoryOpen(false); setIsModelConfigOpen(false); setMessages([]); @@ -159,6 +157,7 @@ export function createPicoclawSidebarSessionActions(options: PicoclawSidebarSess setActiveSessionId(nextSessionId); setIsFreshConversation(true); + await closePromise; const nextRuntimeStatus = await refreshState(); if (!canConnectGateway(nextRuntimeStatus ?? null)) { return; @@ -225,7 +224,7 @@ export function createPicoclawSidebarSessionActions(options: PicoclawSidebarSess }) ); - closeGateway(); + const closePromise = closeGateway(); setMessages(loadedMessages); setActiveSessionId(sessionId); setIsFreshConversation(false); @@ -238,6 +237,9 @@ export function createPicoclawSidebarSessionActions(options: PicoclawSidebarSess setOverlay(HIDDEN_OVERLAY); setTransportState('disconnected'); setRunState('idle'); + + await closePromise; + const nextRuntimeStatus = await refreshState(); setRuntimeStatus((current) => current ? { @@ -247,12 +249,14 @@ export function createPicoclawSidebarSessionActions(options: PicoclawSidebarSess : current ); - if (canConnectGateway(runtimeStatus ?? null)) { - try { - await connectGateway(sessionId); - } catch { - // handled by gateway events - } + if (!canConnectGateway(nextRuntimeStatus ?? null)) { + return; + } + + try { + await connectGateway(sessionId); + } catch { + // handled by gateway events } } catch (error) { const errorMessage = diff --git a/web/src/pages/desktop/picoclaw/use-sidebar.ts b/web/src/pages/desktop/picoclaw/use-sidebar.ts index e112f8e..33d062c 100644 --- a/web/src/pages/desktop/picoclaw/use-sidebar.ts +++ b/web/src/pages/desktop/picoclaw/use-sidebar.ts @@ -107,7 +107,6 @@ export const useSidebar = () => { refreshStateRef.current = actions.refreshState; const sessionActions = createPicoclawSidebarSessionActions({ t, - runtimeStatus, transportState, runState, config,