From b33a72df5c30ebefaaca53fb459f17fb4ebc2969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:11:46 +0900 Subject: [PATCH 01/25] fix tray restart prompt flow for desktop runtime --- scripts/prepare-resources.mjs | 24 ++++++++++++++++++------ src-tauri/src/main.rs | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index 227fcfc2..5e3b33b4 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -212,6 +212,8 @@ const patchMonacoCssNestingWarnings = async (dashboardDir) => { const LEGACY_DESKTOP_BRIDGE_PATTERNS = { trayRestartGuard: /if\s*\(\s*!desktopBridge\?\.isElectron\s*\|\|\s*!desktopBridge\.onTrayRestartBackend\s*\)\s*\{/, + trayRestartHandlerInvoke: /await\s+restartAstrBot\s*\(\s*globalWaitingRef\.value\s*\)/, + restartAstrBotImport: /^\s*import\s+\{\s*restartAstrBot\s*\}\s+from\s+['"]@\/utils\/restartAstrBot['"]\s*;?\r?\n/m, typeIsElectron: /^(\s+)isElectron:\s*boolean;(\r?\n)/m, typeIsElectronRuntime: /^(\s+)isElectronRuntime:\s*\(\)\s*=>\s*Promise;(\r?\n)/m, electronAppFlagToken: /\bisElectronApp\b/, @@ -226,6 +228,7 @@ const LEGACY_DESKTOP_BRIDGE_PATTERNS = { const MODERN_DESKTOP_BRIDGE_PATTERNS = { trayRestartGuard: /if\s*\(\s*!desktopBridge\?\.onTrayRestartBackend\s*\)\s*\{/, + trayRestartPromptInvoke: /await\s+globalWaitingRef\.value\?\.check\?\.\(\s*\)/, desktopBridgeTypeIsDesktop: /^\s+isDesktop:\s*boolean;\r?\n/m, desktopBridgeTypeRuntime: /^\s+isDesktopRuntime:\s*\(\)\s*=>\s*Promise;\r?\n/m, restartCapabilityGuard: /const hasDesktopRestartCapability\s*=/, @@ -268,8 +271,10 @@ const patchRequiredLegacyFile = async ({ filePath, transform, patchLabel, isAlre }; const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { - const hasModernTrayRestartGuard = (source) => - MODERN_DESKTOP_BRIDGE_PATTERNS.trayRestartGuard.test(source); + const hasModernTrayRestartHook = (source) => + MODERN_DESKTOP_BRIDGE_PATTERNS.trayRestartGuard.test(source) && + MODERN_DESKTOP_BRIDGE_PATTERNS.trayRestartPromptInvoke.test(source) && + !LEGACY_DESKTOP_BRIDGE_PATTERNS.restartAstrBotImport.test(source); const hasModernDesktopBridgeTypes = (source) => MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeIsDesktop.test(source) && MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeRuntime.test(source); @@ -282,13 +287,20 @@ const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { await patchRequiredLegacyFile({ filePath: path.join(dashboardDir, 'src', 'App.vue'), - transform: (source) => - source.replace( + transform: (source) => { + let patched = source.replace( LEGACY_DESKTOP_BRIDGE_PATTERNS.trayRestartGuard, 'if (!desktopBridge?.onTrayRestartBackend) {', - ), + ); + patched = patched.replace( + LEGACY_DESKTOP_BRIDGE_PATTERNS.trayRestartHandlerInvoke, + 'await globalWaitingRef.value?.check?.()', + ); + patched = patched.replace(LEGACY_DESKTOP_BRIDGE_PATTERNS.restartAstrBotImport, ''); + return patched; + }, patchLabel: 'tray restart desktop guard', - isAlreadyModern: hasModernTrayRestartGuard, + isAlreadyModern: hasModernTrayRestartHook, }); await patchRequiredLegacyFile({ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index fadc96bf..51beeb47 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1393,6 +1393,7 @@ fn handle_tray_menu_event(app_handle: &AppHandle, menu_id: &str) { TRAY_MENU_RESTART_BACKEND => { append_desktop_log("tray requested backend restart"); show_main_window(app_handle); + emit_tray_restart_backend_event(app_handle); let app_handle_cloned = app_handle.clone(); thread::spawn(move || match do_restart_backend(&app_handle_cloned, None) { @@ -1414,6 +1415,19 @@ fn handle_tray_menu_event(app_handle: &AppHandle, menu_id: &str) { } } +fn emit_tray_restart_backend_event(app_handle: &AppHandle) { + let Some(window) = app_handle.get_webview_window("main") else { + append_desktop_log("tray restart event skipped: main window not found"); + return; + }; + + if let Err(error) = window.eval( + "if (typeof window !== 'undefined' && typeof window.__astrbotDesktopEmitTrayRestart === 'function') { window.__astrbotDesktopEmitTrayRestart(); }", + ) { + append_desktop_log(&format!("failed to emit tray restart event: {error}")); + } +} + fn do_restart_backend(app_handle: &AppHandle, auth_token: Option<&str>) -> Result<(), String> { let state = app_handle.state::(); state.restart_backend(app_handle, auth_token) From 67ac99a3f92336633bea8ee6b42817c503604666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:16:35 +0900 Subject: [PATCH 02/25] fix desktop update mode patch and isolate build source dir --- Makefile | 8 ++++++++ README.md | 10 ++++++++-- scripts/prepare-resources.mjs | 25 ++++++++++++------------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 384d4f68..1b7103db 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ ASTRBOT_LOCAL_DIR ?= $(VENDOR_DIR)/AstrBot-local ASTRBOT_LOCAL_DESKTOP_DIR ?= $(ASTRBOT_LOCAL_DIR)/desktop ASTRBOT_SOURCE_GIT_URL ?= https://github.com/AstrBotDevs/AstrBot.git ASTRBOT_SOURCE_GIT_REF ?= master +ASTRBOT_BUILD_SOURCE_DIR ?= RUST_MANIFEST ?= src-tauri/Cargo.toml NODE_MODULES_DIR ?= node_modules PNPM_STORE_DIR ?= .pnpm-store @@ -30,6 +31,7 @@ help: @echo " make prepare-resources Prepare all resources" @echo " make dev Run Tauri dev" @echo " make build Run Tauri build" + @echo " (set ASTRBOT_BUILD_SOURCE_DIR=... to force local source)" @echo " make rebuild Clean and build" @echo " make lint Run formatting and clippy checks" @echo " make test Run Rust tests" @@ -75,11 +77,17 @@ dev: build: @set -e; \ build_version="$(ASTRBOT_DESKTOP_VERSION)"; \ + build_source_dir="$(ASTRBOT_BUILD_SOURCE_DIR)"; \ if [ -z "$$build_version" ]; then \ build_version="$$(node -e "console.log(require('./package.json').version)")"; \ fi; \ + if [ -n "$$build_source_dir" ]; then \ + echo "Using explicit build source dir: $$build_source_dir"; \ + fi; \ + echo "Build resource source dir: $${build_source_dir:-}"; \ ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)" \ + ASTRBOT_SOURCE_DIR="$$build_source_dir" \ ASTRBOT_DESKTOP_VERSION="$$build_version" \ pnpm run build diff --git a/README.md b/README.md index c829cf80..0fe4b3ab 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ make prune 推荐日常使用 `make update`,避免本地切换分支导致版本漂移。 -补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`,避免构建前资源准备阶段把版本回写到其他值。若需覆盖,可显式传入 `ASTRBOT_DESKTOP_VERSION=...`。 +补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`,并默认忽略全局 `ASTRBOT_SOURCE_DIR`(避免本地环境变量导致版本漂移)。若需覆盖,可显式传入 `ASTRBOT_DESKTOP_VERSION=...`;若需强制使用本地源码构建,可传入 `ASTRBOT_BUILD_SOURCE_DIR=/path/to/AstrBot`。 示例: @@ -168,12 +168,18 @@ export ASTRBOT_SOURCE_GIT_URL=https://github.com/AstrBotDevs/AstrBot.git export ASTRBOT_SOURCE_GIT_REF=master ``` -使用本地 AstrBot 源码(优先级最高): +使用本地 AstrBot 源码(`make sync-version`/`pnpm run prepare:*` 会优先使用): ```bash export ASTRBOT_SOURCE_DIR=/path/to/AstrBot ``` +`make build` 如需使用本地源码,请显式指定: + +```bash +make build ASTRBOT_BUILD_SOURCE_DIR=/path/to/AstrBot +``` + 临时测试仓库示例: ```bash diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index 5e3b33b4..828cb604 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -218,11 +218,10 @@ const LEGACY_DESKTOP_BRIDGE_PATTERNS = { typeIsElectronRuntime: /^(\s+)isElectronRuntime:\s*\(\)\s*=>\s*Promise;(\r?\n)/m, electronAppFlagToken: /\bisElectronApp\b/, electronAppFlagReplace: /\bisElectronApp\b/g, - desktopReleaseEnvGuard: - /typeof\s+window\s*!==\s*'undefined'\s*&&\s*!!window\.astrbotDesktop\?\.isElectron/, - desktopReleaseRuntimeGuard: - /isDesktopReleaseMode\.value\s*=\s*!!window\.astrbotDesktop\?\.isElectron\s*\|\|\s*\r?\n\s*!!\(\s*await\s+window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)\s*\)\s*;/, - legacyRuntimeUsage: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/, + desktopBridgeIsElectronToken: /window\.astrbotDesktop\?\.isElectron\b/, + desktopBridgeIsElectronReplace: /window\.astrbotDesktop\?\.isElectron\b/g, + legacyRuntimeUsageToken: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/, + legacyRuntimeUsageReplace: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/g, restartGuard: /if\s*\(\s*desktopBridge\?\.isElectron\s*\)\s*\{/, }; @@ -280,8 +279,8 @@ const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeRuntime.test(source); const hasLegacyDesktopReleaseGuards = (source) => LEGACY_DESKTOP_BRIDGE_PATTERNS.electronAppFlagToken.test(source) || - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseEnvGuard.test(source) || - LEGACY_DESKTOP_BRIDGE_PATTERNS.legacyRuntimeUsage.test(source); + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeIsElectronToken.test(source) || + LEGACY_DESKTOP_BRIDGE_PATTERNS.legacyRuntimeUsageToken.test(source); const hasModernRestartCapabilityGuard = (source) => MODERN_DESKTOP_BRIDGE_PATTERNS.restartCapabilityGuard.test(source); @@ -328,13 +327,13 @@ const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { LEGACY_DESKTOP_BRIDGE_PATTERNS.electronAppFlagReplace, 'isDesktopReleaseMode', ); - patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseEnvGuard, - 'false', + patched = patched.replaceAll( + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeIsElectronReplace, + 'window.astrbotDesktop?.isDesktop', ); - patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseRuntimeGuard, - 'isDesktopReleaseMode.value = false;', + patched = patched.replaceAll( + LEGACY_DESKTOP_BRIDGE_PATTERNS.legacyRuntimeUsageReplace, + 'window.astrbotDesktop?.isDesktopRuntime?.()', ); return patched; }, From 492c185c26f30f7646d561077f941b9d433b6f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:18:11 +0900 Subject: [PATCH 03/25] chore rename legacy bridge pattern identifiers --- scripts/prepare-resources.mjs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index 828cb604..e1a3b0df 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -214,14 +214,14 @@ const LEGACY_DESKTOP_BRIDGE_PATTERNS = { /if\s*\(\s*!desktopBridge\?\.isElectron\s*\|\|\s*!desktopBridge\.onTrayRestartBackend\s*\)\s*\{/, trayRestartHandlerInvoke: /await\s+restartAstrBot\s*\(\s*globalWaitingRef\.value\s*\)/, restartAstrBotImport: /^\s*import\s+\{\s*restartAstrBot\s*\}\s+from\s+['"]@\/utils\/restartAstrBot['"]\s*;?\r?\n/m, - typeIsElectron: /^(\s+)isElectron:\s*boolean;(\r?\n)/m, - typeIsElectronRuntime: /^(\s+)isElectronRuntime:\s*\(\)\s*=>\s*Promise;(\r?\n)/m, - electronAppFlagToken: /\bisElectronApp\b/, - electronAppFlagReplace: /\bisElectronApp\b/g, - desktopBridgeIsElectronToken: /window\.astrbotDesktop\?\.isElectron\b/, - desktopBridgeIsElectronReplace: /window\.astrbotDesktop\?\.isElectron\b/g, - legacyRuntimeUsageToken: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/, - legacyRuntimeUsageReplace: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/g, + typeDesktopFlag: /^(\s+)isElectron:\s*boolean;(\r?\n)/m, + typeDesktopRuntimeFlag: /^(\s+)isElectronRuntime:\s*\(\)\s*=>\s*Promise;(\r?\n)/m, + desktopReleaseFlagToken: /\bisElectronApp\b/, + desktopReleaseFlagReplace: /\bisElectronApp\b/g, + desktopBridgeLegacyFlagToken: /window\.astrbotDesktop\?\.isElectron\b/, + desktopBridgeLegacyFlagReplace: /window\.astrbotDesktop\?\.isElectron\b/g, + desktopBridgeLegacyRuntimeToken: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/, + desktopBridgeLegacyRuntimeReplace: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/g, restartGuard: /if\s*\(\s*desktopBridge\?\.isElectron\s*\)\s*\{/, }; @@ -278,9 +278,9 @@ const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeIsDesktop.test(source) && MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeRuntime.test(source); const hasLegacyDesktopReleaseGuards = (source) => - LEGACY_DESKTOP_BRIDGE_PATTERNS.electronAppFlagToken.test(source) || - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeIsElectronToken.test(source) || - LEGACY_DESKTOP_BRIDGE_PATTERNS.legacyRuntimeUsageToken.test(source); + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseFlagToken.test(source) || + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyFlagToken.test(source) || + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyRuntimeToken.test(source); const hasModernRestartCapabilityGuard = (source) => MODERN_DESKTOP_BRIDGE_PATTERNS.restartCapabilityGuard.test(source); @@ -307,11 +307,11 @@ const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { transform: (source) => { let patched = source; patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.typeIsElectron, + LEGACY_DESKTOP_BRIDGE_PATTERNS.typeDesktopFlag, '$1isDesktop: boolean;$2', ); patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.typeIsElectronRuntime, + LEGACY_DESKTOP_BRIDGE_PATTERNS.typeDesktopRuntimeFlag, '$1isDesktopRuntime: () => Promise;$2', ); return patched; @@ -324,15 +324,15 @@ const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { filePath: path.join(dashboardDir, 'src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'), transform: (source) => { let patched = source.replaceAll( - LEGACY_DESKTOP_BRIDGE_PATTERNS.electronAppFlagReplace, + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseFlagReplace, 'isDesktopReleaseMode', ); patched = patched.replaceAll( - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeIsElectronReplace, + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyFlagReplace, 'window.astrbotDesktop?.isDesktop', ); patched = patched.replaceAll( - LEGACY_DESKTOP_BRIDGE_PATTERNS.legacyRuntimeUsageReplace, + LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyRuntimeReplace, 'window.astrbotDesktop?.isDesktopRuntime?.()', ); return patched; From 8b399ce9f50e982b30377b1988a4243b10009b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:19:46 +0900 Subject: [PATCH 04/25] feat add clean env reset option --- Makefile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Makefile b/Makefile index 1b7103db..07e53517 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,13 @@ ASTRBOT_LOCAL_DESKTOP_DIR ?= $(ASTRBOT_LOCAL_DIR)/desktop ASTRBOT_SOURCE_GIT_URL ?= https://github.com/AstrBotDevs/AstrBot.git ASTRBOT_SOURCE_GIT_REF ?= master ASTRBOT_BUILD_SOURCE_DIR ?= +ASTRBOT_RESET_ENV_SCRIPT ?= .astrbot-reset-env.sh RUST_MANIFEST ?= src-tauri/Cargo.toml NODE_MODULES_DIR ?= node_modules PNPM_STORE_DIR ?= .pnpm-store TAURI_TARGET_DIR ?= src-tauri/target +CLEAN_ENV ?= $(env) +ASTRBOT_ENV_KEYS := ASTRBOT_SOURCE_DIR ASTRBOT_SOURCE_GIT_URL ASTRBOT_SOURCE_GIT_REF ASTRBOT_DESKTOP_VERSION ASTRBOT_BUILD_SOURCE_DIR .PHONY: help deps sync-version update prepare-webui prepare-backend prepare-resources dev build \ prepare rebuild lint test doctor prune size clean clean-rust clean-resources \ @@ -45,6 +48,7 @@ help: @echo " make clean-vendor Remove vendor and runtime" @echo " make clean-node Remove node_modules and pnpm store" @echo " make clean Clean all build artifacts" + @echo " (use env=1 to generate env reset script)" @echo " make clean-all Alias of clean" deps: @@ -135,5 +139,15 @@ clean-node: rm -rf $(NODE_MODULES_DIR) $(PNPM_STORE_DIR) clean: clean-rust clean-resources clean-vendor clean-node + @if [ "$(CLEAN_ENV)" = "1" ]; then \ + reset_script="$(ASTRBOT_RESET_ENV_SCRIPT)"; \ + { \ + echo "#!/usr/bin/env sh"; \ + echo "unset $(ASTRBOT_ENV_KEYS)"; \ + } > "$$reset_script"; \ + chmod +x "$$reset_script"; \ + echo "Generated $$reset_script"; \ + echo "Run: source $$reset_script"; \ + fi clean-all: clean From 45b8d6a0d90c26a4952a6f61d92f0dfb0e1ddd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:20:37 +0900 Subject: [PATCH 05/25] chore add clean-env target --- Makefile | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 07e53517..4a3bf2da 100644 --- a/Makefile +++ b/Makefile @@ -15,12 +15,11 @@ RUST_MANIFEST ?= src-tauri/Cargo.toml NODE_MODULES_DIR ?= node_modules PNPM_STORE_DIR ?= .pnpm-store TAURI_TARGET_DIR ?= src-tauri/target -CLEAN_ENV ?= $(env) ASTRBOT_ENV_KEYS := ASTRBOT_SOURCE_DIR ASTRBOT_SOURCE_GIT_URL ASTRBOT_SOURCE_GIT_REF ASTRBOT_DESKTOP_VERSION ASTRBOT_BUILD_SOURCE_DIR .PHONY: help deps sync-version update prepare-webui prepare-backend prepare-resources dev build \ prepare rebuild lint test doctor prune size clean clean-rust clean-resources \ - clean-vendor-local clean-vendor clean-node clean-all + clean-vendor-local clean-vendor clean-node clean-env clean-all help: @echo "AstrBot Desktop Make Targets" @@ -47,8 +46,8 @@ help: @echo " make clean-vendor-local Remove vendor/AstrBot-local" @echo " make clean-vendor Remove vendor and runtime" @echo " make clean-node Remove node_modules and pnpm store" + @echo " make clean-env Generate shell script to unset build env vars" @echo " make clean Clean all build artifacts" - @echo " (use env=1 to generate env reset script)" @echo " make clean-all Alias of clean" deps: @@ -138,16 +137,17 @@ clean-vendor: clean-node: rm -rf $(NODE_MODULES_DIR) $(PNPM_STORE_DIR) +clean-env: + @set -e; \ + reset_script="$(ASTRBOT_RESET_ENV_SCRIPT)"; \ + { \ + echo "#!/usr/bin/env sh"; \ + echo "unset $(ASTRBOT_ENV_KEYS)"; \ + } > "$$reset_script"; \ + chmod +x "$$reset_script"; \ + echo "Generated $$reset_script"; \ + echo "Run: source $$reset_script" + clean: clean-rust clean-resources clean-vendor clean-node - @if [ "$(CLEAN_ENV)" = "1" ]; then \ - reset_script="$(ASTRBOT_RESET_ENV_SCRIPT)"; \ - { \ - echo "#!/usr/bin/env sh"; \ - echo "unset $(ASTRBOT_ENV_KEYS)"; \ - } > "$$reset_script"; \ - chmod +x "$$reset_script"; \ - echo "Generated $$reset_script"; \ - echo "Run: source $$reset_script"; \ - fi clean-all: clean From 0bcce9e6866999f942b2023584d9f19ed222feff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:34:13 +0900 Subject: [PATCH 06/25] change build to respect ASTRBOT_SOURCE_DIR by default --- Makefile | 7 +++++-- README.md | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 4a3bf2da..08e72217 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ help: @echo " make prepare-resources Prepare all resources" @echo " make dev Run Tauri dev" @echo " make build Run Tauri build" - @echo " (set ASTRBOT_BUILD_SOURCE_DIR=... to force local source)" + @echo " (set ASTRBOT_SOURCE_DIR=... or ASTRBOT_BUILD_SOURCE_DIR=...)" @echo " make rebuild Clean and build" @echo " make lint Run formatting and clippy checks" @echo " make test Run Rust tests" @@ -81,11 +81,14 @@ build: @set -e; \ build_version="$(ASTRBOT_DESKTOP_VERSION)"; \ build_source_dir="$(ASTRBOT_BUILD_SOURCE_DIR)"; \ + if [ -z "$$build_source_dir" ]; then \ + build_source_dir="$(ASTRBOT_SOURCE_DIR)"; \ + fi; \ if [ -z "$$build_version" ]; then \ build_version="$$(node -e "console.log(require('./package.json').version)")"; \ fi; \ if [ -n "$$build_source_dir" ]; then \ - echo "Using explicit build source dir: $$build_source_dir"; \ + echo "Using build source dir: $$build_source_dir"; \ fi; \ echo "Build resource source dir: $${build_source_dir:-}"; \ ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ diff --git a/README.md b/README.md index 0fe4b3ab..39f70e49 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ make prune 推荐日常使用 `make update`,避免本地切换分支导致版本漂移。 -补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`,并默认忽略全局 `ASTRBOT_SOURCE_DIR`(避免本地环境变量导致版本漂移)。若需覆盖,可显式传入 `ASTRBOT_DESKTOP_VERSION=...`;若需强制使用本地源码构建,可传入 `ASTRBOT_BUILD_SOURCE_DIR=/path/to/AstrBot`。 +补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`。若当前环境设置了 `ASTRBOT_SOURCE_DIR`,构建会直接使用该本地源码;也可通过 `ASTRBOT_BUILD_SOURCE_DIR` 显式覆盖。若需覆盖版本,可传入 `ASTRBOT_DESKTOP_VERSION=...`。 示例: @@ -174,9 +174,13 @@ export ASTRBOT_SOURCE_GIT_REF=master export ASTRBOT_SOURCE_DIR=/path/to/AstrBot ``` -`make build` 如需使用本地源码,请显式指定: +`make build` 使用本地源码有两种方式: ```bash +# 方式 1:使用环境变量(make build 默认会读取) +export ASTRBOT_SOURCE_DIR=/path/to/AstrBot + +# 方式 2:单次构建显式覆盖 make build ASTRBOT_BUILD_SOURCE_DIR=/path/to/AstrBot ``` From 74a6a9a0c5c883fd7a539de71850cb6b7cf24e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:41:54 +0900 Subject: [PATCH 07/25] refactor sync prepare-resources with upstream desktop bridge fields --- scripts/prepare-resources.mjs | 206 +++++++++------------------------- 1 file changed, 50 insertions(+), 156 deletions(-) diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index e1a3b0df..231fb1cc 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -209,166 +209,60 @@ const patchMonacoCssNestingWarnings = async (dashboardDir) => { } }; -const LEGACY_DESKTOP_BRIDGE_PATTERNS = { - trayRestartGuard: - /if\s*\(\s*!desktopBridge\?\.isElectron\s*\|\|\s*!desktopBridge\.onTrayRestartBackend\s*\)\s*\{/, - trayRestartHandlerInvoke: /await\s+restartAstrBot\s*\(\s*globalWaitingRef\.value\s*\)/, - restartAstrBotImport: /^\s*import\s+\{\s*restartAstrBot\s*\}\s+from\s+['"]@\/utils\/restartAstrBot['"]\s*;?\r?\n/m, - typeDesktopFlag: /^(\s+)isElectron:\s*boolean;(\r?\n)/m, - typeDesktopRuntimeFlag: /^(\s+)isElectronRuntime:\s*\(\)\s*=>\s*Promise;(\r?\n)/m, - desktopReleaseFlagToken: /\bisElectronApp\b/, - desktopReleaseFlagReplace: /\bisElectronApp\b/g, - desktopBridgeLegacyFlagToken: /window\.astrbotDesktop\?\.isElectron\b/, - desktopBridgeLegacyFlagReplace: /window\.astrbotDesktop\?\.isElectron\b/g, - desktopBridgeLegacyRuntimeToken: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/, - desktopBridgeLegacyRuntimeReplace: /window\.astrbotDesktop\?\.isElectronRuntime\?\.\(\)/g, - restartGuard: /if\s*\(\s*desktopBridge\?\.isElectron\s*\)\s*\{/, -}; - -const MODERN_DESKTOP_BRIDGE_PATTERNS = { - trayRestartGuard: /if\s*\(\s*!desktopBridge\?\.onTrayRestartBackend\s*\)\s*\{/, - trayRestartPromptInvoke: /await\s+globalWaitingRef\.value\?\.check\?\.\(\s*\)/, - desktopBridgeTypeIsDesktop: /^\s+isDesktop:\s*boolean;\r?\n/m, - desktopBridgeTypeRuntime: /^\s+isDesktopRuntime:\s*\(\)\s*=>\s*Promise;\r?\n/m, - restartCapabilityGuard: /const hasDesktopRestartCapability\s*=/, -}; - -const patchRequiredLegacyFile = async ({ filePath, transform, patchLabel, isAlreadyModern }) => { - if (!existsSync(filePath)) { - throw new Error( - `[prepare-resources] Missing required file for ${patchLabel}: ${path.relative(projectRoot, filePath)}`, - ); - } - - const source = await readFile(filePath, 'utf8'); - const patched = transform(source); - - // Invariant: transformed content must be modern/compatible. - if (!isAlreadyModern(patched)) { - throw new Error( - `[prepare-resources] ${patchLabel} failed invariant check in ${path.relative(projectRoot, filePath)}`, - ); - } - - if (patched !== source) { - await writeFile(filePath, patched, 'utf8'); - console.log( - `[prepare-resources] Patched ${patchLabel} in ${path.relative(projectRoot, filePath)}`, - ); - return; - } - - if (!isAlreadyModern(source)) { - throw new Error( - `[prepare-resources] ${patchLabel} did not match expected legacy pattern in ${path.relative(projectRoot, filePath)}`, - ); - } - - console.warn( - `[prepare-resources] WARN: No changes applied for ${patchLabel} in ${path.relative(projectRoot, filePath)} (already compatible)`, - ); -}; - -const patchLegacyDesktopBridgeArtifacts = async (dashboardDir) => { - const hasModernTrayRestartHook = (source) => - MODERN_DESKTOP_BRIDGE_PATTERNS.trayRestartGuard.test(source) && - MODERN_DESKTOP_BRIDGE_PATTERNS.trayRestartPromptInvoke.test(source) && - !LEGACY_DESKTOP_BRIDGE_PATTERNS.restartAstrBotImport.test(source); - const hasModernDesktopBridgeTypes = (source) => - MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeIsDesktop.test(source) && - MODERN_DESKTOP_BRIDGE_PATTERNS.desktopBridgeTypeRuntime.test(source); - const hasLegacyDesktopReleaseGuards = (source) => - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseFlagToken.test(source) || - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyFlagToken.test(source) || - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyRuntimeToken.test(source); - const hasModernRestartCapabilityGuard = (source) => - MODERN_DESKTOP_BRIDGE_PATTERNS.restartCapabilityGuard.test(source); - - await patchRequiredLegacyFile({ - filePath: path.join(dashboardDir, 'src', 'App.vue'), - transform: (source) => { - let patched = source.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.trayRestartGuard, - 'if (!desktopBridge?.onTrayRestartBackend) {', - ); - patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.trayRestartHandlerInvoke, - 'await globalWaitingRef.value?.check?.()', - ); - patched = patched.replace(LEGACY_DESKTOP_BRIDGE_PATTERNS.restartAstrBotImport, ''); - return patched; - }, - patchLabel: 'tray restart desktop guard', - isAlreadyModern: hasModernTrayRestartHook, - }); - - await patchRequiredLegacyFile({ - filePath: path.join(dashboardDir, 'src', 'types', 'electron-bridge.d.ts'), - transform: (source) => { - let patched = source; - patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.typeDesktopFlag, - '$1isDesktop: boolean;$2', - ); - patched = patched.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.typeDesktopRuntimeFlag, - '$1isDesktopRuntime: () => Promise;$2', +const DESKTOP_BRIDGE_EXPECTATIONS = [ + { + filePath: ['src', 'App.vue'], + pattern: /if\s*\(\s*!desktopBridge\?\.onTrayRestartBackend\s*\)\s*\{/, + label: 'tray restart desktop guard', + }, + { + filePath: ['src', 'App.vue'], + pattern: /await\s+globalWaitingRef\.value\?\.check\?\.\(\s*\)/, + label: 'tray restart waiting prompt', + }, + { + filePath: ['src', 'utils', 'restartAstrBot.ts'], + pattern: /import\s+\{\s*getDesktopRuntimeInfo\s*\}\s+from\s+['"]@\/utils\/desktopRuntime['"]/, + label: 'desktop runtime helper import', + }, + { + filePath: ['src', 'utils', 'restartAstrBot.ts'], + pattern: /await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + label: 'desktop runtime helper usage in restart flow', + }, + { + filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], + pattern: /\bisDesktopReleaseMode\b/, + label: 'desktop release mode flag', + }, + { + filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], + pattern: /await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + label: 'desktop runtime helper usage in header', + }, + { + filePath: ['src', 'utils', 'desktopRuntime.ts'], + pattern: /Failed to detect desktop runtime/, + label: 'desktop runtime probe warning', + }, +]; + +const verifyDesktopBridgeArtifacts = async (dashboardDir) => { + for (const expectation of DESKTOP_BRIDGE_EXPECTATIONS) { + const file = path.join(dashboardDir, ...expectation.filePath); + if (!existsSync(file)) { + throw new Error( + `[prepare-resources] Missing required file for ${expectation.label}: ${path.relative(projectRoot, file)}`, ); - return patched; - }, - patchLabel: 'desktop bridge type definitions', - isAlreadyModern: hasModernDesktopBridgeTypes, - }); + } - await patchRequiredLegacyFile({ - filePath: path.join(dashboardDir, 'src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'), - transform: (source) => { - let patched = source.replaceAll( - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopReleaseFlagReplace, - 'isDesktopReleaseMode', - ); - patched = patched.replaceAll( - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyFlagReplace, - 'window.astrbotDesktop?.isDesktop', - ); - patched = patched.replaceAll( - LEGACY_DESKTOP_BRIDGE_PATTERNS.desktopBridgeLegacyRuntimeReplace, - 'window.astrbotDesktop?.isDesktopRuntime?.()', + const source = await readFile(file, 'utf8'); + if (!expectation.pattern.test(source)) { + throw new Error( + `[prepare-resources] Expected ${expectation.label} in ${path.relative(projectRoot, file)}. Please sync AstrBot dashboard sources.`, ); - return patched; - }, - patchLabel: 'desktop update mode guards', - isAlreadyModern: (source) => !hasLegacyDesktopReleaseGuards(source), - }); - - await patchRequiredLegacyFile({ - filePath: path.join(dashboardDir, 'src', 'utils', 'restartAstrBot.ts'), - transform: (source) => { - if (MODERN_DESKTOP_BRIDGE_PATTERNS.restartCapabilityGuard.test(source)) { - return source; - } - return source.replace( - LEGACY_DESKTOP_BRIDGE_PATTERNS.restartGuard, - `const hasDesktopRestartCapability = - !!desktopBridge && - typeof desktopBridge.restartBackend === 'function' && - typeof desktopBridge.isDesktopRuntime === 'function' - - let isDesktopRuntime = false - if (hasDesktopRestartCapability) { - try { - isDesktopRuntime = !!(await desktopBridge.isDesktopRuntime()) - } catch (_error) { - isDesktopRuntime = false } } - - if (hasDesktopRestartCapability && isDesktopRuntime) {`, - ); - }, - patchLabel: 'desktop restart capability guard', - isAlreadyModern: hasModernRestartCapabilityGuard, - }); }; const readAstrbotVersionFromPyproject = async (sourceDir) => { @@ -548,7 +442,7 @@ const prepareWebui = async (sourceDir) => { const dashboardDir = path.join(sourceDir, 'dashboard'); ensurePackageInstall(dashboardDir, 'AstrBot dashboard'); await patchMonacoCssNestingWarnings(dashboardDir); - await patchLegacyDesktopBridgeArtifacts(dashboardDir); + await verifyDesktopBridgeArtifacts(dashboardDir); runPnpmChecked(['--dir', dashboardDir, 'build'], sourceDir); const sourceWebuiDir = path.join(sourceDir, 'dashboard', 'dist'); From 3b93005440b6c1d6d56325141450f538a35facdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:45:17 +0900 Subject: [PATCH 08/25] fix build source env injection and document source precedence --- Makefile | 17 ++++++++++++----- README.md | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 08e72217..1d8b1ed0 100644 --- a/Makefile +++ b/Makefile @@ -91,11 +91,18 @@ build: echo "Using build source dir: $$build_source_dir"; \ fi; \ echo "Build resource source dir: $${build_source_dir:-}"; \ - ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ - ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)" \ - ASTRBOT_SOURCE_DIR="$$build_source_dir" \ - ASTRBOT_DESKTOP_VERSION="$$build_version" \ - pnpm run build + if [ -n "$$build_source_dir" ]; then \ + ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ + ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)" \ + ASTRBOT_SOURCE_DIR="$$build_source_dir" \ + ASTRBOT_DESKTOP_VERSION="$$build_version" \ + pnpm run build; \ + else \ + ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ + ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)" \ + ASTRBOT_DESKTOP_VERSION="$$build_version" \ + pnpm run build; \ + fi rebuild: clean build diff --git a/README.md b/README.md index 39f70e49..abb4381c 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ make prune 推荐日常使用 `make update`,避免本地切换分支导致版本漂移。 -补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`。若当前环境设置了 `ASTRBOT_SOURCE_DIR`,构建会直接使用该本地源码;也可通过 `ASTRBOT_BUILD_SOURCE_DIR` 显式覆盖。若需覆盖版本,可传入 `ASTRBOT_DESKTOP_VERSION=...`。 +补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`。若当前环境设置了 `ASTRBOT_SOURCE_DIR`,构建会直接使用该本地源码;也可通过 `ASTRBOT_BUILD_SOURCE_DIR` 显式覆盖,若同时设置则以 `ASTRBOT_BUILD_SOURCE_DIR` 为准。若需覆盖版本,可传入 `ASTRBOT_DESKTOP_VERSION=...`。 示例: From 6e7301b73ce2c533ddc8a55ba1313731588e2d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:50:25 +0900 Subject: [PATCH 09/25] refactor tray restart signaling and simplify build env setup --- Makefile | 17 ++++++----------- src-tauri/src/main.rs | 28 +++++++++++++++++++++------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 1d8b1ed0..50edcc79 100644 --- a/Makefile +++ b/Makefile @@ -91,18 +91,13 @@ build: echo "Using build source dir: $$build_source_dir"; \ fi; \ echo "Build resource source dir: $${build_source_dir:-}"; \ + export ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)"; \ + export ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)"; \ + export ASTRBOT_DESKTOP_VERSION="$$build_version"; \ if [ -n "$$build_source_dir" ]; then \ - ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ - ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)" \ - ASTRBOT_SOURCE_DIR="$$build_source_dir" \ - ASTRBOT_DESKTOP_VERSION="$$build_version" \ - pnpm run build; \ - else \ - ASTRBOT_SOURCE_GIT_URL="$(ASTRBOT_SOURCE_GIT_URL)" \ - ASTRBOT_SOURCE_GIT_REF="$(ASTRBOT_SOURCE_GIT_REF)" \ - ASTRBOT_DESKTOP_VERSION="$$build_version" \ - pnpm run build; \ - fi + export ASTRBOT_SOURCE_DIR="$$build_source_dir"; \ + fi; \ + pnpm run build rebuild: clean build diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 51beeb47..de6b279c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -24,7 +24,7 @@ use tauri::{ path::BaseDirectory, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, webview::PageLoadEvent, - AppHandle, Manager, RunEvent, WindowEvent, + AppHandle, Emitter, Manager, RunEvent, WindowEvent, }; use url::Url; @@ -52,6 +52,7 @@ const TRAY_MENU_TOGGLE_WINDOW: &str = "tray_toggle_window"; const TRAY_MENU_RELOAD_WINDOW: &str = "tray_reload_window"; const TRAY_MENU_RESTART_BACKEND: &str = "tray_restart_backend"; const TRAY_MENU_QUIT: &str = "tray_quit"; +const TRAY_RESTART_BACKEND_EVENT: &str = "astrbot://tray-restart-backend"; const DEFAULT_SHELL_LOCALE: &str = "zh-CN"; const STARTUP_MODE_ENV: &str = "ASTRBOT_DESKTOP_STARTUP_MODE"; // Keep in sync with STARTUP_MODES in ui/index.html. @@ -1421,10 +1422,10 @@ fn emit_tray_restart_backend_event(app_handle: &AppHandle) { return; }; - if let Err(error) = window.eval( - "if (typeof window !== 'undefined' && typeof window.__astrbotDesktopEmitTrayRestart === 'function') { window.__astrbotDesktopEmitTrayRestart(); }", - ) { - append_desktop_log(&format!("failed to emit tray restart event: {error}")); + if let Err(error) = window.emit(TRAY_RESTART_BACKEND_EVENT, ()) { + append_desktop_log(&format!( + "failed to emit tray restart backend event: {error}" + )); } } @@ -1634,6 +1635,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" } const invoke = window.__TAURI_INTERNALS__?.invoke; + const tauriEvent = window.__TAURI_INTERNALS__?.event; if (typeof invoke !== 'function') return; const BRIDGE_COMMANDS = Object.freeze({ @@ -1643,6 +1645,9 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" RESTART_BACKEND: 'desktop_bridge_restart_backend', STOP_BACKEND: 'desktop_bridge_stop_backend', }); + const BRIDGE_EVENTS = Object.freeze({ + TRAY_RESTART_BACKEND: 'astrbot://tray-restart-backend', + }); const invokeBridge = async (command, payload = {}) => { try { @@ -1668,8 +1673,6 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" } }; - window.__astrbotDesktopEmitTrayRestart = emitTrayRestart; - const onTrayRestartBackend = (callback) => { if (typeof callback !== 'function') return () => {}; const handler = () => callback(); @@ -1681,6 +1684,16 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" return () => trayRestartState.handlers.delete(handler); }; + const listenToTrayRestartBackendEvent = async () => { + const listen = tauriEvent?.listen; + if (typeof listen !== 'function') return; + try { + await listen(BRIDGE_EVENTS.TRAY_RESTART_BACKEND, () => { + emitTrayRestart(); + }); + } catch {} + }; + const getStoredAuthToken = () => { try { const token = window.localStorage?.getItem('token'); @@ -1948,6 +1961,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" onTrayRestartBackend, }; + void listenToTrayRestartBackendEvent(); patchLocalStorageTokenSync(); void syncAuthToken(); })(); From 9325f5bfb2dbccfd1bdbca1698ea4ba2b1ccf1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:52:32 +0900 Subject: [PATCH 10/25] relax bridge checks and harden clean-env safeguards --- Makefile | 13 +++++++ README.md | 13 +++++++ scripts/prepare-resources.mjs | 65 ++++++++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 50edcc79..a0830f9b 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,13 @@ ASTRBOT_SOURCE_GIT_URL ?= https://github.com/AstrBotDevs/AstrBot.git ASTRBOT_SOURCE_GIT_REF ?= master ASTRBOT_BUILD_SOURCE_DIR ?= ASTRBOT_RESET_ENV_SCRIPT ?= .astrbot-reset-env.sh +ASTRBOT_RESET_ENV_OVERWRITE ?= 0 RUST_MANIFEST ?= src-tauri/Cargo.toml NODE_MODULES_DIR ?= node_modules PNPM_STORE_DIR ?= .pnpm-store TAURI_TARGET_DIR ?= src-tauri/target +# Single source of env keys managed by `make clean-env`. +# If build/resource scripts start consuming a new persistent env var, add it here. ASTRBOT_ENV_KEYS := ASTRBOT_SOURCE_DIR ASTRBOT_SOURCE_GIT_URL ASTRBOT_SOURCE_GIT_REF ASTRBOT_DESKTOP_VERSION ASTRBOT_BUILD_SOURCE_DIR .PHONY: help deps sync-version update prepare-webui prepare-backend prepare-resources dev build \ @@ -47,6 +50,7 @@ help: @echo " make clean-vendor Remove vendor and runtime" @echo " make clean-node Remove node_modules and pnpm store" @echo " make clean-env Generate shell script to unset build env vars" + @echo " (set ASTRBOT_RESET_ENV_OVERWRITE=1 to overwrite existing script)" @echo " make clean Clean all build artifacts" @echo " make clean-all Alias of clean" @@ -145,8 +149,17 @@ clean-node: clean-env: @set -e; \ reset_script="$(ASTRBOT_RESET_ENV_SCRIPT)"; \ + if [ -f "$$reset_script" ] && [ "$(ASTRBOT_RESET_ENV_OVERWRITE)" != "1" ]; then \ + echo "Refusing to overwrite existing $$reset_script"; \ + echo "Run with ASTRBOT_RESET_ENV_OVERWRITE=1 to overwrite it."; \ + exit 1; \ + fi; \ + if [ -f "$$reset_script" ]; then \ + echo "Overwriting $$reset_script"; \ + fi; \ { \ echo "#!/usr/bin/env sh"; \ + echo "# Generated by make clean-env. Keys come from ASTRBOT_ENV_KEYS in Makefile."; \ echo "unset $(ASTRBOT_ENV_KEYS)"; \ } > "$$reset_script"; \ chmod +x "$$reset_script"; \ diff --git a/README.md b/README.md index abb4381c..2a8de20c 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,19 @@ export ASTRBOT_SOURCE_DIR=/path/to/AstrBot make build ASTRBOT_BUILD_SOURCE_DIR=/path/to/AstrBot ``` +如需快速清理这些构建相关环境变量,可运行: + +```bash +make clean-env +source .astrbot-reset-env.sh +``` + +如果重置脚本已存在,需要显式允许覆盖: + +```bash +make clean-env ASTRBOT_RESET_ENV_OVERWRITE=1 +``` + 临时测试仓库示例: ```bash diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index 231fb1cc..44beecff 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -209,60 +209,99 @@ const patchMonacoCssNestingWarnings = async (dashboardDir) => { } }; +const TRUTHY_ENV_VALUES = new Set(['1', 'true', 'yes', 'on']); +const isDesktopBridgeExpectationStrict = TRUTHY_ENV_VALUES.has( + String(process.env.ASTRBOT_DESKTOP_STRICT_BRIDGE_EXPECTATIONS || '') + .trim() + .toLowerCase(), +); + +const DESKTOP_BRIDGE_PATTERNS = { + trayRestartGuard: /if\s*\(\s*!desktopBridge\s*\?\.\s*onTrayRestartBackend\s*\)\s*\{/, + trayRestartPromptInvoke: + /await\s+globalWaitingRef\s*\.\s*value\s*\?\.\s*check\s*\?\.\s*\(\s*[^)]*\s*\)\s*;?/, + desktopRuntimeImport: + /import\s+\{\s*getDesktopRuntimeInfo\s*\}\s+from\s+['"]@\/utils\/desktopRuntime['"]\s*;?/, + desktopRuntimeUsage: /await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + desktopReleaseModeFlag: /\bisDesktopReleaseMode\b/, + desktopRuntimeProbeWarn: /console\.warn\([\s\S]*desktop runtime/i, +}; + const DESKTOP_BRIDGE_EXPECTATIONS = [ { filePath: ['src', 'App.vue'], - pattern: /if\s*\(\s*!desktopBridge\?\.onTrayRestartBackend\s*\)\s*\{/, + pattern: DESKTOP_BRIDGE_PATTERNS.trayRestartGuard, label: 'tray restart desktop guard', + required: false, }, { filePath: ['src', 'App.vue'], - pattern: /await\s+globalWaitingRef\.value\?\.check\?\.\(\s*\)/, + pattern: DESKTOP_BRIDGE_PATTERNS.trayRestartPromptInvoke, label: 'tray restart waiting prompt', + required: false, }, { filePath: ['src', 'utils', 'restartAstrBot.ts'], - pattern: /import\s+\{\s*getDesktopRuntimeInfo\s*\}\s+from\s+['"]@\/utils\/desktopRuntime['"]/, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeImport, label: 'desktop runtime helper import', + required: true, }, { filePath: ['src', 'utils', 'restartAstrBot.ts'], - pattern: /await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsage, label: 'desktop runtime helper usage in restart flow', + required: true, }, { filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], - pattern: /\bisDesktopReleaseMode\b/, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopReleaseModeFlag, label: 'desktop release mode flag', + required: false, }, { filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], - pattern: /await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsage, label: 'desktop runtime helper usage in header', + required: true, }, { filePath: ['src', 'utils', 'desktopRuntime.ts'], - pattern: /Failed to detect desktop runtime/, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeProbeWarn, label: 'desktop runtime probe warning', + required: false, }, ]; const verifyDesktopBridgeArtifacts = async (dashboardDir) => { + const issues = []; + for (const expectation of DESKTOP_BRIDGE_EXPECTATIONS) { + const mustPass = expectation.required || isDesktopBridgeExpectationStrict; const file = path.join(dashboardDir, ...expectation.filePath); if (!existsSync(file)) { - throw new Error( - `[prepare-resources] Missing required file for ${expectation.label}: ${path.relative(projectRoot, file)}`, - ); + const message = `[prepare-resources] Missing required file for ${expectation.label}: ${path.relative(projectRoot, file)}`; + if (mustPass) { + issues.push(message); + } else { + console.warn(`${message} (compatibility check skipped)`); + } + continue; } const source = await readFile(file, 'utf8'); if (!expectation.pattern.test(source)) { - throw new Error( - `[prepare-resources] Expected ${expectation.label} in ${path.relative(projectRoot, file)}. Please sync AstrBot dashboard sources.`, - ); + const message = `[prepare-resources] Expected ${expectation.label} in ${path.relative(projectRoot, file)}. Please sync AstrBot dashboard sources.`; + if (mustPass) { + issues.push(message); + } else { + console.warn(`${message} (compatibility check skipped)`); + } } } + + if (issues.length > 0) { + throw new Error(issues.join('\n')); + } }; const readAstrbotVersionFromPyproject = async (sourceDir) => { From 4f67a6e8c3f52e9c6740d618b5249df4e8e137df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:54:33 +0900 Subject: [PATCH 11/25] ignore reset script and clarify clean-env source behavior --- .gitignore | 3 +++ Makefile | 4 +++- README.md | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d806ccae..790eede2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ yarn-error.log* # python cache __pycache__/ *.py[cod] + +# local helper scripts +.astrbot-reset-env.sh diff --git a/Makefile b/Makefile index a0830f9b..37402899 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ help: @echo " make clean-node Remove node_modules and pnpm store" @echo " make clean-env Generate shell script to unset build env vars" @echo " (set ASTRBOT_RESET_ENV_OVERWRITE=1 to overwrite existing script)" + @echo " (then source the script in current shell)" @echo " make clean Clean all build artifacts" @echo " make clean-all Alias of clean" @@ -164,7 +165,8 @@ clean-env: } > "$$reset_script"; \ chmod +x "$$reset_script"; \ echo "Generated $$reset_script"; \ - echo "Run: source $$reset_script" + echo "Run: source $$reset_script"; \ + echo "Note: executing $$reset_script directly runs in a child shell and cannot clear parent-shell env." clean: clean-rust clean-resources clean-vendor clean-node diff --git a/README.md b/README.md index 2a8de20c..45c2768a 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,8 @@ make clean-env source .astrbot-reset-env.sh ``` +说明:`./.astrbot-reset-env.sh` 直接执行只会影响子 shell,不能修改你当前终端会话里的环境变量,所以需要 `source`。 + 如果重置脚本已存在,需要显式允许覆盖: ```bash From 20b4236587e7dadfff954bccbebbb07bad966c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:56:01 +0900 Subject: [PATCH 12/25] docs: simplify README build and version sections --- README.md | 160 ++++++++---------------------------------------------- 1 file changed, 22 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 45c2768a..d12c593f 100644 --- a/README.md +++ b/README.md @@ -28,197 +28,81 @@ AstrBot 桌面应用(Tauri)。 ## 手动构建 -适用于需要调试桌面应用、切换上游分支或验证本地改动的场景。 -推荐优先使用 `make` 命令,仓库已封装常用流程。 - -### 1. 查看可用命令(推荐) - -仓库内置了 `Makefile`,可直接查看常用命令: - -```bash -make help -``` - -### 2. 安装依赖 +推荐直接使用 Makefile: ```bash make deps -``` - -也可以使用: - -```bash -pnpm install -``` - -### 3. 准备资源 - -```bash make prepare -``` - -也可以使用: - -```bash -pnpm run prepare:resources -``` - -### 4. 本地开发运行 - -```bash make dev -``` - -也可以使用: - -```bash -pnpm run dev -``` - -### 5. 构建安装包 - -```bash make build ``` -也可以使用: - -```bash -pnpm run build -``` - -等价命令(直接使用 Tauri CLI): +可用命令总览: ```bash -cargo tauri build +make help ``` -构建产物目录: - -- `src-tauri/target/release/bundle/` -- 若使用 `--target` 显式指定目标(例如 CI 的 macOS 构建),产物目录为 `src-tauri/target//release/bundle/` +构建产物默认在 `src-tauri/target/release/bundle/`。 ## 常用维护命令 -代码检查与测试: - ```bash make lint make test -``` - -环境排查: - -```bash make doctor -``` - -清理构建产物: - -```bash make clean -``` - -仅清理占用空间较大的本地缓存: - -```bash make prune ``` ## 版本维护(重要) -桌面端版本会同步到以下三个文件: +- `make update`:从上游同步版本(推荐日常使用)。 +- `make sync-version`:从当前解析到的 AstrBot 源同步版本(会受本地环境变量影响)。 +- `make build`:默认使用当前 `package.json` 的版本,可用 `ASTRBOT_DESKTOP_VERSION=...` 覆盖。 +桌面端版本会同步到: - `package.json` - `src-tauri/Cargo.toml` - `src-tauri/tauri.conf.json` -### `make sync-version` 与 `make update` 的区别 - -- `make sync-version`:从当前解析到的 AstrBot 源同步版本,受本地环境变量影响(例如 `ASTRBOT_SOURCE_DIR`)。 -- `make update`:用于“对齐上游”,会忽略 `ASTRBOT_SOURCE_DIR`,并使用 `ASTRBOT_SOURCE_GIT_URL` + `ASTRBOT_SOURCE_GIT_REF` 同步版本。 - -推荐日常使用 `make update`,避免本地切换分支导致版本漂移。 +### 常用环境变量 -补充:`make build` 会默认使用当前 `package.json` 中的版本作为 `ASTRBOT_DESKTOP_VERSION`。若当前环境设置了 `ASTRBOT_SOURCE_DIR`,构建会直接使用该本地源码;也可通过 `ASTRBOT_BUILD_SOURCE_DIR` 显式覆盖,若同时设置则以 `ASTRBOT_BUILD_SOURCE_DIR` 为准。若需覆盖版本,可传入 `ASTRBOT_DESKTOP_VERSION=...`。 +- `ASTRBOT_SOURCE_GIT_URL` / `ASTRBOT_SOURCE_GIT_REF`:指定上游仓库与分支/标签(默认 `https://github.com/AstrBotDevs/AstrBot.git` + `master`)。 +- `ASTRBOT_SOURCE_DIR`:指定本地 AstrBot 源码目录(用于 `sync-version`/资源准备,`build` 也会读取)。 +- `ASTRBOT_BUILD_SOURCE_DIR`:仅用于本次 `make build` 的源码目录,优先级高于 `ASTRBOT_SOURCE_DIR`。 +- `ASTRBOT_DESKTOP_VERSION`:覆盖写入桌面版本号。 示例: ```bash -# 同步到上游 master make update - -# 同步到指定上游 tag make update ASTRBOT_SOURCE_GIT_REF=v4.17.5 - -# 强制写入指定版本(通常用于 CI) -make update ASTRBOT_DESKTOP_VERSION=4.17.5 -``` - -## 上游仓库策略 - -默认上游仓库: - -- `https://github.com/AstrBotDevs/AstrBot.git` - -如需覆盖默认值: - -```bash -export ASTRBOT_SOURCE_GIT_URL=https://github.com/AstrBotDevs/AstrBot.git -export ASTRBOT_SOURCE_GIT_REF=master -``` - -使用本地 AstrBot 源码(`make sync-version`/`pnpm run prepare:*` 会优先使用): - -```bash -export ASTRBOT_SOURCE_DIR=/path/to/AstrBot -``` - -`make build` 使用本地源码有两种方式: - -```bash -# 方式 1:使用环境变量(make build 默认会读取) -export ASTRBOT_SOURCE_DIR=/path/to/AstrBot - -# 方式 2:单次构建显式覆盖 make build ASTRBOT_BUILD_SOURCE_DIR=/path/to/AstrBot ``` -如需快速清理这些构建相关环境变量,可运行: +清理构建相关环境变量: ```bash make clean-env source .astrbot-reset-env.sh ``` -说明:`./.astrbot-reset-env.sh` 直接执行只会影响子 shell,不能修改你当前终端会话里的环境变量,所以需要 `source`。 - -如果重置脚本已存在,需要显式允许覆盖: +如果重置脚本已存在: ```bash make clean-env ASTRBOT_RESET_ENV_OVERWRITE=1 ``` -临时测试仓库示例: - -```bash -export ASTRBOT_SOURCE_GIT_URL=https://github.com/zouyonghe/AstrBot.git -export ASTRBOT_SOURCE_GIT_REF=cpython-runtime-refactor -``` - ## CI 版本同步策略 -`build-desktop-tauri` 工作流在定时任务(`schedule`)检测到上游新 tag 且需要构建时,会先自动同步并提交上述三个版本文件,然后继续构建产物。 - -- 定时构建:会自动回写版本到仓库(commit + push)。 -- 手动触发(`workflow_dispatch`):默认只构建,不自动回写版本文件。 +- 定时构建(`schedule`)检测到上游新 tag 时,会先自动同步版本文件并提交,再继续构建。 +- 手动触发(`workflow_dispatch`)默认只构建,不自动回写版本文件。 ## 构建流程说明 -`src-tauri/tauri.conf.json` 已配置 `beforeBuildCommand=pnpm run prepare:resources`,构建时会自动执行以下流程: - -1. 拉取或更新 AstrBot 上游源码 -2. 构建 Dashboard 并同步 `resources/webui` -3. 下载或复用 CPython 运行时(缓存到 `runtime/`) -4. 生成 `resources/backend`(含 Python 运行时、依赖、启动脚本) -5. 调用 `cargo tauri build` 输出安装包 +`src-tauri/tauri.conf.json` 配置了 `beforeBuildCommand=pnpm run prepare:resources`。构建时会自动完成: +1. 拉取/更新 AstrBot 源码 +2. 构建并同步 `resources/webui` +3. 准备 `resources/backend`(含运行时与启动脚本) +4. 执行 Tauri 打包 From 9f3418966ae7cc989337e6b60a3bd285ecd8a6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=82=AB=E3=82=AF?= <62183434+zouyonghe@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:57:26 +0900 Subject: [PATCH 13/25] Update README to remove reset script instructions Remove instructions for existing reset script. --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index d12c593f..c51f37e6 100644 --- a/README.md +++ b/README.md @@ -88,11 +88,6 @@ make clean-env source .astrbot-reset-env.sh ``` -如果重置脚本已存在: - -```bash -make clean-env ASTRBOT_RESET_ENV_OVERWRITE=1 -``` ## CI 版本同步策略 From e56a99dae4a15f9aa3e9872f277b5abe0b943792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 01:59:47 +0900 Subject: [PATCH 14/25] harden tray restart bridge event wiring --- src-tauri/src/main.rs | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index de6b279c..9bb54be8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -53,6 +53,7 @@ const TRAY_MENU_RELOAD_WINDOW: &str = "tray_reload_window"; const TRAY_MENU_RESTART_BACKEND: &str = "tray_restart_backend"; const TRAY_MENU_QUIT: &str = "tray_quit"; const TRAY_RESTART_BACKEND_EVENT: &str = "astrbot://tray-restart-backend"; +const DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER: &str = "__ASTRBOT_TRAY_RESTART_BACKEND_EVENT__"; const DEFAULT_SHELL_LOCALE: &str = "zh-CN"; const STARTUP_MODE_ENV: &str = "ASTRBOT_DESKTOP_STARTUP_MODE"; // Keep in sync with STARTUP_MODES in ui/index.html. @@ -1624,7 +1625,7 @@ fn update_tray_menu_labels(app_handle: &AppHandle) { set_menu_text_safe(&tray_state.quit_item, shell_texts.tray_quit, TRAY_MENU_QUIT); } -const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" +const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" (() => { if ( window.astrbotDesktop && @@ -1645,9 +1646,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" RESTART_BACKEND: 'desktop_bridge_restart_backend', STOP_BACKEND: 'desktop_bridge_stop_backend', }); - const BRIDGE_EVENTS = Object.freeze({ - TRAY_RESTART_BACKEND: 'astrbot://tray-restart-backend', - }); + const TRAY_RESTART_BACKEND_EVENT = '__ASTRBOT_TRAY_RESTART_BACKEND_EVENT__'; const invokeBridge = async (command, payload = {}) => { try { @@ -1659,7 +1658,14 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" const trayRestartState = window.__astrbotDesktopTrayRestartState || - (window.__astrbotDesktopTrayRestartState = { handlers: new Set(), pending: 0 }); + (window.__astrbotDesktopTrayRestartState = { + handlers: new Set(), + pending: 0, + unlistenTrayRestartBackendEvent: null + }); + if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'undefined') { + trayRestartState.unlistenTrayRestartBackendEvent = null; + } const emitTrayRestart = () => { if (trayRestartState.handlers.size === 0) { @@ -1687,11 +1693,17 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" const listenToTrayRestartBackendEvent = async () => { const listen = tauriEvent?.listen; if (typeof listen !== 'function') return; + if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'function') return; try { - await listen(BRIDGE_EVENTS.TRAY_RESTART_BACKEND, () => { + const unlisten = await listen(TRAY_RESTART_BACKEND_EVENT, () => { emitTrayRestart(); }); - } catch {} + if (typeof unlisten === 'function') { + trayRestartState.unlistenTrayRestartBackendEvent = unlisten; + } + } catch (error) { + console.warn('Failed to listen for tray restart backend event', error); + } }; const getStoredAuthToken = () => { @@ -1967,6 +1979,19 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: &str = r#" })(); "#; +static DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: OnceLock = OnceLock::new(); + +fn desktop_bridge_bootstrap_script() -> &'static str { + DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT + .get_or_init(|| { + DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE.replace( + DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER, + TRAY_RESTART_BACKEND_EVENT, + ) + }) + .as_str() +} + fn same_origin(left: &Url, right: &Url) -> bool { left.scheme() == right.scheme() && left.host_str() == right.host_str() @@ -2018,7 +2043,7 @@ fn should_inject_desktop_bridge(app_handle: &AppHandle, page_url: &Url) -> bool } fn inject_desktop_bridge(webview: &tauri::Webview) { - if let Err(error) = webview.eval(DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT) { + if let Err(error) = webview.eval(desktop_bridge_bootstrap_script()) { append_desktop_log(&format!("failed to inject desktop bridge script: {error}")); } } From 98223b6e7abe62a0a338b32abd0e8a245e5326cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:03:10 +0900 Subject: [PATCH 15/25] make clean-env no-op when reset script already exists --- Makefile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 37402899..24e068e2 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,6 @@ ASTRBOT_SOURCE_GIT_URL ?= https://github.com/AstrBotDevs/AstrBot.git ASTRBOT_SOURCE_GIT_REF ?= master ASTRBOT_BUILD_SOURCE_DIR ?= ASTRBOT_RESET_ENV_SCRIPT ?= .astrbot-reset-env.sh -ASTRBOT_RESET_ENV_OVERWRITE ?= 0 RUST_MANIFEST ?= src-tauri/Cargo.toml NODE_MODULES_DIR ?= node_modules PNPM_STORE_DIR ?= .pnpm-store @@ -50,7 +49,6 @@ help: @echo " make clean-vendor Remove vendor and runtime" @echo " make clean-node Remove node_modules and pnpm store" @echo " make clean-env Generate shell script to unset build env vars" - @echo " (set ASTRBOT_RESET_ENV_OVERWRITE=1 to overwrite existing script)" @echo " (then source the script in current shell)" @echo " make clean Clean all build artifacts" @echo " make clean-all Alias of clean" @@ -150,13 +148,10 @@ clean-node: clean-env: @set -e; \ reset_script="$(ASTRBOT_RESET_ENV_SCRIPT)"; \ - if [ -f "$$reset_script" ] && [ "$(ASTRBOT_RESET_ENV_OVERWRITE)" != "1" ]; then \ - echo "Refusing to overwrite existing $$reset_script"; \ - echo "Run with ASTRBOT_RESET_ENV_OVERWRITE=1 to overwrite it."; \ - exit 1; \ - fi; \ if [ -f "$$reset_script" ]; then \ - echo "Overwriting $$reset_script"; \ + echo "$$reset_script already exists"; \ + echo "Run: source $$reset_script"; \ + exit 0; \ fi; \ { \ echo "#!/usr/bin/env sh"; \ From 7c86d926ef4e24fabacaa34ccdbc1a0975e436ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:08:50 +0900 Subject: [PATCH 16/25] harden bridge template checks and refresh clean-env script --- Makefile | 29 +++++++++++++++++++---------- scripts/prepare-resources.mjs | 9 ++++++--- src-tauri/src/main.rs | 5 +++++ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 24e068e2..46d4d95a 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,8 @@ TAURI_TARGET_DIR ?= src-tauri/target # Single source of env keys managed by `make clean-env`. # If build/resource scripts start consuming a new persistent env var, add it here. ASTRBOT_ENV_KEYS := ASTRBOT_SOURCE_DIR ASTRBOT_SOURCE_GIT_URL ASTRBOT_SOURCE_GIT_REF ASTRBOT_DESKTOP_VERSION ASTRBOT_BUILD_SOURCE_DIR +# Hash of ASTRBOT_ENV_KEYS for stale reset-script detection in `make clean-env`. +ASTRBOT_ENV_KEYS_HASH := $(shell (printf '%s\n' "$(ASTRBOT_ENV_KEYS)" | shasum -a 256 2>/dev/null || printf '%s\n' "$(ASTRBOT_ENV_KEYS)" | sha256sum 2>/dev/null || printf '%s\n' "$(ASTRBOT_ENV_KEYS)" | cksum 2>/dev/null) | awk '{print $$1}' | head -n 1) .PHONY: help deps sync-version update prepare-webui prepare-backend prepare-resources dev build \ prepare rebuild lint test doctor prune size clean clean-rust clean-resources \ @@ -148,18 +150,25 @@ clean-node: clean-env: @set -e; \ reset_script="$(ASTRBOT_RESET_ENV_SCRIPT)"; \ + current_hash="$(ASTRBOT_ENV_KEYS_HASH)"; \ + existing_hash=""; \ if [ -f "$$reset_script" ]; then \ - echo "$$reset_script already exists"; \ - echo "Run: source $$reset_script"; \ - exit 0; \ + existing_hash="$$(sed -n 's/^# ASTRBOT_ENV_KEYS_HASH=//p' "$$reset_script" | head -n 1)"; \ + fi; \ + if [ "$$existing_hash" != "$$current_hash" ]; then \ + { \ + echo "#!/usr/bin/env sh"; \ + echo "# Generated by make clean-env. Keys come from ASTRBOT_ENV_KEYS in Makefile."; \ + echo "# ASTRBOT_ENV_KEYS_HASH=$$current_hash"; \ + for key in $(ASTRBOT_ENV_KEYS); do \ + printf 'unset %s\n' "$$key"; \ + done; \ + } > "$$reset_script"; \ + chmod +x "$$reset_script"; \ + echo "Generated $$reset_script"; \ + else \ + echo "$$reset_script is up to date"; \ fi; \ - { \ - echo "#!/usr/bin/env sh"; \ - echo "# Generated by make clean-env. Keys come from ASTRBOT_ENV_KEYS in Makefile."; \ - echo "unset $(ASTRBOT_ENV_KEYS)"; \ - } > "$$reset_script"; \ - chmod +x "$$reset_script"; \ - echo "Generated $$reset_script"; \ echo "Run: source $$reset_script"; \ echo "Note: executing $$reset_script directly runs in a child shell and cannot clear parent-shell env." diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index 44beecff..e666e63c 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -222,7 +222,10 @@ const DESKTOP_BRIDGE_PATTERNS = { /await\s+globalWaitingRef\s*\.\s*value\s*\?\.\s*check\s*\?\.\s*\(\s*[^)]*\s*\)\s*;?/, desktopRuntimeImport: /import\s+\{\s*getDesktopRuntimeInfo\s*\}\s+from\s+['"]@\/utils\/desktopRuntime['"]\s*;?/, - desktopRuntimeUsage: /await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + desktopRuntimeUsageInRestart: + /hasDesktopRestartCapability[\s\S]{0,220}await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + desktopRuntimeUsageInHeader: + /const\s+runtimeInfo\s*=\s*await\s+getDesktopRuntimeInfo\s*\(\s*\)\s*;?[\s\S]{0,220}isDesktopReleaseMode\.value\s*=\s*runtimeInfo\.isDesktopRuntime/, desktopReleaseModeFlag: /\bisDesktopReleaseMode\b/, desktopRuntimeProbeWarn: /console\.warn\([\s\S]*desktop runtime/i, }; @@ -248,7 +251,7 @@ const DESKTOP_BRIDGE_EXPECTATIONS = [ }, { filePath: ['src', 'utils', 'restartAstrBot.ts'], - pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsage, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsageInRestart, label: 'desktop runtime helper usage in restart flow', required: true, }, @@ -260,7 +263,7 @@ const DESKTOP_BRIDGE_EXPECTATIONS = [ }, { filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], - pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsage, + pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsageInHeader, label: 'desktop runtime helper usage in header', required: true, }, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9bb54be8..dddfffef 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1984,6 +1984,11 @@ static DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: OnceLock = OnceLock::new(); fn desktop_bridge_bootstrap_script() -> &'static str { DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT .get_or_init(|| { + assert!( + DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE + .contains(DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER), + "desktop bridge template is missing tray restart event placeholder" + ); DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE.replace( DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER, TRAY_RESTART_BACKEND_EVENT, From 63ca8c23ff6d8721a0abbf851cc129db702a1701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:12:06 +0900 Subject: [PATCH 17/25] retry tray restart listener init when bridge exists but unbound --- src-tauri/src/main.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index dddfffef..b593efbe 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1627,16 +1627,18 @@ fn update_tray_menu_labels(app_handle: &AppHandle) { const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" (() => { + const existingTrayRestartState = window.__astrbotDesktopTrayRestartState; if ( window.astrbotDesktop && window.astrbotDesktop.__tauriBridge === true && - typeof window.astrbotDesktop.onTrayRestartBackend === 'function' + typeof window.astrbotDesktop.onTrayRestartBackend === 'function' && + typeof existingTrayRestartState?.unlistenTrayRestartBackendEvent === 'function' ) { return; } const invoke = window.__TAURI_INTERNALS__?.invoke; - const tauriEvent = window.__TAURI_INTERNALS__?.event; + const tauriEvent = window.__TAURI_INTERNALS__?.event ?? window.__TAURI__?.event; if (typeof invoke !== 'function') return; const BRIDGE_COMMANDS = Object.freeze({ @@ -1692,7 +1694,10 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" const listenToTrayRestartBackendEvent = async () => { const listen = tauriEvent?.listen; - if (typeof listen !== 'function') return; + if (typeof listen !== 'function') { + console.warn('Tray restart backend event listen API is unavailable'); + return; + } if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'function') return; try { const unlisten = await listen(TRAY_RESTART_BACKEND_EVENT, () => { From 0e6996ba55d9b5e46de61d447b108bba6e0b62a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:21:19 +0900 Subject: [PATCH 18/25] add tokenized fallback tray restart signal delivery --- src-tauri/src/main.rs | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b593efbe..97b06a9d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -13,7 +13,7 @@ use std::{ path::{Path, PathBuf}, process::{Child, Command, ExitStatus, Stdio}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicU64, Ordering}, Arc, Mutex, OnceLock, }, thread, @@ -66,6 +66,7 @@ const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200; static BACKEND_PING_TIMEOUT_MS: OnceLock = OnceLock::new(); static BRIDGE_BACKEND_PING_TIMEOUT_MS: OnceLock = OnceLock::new(); static DESKTOP_LOG_WRITE_LOCK: OnceLock> = OnceLock::new(); +static TRAY_RESTART_SIGNAL_TOKEN: AtomicU64 = AtomicU64::new(0); #[derive(Debug, Clone, Copy)] struct ShellTexts { @@ -1422,12 +1423,24 @@ fn emit_tray_restart_backend_event(app_handle: &AppHandle) { append_desktop_log("tray restart event skipped: main window not found"); return; }; + let token = TRAY_RESTART_SIGNAL_TOKEN.fetch_add(1, Ordering::Relaxed) + 1; - if let Err(error) = window.emit(TRAY_RESTART_BACKEND_EVENT, ()) { + if let Err(error) = window.emit(TRAY_RESTART_BACKEND_EVENT, token) { append_desktop_log(&format!( "failed to emit tray restart backend event: {error}" )); } + + // Compatibility fallback when the JS Tauri event listener API is unavailable. + // The same token is used so JS can deduplicate if both paths are delivered. + let fallback_script = format!( + "if (typeof window !== 'undefined' && typeof window.__astrbotDesktopEmitTrayRestart === 'function') {{ window.__astrbotDesktopEmitTrayRestart({token}); }}" + ); + if let Err(error) = window.eval(&fallback_script) { + append_desktop_log(&format!( + "failed to eval tray restart backend fallback emit: {error}" + )); + } } fn do_restart_backend(app_handle: &AppHandle, auth_token: Option<&str>) -> Result<(), String> { @@ -1663,13 +1676,28 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" (window.__astrbotDesktopTrayRestartState = { handlers: new Set(), pending: 0, + lastToken: 0, unlistenTrayRestartBackendEvent: null }); + if ( + typeof trayRestartState.lastToken !== 'number' || + !Number.isFinite(trayRestartState.lastToken) + ) { + trayRestartState.lastToken = 0; + } if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'undefined') { trayRestartState.unlistenTrayRestartBackendEvent = null; } - const emitTrayRestart = () => { + const emitTrayRestart = (token = null) => { + const numericToken = Number(token); + if (Number.isFinite(numericToken) && numericToken > 0) { + if (numericToken <= trayRestartState.lastToken) return; + trayRestartState.lastToken = numericToken; + } else { + trayRestartState.lastToken += 1; + } + if (trayRestartState.handlers.size === 0) { trayRestartState.pending = Number(trayRestartState.pending || 0) + 1; return; @@ -1680,6 +1708,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } catch {} } }; + window.__astrbotDesktopEmitTrayRestart = (token) => emitTrayRestart(token); const onTrayRestartBackend = (callback) => { if (typeof callback !== 'function') return () => {}; @@ -1700,8 +1729,8 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'function') return; try { - const unlisten = await listen(TRAY_RESTART_BACKEND_EVENT, () => { - emitTrayRestart(); + const unlisten = await listen(TRAY_RESTART_BACKEND_EVENT, (event) => { + emitTrayRestart(event?.payload); }); if (typeof unlisten === 'function') { trayRestartState.unlistenTrayRestartBackendEvent = unlisten; From f6310d108363b292a2ece5cce67f40377b626441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:24:04 +0900 Subject: [PATCH 19/25] remove tray restart fallback and add bridge trace logs --- src-tauri/src/main.rs | 76 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 97b06a9d..c9736d2b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1147,6 +1147,20 @@ fn desktop_bridge_stop_backend(app_handle: AppHandle) -> BackendBridgeResult { } } +#[tauri::command] +fn desktop_bridge_log(message: String) { + let trimmed = message.trim(); + if trimmed.is_empty() { + return; + } + let message = if trimmed.len() > 320 { + format!("{}...", &trimmed[..320]) + } else { + trimmed.to_string() + }; + append_desktop_log(&format!("desktop-bridge {message}")); +} + fn main() { append_desktop_log("desktop process starting"); append_desktop_log(&format!( @@ -1160,7 +1174,8 @@ fn main() { desktop_bridge_get_backend_state, desktop_bridge_set_auth_token, desktop_bridge_restart_backend, - desktop_bridge_stop_backend + desktop_bridge_stop_backend, + desktop_bridge_log ]) .on_window_event(|window, event| { if window.label() != "main" { @@ -1424,22 +1439,14 @@ fn emit_tray_restart_backend_event(app_handle: &AppHandle) { return; }; let token = TRAY_RESTART_SIGNAL_TOKEN.fetch_add(1, Ordering::Relaxed) + 1; + append_desktop_log(&format!("tray restart signal dispatch: token={token}")); if let Err(error) = window.emit(TRAY_RESTART_BACKEND_EVENT, token) { append_desktop_log(&format!( "failed to emit tray restart backend event: {error}" )); - } - - // Compatibility fallback when the JS Tauri event listener API is unavailable. - // The same token is used so JS can deduplicate if both paths are delivered. - let fallback_script = format!( - "if (typeof window !== 'undefined' && typeof window.__astrbotDesktopEmitTrayRestart === 'function') {{ window.__astrbotDesktopEmitTrayRestart({token}); }}" - ); - if let Err(error) = window.eval(&fallback_script) { - append_desktop_log(&format!( - "failed to eval tray restart backend fallback emit: {error}" - )); + } else { + append_desktop_log(&format!("tray restart signal emitted: token={token}")); } } @@ -1660,6 +1667,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" SET_AUTH_TOKEN: 'desktop_bridge_set_auth_token', RESTART_BACKEND: 'desktop_bridge_restart_backend', STOP_BACKEND: 'desktop_bridge_stop_backend', + LOG: 'desktop_bridge_log', }); const TRAY_RESTART_BACKEND_EVENT = '__ASTRBOT_TRAY_RESTART_BACKEND_EVENT__'; @@ -1671,6 +1679,18 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } }; + const bridgeLog = (stage, detail = '') => { + const stageText = String(stage || '').trim(); + if (!stageText) return; + const detailText = String(detail || '').trim(); + const message = detailText + ? `tray-restart ${stageText} ${detailText}` + : `tray-restart ${stageText}`; + try { + void invoke(BRIDGE_COMMANDS.LOG, { message }); + } catch {} + }; + const trayRestartState = window.__astrbotDesktopTrayRestartState || (window.__astrbotDesktopTrayRestartState = { @@ -1692,7 +1712,10 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" const emitTrayRestart = (token = null) => { const numericToken = Number(token); if (Number.isFinite(numericToken) && numericToken > 0) { - if (numericToken <= trayRestartState.lastToken) return; + if (numericToken <= trayRestartState.lastToken) { + bridgeLog('drop-duplicate', `token=${numericToken} last=${trayRestartState.lastToken}`); + return; + } trayRestartState.lastToken = numericToken; } else { trayRestartState.lastToken += 1; @@ -1700,42 +1723,63 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" if (trayRestartState.handlers.size === 0) { trayRestartState.pending = Number(trayRestartState.pending || 0) + 1; + bridgeLog( + 'queue', + `token=${trayRestartState.lastToken} pending=${trayRestartState.pending}` + ); return; } + bridgeLog( + 'dispatch', + `token=${trayRestartState.lastToken} handlers=${trayRestartState.handlers.size}` + ); for (const handler of trayRestartState.handlers) { try { handler(); - } catch {} + } catch (error) { + bridgeLog('handler-error', String(error)); + } } }; - window.__astrbotDesktopEmitTrayRestart = (token) => emitTrayRestart(token); const onTrayRestartBackend = (callback) => { if (typeof callback !== 'function') return () => {}; const handler = () => callback(); trayRestartState.handlers.add(handler); + bridgeLog('handler-add', `count=${trayRestartState.handlers.size}`); while (trayRestartState.pending > 0) { trayRestartState.pending -= 1; + bridgeLog( + 'drain-pending', + `token=${trayRestartState.lastToken} remaining=${trayRestartState.pending}` + ); handler(); } - return () => trayRestartState.handlers.delete(handler); + return () => { + trayRestartState.handlers.delete(handler); + bridgeLog('handler-remove', `count=${trayRestartState.handlers.size}`); + }; }; const listenToTrayRestartBackendEvent = async () => { const listen = tauriEvent?.listen; if (typeof listen !== 'function') { + bridgeLog('listen-api-unavailable'); console.warn('Tray restart backend event listen API is unavailable'); return; } if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'function') return; try { const unlisten = await listen(TRAY_RESTART_BACKEND_EVENT, (event) => { + bridgeLog('event-received', `payload=${String(event?.payload ?? 'null')}`); emitTrayRestart(event?.payload); }); if (typeof unlisten === 'function') { trayRestartState.unlistenTrayRestartBackendEvent = unlisten; + bridgeLog('listen-registered'); } } catch (error) { + bridgeLog('listen-failed', String(error)); console.warn('Failed to listen for tray restart backend event', error); } }; From 22677d25beadb6d75d66837ab25289dde27e12ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:31:05 +0900 Subject: [PATCH 20/25] use plugin event listen path for tray restart bridge --- src-tauri/src/main.rs | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c9736d2b..3be1fd37 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1658,6 +1658,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } const invoke = window.__TAURI_INTERNALS__?.invoke; + const transformCallback = window.__TAURI_INTERNALS__?.transformCallback; const tauriEvent = window.__TAURI_INTERNALS__?.event ?? window.__TAURI__?.event; if (typeof invoke !== 'function') return; @@ -1691,6 +1692,33 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } catch {} }; + const createEventListener = async (eventName, handler) => { + if (typeof tauriEvent?.listen === 'function') { + bridgeLog('listen-register-via-global', eventName); + return tauriEvent.listen(eventName, handler); + } + if (typeof transformCallback !== 'function') { + throw new Error('transformCallback is unavailable'); + } + + bridgeLog('listen-register-via-plugin', eventName); + const eventId = await invoke('plugin:event|listen', { + event: eventName, + target: { kind: 'Any' }, + handler: transformCallback(handler), + }); + + return async () => { + try { + window.__TAURI_EVENT_PLUGIN_INTERNALS__?.unregisterListener?.(eventName, eventId); + } catch {} + await invoke('plugin:event|unlisten', { + event: eventName, + eventId, + }); + }; + }; + const trayRestartState = window.__astrbotDesktopTrayRestartState || (window.__astrbotDesktopTrayRestartState = { @@ -1762,15 +1790,9 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" }; const listenToTrayRestartBackendEvent = async () => { - const listen = tauriEvent?.listen; - if (typeof listen !== 'function') { - bridgeLog('listen-api-unavailable'); - console.warn('Tray restart backend event listen API is unavailable'); - return; - } if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'function') return; try { - const unlisten = await listen(TRAY_RESTART_BACKEND_EVENT, (event) => { + const unlisten = await createEventListener(TRAY_RESTART_BACKEND_EVENT, (event) => { bridgeLog('event-received', `payload=${String(event?.payload ?? 'null')}`); emitTrayRestart(event?.payload); }); From ac73562d8b8fd119c57a14199fa62a87ff0b4c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:39:16 +0900 Subject: [PATCH 21/25] grant loopback dashboard core event capability --- src-tauri/capabilities/default.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src-tauri/capabilities/default.json diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 00000000..132039fc --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-capability", + "description": "Default IPC capability for the main window and loopback dashboard origin.", + "windows": ["main"], + "local": true, + "remote": { + "urls": ["http://127.0.0.1:*", "http://localhost:*"] + }, + "permissions": ["core:default"] +} From 69ac6853fe3f6b24ce28d67296e8cea0f74cb7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:49:11 +0900 Subject: [PATCH 22/25] chore trim tray restart diagnostic logging --- src-tauri/src/main.rs | 61 +++---------------------------------------- 1 file changed, 3 insertions(+), 58 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3be1fd37..890a80ec 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1147,20 +1147,6 @@ fn desktop_bridge_stop_backend(app_handle: AppHandle) -> BackendBridgeResult { } } -#[tauri::command] -fn desktop_bridge_log(message: String) { - let trimmed = message.trim(); - if trimmed.is_empty() { - return; - } - let message = if trimmed.len() > 320 { - format!("{}...", &trimmed[..320]) - } else { - trimmed.to_string() - }; - append_desktop_log(&format!("desktop-bridge {message}")); -} - fn main() { append_desktop_log("desktop process starting"); append_desktop_log(&format!( @@ -1174,8 +1160,7 @@ fn main() { desktop_bridge_get_backend_state, desktop_bridge_set_auth_token, desktop_bridge_restart_backend, - desktop_bridge_stop_backend, - desktop_bridge_log + desktop_bridge_stop_backend ]) .on_window_event(|window, event| { if window.label() != "main" { @@ -1439,14 +1424,11 @@ fn emit_tray_restart_backend_event(app_handle: &AppHandle) { return; }; let token = TRAY_RESTART_SIGNAL_TOKEN.fetch_add(1, Ordering::Relaxed) + 1; - append_desktop_log(&format!("tray restart signal dispatch: token={token}")); if let Err(error) = window.emit(TRAY_RESTART_BACKEND_EVENT, token) { append_desktop_log(&format!( "failed to emit tray restart backend event: {error}" )); - } else { - append_desktop_log(&format!("tray restart signal emitted: token={token}")); } } @@ -1668,7 +1650,6 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" SET_AUTH_TOKEN: 'desktop_bridge_set_auth_token', RESTART_BACKEND: 'desktop_bridge_restart_backend', STOP_BACKEND: 'desktop_bridge_stop_backend', - LOG: 'desktop_bridge_log', }); const TRAY_RESTART_BACKEND_EVENT = '__ASTRBOT_TRAY_RESTART_BACKEND_EVENT__'; @@ -1680,28 +1661,14 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } }; - const bridgeLog = (stage, detail = '') => { - const stageText = String(stage || '').trim(); - if (!stageText) return; - const detailText = String(detail || '').trim(); - const message = detailText - ? `tray-restart ${stageText} ${detailText}` - : `tray-restart ${stageText}`; - try { - void invoke(BRIDGE_COMMANDS.LOG, { message }); - } catch {} - }; - const createEventListener = async (eventName, handler) => { if (typeof tauriEvent?.listen === 'function') { - bridgeLog('listen-register-via-global', eventName); return tauriEvent.listen(eventName, handler); } if (typeof transformCallback !== 'function') { throw new Error('transformCallback is unavailable'); } - bridgeLog('listen-register-via-plugin', eventName); const eventId = await invoke('plugin:event|listen', { event: eventName, target: { kind: 'Any' }, @@ -1740,10 +1707,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" const emitTrayRestart = (token = null) => { const numericToken = Number(token); if (Number.isFinite(numericToken) && numericToken > 0) { - if (numericToken <= trayRestartState.lastToken) { - bridgeLog('drop-duplicate', `token=${numericToken} last=${trayRestartState.lastToken}`); - return; - } + if (numericToken <= trayRestartState.lastToken) return; trayRestartState.lastToken = numericToken; } else { trayRestartState.lastToken += 1; @@ -1751,22 +1715,12 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" if (trayRestartState.handlers.size === 0) { trayRestartState.pending = Number(trayRestartState.pending || 0) + 1; - bridgeLog( - 'queue', - `token=${trayRestartState.lastToken} pending=${trayRestartState.pending}` - ); return; } - bridgeLog( - 'dispatch', - `token=${trayRestartState.lastToken} handlers=${trayRestartState.handlers.size}` - ); for (const handler of trayRestartState.handlers) { try { handler(); - } catch (error) { - bridgeLog('handler-error', String(error)); - } + } catch {} } }; @@ -1774,18 +1728,12 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" if (typeof callback !== 'function') return () => {}; const handler = () => callback(); trayRestartState.handlers.add(handler); - bridgeLog('handler-add', `count=${trayRestartState.handlers.size}`); while (trayRestartState.pending > 0) { trayRestartState.pending -= 1; - bridgeLog( - 'drain-pending', - `token=${trayRestartState.lastToken} remaining=${trayRestartState.pending}` - ); handler(); } return () => { trayRestartState.handlers.delete(handler); - bridgeLog('handler-remove', `count=${trayRestartState.handlers.size}`); }; }; @@ -1793,15 +1741,12 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" if (typeof trayRestartState.unlistenTrayRestartBackendEvent === 'function') return; try { const unlisten = await createEventListener(TRAY_RESTART_BACKEND_EVENT, (event) => { - bridgeLog('event-received', `payload=${String(event?.payload ?? 'null')}`); emitTrayRestart(event?.payload); }); if (typeof unlisten === 'function') { trayRestartState.unlistenTrayRestartBackendEvent = unlisten; - bridgeLog('listen-registered'); } } catch (error) { - bridgeLog('listen-failed', String(error)); console.warn('Failed to listen for tray restart backend event', error); } }; From c655917b4e76a8507d6fb0a2ddef74409d00dd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:54:16 +0900 Subject: [PATCH 23/25] harden bridge expectation hints and event listener feature checks --- scripts/prepare-resources.mjs | 13 +++++++++--- src-tauri/src/main.rs | 37 +++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index e666e63c..82b052d1 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -223,9 +223,9 @@ const DESKTOP_BRIDGE_PATTERNS = { desktopRuntimeImport: /import\s+\{\s*getDesktopRuntimeInfo\s*\}\s+from\s+['"]@\/utils\/desktopRuntime['"]\s*;?/, desktopRuntimeUsageInRestart: - /hasDesktopRestartCapability[\s\S]{0,220}await\s+getDesktopRuntimeInfo\s*\(\s*\)/, + /hasDesktopRestartCapability[\s\S]*?await\s+getDesktopRuntimeInfo\s*\(\s*\)/, desktopRuntimeUsageInHeader: - /const\s+runtimeInfo\s*=\s*await\s+getDesktopRuntimeInfo\s*\(\s*\)\s*;?[\s\S]{0,220}isDesktopReleaseMode\.value\s*=\s*runtimeInfo\.isDesktopRuntime/, + /const\s+runtimeInfo\s*=\s*await\s+getDesktopRuntimeInfo\s*\(\s*\)\s*;?[\s\S]*?isDesktopReleaseMode\.value\s*=\s*runtimeInfo\.isDesktopRuntime/, desktopReleaseModeFlag: /\bisDesktopReleaseMode\b/, desktopRuntimeProbeWarn: /console\.warn\([\s\S]*desktop runtime/i, }; @@ -235,42 +235,49 @@ const DESKTOP_BRIDGE_EXPECTATIONS = [ filePath: ['src', 'App.vue'], pattern: DESKTOP_BRIDGE_PATTERNS.trayRestartGuard, label: 'tray restart desktop guard', + hint: "Expected `if (!desktopBridge?.onTrayRestartBackend) {` in App.vue.", required: false, }, { filePath: ['src', 'App.vue'], pattern: DESKTOP_BRIDGE_PATTERNS.trayRestartPromptInvoke, label: 'tray restart waiting prompt', + hint: 'Expected tray callback to call `globalWaitingRef.value?.check?.(...)`.', required: false, }, { filePath: ['src', 'utils', 'restartAstrBot.ts'], pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeImport, label: 'desktop runtime helper import', + hint: 'Expected `import { getDesktopRuntimeInfo } from "@/utils/desktopRuntime"`.', required: true, }, { filePath: ['src', 'utils', 'restartAstrBot.ts'], pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsageInRestart, label: 'desktop runtime helper usage in restart flow', + hint: 'Expected restart flow to use `hasDesktopRestartCapability` + `await getDesktopRuntimeInfo()`.', required: true, }, { filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], pattern: DESKTOP_BRIDGE_PATTERNS.desktopReleaseModeFlag, label: 'desktop release mode flag', + hint: 'Expected `isDesktopReleaseMode` flag in header update UI.', required: false, }, { filePath: ['src', 'layouts', 'full', 'vertical-header', 'VerticalHeader.vue'], pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeUsageInHeader, label: 'desktop runtime helper usage in header', + hint: 'Expected header runtime probe: `const runtimeInfo = await getDesktopRuntimeInfo()`.', required: true, }, { filePath: ['src', 'utils', 'desktopRuntime.ts'], pattern: DESKTOP_BRIDGE_PATTERNS.desktopRuntimeProbeWarn, label: 'desktop runtime probe warning', + hint: 'Expected warning log when desktop runtime detection fails.', required: false, }, ]; @@ -293,7 +300,7 @@ const verifyDesktopBridgeArtifacts = async (dashboardDir) => { const source = await readFile(file, 'utf8'); if (!expectation.pattern.test(source)) { - const message = `[prepare-resources] Expected ${expectation.label} in ${path.relative(projectRoot, file)}. Please sync AstrBot dashboard sources.`; + const message = `[prepare-resources] Expected ${expectation.label} in ${path.relative(projectRoot, file)}. ${expectation.hint || ''} Please sync AstrBot dashboard sources.`; if (mustPass) { issues.push(message); } else { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 890a80ec..e9245f2f 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1662,27 +1662,40 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" }; const createEventListener = async (eventName, handler) => { - if (typeof tauriEvent?.listen === 'function') { + const hasGlobalEventListenerApi = typeof tauriEvent?.listen === 'function'; + const hasPluginEventListenerApi = + typeof invoke === 'function' && typeof transformCallback === 'function'; + + if (hasGlobalEventListenerApi) { return tauriEvent.listen(eventName, handler); } - if (typeof transformCallback !== 'function') { - throw new Error('transformCallback is unavailable'); + if (!hasPluginEventListenerApi) { + throw new Error( + 'No supported Tauri event listener API: expected tauriEvent.listen or __TAURI_INTERNALS__.invoke + transformCallback' + ); } - const eventId = await invoke('plugin:event|listen', { - event: eventName, - target: { kind: 'Any' }, - handler: transformCallback(handler), - }); + let eventId; + try { + eventId = await invoke('plugin:event|listen', { + event: eventName, + target: { kind: 'Any' }, + handler: transformCallback(handler), + }); + } catch (error) { + throw new Error(`plugin:event|listen failed: ${String(error)}`); + } return async () => { try { window.__TAURI_EVENT_PLUGIN_INTERNALS__?.unregisterListener?.(eventName, eventId); } catch {} - await invoke('plugin:event|unlisten', { - event: eventName, - eventId, - }); + try { + await invoke('plugin:event|unlisten', { + event: eventName, + eventId, + }); + } catch {} }; }; From e2e7fd7f6944f2a10cb14423e0abc7a01e3e1bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:57:40 +0900 Subject: [PATCH 24/25] refactor simplify tray restart bootstrap wiring --- src-tauri/src/main.rs | 44 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e9245f2f..9a6f35ce 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -53,7 +53,6 @@ const TRAY_MENU_RELOAD_WINDOW: &str = "tray_reload_window"; const TRAY_MENU_RESTART_BACKEND: &str = "tray_restart_backend"; const TRAY_MENU_QUIT: &str = "tray_quit"; const TRAY_RESTART_BACKEND_EVENT: &str = "astrbot://tray-restart-backend"; -const DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER: &str = "__ASTRBOT_TRAY_RESTART_BACKEND_EVENT__"; const DEFAULT_SHELL_LOCALE: &str = "zh-CN"; const STARTUP_MODE_ENV: &str = "ASTRBOT_DESKTOP_STARTUP_MODE"; // Keep in sync with STARTUP_MODES in ui/index.html. @@ -1627,7 +1626,7 @@ fn update_tray_menu_labels(app_handle: &AppHandle) { set_menu_text_safe(&tray_state.quit_item, shell_texts.tray_quit, TRAY_MENU_QUIT); } -const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" +const DESKTOP_BRIDGE_BOOTSTRAP_TEMPLATE: &str = r#" (() => { const existingTrayRestartState = window.__astrbotDesktopTrayRestartState; if ( @@ -1651,7 +1650,7 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" RESTART_BACKEND: 'desktop_bridge_restart_backend', STOP_BACKEND: 'desktop_bridge_stop_backend', }); - const TRAY_RESTART_BACKEND_EVENT = '__ASTRBOT_TRAY_RESTART_BACKEND_EVENT__'; + const TRAY_RESTART_BACKEND_EVENT = '{TRAY_RESTART_BACKEND_EVENT}'; const invokeBridge = async (command, payload = {}) => { try { @@ -1661,15 +1660,8 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" } }; - const createEventListener = async (eventName, handler) => { - const hasGlobalEventListenerApi = typeof tauriEvent?.listen === 'function'; - const hasPluginEventListenerApi = - typeof invoke === 'function' && typeof transformCallback === 'function'; - - if (hasGlobalEventListenerApi) { - return tauriEvent.listen(eventName, handler); - } - if (!hasPluginEventListenerApi) { + const createLegacyEventListener = async (eventName, handler) => { + if (typeof transformCallback !== 'function') { throw new Error( 'No supported Tauri event listener API: expected tauriEvent.listen or __TAURI_INTERNALS__.invoke + transformCallback' ); @@ -1699,6 +1691,13 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" }; }; + const createEventListener = async (eventName, handler) => { + if (typeof tauriEvent?.listen === 'function') { + return tauriEvent.listen(eventName, handler); + } + return createLegacyEventListener(eventName, handler); + }; + const trayRestartState = window.__astrbotDesktopTrayRestartState || (window.__astrbotDesktopTrayRestartState = { @@ -1717,14 +1716,20 @@ const DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE: &str = r#" trayRestartState.unlistenTrayRestartBackendEvent = null; } - const emitTrayRestart = (token = null) => { + const shouldEmitForToken = (token) => { const numericToken = Number(token); if (Number.isFinite(numericToken) && numericToken > 0) { - if (numericToken <= trayRestartState.lastToken) return; + if (numericToken <= trayRestartState.lastToken) return false; trayRestartState.lastToken = numericToken; + return true; } else { trayRestartState.lastToken += 1; + return true; } + }; + + const emitTrayRestart = (token = null) => { + if (!shouldEmitForToken(token)) return; if (trayRestartState.handlers.size === 0) { trayRestartState.pending = Number(trayRestartState.pending || 0) + 1; @@ -2042,15 +2047,8 @@ static DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT: OnceLock = OnceLock::new(); fn desktop_bridge_bootstrap_script() -> &'static str { DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT .get_or_init(|| { - assert!( - DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE - .contains(DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER), - "desktop bridge template is missing tray restart event placeholder" - ); - DESKTOP_BRIDGE_BOOTSTRAP_SCRIPT_TEMPLATE.replace( - DESKTOP_BRIDGE_TRAY_EVENT_PLACEHOLDER, - TRAY_RESTART_BACKEND_EVENT, - ) + DESKTOP_BRIDGE_BOOTSTRAP_TEMPLATE + .replace("{TRAY_RESTART_BACKEND_EVENT}", TRAY_RESTART_BACKEND_EVENT) }) .as_str() } From 72836555f4a521d9f00c039fbbd2739568deabcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Sat, 21 Feb 2026 02:58:38 +0900 Subject: [PATCH 25/25] fix clarify optional desktop bridge file warnings --- scripts/prepare-resources.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/prepare-resources.mjs b/scripts/prepare-resources.mjs index 82b052d1..299197f9 100644 --- a/scripts/prepare-resources.mjs +++ b/scripts/prepare-resources.mjs @@ -289,7 +289,10 @@ const verifyDesktopBridgeArtifacts = async (dashboardDir) => { const mustPass = expectation.required || isDesktopBridgeExpectationStrict; const file = path.join(dashboardDir, ...expectation.filePath); if (!existsSync(file)) { - const message = `[prepare-resources] Missing required file for ${expectation.label}: ${path.relative(projectRoot, file)}`; + const relativePath = path.relative(projectRoot, file); + const message = mustPass + ? `[prepare-resources] Missing required file for ${expectation.label}: ${relativePath}` + : `[prepare-resources] Missing optional (best-effort) file for ${expectation.label}: ${relativePath}`; if (mustPass) { issues.push(message); } else {