fix(network): 修复长耗时请求被误报「Network disconnected」中断的问题

客户端扩展在慢请求(如提交信息生成)开始约 10 秒后会探测
NetworkService/IsConnected,非 200 应答即判定断网并中止进行中的请求。
此前该探测落入通配路由返回 404,导致模型仍在正常返回时界面先报断连。

- host.go: 注册 IsConnected 精确路由,恒定以 200 + 空 IsConnectedResponse 应答
- client.go: newProtoMessage 补齐 aiserver.v1.IsConnectedResponse 类型,
  避免 mock 编码失败导致探测返回 502
- state_db.go: 新增强开 statsig gate 机制(cursorStateEnabledStatsigGates),
  强制启用 disable_network_change_monitor_local 从源头关闭该探测;
  disableCursorStatsigGates 泛化为 syncCursorStatsigGateOverrides
This commit is contained in:
杨超
2026-08-14 12:19:47 +08:00
parent a3ec2a0dfc
commit e9dc2e098d
3 changed files with 42 additions and 11 deletions
+26 -11
View File
@@ -33,6 +33,14 @@ var cursorStateDisabledStatsigGates = []string{
"disable_terminal_output_ui_streaming",
}
// cursorStateEnabledStatsigGates forces gates to true. Disabling the network
// change monitor stops the always-local extension from probing
// NetworkService/IsConnected and aborting slow in-flight requests (such as
// commit message generation) with "Network disconnected".
var cursorStateEnabledStatsigGates = []string{
"disable_network_change_monitor_local",
}
// 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 {
@@ -50,12 +58,13 @@ func InjectCursorUserInfo(email, token string) error {
}
logger.Infof(
"injectCursorUserInfo synced path=%s email=%s membership=%s subscription=%s disabled_statsig_gates=%s",
"injectCursorUserInfo synced path=%s email=%s membership=%s subscription=%s disabled_statsig_gates=%s enabled_statsig_gates=%s",
stateDBPath,
values["cursorAuth/cachedEmail"],
values["cursorAuth/stripeMembershipType"],
values["cursorAuth/stripeSubscriptionStatus"],
strings.Join(cursorStateDisabledStatsigGates, ","),
strings.Join(cursorStateEnabledStatsigGates, ","),
)
return nil
}
@@ -120,7 +129,7 @@ func syncCursorAuthStateDB(path string, values map[string]string) error {
}
}
if err := disableCursorStatsigGates(ctx, tx); err != nil {
if err := syncCursorStatsigGateOverrides(ctx, tx); err != nil {
return err
}
@@ -131,7 +140,7 @@ func syncCursorAuthStateDB(path string, values map[string]string) error {
return nil
}
func disableCursorStatsigGates(ctx context.Context, tx *sql.Tx) error {
func syncCursorStatsigGateOverrides(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 {
@@ -154,9 +163,15 @@ func disableCursorStatsigGates(ctx context.Context, tx *sql.Tx) error {
hashUsed, _ := payload["hash_used"].(string)
for _, gate := range cursorStateDisabledStatsigGates {
disableCursorStatsigGate(featureGates, gate)
setCursorStatsigGate(featureGates, gate, false, "local_disabled")
if strings.EqualFold(hashUsed, "djb2") {
disableCursorStatsigGate(featureGates, cursorStateDJB2Hash(gate))
setCursorStatsigGate(featureGates, cursorStateDJB2Hash(gate), false, "local_disabled")
}
}
for _, gate := range cursorStateEnabledStatsigGates {
setCursorStatsigGate(featureGates, gate, true, "local_enabled")
if strings.EqualFold(hashUsed, "djb2") {
setCursorStatsigGate(featureGates, cursorStateDJB2Hash(gate), true, "local_enabled")
}
}
@@ -170,21 +185,21 @@ func disableCursorStatsigGates(ctx context.Context, tx *sql.Tx) error {
return nil
}
func disableCursorStatsigGate(featureGates map[string]any, key string) {
func setCursorStatsigGate(featureGates map[string]any, key string, value bool, rule 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",
"rule_id": rule,
"ruleID": rule,
"group_name": rule,
"groupName": rule,
"id_type": "userID",
"idType": "userID",
}
featureGates[key] = gate
}
gate["value"] = false
gate["value"] = value
}
func cursorStateDJB2Hash(value string) string {