mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add privacy settings for time and timezone in context
- Add includeCurrentTime and includeTimezone settings to ProviderSettings - Update getEnvironmentDetails to respect privacy settings - Create PrivacySettings UI component with proper controls - Add timezone dependency on current time setting - Include comprehensive test coverage for privacy settings - Add translations for privacy settings UI - Default to true for backward compatibility Fixes #8731
This commit is contained in:
parent
a8f87d2b1d
commit
286fdbba57
6 changed files with 213 additions and 8 deletions
|
|
@ -173,6 +173,10 @@ const baseProviderSettingsSchema = z.object({
|
|||
rateLimitSeconds: z.number().optional(),
|
||||
consecutiveMistakeLimit: z.number().min(0).optional(),
|
||||
|
||||
// Privacy settings
|
||||
includeCurrentTime: z.boolean().optional(),
|
||||
includeTimezone: z.boolean().optional(),
|
||||
|
||||
// Model reasoning.
|
||||
enableReasoningEffort: z.boolean().optional(),
|
||||
reasoningEffort: reasoningEffortWithMinimalSchema.optional(),
|
||||
|
|
|
|||
|
|
@ -390,4 +390,73 @@ describe("getEnvironmentDetails", () => {
|
|||
const result = await getEnvironmentDetails(cline as Task)
|
||||
expect(result).toContain("REMINDERS")
|
||||
})
|
||||
|
||||
describe("Privacy settings for time and timezone", () => {
|
||||
it("should include both time and timezone when both settings are true", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
...mockState,
|
||||
apiConfiguration: { includeCurrentTime: true, includeTimezone: true },
|
||||
})
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
expect(result).toContain("# Current Time")
|
||||
expect(result).toContain("Current time in ISO 8601 UTC format:")
|
||||
expect(result).toContain("User time zone:")
|
||||
})
|
||||
|
||||
it("should include only time when includeCurrentTime is true but includeTimezone is false", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
...mockState,
|
||||
apiConfiguration: { includeCurrentTime: true, includeTimezone: false },
|
||||
})
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
expect(result).toContain("# Current Time")
|
||||
expect(result).toContain("Current time in ISO 8601 UTC format:")
|
||||
expect(result).not.toContain("User time zone:")
|
||||
})
|
||||
|
||||
it("should exclude time section when includeCurrentTime is false", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
...mockState,
|
||||
apiConfiguration: { includeCurrentTime: false, includeTimezone: true },
|
||||
})
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
expect(result).not.toContain("# Current Time")
|
||||
expect(result).not.toContain("Current time in ISO 8601 UTC format:")
|
||||
expect(result).not.toContain("User time zone:")
|
||||
})
|
||||
|
||||
it("should include both time and timezone by default when settings are undefined", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
...mockState,
|
||||
apiConfiguration: {},
|
||||
})
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
expect(result).toContain("# Current Time")
|
||||
expect(result).toContain("Current time in ISO 8601 UTC format:")
|
||||
expect(result).toContain("User time zone:")
|
||||
})
|
||||
|
||||
it("should include both time and timezone when apiConfiguration is undefined", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
...mockState,
|
||||
apiConfiguration: undefined,
|
||||
})
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
expect(result).toContain("# Current Time")
|
||||
expect(result).toContain("Current time in ISO 8601 UTC format:")
|
||||
expect(result).toContain("User time zone:")
|
||||
})
|
||||
|
||||
it("should handle null apiConfiguration gracefully", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
...mockState,
|
||||
apiConfiguration: null,
|
||||
})
|
||||
const result = await getEnvironmentDetails(mockCline as Task)
|
||||
// Should default to including both
|
||||
expect(result).toContain("# Current Time")
|
||||
expect(result).toContain("Current time in ISO 8601 UTC format:")
|
||||
expect(result).toContain("User time zone:")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -190,15 +190,24 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
|
|||
details += terminalDetails
|
||||
}
|
||||
|
||||
// Add current time information with timezone.
|
||||
const now = new Date()
|
||||
// Add current time information with timezone (respecting privacy settings).
|
||||
// Default to true for backward compatibility
|
||||
const includeCurrentTime = state?.apiConfiguration?.includeCurrentTime ?? true
|
||||
const includeTimezone = state?.apiConfiguration?.includeTimezone ?? true
|
||||
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation
|
||||
const timeZoneOffsetHours = Math.floor(Math.abs(timeZoneOffset))
|
||||
const timeZoneOffsetMinutes = Math.abs(Math.round((Math.abs(timeZoneOffset) - timeZoneOffsetHours) * 60))
|
||||
const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : "-"}${timeZoneOffsetHours}:${timeZoneOffsetMinutes.toString().padStart(2, "0")}`
|
||||
details += `\n\n# Current Time\nCurrent time in ISO 8601 UTC format: ${now.toISOString()}\nUser time zone: ${timeZone}, UTC${timeZoneOffsetStr}`
|
||||
if (includeCurrentTime) {
|
||||
const now = new Date()
|
||||
details += `\n\n# Current Time\nCurrent time in ISO 8601 UTC format: ${now.toISOString()}`
|
||||
|
||||
if (includeTimezone) {
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation
|
||||
const timeZoneOffsetHours = Math.floor(Math.abs(timeZoneOffset))
|
||||
const timeZoneOffsetMinutes = Math.abs(Math.round((Math.abs(timeZoneOffset) - timeZoneOffsetHours) * 60))
|
||||
const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : "-"}${timeZoneOffsetHours}:${timeZoneOffsetMinutes.toString().padStart(2, "0")}`
|
||||
details += `\nUser time zone: ${timeZone}, UTC${timeZoneOffsetStr}`
|
||||
}
|
||||
}
|
||||
|
||||
// Add context tokens information.
|
||||
const { contextTokens, totalCost } = getApiMetrics(cline.clineMessages)
|
||||
|
|
|
|||
98
webview-ui/src/components/settings/PrivacySettings.tsx
Normal file
98
webview-ui/src/components/settings/PrivacySettings.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { HTMLAttributes } from "react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { Shield } from "lucide-react"
|
||||
import { telemetryClient } from "@/utils/TelemetryClient"
|
||||
import type { ProviderSettings } from "@roo-code/types"
|
||||
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
import { Section } from "./Section"
|
||||
|
||||
interface PrivacySettingsProps extends HTMLAttributes<HTMLDivElement> {
|
||||
includeCurrentTime: boolean
|
||||
includeTimezone: boolean
|
||||
setApiConfigurationField: <K extends keyof ProviderSettings>(
|
||||
field: K,
|
||||
value: ProviderSettings[K],
|
||||
isUserAction?: boolean,
|
||||
) => void
|
||||
}
|
||||
|
||||
export const PrivacySettings = ({
|
||||
includeCurrentTime,
|
||||
includeTimezone,
|
||||
setApiConfigurationField,
|
||||
...props
|
||||
}: PrivacySettingsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleIncludeCurrentTimeChange = (value: boolean) => {
|
||||
setApiConfigurationField("includeCurrentTime", value)
|
||||
|
||||
// If disabling current time, also disable timezone
|
||||
if (!value) {
|
||||
setApiConfigurationField("includeTimezone", false)
|
||||
}
|
||||
|
||||
// Track telemetry event
|
||||
telemetryClient.capture("privacy_settings_include_time_changed", {
|
||||
enabled: value,
|
||||
})
|
||||
}
|
||||
|
||||
const handleIncludeTimezoneChange = (value: boolean) => {
|
||||
setApiConfigurationField("includeTimezone", value)
|
||||
|
||||
// Track telemetry event
|
||||
telemetryClient.capture("privacy_settings_include_timezone_changed", {
|
||||
enabled: value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
<SectionHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-4" />
|
||||
<div>{t("settings:sections.privacy")}</div>
|
||||
</div>
|
||||
</SectionHeader>
|
||||
|
||||
<Section>
|
||||
<div className="space-y-6">
|
||||
{/* Include Current Time Setting */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<VSCodeCheckbox
|
||||
checked={includeCurrentTime}
|
||||
onChange={(e: any) => handleIncludeCurrentTimeChange(e.target.checked)}
|
||||
data-testid="include-current-time-checkbox">
|
||||
<span className="font-medium">{t("settings:privacy.includeCurrentTime.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1">
|
||||
{t("settings:privacy.includeCurrentTime.description")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Include Timezone Setting */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<VSCodeCheckbox
|
||||
checked={includeTimezone}
|
||||
disabled={!includeCurrentTime}
|
||||
onChange={(e: any) => handleIncludeTimezoneChange(e.target.checked)}
|
||||
data-testid="include-timezone-checkbox">
|
||||
<span className="font-medium">{t("settings:privacy.includeTimezone.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1">
|
||||
{t("settings:privacy.includeTimezone.description")}
|
||||
</div>
|
||||
{!includeCurrentTime && (
|
||||
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1 italic">
|
||||
{t("settings:privacy.includeTimezone.disabled")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import {
|
|||
LucideIcon,
|
||||
SquareSlash,
|
||||
Glasses,
|
||||
Shield,
|
||||
} from "lucide-react"
|
||||
|
||||
import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
|
||||
|
|
@ -68,6 +69,7 @@ import { Section } from "./Section"
|
|||
import PromptsSettings from "./PromptsSettings"
|
||||
import { SlashCommandsSettings } from "./SlashCommandsSettings"
|
||||
import { UISettings } from "./UISettings"
|
||||
import { PrivacySettings } from "./PrivacySettings"
|
||||
|
||||
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
|
||||
export const settingsTabList =
|
||||
|
|
@ -90,6 +92,7 @@ const sectionNames = [
|
|||
"contextManagement",
|
||||
"terminal",
|
||||
"prompts",
|
||||
"privacy",
|
||||
"ui",
|
||||
"experimental",
|
||||
"language",
|
||||
|
|
@ -478,6 +481,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
{ id: "contextManagement", icon: Database },
|
||||
{ id: "terminal", icon: SquareTerminal },
|
||||
{ id: "prompts", icon: MessageSquare },
|
||||
{ id: "privacy", icon: Shield },
|
||||
{ id: "ui", icon: Glasses },
|
||||
{ id: "experimental", icon: FlaskConical },
|
||||
{ id: "language", icon: Globe },
|
||||
|
|
@ -778,6 +782,15 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* Privacy Section */}
|
||||
{activeTab === "privacy" && (
|
||||
<PrivacySettings
|
||||
includeCurrentTime={apiConfiguration?.includeCurrentTime ?? true}
|
||||
includeTimezone={apiConfiguration?.includeTimezone ?? true}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* UI Section */}
|
||||
{activeTab === "ui" && (
|
||||
<UISettings
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"terminal": "Terminal",
|
||||
"slashCommands": "Slash Commands",
|
||||
"prompts": "Prompts",
|
||||
"privacy": "Privacy",
|
||||
"ui": "UI",
|
||||
"experimental": "Experimental",
|
||||
"language": "Language",
|
||||
|
|
@ -44,6 +45,17 @@
|
|||
"description": "When enabled, thinking blocks will be collapsed by default until you interact with them"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"includeCurrentTime": {
|
||||
"label": "Include current time in context",
|
||||
"description": "When enabled, the current time will be included in the context sent to AI providers. This helps the AI understand temporal context but may reveal your time zone."
|
||||
},
|
||||
"includeTimezone": {
|
||||
"label": "Include timezone information",
|
||||
"description": "When enabled, your timezone information will be included along with the current time. This provides more accurate temporal context but reveals your geographic location.",
|
||||
"disabled": "Timezone can only be included when current time is enabled."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"description": "Configure support prompts that are used for quick actions like enhancing prompts, explaining code, and fixing issues. These prompts help Roo provide better assistance for common development tasks."
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue