From 4ec7b4289f770630434372ff251fc5f803938e27 Mon Sep 17 00:00:00 2001 From: leokun Date: Wed, 1 Jul 2026 15:31:24 +0800 Subject: [PATCH] v0.0.39 --- build/config.yml | 2 +- build/darwin/Info.dev.plist | 4 +- build/darwin/Info.plist | 4 +- build/linux/nfpm/nfpm.yaml | 43 ++++++---- build/windows/info.json | 4 +- build/windows/nsis/wails_tools.nsh | 49 +++++++++--- build/windows/wails.exe.manifest | 2 +- internal/backend/server/upstream/mocks.go | 4 + .../backend/server/upstream/mocks_test.go | 29 +++++++ internal/cursor/state_db.go | 78 ++++++++++++++++++- release-notes.md | 11 +-- 11 files changed, 186 insertions(+), 44 deletions(-) create mode 100644 internal/backend/server/upstream/mocks_test.go diff --git a/build/config.yml b/build/config.yml index d092737..ce9c027 100644 --- a/build/config.yml +++ b/build/config.yml @@ -8,7 +8,7 @@ info: description: "Cursor助手" copyright: "© 2026, Cursor助手" comments: "Cursor助手" - version: "0.0.38" + version: "0.0.39" dev_mode: root_path: . diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist index 37aab3e..3dc94b2 100644 --- a/build/darwin/Info.dev.plist +++ b/build/darwin/Info.dev.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.0.38 + 0.0.39 CFBundleVersion - 0.0.38 + 0.0.39 LSMinimumSystemVersion 12.0.0 LSUIElement diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist index 973c13e..c589fc9 100644 --- a/build/darwin/Info.plist +++ b/build/darwin/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.0.38 + 0.0.39 CFBundleVersion - 0.0.38 + 0.0.39 LSMinimumSystemVersion 12.0.0 LSUIElement diff --git a/build/linux/nfpm/nfpm.yaml b/build/linux/nfpm/nfpm.yaml index 5103aea..168c415 100644 --- a/build/linux/nfpm/nfpm.yaml +++ b/build/linux/nfpm/nfpm.yaml @@ -6,7 +6,7 @@ name: "Cursor助手" arch: ${GOARCH} platform: "linux" -version: "0.0.38" +version: "0.0.39" section: "default" priority: "extra" maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> @@ -24,24 +24,24 @@ contents: - src: "./build/linux/Cursor助手.desktop" dst: "/usr/share/applications/Cursor助手.desktop" -# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1 +# Default dependencies for the GTK4 + WebKitGTK 6.0 stack (Ubuntu 24.04+ / Debian 13+) depends: - - libgtk-3-0 - - libwebkit2gtk-4.1-0 + - libgtk-4-1 + - libwebkitgtk-6.0-4 -# Distribution-specific overrides for different package formats and WebKit versions +# Distribution-specific overrides for different package formats overrides: - # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) + # RPM packages for Fedora / RHEL / AlmaLinux / Rocky Linux rpm: depends: - - gtk3 - - webkit2gtk4.1 - - # Arch Linux packages (WebKit 4.1) + - gtk4 + - webkitgtk6.0 + + # Arch Linux packages archlinux: depends: - - gtk3 - - webkit2gtk-4.1 + - gtk4 + - webkitgtk-6.0 # scripts section to ensure desktop database is updated after install scripts: @@ -50,13 +50,26 @@ scripts: # preremove: "./build/linux/nfpm/scripts/preremove.sh" # postremove: "./build/linux/nfpm/scripts/postremove.sh" +# If you build your app with -tags gtk3 (legacy WebKit2GTK 4.1 stack — supported through v3.0.x, removed in v3.1), +# replace the depends/overrides above with these: +# +# depends: +# - libgtk-3-0 +# - libwebkit2gtk-4.1-0 +# overrides: +# rpm: +# depends: +# - gtk3 +# - webkit2gtk4.1 +# archlinux: +# depends: +# - gtk3 +# - webkit2gtk-4.1 +# # replaces: # - foobar # provides: # - bar -# depends: -# - gtk3 -# - libwebkit2gtk # recommends: # - whatever # suggests: diff --git a/build/windows/info.json b/build/windows/info.json index 6d5b65b..cd3e88d 100644 --- a/build/windows/info.json +++ b/build/windows/info.json @@ -1,10 +1,10 @@ { "fixed": { - "file_version": "0.0.38" + "file_version": "0.0.39" }, "info": { "0000": { - "ProductVersion": "0.0.38", + "ProductVersion": "0.0.39", "CompanyName": "Cursor助手", "FileDescription": "Cursor助手", "LegalCopyright": "© 2026, Cursor助手", diff --git a/build/windows/nsis/wails_tools.nsh b/build/windows/nsis/wails_tools.nsh index 8f49c05..6ac4944 100644 --- a/build/windows/nsis/wails_tools.nsh +++ b/build/windows/nsis/wails_tools.nsh @@ -14,7 +14,7 @@ !define INFO_PRODUCTNAME "Cursor助手" !endif !ifndef INFO_PRODUCTVERSION - !define INFO_PRODUCTVERSION "0.0.38" + !define INFO_PRODUCTVERSION "0.0.39" !endif !ifndef INFO_COPYRIGHT !define INFO_COPYRIGHT "© 2026, Cursor助手" @@ -27,8 +27,16 @@ !endif !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" +!ifndef WAILS_INSTALL_SCOPE + !define WAILS_INSTALL_SCOPE "machine" +!endif + !ifndef REQUEST_EXECUTION_LEVEL - !define REQUEST_EXECUTION_LEVEL "admin" + !if "${WAILS_INSTALL_SCOPE}" == "user" + !define REQUEST_EXECUTION_LEVEL "user" + !else + !define REQUEST_EXECUTION_LEVEL "admin" + !endif !endif RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" @@ -115,23 +123,40 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" WriteUninstaller "$INSTDIR\uninstall.exe" SetRegView 64 - WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" - WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" - WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + !if "${WAILS_INSTALL_SCOPE}" == "user" + WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" - ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 - IntFmt $0 "0x%08X" $0 - WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" + !else + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" + !endif !macroend !macro wails.deleteUninstaller Delete "$INSTDIR\uninstall.exe" SetRegView 64 - DeleteRegKey HKLM "${UNINST_KEY}" + !if "${WAILS_INSTALL_SCOPE}" == "user" + DeleteRegKey HKCU "${UNINST_KEY}" + !else + DeleteRegKey HKLM "${UNINST_KEY}" + !endif !macroend !macro wails.setShellContext diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest index 5d2d5ce..c0697a6 100644 --- a/build/windows/wails.exe.manifest +++ b/build/windows/wails.exe.manifest @@ -1,6 +1,6 @@ - + diff --git a/internal/backend/server/upstream/mocks.go b/internal/backend/server/upstream/mocks.go index be133d3..ae87a52 100644 --- a/internal/backend/server/upstream/mocks.go +++ b/internal/backend/server/upstream/mocks.go @@ -46,6 +46,8 @@ const ( bootstrapStatsigDisableTerminalOutputUIStreaming = "disable_terminal_output_ui_streaming" bootstrapStatsigBrowserCanvas = "browser_canvas" bootstrapStatsigEnableMultitaskMode = "enable_multitask_mode" + bootstrapStatsigDecomposeAlwaysLocalExtHostGate = "decompose_always_local_ext_host" + bootstrapStatsigCursorExtensionsIsolationV2Gate = "cursor_extensions_isolation_v2" bootstrapStatsigCursorAgentWorkerExtension = "enable_cursor_agent_worker_extension" bootstrapStatsigExperimentName = "free_user_model_picker" bootstrapStatsigVariantParam = "variant" @@ -139,6 +141,8 @@ var bootstrapStatsigTemplate = statsigBootstrapTemplate{ bootstrapStatsigDisableTerminalOutputUIStreaming: buildEnabledStatsigGate(bootstrapStatsigDisableTerminalOutputUIStreaming), bootstrapStatsigBrowserCanvas: buildEnabledStatsigGate(bootstrapStatsigBrowserCanvas), bootstrapStatsigEnableMultitaskMode: buildEnabledStatsigGate(bootstrapStatsigEnableMultitaskMode), + bootstrapStatsigDecomposeAlwaysLocalExtHostGate: buildDisabledStatsigGate(bootstrapStatsigDecomposeAlwaysLocalExtHostGate), + bootstrapStatsigCursorExtensionsIsolationV2Gate: buildDisabledStatsigGate(bootstrapStatsigCursorExtensionsIsolationV2Gate), bootstrapStatsigCursorAgentWorkerExtension: buildDisabledStatsigGate(bootstrapStatsigCursorAgentWorkerExtension), }, DynamicConfigs: map[string]statsigDynamicConfigTemplate{ diff --git a/internal/backend/server/upstream/mocks_test.go b/internal/backend/server/upstream/mocks_test.go new file mode 100644 index 0000000..5c878b9 --- /dev/null +++ b/internal/backend/server/upstream/mocks_test.go @@ -0,0 +1,29 @@ +package upstream + +import ( + "encoding/json" + "testing" +) + +func TestBuildBootstrapStatsigConfigJSONDisablesAlwaysLocalDecompositionGate(t *testing.T) { + payload, err := buildBootstrapStatsigConfigJSON(12345, "test-auth-id") + if err != nil { + t.Fatalf("build bootstrap statsig config: %v", err) + } + + var decoded statsigBootstrapTemplate + if err := json.Unmarshal(payload, &decoded); err != nil { + t.Fatalf("decode bootstrap statsig config: %v", err) + } + + gate, ok := decoded.FeatureGates[bootstrapStatsigDecomposeAlwaysLocalExtHostGate] + if !ok { + t.Fatalf("missing feature gate %q", bootstrapStatsigDecomposeAlwaysLocalExtHostGate) + } + if value, _ := gate["value"].(bool); value { + t.Fatalf("expected %q to be disabled", bootstrapStatsigDecomposeAlwaysLocalExtHostGate) + } + if ruleID, _ := gate["rule_id"].(string); ruleID != "local_disabled" { + t.Fatalf("unexpected rule_id: %q", ruleID) + } +} diff --git a/internal/cursor/state_db.go b/internal/cursor/state_db.go index cae6d15..e7bc0c6 100644 --- a/internal/cursor/state_db.go +++ b/internal/cursor/state_db.go @@ -3,6 +3,7 @@ package cursor import ( "context" "database/sql" + "encoding/json" "fmt" "os" "path/filepath" @@ -23,8 +24,14 @@ const ( cursorStateDBRelativePath = "Cursor/User/globalStorage/state.vscdb" cursorStateDarwinRelativePath = "Library/Application Support/Cursor/User/globalStorage/state.vscdb" cursorStateLinuxRelativePath = ".config/Cursor/User/globalStorage/state.vscdb" + cursorStateStatsigBootstrapKey = "workbench.experiments.statsigBootstrap" ) +var cursorStateDisabledStatsigGates = []string{ + "decompose_always_local_ext_host", + "cursor_extensions_isolation_v2", +} + // InjectCursorUserInfo synchronizes the Cursor user-level auth cache used by the // Settings page. It does not modify the installed Cursor app bundle. func InjectCursorUserInfo(email, token string) error { @@ -42,11 +49,12 @@ func InjectCursorUserInfo(email, token string) error { } logger.Infof( - "injectCursorUserInfo synced path=%s email=%s membership=%s subscription=%s", + "injectCursorUserInfo synced path=%s email=%s membership=%s subscription=%s disabled_statsig_gates=%s", stateDBPath, values["cursorAuth/cachedEmail"], values["cursorAuth/stripeMembershipType"], values["cursorAuth/stripeSubscriptionStatus"], + strings.Join(cursorStateDisabledStatsigGates, ","), ) return nil } @@ -111,6 +119,10 @@ func syncCursorAuthStateDB(path string, values map[string]string) error { } } + if err := disableCursorStatsigGates(ctx, tx); err != nil { + return err + } + if err := tx.Commit(); err != nil { return err } @@ -118,6 +130,70 @@ func syncCursorAuthStateDB(path string, values map[string]string) error { return nil } +func disableCursorStatsigGates(ctx context.Context, tx *sql.Tx) error { + var raw []byte + err := tx.QueryRowContext(ctx, "SELECT value FROM ItemTable WHERE key = ?", cursorStateStatsigBootstrapKey).Scan(&raw) + if err != nil { + if err == sql.ErrNoRows { + return nil + } + return err + } + + var payload map[string]any + if err := json.Unmarshal(raw, &payload); err != nil { + return fmt.Errorf("解析 Cursor Statsig bootstrap 失败: %w", err) + } + + featureGates, _ := payload["feature_gates"].(map[string]any) + if featureGates == nil { + featureGates = map[string]any{} + payload["feature_gates"] = featureGates + } + + hashUsed, _ := payload["hash_used"].(string) + for _, gate := range cursorStateDisabledStatsigGates { + disableCursorStatsigGate(featureGates, gate) + if strings.EqualFold(hashUsed, "djb2") { + disableCursorStatsigGate(featureGates, cursorStateDJB2Hash(gate)) + } + } + + updated, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("编码 Cursor Statsig bootstrap 失败: %w", err) + } + if _, err := tx.ExecContext(ctx, "UPDATE ItemTable SET value = ? WHERE key = ?", updated, cursorStateStatsigBootstrapKey); err != nil { + return err + } + return nil +} + +func disableCursorStatsigGate(featureGates map[string]any, key string) { + gate, _ := featureGates[key].(map[string]any) + if gate == nil { + gate = map[string]any{ + "name": key, + "rule_id": "local_disabled", + "ruleID": "local_disabled", + "group_name": "local_disabled", + "groupName": "local_disabled", + "id_type": "userID", + "idType": "userID", + } + featureGates[key] = gate + } + gate["value"] = false +} + +func cursorStateDJB2Hash(value string) string { + var hash uint32 + for _, b := range []byte(value) { + hash = hash*31 + uint32(b) + } + return fmt.Sprintf("%d", hash) +} + func resolveCursorStateDBPath() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { diff --git a/release-notes.md b/release-notes.md index 6f5f35d..58f2cd0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,10 +1,5 @@ QQ10群:1049354347 ---- -- 修复 Taking longer than expected. -- 修复gpt5.5思考内容(加密内容展示) -- 新增多任务能力,支持在会话中并行处理多个任务 -- 优化 Subagent 模型选择逻辑,提升分发稳定性 -- 修复 macOS 下证书信任失败导致无法正常连接的问题 -- 新增 ServerTime 接口及 Mock 实现,补齐本地模式兼容性 -- 优化 OpenAI / Anthropic 适配器的请求结构,提升模型请求稳定性 -- 优化提示词表达,精简系统前缀,提升响应质量 \ No newline at end of file +- 更新支持最新版Cursor 3.9.16 +- 支持多工作区模式 +