Skip to content

Commit ac88450

Browse files
author
SqlRush
committed
Add advanced integration gates
1 parent cee6446 commit ac88450

9 files changed

Lines changed: 213 additions & 2 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,7 @@ M7 补充:terminal input parser 和 configurable keybinding name parser 现在
14561456
- Chrome/computer-use/voice/native integrations。
14571457
- enterprise/gated/platform-specific behavior。
14581458

1459-
当前状态:未开始完整实现
1459+
当前状态:已新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced``/config search` 审计;未启用时仍不注册或泄露 gated 工具 schema。实际 repl bridge、session websocket/direct connect、LSP manager/tool、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成
14601460

14611461
## Recommended Next Steps
14621462

docs/claude-code-go-rewrite-plan.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,8 @@ test/parity/ # golden tests against TS/official behavior
745745
- 每个 gated feature 独立开关测试。
746746
- 不启用 feature 时二进制行为和可见 schema 不泄露 gated 工具/命令。
747747

748+
- 本轮补充:新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced``/config search` 审计;未启用时仍不注册或泄露 gated 工具 schema。实际 repl bridge、session websocket/direct connect、LSP manager/tool、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
749+
748750
### M12: Parity hardening
749751

750752
产出:

internal/config/settings.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ func MergeSettings(settings ...contracts.Settings) contracts.Settings {
223223
cp := *s.Remote
224224
out.Remote = &cp
225225
}
226+
if s.Advanced != nil {
227+
out.Advanced = mergeAdvancedSetting(out.Advanced, s.Advanced)
228+
}
226229
if s.AutoUpdatesChannel != "" {
227230
out.AutoUpdatesChannel = s.AutoUpdatesChannel
228231
}
@@ -262,6 +265,78 @@ func MergeSettingsSources(sources ...SourceSettings) contracts.Settings {
262265
return merged
263266
}
264267

