feat: add Tool Search domain restriction bypass with active-installation patching

Resolve the active `claude` command from PATH and apply an equal-length
byte patch to remove the domain whitelist check. Backups are stored in
~/.cc-switch/toolsearch-backups/ (SHA-256 of path) so they survive
Claude Code version upgrades. The patch auto-reapplies on app startup
when the setting is enabled.

Frontend checks PatchResult.success and rolls back the setting on failure.
This commit is contained in:
Jason
2026-03-14 21:20:05 +08:00
parent 7097a0d710
commit 9439153f05
13 changed files with 699 additions and 2 deletions
+39 -1
View File
@@ -197,6 +197,44 @@ export function useSettings(): UseSettingsResult {
}
}
// Tool Search bypass: apply/restore patch when toggled
const nextToolSearchBypass = updates.toolSearchBypass;
if (
nextToolSearchBypass !== undefined &&
nextToolSearchBypass !== (data?.toolSearchBypass ?? false)
) {
try {
const results = nextToolSearchBypass
? await settingsApi.applyToolSearchPatch()
: await settingsApi.restoreToolSearchPatch();
const failed = results.find((r) => !r.success);
if (failed) {
throw new Error(failed.error ?? "Tool Search patch failed");
}
} catch (error) {
console.warn(
"[useSettings] Failed to sync Tool Search bypass",
error,
);
// Rollback: revert the setting we already saved
const rolledBack = {
...payload,
toolSearchBypass: !nextToolSearchBypass,
};
await saveMutation.mutateAsync(rolledBack);
updateSettings({ toolSearchBypass: !nextToolSearchBypass });
toast.error(
nextToolSearchBypass
? t("notifications.toolSearchPatchFailed", {
defaultValue: "Tool Search 补丁操作失败",
})
: t("notifications.toolSearchRestoreFailed", {
defaultValue: "Tool Search 恢复操作失败",
}),
);
}
}
// 持久化语言偏好
try {
if (typeof window !== "undefined" && updates.language) {
@@ -228,7 +266,7 @@ export function useSettings(): UseSettingsResult {
throw error;
}
},
[data, saveMutation, settings, t],
[data, saveMutation, settings, t, updateSettings],
);
// 完整保存设置(用于 Advanced 标签页的手动保存)
+3
View File
@@ -85,6 +85,7 @@ export function useSettingsForm(): UseSettingsFormResult {
data.enableClaudePluginIntegration ?? false,
silentStartup: data.silentStartup ?? false,
skipClaudeOnboarding: data.skipClaudeOnboarding ?? false,
toolSearchBypass: data.toolSearchBypass ?? false,
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
codexConfigDir: sanitizeDir(data.codexConfigDir),
geminiConfigDir: sanitizeDir(data.geminiConfigDir),
@@ -107,6 +108,7 @@ export function useSettingsForm(): UseSettingsFormResult {
minimizeToTrayOnClose: true,
enableClaudePluginIntegration: false,
skipClaudeOnboarding: false,
toolSearchBypass: false,
language: readPersistedLanguage(),
} as SettingsFormState);
@@ -143,6 +145,7 @@ export function useSettingsForm(): UseSettingsFormResult {
serverData.enableClaudePluginIntegration ?? false,
silentStartup: serverData.silentStartup ?? false,
skipClaudeOnboarding: serverData.skipClaudeOnboarding ?? false,
toolSearchBypass: serverData.toolSearchBypass ?? false,
claudeConfigDir: sanitizeDir(serverData.claudeConfigDir),
codexConfigDir: sanitizeDir(serverData.codexConfigDir),
geminiConfigDir: sanitizeDir(serverData.geminiConfigDir),