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
+- 支持多工作区模式
+