mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-22 08:50:08 +08:00
feat(diagnose): detect and inform about @homebridge/ciao cmd popup bug (#250)
* feat(diagnose): detect and inform about @homebridge/ciao cmd popup bug
On Windows, OpenClaw's transitive dependency @homebridge/ciao (<=1.3.6)
calls child_process.exec('arp -a ...') every 15-30 seconds without
passing windowsHide:true, causing a cmd.exe popup to flash.
This is an upstream library bug:
- Issue: homebridge/ciao#64
- PR: homebridge/ciao#65 (open, not merged)
ClawPanel deliberately chooses 'detect and inform' rather than silently
patching the user's node_modules. We respect the user's control over
their own machine.
Changes:
- src-tauri/src/commands/diagnose.rs: new check_ciao_windowshide_bug
command; scans openclaw's @homebridge/ciao/lib/NetworkManager.js and
reports whether the buggy exec pattern is present
- src-tauri/src/lib.rs: register the new command
- scripts/dev-api.js: Web-mode stub (returns affected:false since the
bug does not manifest off-Windows)
- src/lib/tauri-api.js: add api.checkCiaoWindowsHideBug
- src/lib/ciao-bug-warning.js: new module with toast + modal flow,
version-scoped dismiss (localStorage)
- src/locales/modules/ciaoBug.js: translations in 5 primary languages
- src/locales/index.js: register the ciaoBug module
- src/main.js: call checker 3s after splash hides
Non-Windows users see nothing; Windows users see a single warning toast
(version-dismissible) linking to three fix paths: wait for upstream,
apply patch-package, or edit NetworkManager.js manually.
* fix(diagnose): gate helper with cfg(windows), drop unneeded return
CI failures on Linux + macOS:
- openclaw_module_root was dead code when target_os != windows
since the only caller is the #[cfg(target_os = "windows")] block
inside check_ciao_windowshide_bug
- Explicit `return CiaoCheckResult {...};` in the non-Windows branch
triggered clippy::needless_return
Fix:
- Add #[cfg(target_os = "windows")] to openclaw_module_root so it
is not compiled on other platforms
- Convert the non-Windows early exit to a tail expression
This commit is contained in:
136
src/lib/ciao-bug-warning.js
Normal file
136
src/lib/ciao-bug-warning.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @homebridge/ciao Windows cmd 弹窗 bug 检测与提示
|
||||
*
|
||||
* 背景:openclaw 的依赖 @homebridge/ciao (<= 1.3.6) 在 Windows 上每 15-30 秒
|
||||
* 调用 `child_process.exec("arp -a ...")` 时未传 `windowsHide: true`,
|
||||
* 导致 cmd.exe / conhost.exe 窗口闪烁。这是上游库的 bug,
|
||||
* 不在 ClawPanel 控制范围内。上游 issue #64 和 PR #65 尚未合并。
|
||||
*
|
||||
* 我们只做两件事:检测 + 给用户展示修复指引。不触碰用户 node_modules。
|
||||
*/
|
||||
|
||||
import { api } from './tauri-api.js'
|
||||
import { toast } from '../components/toast.js'
|
||||
import { t } from './i18n.js'
|
||||
|
||||
const DISMISS_KEY_PREFIX = 'clawpanel_ciao_bug_dismissed_v'
|
||||
|
||||
function dismissKey(version) {
|
||||
return `${DISMISS_KEY_PREFIX}${version || 'unknown'}`
|
||||
}
|
||||
|
||||
function isDismissed(version) {
|
||||
try {
|
||||
return localStorage.getItem(dismissKey(version)) === '1'
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function markDismissed(version) {
|
||||
try {
|
||||
localStorage.setItem(dismissKey(version), '1')
|
||||
} catch (_) { /* quota 等异常忽略 */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动后异步检测;若确实受影响,展示一个可 dismiss 的 toast。
|
||||
* 用户点"详情"会打开带修复步骤和官方链接的 modal。
|
||||
*/
|
||||
export async function checkAndWarnCiaoBug() {
|
||||
let result
|
||||
try {
|
||||
result = await api.checkCiaoWindowsHideBug()
|
||||
} catch (err) {
|
||||
console.debug('[ciao-bug] check failed:', err)
|
||||
return
|
||||
}
|
||||
|
||||
if (!result || !result.affected) return
|
||||
if (isDismissed(result.version)) return
|
||||
|
||||
const detailBtn = document.createElement('button')
|
||||
detailBtn.className = 'btn btn-sm btn-primary'
|
||||
detailBtn.textContent = t('ciaoBug.viewDetail')
|
||||
detailBtn.style.marginLeft = '8px'
|
||||
detailBtn.onclick = () => openCiaoBugModal(result)
|
||||
|
||||
toast(
|
||||
t('ciaoBug.toastTitle'),
|
||||
'warning',
|
||||
{ action: detailBtn, duration: 12000 },
|
||||
)
|
||||
}
|
||||
|
||||
function openCiaoBugModal(result) {
|
||||
const overlay = document.createElement('div')
|
||||
overlay.className = 'modal-overlay'
|
||||
|
||||
const versionLine = result.version
|
||||
? `<div class="ciao-bug-row"><span class="muted">@homebridge/ciao</span> <code>${escapeHtml(result.version)}</code></div>`
|
||||
: ''
|
||||
const pathLine = result.networkManagerPath
|
||||
? `<div class="ciao-bug-row"><span class="muted">${escapeHtml(t('ciaoBug.pathLabel'))}</span> <code>${escapeHtml(result.networkManagerPath)}</code></div>`
|
||||
: ''
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div class="modal" style="max-width:640px;">
|
||||
<div class="modal-title">${escapeHtml(t('ciaoBug.modalTitle'))}</div>
|
||||
<div class="modal-body" style="font-size:var(--font-size-sm);line-height:1.6;">
|
||||
<p style="margin:0 0 12px;">${escapeHtml(t('ciaoBug.summary'))}</p>
|
||||
|
||||
<h4 style="margin:16px 0 6px;font-size:13px;color:var(--text-secondary);">${escapeHtml(t('ciaoBug.envTitle'))}</h4>
|
||||
<div class="ciao-bug-env" style="font-size:12px;color:var(--text-secondary);word-break:break-all;">
|
||||
${versionLine}
|
||||
${pathLine}
|
||||
</div>
|
||||
|
||||
<h4 style="margin:16px 0 6px;font-size:13px;color:var(--text-secondary);">${escapeHtml(t('ciaoBug.fixTitle'))}</h4>
|
||||
<ol style="margin:0;padding-left:20px;">
|
||||
<li style="margin-bottom:6px;">${t('ciaoBug.fixUpstream')}</li>
|
||||
<li style="margin-bottom:6px;">${t('ciaoBug.fixPatchPackage')}</li>
|
||||
<li style="margin-bottom:6px;">${t('ciaoBug.fixManual')}</li>
|
||||
</ol>
|
||||
|
||||
<div style="margin-top:14px;display:flex;gap:12px;flex-wrap:wrap;font-size:12px;">
|
||||
<a href="https://github.com/homebridge/ciao/issues/64" target="_blank" rel="noopener" style="color:var(--accent);">${escapeHtml(t('ciaoBug.linkIssue'))}</a>
|
||||
<a href="https://github.com/homebridge/ciao/pull/65" target="_blank" rel="noopener" style="color:var(--accent);">${escapeHtml(t('ciaoBug.linkPr'))}</a>
|
||||
</div>
|
||||
|
||||
<p style="margin:16px 0 0;font-size:12px;color:var(--text-tertiary);">${escapeHtml(t('ciaoBug.disclaimer'))}</p>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary btn-sm" data-action="close">${escapeHtml(t('common.close'))}</button>
|
||||
<button class="btn btn-primary btn-sm" data-action="dismiss">${escapeHtml(t('ciaoBug.dismissForVersion'))}</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const close = () => overlay.remove()
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) close()
|
||||
})
|
||||
overlay.querySelector('[data-action="close"]').onclick = close
|
||||
overlay.querySelector('[data-action="dismiss"]').onclick = () => {
|
||||
markDismissed(result.version)
|
||||
close()
|
||||
toast(t('ciaoBug.dismissed'), 'info')
|
||||
}
|
||||
document.addEventListener('keydown', function onEsc(e) {
|
||||
if (e.key === 'Escape') {
|
||||
close()
|
||||
document.removeEventListener('keydown', onEsc)
|
||||
}
|
||||
})
|
||||
|
||||
document.body.appendChild(overlay)
|
||||
}
|
||||
|
||||
function escapeHtml(raw) {
|
||||
return String(raw || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
@@ -196,6 +196,7 @@ export const api = {
|
||||
probeGatewayPort: () => invoke('probe_gateway_port'),
|
||||
diagnoseGatewayConnection: () => invoke('diagnose_gateway_connection'),
|
||||
guardianStatus: () => invoke('guardian_status'),
|
||||
checkCiaoWindowsHideBug: () => invoke('check_ciao_windowshide_bug'),
|
||||
|
||||
// 配置(读缓存,写清缓存)
|
||||
getVersionInfo: () => cachedInvoke('get_version_info', {}, 30000),
|
||||
|
||||
@@ -35,13 +35,14 @@ import diagnose from './modules/diagnose.js'
|
||||
import routeMap from './modules/routeMap.js'
|
||||
import extensions from './modules/extensions.js'
|
||||
import engine from './modules/engine.js'
|
||||
import ciaoBug from './modules/ciaoBug.js'
|
||||
|
||||
const MODULES = {
|
||||
common, sidebar, instance, dashboard, services, settings,
|
||||
models, agents, agentDetail, gateway, security, communication, channels,
|
||||
memory, dreaming, cron, usage, skills, chat, chatDebug, setup, about,
|
||||
ext, logs, assistant, toast, modal, engagement, diagnose, routeMap, extensions,
|
||||
engine,
|
||||
engine, ciaoBug,
|
||||
}
|
||||
|
||||
/** 构建所有语言字典 { 'zh-CN': { common: {...}, sidebar: {...}, ... }, ... } */
|
||||
|
||||
115
src/locales/modules/ciaoBug.js
Normal file
115
src/locales/modules/ciaoBug.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { _ } from '../helper.js'
|
||||
|
||||
/**
|
||||
* @homebridge/ciao Windows cmd 弹窗 bug 的用户提示文案
|
||||
* 上游 issue: https://github.com/homebridge/ciao/issues/64
|
||||
* 上游 PR: https://github.com/homebridge/ciao/pull/65
|
||||
*/
|
||||
export default {
|
||||
toastTitle: _(
|
||||
'检测到已知问题:OpenClaw 运行时 Windows 上每 15 秒会弹一次 cmd 窗口',
|
||||
'Known issue detected: OpenClaw causes a cmd popup every 15s on Windows',
|
||||
'偵測到已知問題:OpenClaw 執行時 Windows 每 15 秒會彈出 cmd 視窗',
|
||||
'既知の問題を検出:Windows で OpenClaw 実行時、15 秒ごとに cmd ウィンドウが点滅します',
|
||||
'알려진 문제 감지: Windows에서 OpenClaw 실행 시 15초마다 cmd 창이 깜박임',
|
||||
),
|
||||
viewDetail: _(
|
||||
'查看详情',
|
||||
'View details',
|
||||
'檢視詳情',
|
||||
'詳細を表示',
|
||||
'자세히 보기',
|
||||
),
|
||||
modalTitle: _(
|
||||
'Windows cmd 弹窗问题 — 第三方库 bug',
|
||||
'Windows cmd popup — third-party library bug',
|
||||
'Windows cmd 彈窗問題 — 第三方函式庫 bug',
|
||||
'Windows cmd ポップアップ — サードパーティ製ライブラリの不具合',
|
||||
'Windows cmd 팝업 — 서드파티 라이브러리 버그',
|
||||
),
|
||||
summary: _(
|
||||
'这是 OpenClaw 依赖的 @homebridge/ciao 库的已知 bug,不是 ClawPanel 或 OpenClaw 本身的问题。每 15-30 秒 ciao 会调用 arp -a 刷新网络接口缓存,但未使用 windowsHide 参数,所以 Windows 上会弹出一个短暂的 cmd 窗口。功能本身完全正常,只是视觉干扰。',
|
||||
'This is a known bug in @homebridge/ciao, which OpenClaw depends on. It is not a bug of ClawPanel or OpenClaw itself. Every 15–30 seconds ciao calls "arp -a" to refresh the network interface cache, but without the windowsHide option, so a cmd window flashes briefly on Windows. Functionality is unaffected — it is purely a visual annoyance.',
|
||||
'這是 OpenClaw 相依的 @homebridge/ciao 函式庫的已知 bug,不是 ClawPanel 或 OpenClaw 本身的問題。每 15–30 秒 ciao 會呼叫 arp -a 重新整理網路介面快取,但沒有使用 windowsHide 參數,所以 Windows 上會彈出短暫的 cmd 視窗。功能本身完全正常,只是視覺干擾。',
|
||||
'これは OpenClaw が依存している @homebridge/ciao ライブラリの既知の不具合であり、ClawPanel や OpenClaw 本体の問題ではありません。ciao は 15〜30 秒ごとに "arp -a" を呼び出してネットワークインターフェースのキャッシュを更新しますが、windowsHide オプションが指定されていないため、Windows では cmd ウィンドウが一瞬点滅します。動作自体は正常で、視覚的な煩わしさのみです。',
|
||||
'이것은 OpenClaw가 의존하는 @homebridge/ciao 라이브러리의 알려진 버그이며 ClawPanel이나 OpenClaw 자체의 문제가 아닙니다. ciao는 15~30초마다 "arp -a"를 호출하여 네트워크 인터페이스 캐시를 갱신하는데 windowsHide 옵션을 지정하지 않아 Windows에서 cmd 창이 순간적으로 깜박입니다. 기능 자체는 정상이며 시각적 방해일 뿐입니다.',
|
||||
),
|
||||
envTitle: _(
|
||||
'当前环境',
|
||||
'Environment',
|
||||
'目前環境',
|
||||
'現在の環境',
|
||||
'현재 환경',
|
||||
),
|
||||
pathLabel: _(
|
||||
'源文件路径',
|
||||
'Source file',
|
||||
'原始檔路徑',
|
||||
'ソースファイルパス',
|
||||
'소스 파일 경로',
|
||||
),
|
||||
fixTitle: _(
|
||||
'解决方案',
|
||||
'How to fix',
|
||||
'解決方式',
|
||||
'対処方法',
|
||||
'해결 방법',
|
||||
),
|
||||
// HTML 允许:可包含超链接。escapeHtml 在这些条目上不启用。
|
||||
fixUpstream: _(
|
||||
'<b>等待上游合并</b> —— 上游已有 <a href="https://github.com/homebridge/ciao/pull/65" target="_blank" rel="noopener">PR #65</a> 提供修复,未合并。OpenClaw 升级 ciao 后自动消失。',
|
||||
'<b>Wait for upstream merge</b> — <a href="https://github.com/homebridge/ciao/pull/65" target="_blank" rel="noopener">PR #65</a> already provides the fix but has not been merged. Will disappear once OpenClaw upgrades its ciao dependency.',
|
||||
'<b>等待上游合併</b> —— 上游已有 <a href="https://github.com/homebridge/ciao/pull/65" target="_blank" rel="noopener">PR #65</a> 提供修復,尚未合併。OpenClaw 升級 ciao 後會自動消失。',
|
||||
'<b>上流のマージを待つ</b> —— <a href="https://github.com/homebridge/ciao/pull/65" target="_blank" rel="noopener">PR #65</a> で既に修正が提供されていますが、未マージです。OpenClaw が ciao を更新すれば自動的に解消されます。',
|
||||
'<b>업스트림 병합 대기</b> —— <a href="https://github.com/homebridge/ciao/pull/65" target="_blank" rel="noopener">PR #65</a>에 이미 수정이 올라와 있지만 병합되지 않았습니다. OpenClaw가 ciao 의존성을 업데이트하면 자동으로 사라집니다.',
|
||||
),
|
||||
fixPatchPackage: _(
|
||||
'<b>使用 patch-package 给 OpenClaw 打补丁</b>:在 OpenClaw 源码仓库(或 npm 全局安装目录下的 openclaw 包目录)执行 <code>npx patch-package @homebridge/ciao</code>,在 NetworkManager.js 的 exec 调用中加 <code>{ windowsHide: true }</code>。',
|
||||
'<b>Apply a patch-package patch to OpenClaw</b>: in the OpenClaw source repo (or the globally installed openclaw directory), run <code>npx patch-package @homebridge/ciao</code> after adding <code>{ windowsHide: true }</code> to the exec calls in NetworkManager.js.',
|
||||
'<b>使用 patch-package 為 OpenClaw 套用修補</b>:在 OpenClaw 原始碼倉庫(或 npm 全域安裝目錄的 openclaw 包目錄)執行 <code>npx patch-package @homebridge/ciao</code>,在 NetworkManager.js 的 exec 呼叫中加入 <code>{ windowsHide: true }</code>。',
|
||||
'<b>patch-package で OpenClaw にパッチを適用</b>:OpenClaw のソースリポジトリ(または npm グローバル インストール ディレクトリ内の openclaw パッケージ ディレクトリ)で <code>npx patch-package @homebridge/ciao</code> を実行し、NetworkManager.js の exec 呼び出しに <code>{ windowsHide: true }</code> を追加してください。',
|
||||
'<b>patch-package로 OpenClaw에 패치 적용</b>: OpenClaw 소스 저장소(또는 npm 전역 설치 디렉터리의 openclaw 패키지 디렉터리)에서 NetworkManager.js의 exec 호출에 <code>{ windowsHide: true }</code>를 추가한 뒤 <code>npx patch-package @homebridge/ciao</code>를 실행하세요.',
|
||||
),
|
||||
fixManual: _(
|
||||
'<b>手动编辑 NetworkManager.js</b>(最简单,但升级 openclaw 后需重做):用编辑器打开上面显示的文件路径,找到 6 处 <code>child_process.exec("arp ...")</code> 调用,在 URL 参数和回调之间加 <code>{ windowsHide: true },</code>,保存后重启 Gateway。',
|
||||
'<b>Manually edit NetworkManager.js</b> (simplest, but you must redo it after upgrading openclaw): open the file at the path above, find the 6 <code>child_process.exec("arp ...")</code> calls, add <code>{ windowsHide: true },</code> between the first argument and the callback, save and restart Gateway.',
|
||||
'<b>手動編輯 NetworkManager.js</b>(最簡單,但升級 openclaw 後需重做):用編輯器打開上面顯示的檔案路徑,找到 6 處 <code>child_process.exec("arp ...")</code> 呼叫,在 URL 參數和回呼之間加入 <code>{ windowsHide: true },</code>,儲存後重新啟動 Gateway。',
|
||||
'<b>NetworkManager.js を手動で編集</b>(最も簡単ですが、openclaw を更新するたびにやり直しが必要):上記のパスのファイルを開き、<code>child_process.exec("arp ...")</code> の 6 箇所の呼び出しを探して、URL 引数とコールバックの間に <code>{ windowsHide: true },</code> を追加し、保存して Gateway を再起動してください。',
|
||||
'<b>NetworkManager.js 수동 편집</b>(가장 간단하지만 openclaw 업그레이드 후 다시 해야 함): 위 경로의 파일을 편집기로 열고 6개의 <code>child_process.exec("arp ...")</code> 호출을 찾아 URL 인수와 콜백 사이에 <code>{ windowsHide: true },</code>를 추가한 후 저장하고 Gateway를 재시작하세요.',
|
||||
),
|
||||
linkIssue: _(
|
||||
'上游 Issue #64',
|
||||
'Upstream issue #64',
|
||||
'上游 Issue #64',
|
||||
'上流 Issue #64',
|
||||
'업스트림 Issue #64',
|
||||
),
|
||||
linkPr: _(
|
||||
'上游修复 PR #65',
|
||||
'Upstream fix PR #65',
|
||||
'上游修復 PR #65',
|
||||
'上流修正 PR #65',
|
||||
'업스트림 수정 PR #65',
|
||||
),
|
||||
disclaimer: _(
|
||||
'说明:ClawPanel 选择「检测并告知」而不是「自动修改你的 node_modules」—— 我们尊重你对本机软件的控制权。',
|
||||
'Note: ClawPanel chose "detect & inform" instead of "silently patch your node_modules" — we respect your control over local software.',
|
||||
'說明:ClawPanel 選擇「偵測並告知」而不是「自動修改你的 node_modules」—— 我們尊重你對本機軟體的控制權。',
|
||||
'注:ClawPanel は「検出して通知する」ことを選択しました。「node_modules を自動改変する」のではなく、ローカルソフトウェアに対するユーザーのコントロールを尊重しています。',
|
||||
'참고: ClawPanel은 node_modules를 자동으로 수정하는 대신 "감지하고 알리는" 방식을 선택했습니다 — 로컬 소프트웨어에 대한 사용자의 통제권을 존중합니다.',
|
||||
),
|
||||
dismissForVersion: _(
|
||||
'已了解,不再提醒本版本',
|
||||
'Got it, don’t remind for this version',
|
||||
'已了解,不再提醒本版本',
|
||||
'了解しました。このバージョンでは再通知しません',
|
||||
'이해했습니다, 이 버전에서는 다시 알리지 마세요',
|
||||
),
|
||||
dismissed: _(
|
||||
'已忽略此版本的提醒',
|
||||
'Reminder dismissed for this version',
|
||||
'已忽略此版本的提醒',
|
||||
'このバージョンの通知を無視しました',
|
||||
'이 버전의 알림을 무시했습니다',
|
||||
),
|
||||
}
|
||||
11
src/main.js
11
src/main.js
@@ -344,6 +344,17 @@ async function boot() {
|
||||
setTimeout(() => splash.remove(), 500)
|
||||
}
|
||||
|
||||
// 启动 3 秒后提示 @homebridge/ciao cmd 弹窗问题(仅 Windows 受影响)
|
||||
// 只在桌面端跑——Web 模式下的 dev-api.js 桩会直接返回 affected:false
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const { checkAndWarnCiaoBug } = await import('./lib/ciao-bug-warning.js')
|
||||
checkAndWarnCiaoBug()
|
||||
} catch (err) {
|
||||
console.debug('[ciao-bug] module skipped:', err)
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
// 默认密码提醒横幅
|
||||
if (sessionStorage.getItem('clawpanel_must_change_pw') === '1') {
|
||||
const banner = document.createElement('div')
|
||||
|
||||
Reference in New Issue
Block a user