268+
func mergeAdvancedSetting(a, b *contracts.AdvancedSetting) *contracts.AdvancedSetting {
269+
if a == nil {
270+
cp := *b
271+
if b.Bridge != nil {
272+
cp.Bridge = clonePtr(b.Bridge)
273+
}
274+
if b.LSP != nil {
275+
cp.LSP = clonePtr(b.LSP)
276+
}
277+
if b.Telemetry != nil {
278+
cp.Telemetry = clonePtr(b.Telemetry)
279+
}
280+
if b.Chrome != nil {
281+
cp.Chrome = clonePtr(b.Chrome)
282+
}
283+
if b.Voice != nil {
284+
cp.Voice = clonePtr(b.Voice)
285+
}
286+
if b.ComputerUse != nil {
287+
cp.ComputerUse = clonePtr(b.ComputerUse)
288+
}
289+
if b.NativeIntegrations != nil {
290+
cp.NativeIntegrations = clonePtr(b.NativeIntegrations)
291+
}
292+
return &cp
293+
}
294+
out := *a
295+
if a.Bridge != nil {
296+
out.Bridge = clonePtr(a.Bridge)
297+
}
298+
if a.LSP != nil {
299+
out.LSP = clonePtr(a.LSP)
300+
}
301+
if a.Telemetry != nil {
302+
out.Telemetry = clonePtr(a.Telemetry)
303+
}
304+
if a.Chrome != nil {
305+
out.Chrome = clonePtr(a.Chrome)
306+
}
307+
if a.Voice != nil {
308+
out.Voice = clonePtr(a.Voice)
309+
}
310+
if a.ComputerUse != nil {
311+
out.ComputerUse = clonePtr(a.ComputerUse)
312+
}
313+
if a.NativeIntegrations != nil {
314+
out.NativeIntegrations = clonePtr(a.NativeIntegrations)
315+
}
316+
if b.Bridge != nil {
317+
out.Bridge = clonePtr(b.Bridge)
318+
}
319+
if b.LSP != nil {
320+
out.LSP = clonePtr(b.LSP)
321+
}
322+
if b.Telemetry != nil {
323+
out.Telemetry = clonePtr(b.Telemetry)
324+
}
325+
if b.Chrome != nil {
326+
out.Chrome = clonePtr(b.Chrome)
327+
}
328+
if b.Voice != nil {
329+
out.Voice = clonePtr(b.Voice)
330+
}
331+
if b.ComputerUse != nil {
332+
out.ComputerUse = clonePtr(b.ComputerUse)
333+
}
334+
if b.NativeIntegrations != nil {
335+
out.NativeIntegrations = clonePtr(b.NativeIntegrations)
336+
}
337+
return &out
338+
}
339+
265340
func mergePermissions(a, b *contracts.PermissionsSetting) *contracts.PermissionsSetting {
266341
if a == nil {
267342
cp := *b

internal/config/settings_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ func TestValidateSettingsWarnsForInvalidSandboxFilesystem(t *testing.T) {
9292
func TestMergeSettings(t *testing.T) {
9393
defaultWorktree := true
9494
overrideWorktree := false
95+
bridgeEnabled := true
96+
telemetryDisabled := false
97+
telemetryEnabled := true
9598
a := contracts.Settings{
9699
Env: map[string]string{"A": "1"},
97100
Permissions: &contracts.PermissionsSetting{
@@ -103,6 +106,10 @@ func TestMergeSettings(t *testing.T) {
103106
SparsePaths: []string{"README.md"},
104107
SymlinkDirectories: []string{"cache"},
105108
},
109+
Advanced: &contracts.AdvancedSetting{
110+
Bridge: &bridgeEnabled,
111+
Telemetry: &telemetryDisabled,
112+
},
106113
}
107114
b := contracts.Settings{
108115
Env: map[string]string{"A": "2", "B": "3"},
@@ -116,6 +123,9 @@ func TestMergeSettings(t *testing.T) {
116123
SparsePaths: []string{"docs"},
117124
SymlinkDirectories: []string{"node_modules"},
118125
},
126+
Advanced: &contracts.AdvancedSetting{
127+
Telemetry: &telemetryEnabled,
128+
},
119129
}
120130
merged := MergeSettings(a, b)
121131
if merged.Env["A"] != "2" || merged.Env["B"] != "3" {
@@ -136,6 +146,9 @@ func TestMergeSettings(t *testing.T) {
136146
if len(merged.Worktree.SymlinkDirectories) != 2 || merged.Worktree.SymlinkDirectories[0] != "cache" || merged.Worktree.SymlinkDirectories[1] != "node_modules" {
137147
t.Fatalf("worktree symlink dirs = %#v", merged.Worktree.SymlinkDirectories)
138148
}
149+
if merged.Advanced == nil || merged.Advanced.Bridge == nil || !*merged.Advanced.Bridge || merged.Advanced.Telemetry == nil || !*merged.Advanced.Telemetry {
150+
t.Fatalf("advanced = %#v", merged.Advanced)
151+
}
139152
}
140153

141154
func TestMergeSettingsPreservesSandboxSettings(t *testing.T) {

internal/contracts/settings.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type Settings struct {
6262
CompanyAnnouncements []string `json:"companyAnnouncements,omitempty"`
6363
PluginConfigs map[string]PluginConfig `json:"pluginConfigs,omitempty"`
6464
Remote *RemoteSetting `json:"remote,omitempty"`
65+
Advanced *AdvancedSetting `json:"advanced,omitempty"`
6566
AutoUpdatesChannel string `json:"autoUpdatesChannel,omitempty"`
6667
Plugins map[string]any `json:"plugins,omitempty"`
6768
Extra map[string]any `json:"-"`
@@ -114,6 +115,16 @@ type RemoteSetting struct {
114115
DefaultEnvironmentID string `json:"defaultEnvironmentId,omitempty"`
115116
}
116117

118+
type AdvancedSetting struct {
119+
Bridge *bool `json:"bridge,omitempty"`
120+
LSP *bool `json:"lsp,omitempty"`
121+
Telemetry *bool `json:"telemetry,omitempty"`
122+
Chrome *bool `json:"chrome,omitempty"`
123+
Voice *bool `json:"voice,omitempty"`
124+
ComputerUse *bool `json:"computerUse,omitempty"`
125+
NativeIntegrations *bool `json:"nativeIntegrations,omitempty"`
126+
}
127+
117128
type PermissionsSetting struct {
118129
Allow []string `json:"allow,omitempty"`
119130
Deny []string `json:"deny,omitempty"`

internal/contracts/settings_json.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ var knownSettingsJSONKeys = []string{
9898
"companyAnnouncements",
9999
"pluginConfigs",
100100
"remote",
101+
"advanced",
101102
"autoUpdatesChannel",
102103
"plugins",
103104
}

internal/contracts/settings_json_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ func TestSettingsPreservesUnknownFields(t *testing.T) {
3131
}
3232
}
3333

34+
func TestSettingsParsesAdvancedFeatureGates(t *testing.T) {
35+
input := []byte(`{"advanced":{"bridge":true,"lsp":false,"telemetry":true,"computerUse":true}}`)
36+
var settings Settings
37+
if err := json.Unmarshal(input, &settings); err != nil {
38+
t.Fatal(err)
39+
}
40+
if settings.Advanced == nil || settings.Advanced.Bridge == nil || !*settings.Advanced.Bridge {
41+
t.Fatalf("advanced bridge = %#v", settings.Advanced)
42+
}
43+
if settings.Advanced.LSP == nil || *settings.Advanced.LSP {
44+
t.Fatalf("advanced lsp = %#v", settings.Advanced)
45+
}
46+
if settings.Advanced.Telemetry == nil || !*settings.Advanced.Telemetry || settings.Advanced.ComputerUse == nil || !*settings.Advanced.ComputerUse {
47+
t.Fatalf("advanced gates = %#v", settings.Advanced)
48+
}
49+
if settings.Extra["advanced"] != nil {
50+
t.Fatalf("advanced should be a known setting, extra = %#v", settings.Extra)
51+
}
52+
}
53+
3454
func TestSettingsParsesOfficialMCPPolicyEntries(t *testing.T) {
3555
input := []byte(`{
3656
"allowedMcpServers": [

internal/conversation/run.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,8 +972,10 @@ func (r *Runner) formatConfigShow(raw string) string {
972972
lines = append(lines, "Keys: "+strings.Join(sortedAnyMapKeys(merged.Sandbox), ", "))
973973
}
974974
return strings.Join(lines, "\n")
975+
case "advanced":
976+
return formatAdvancedSettings(merged.Advanced)
975977
default:
976-
return "Unknown config section " + strings.TrimSpace(raw) + ". Available sections: settings, model, output-style, auth, fast-mode, betas, env, permissions, mcp, hooks, plugins, marketplaces, sandbox"
978+
return "Unknown config section " + strings.TrimSpace(raw) + ". Available sections: settings, model, output-style, auth, fast-mode, betas, env, permissions, mcp, hooks, plugins, marketplaces, sandbox, advanced"
977979
}
978980
}
979981

@@ -1027,6 +1029,8 @@ func normalizeConfigSection(raw string) string {
10271029
return "auth"
10281030
case "sandbox":
10291031
return "sandbox"
1032+
case "advanced", "advance", "adv", "gated", "gates", "feature", "features", "integration", "integrations":
1033+
return "advanced"
10301034
default:
10311035
return compact
10321036
}
@@ -1137,6 +1141,7 @@ func configSearchResults(r Runner, query string) []configSearchResult {
11371141
for _, key := range sortedAnyMapKeys(merged.Sandbox) {
11381142
add("sandbox", "sandbox key "+key, key)
11391143
}
1144+
addConfigAdvancedSearchResults(add, merged.Advanced)
11401145

11411146
sort.Slice(results, func(i, j int) bool {
11421147
if results[i].Section != results[j].Section {
@@ -1147,6 +1152,61 @@ func configSearchResults(r Runner, query string) []configSearchResult {
11471152
return results
11481153
}
11491154

1155+
func formatAdvancedSettings(setting *contracts.AdvancedSetting) string {
1156+
return strings.Join([]string{
1157+
"Config advanced integrations",
1158+
"Bridge: " + boolPtrEnabledText(advancedBool(setting, "bridge")),
1159+
"LSP: " + boolPtrEnabledText(advancedBool(setting, "lsp")),
1160+
"Telemetry: " + boolPtrEnabledText(advancedBool(setting, "telemetry")),
1161+
"Chrome: " + boolPtrEnabledText(advancedBool(setting, "chrome")),
1162+
"Voice: " + boolPtrEnabledText(advancedBool(setting, "voice")),
1163+
"Computer use: " + boolPtrEnabledText(advancedBool(setting, "computerUse")),
1164+
"Native integrations: " + boolPtrEnabledText(advancedBool(setting, "nativeIntegrations")),
1165+
}, "\n")
1166+
}
1167+
1168+
func addConfigAdvancedSearchResults(add func(string, string, ...string), setting *contracts.AdvancedSetting) {
1169+
for _, item := range []struct {
1170+
Name string
1171+
Value *bool
1172+
}{
1173+
{Name: "bridge", Value: advancedBool(setting, "bridge")},
1174+
{Name: "lsp", Value: advancedBool(setting, "lsp")},
1175+
{Name: "telemetry", Value: advancedBool(setting, "telemetry")},
1176+
{Name: "chrome", Value: advancedBool(setting, "chrome")},
1177+
{Name: "voice", Value: advancedBool(setting, "voice")},
1178+
{Name: "computer use", Value: advancedBool(setting, "computerUse")},
1179+
{Name: "native integrations", Value: advancedBool(setting, "nativeIntegrations")},
1180+
} {
1181+
state := boolPtrEnabledText(item.Value)
1182+
add("advanced", item.Name+" "+state, item.Name, state)
1183+
}
1184+
}
1185+
1186+
func advancedBool(setting *contracts.AdvancedSetting, name string) *bool {
1187+
if setting == nil {
1188+
return nil
1189+
}
1190+
switch name {
1191+
case "bridge":
1192+
return setting.Bridge
1193+
case "lsp":
1194+
return setting.LSP
1195+
case "telemetry":
1196+
return setting.Telemetry
1197+
case "chrome":
1198+
return setting.Chrome
1199+
case "voice":
1200+
return setting.Voice
1201+
case "computerUse":
1202+
return setting.ComputerUse
1203+
case "nativeIntegrations":
1204+
return setting.NativeIntegrations
1205+
default:
1206+
return nil
1207+
}
1208+
}
1209+
11501210
func addConfigPermissionsSearchResults(add func(string, string, ...string), permissions *contracts.PermissionsSetting) {
11511211
if permissions == nil {
11521212
return

internal/conversation/run_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,9 @@ func TestRunnerExecutesConfigShowSectionsWithoutQuery(t *testing.T) {
18591859
t.Setenv("CLAUDE_CONFIG_DIR", t.TempDir())
18601860
cwd := t.TempDir()
18611861
disableBypass := true
1862+
bridgeEnabled := true
1863+
lspDisabled := false
1864+
telemetryEnabled := true
18621865
runner := Runner{
18631866
Client: client,
18641867
Model: "sonnet",
@@ -1893,6 +1896,11 @@ func TestRunnerExecutesConfigShowSectionsWithoutQuery(t *testing.T) {
18931896
"market/a": {Options: map[string]any{"token": "plugin-secret"}},
18941897
},
18951898
Sandbox: map[string]any{"allowUnsandboxedCommands": false},
1899+
Advanced: &contracts.AdvancedSetting{
1900+
Bridge: &bridgeEnabled,
1901+
LSP: &lspDisabled,
1902+
Telemetry: &telemetryEnabled,
1903+
},
18961904
}},
18971905
}
18981906

@@ -1949,16 +1957,27 @@ func TestRunnerExecutesConfigShowSectionsWithoutQuery(t *testing.T) {
19491957
"- market/b: disabled",
19501958
"Plugin config names: market/a",
19511959
}, []string{"plugin-secret"})
1960+
assertConfigShow("/config show advanced", []string{
1961+
"Config advanced integrations",
1962+
"Bridge: enabled",
1963+
"LSP: disabled",
1964+
"Telemetry: enabled",
1965+
"Chrome: (unset)",
1966+
"Computer use: (unset)",
1967+
"Native integrations: (unset)",
1968+
}, nil)
19521969
assertConfigShow("/config show unknown", []string{
19531970
"Unknown config section unknown.",
19541971
"Available sections:",
1972+
"advanced",
19551973
}, nil)
19561974
}
19571975

19581976
func TestRunnerConfigSearchFindsSettingsWithoutLeakingValues(t *testing.T) {
19591977
client := &fakeClient{}
19601978
t.Setenv("CLAUDE_CONFIG_DIR", t.TempDir())
19611979
cwd := t.TempDir()
1980+
telemetryEnabled := true
19621981
runner := Runner{
19631982
Client: client,
19641983
Model: "sonnet",
@@ -1999,6 +2018,7 @@ func TestRunnerConfigSearchFindsSettingsWithoutLeakingValues(t *testing.T) {
19992018
},
20002019
ExtraKnownMarketplaces: map[string]any{"internal-market": map[string]any{"url": "https://market.example"}},
20012020
Sandbox: map[string]any{"allowUnsandboxedCommands": false},
2021+
Advanced: &contracts.AdvancedSetting{Telemetry: &telemetryEnabled},
20022022
}},
20032023
}
20042024

@@ -2038,6 +2058,15 @@ func TestRunnerConfigSearchFindsSettingsWithoutLeakingValues(t *testing.T) {
20382058
t.Fatalf("config find option key text = %q", text)
20392059
}
20402060

2061+
result, err = runner.RunTurn(context.Background(), nil, messages.UserText("/config search telemetry"))
2062+
if err != nil {
2063+
t.Fatal(err)
2064+
}
2065+
text = result.Messages[1].Content[0].Text
2066+
if !strings.Contains(text, "- advanced: telemetry enabled") {
2067+
t.Fatalf("config search advanced text = %q", text)
2068+
}
2069+
20412070
result, err = runner.RunTurn(context.Background(), nil, messages.UserText("/config search hidden-token"))
20422071
if err != nil {
20432072
t.Fatal(err)

0 commit comments

Comments
 (0)