diff --git a/docs/agent-mode-and-tools.md b/docs/agent-mode-and-tools.md index 2f9ce247..dd251aea 100644 --- a/docs/agent-mode-and-tools.md +++ b/docs/agent-mode-and-tools.md @@ -191,12 +191,13 @@ Clicking it opens a short form where you describe what happened. When you submit 1. Saves a screenshot of the **Agent Mode chat pane** (just the agent panel, not your whole screen). 2. Saves a recent **Agent Mode log** of the behind-the-scenes messages between Copilot and the agent. This log is captured automatically so a report always has recent activity to include; you can turn it off under **Settings → Copilot → Advanced → Keep an Agent Mode activity log**. -3. Opens the folder containing those files in your file manager. -4. Opens a prefilled GitHub issue in your browser. +3. Saves a copy of your **Copilot settings** (the plugin's `data.json`), which helps maintainers reproduce configuration-dependent problems. All secrets — API keys, license keys, tokens, and backend environment variable values — are automatically replaced with `[REDACTED]` before the copy is written. +4. Opens the folder containing those files in your file manager. +5. Opens a prefilled GitHub issue in your browser. The files are **not uploaded for you** — drag them from the opened folder into the GitHub issue to attach them. -> **Privacy note:** The Agent Mode log can contain your prompts, note contents, and tool inputs/outputs in plaintext. Review the saved files before sharing them publicly. +> **Privacy note:** The Agent Mode log can contain your prompts, note contents, and tool inputs/outputs in plaintext, and the settings copy still describes your configuration (model names, folder names, feature toggles) even though secrets are redacted. Review the saved files before sharing them publicly. ### Including the OpenCode log diff --git a/src/agentMode/ui/ReportIssueModal.tsx b/src/agentMode/ui/ReportIssueModal.tsx index f8dab406..df25334f 100644 --- a/src/agentMode/ui/ReportIssueModal.tsx +++ b/src/agentMode/ui/ReportIssueModal.tsx @@ -7,6 +7,7 @@ import { captureViewScreenshot } from "@/utils/captureViewScreenshot"; import { isDesktopRuntime } from "@/utils/desktopRuntime"; import { assembleReportBundle, type ReportEnvInfo } from "@/utils/issueReport"; import { findLatestOpencodeLog } from "@/utils/opencodeLog"; +import { sanitizeSettingsDataForReport } from "@/utils/sanitizeSettingsData"; import { createPluginRoot } from "@/utils/react/createPluginRoot"; import { cn } from "@/lib/utils"; import { getSettings } from "@/settings/model"; @@ -30,6 +31,13 @@ export interface ReportIssueModalParams { activeBackend: string; /** Plugin version for the report's environment block. */ pluginVersion: string; + /** + * Loads the raw persisted plugin settings (the on-disk `data.json`, e.g. via + * `Plugin.loadData()`). The modal sanitizes the result (masks API keys, + * license keys, and other secrets) before bundling it into the report. + * Returns null when the data cannot be loaded; the report then skips it. + */ + loadSettingsData: () => Promise | null>; } interface ElectronShell { @@ -119,6 +127,11 @@ function ReportIssueContent({ showOpencodeOption, onSubmit, onCancel }: ReportIs (not your whole screen) and a recent activity log are saved to a folder on your computer. +
  • + A copy of your Copilot settings (data.json) + is included, with API keys, license keys, and other secrets replaced by{" "} + [REDACTED]. +
  • That folder opens, and a pre-filled GitHub issue opens in your browser.
  • Drag the saved files into the issue to attach them, then submit.
  • @@ -136,8 +149,9 @@ function ReportIssueContent({ showOpencodeOption, onSubmit, onCancel }: ReportIs Before you share these files The activity log can include your prompts, note contents, and tool inputs/outputs in - plain text. Review the saved files and remove anything sensitive before posting them - publicly. + plain text. The settings copy has API keys and other secrets automatically redacted, but + still describes your configuration. Review the saved files and remove anything sensitive + before posting them publicly. @@ -226,6 +240,8 @@ export class ReportIssueModal extends Modal { ? await resolveOpencodeLogPath() : null; + const settingsJson = await this.buildSanitizedSettingsJson(); + const env: ReportEnvInfo = { pluginVersion: this.params.pluginVersion, platform: process.platform, @@ -239,6 +255,7 @@ export class ReportIssueModal extends Modal { screenshotPng, frameLogPath, opencodeLogPath, + settingsJson, reportsRootDir: root, timestamp: formatTimestamp(new Date()), }); @@ -254,6 +271,22 @@ export class ReportIssueModal extends Modal { new Notice("Failed to prepare the issue report. See the console for details."); } } + + /** + * Load the raw persisted settings and return a pretty-printed, sanitized + * JSON string for the bundle, or null when loading fails (the report is + * still produced without it). + */ + private async buildSanitizedSettingsJson(): Promise { + try { + const raw = await this.params.loadSettingsData(); + if (!raw) return null; + return JSON.stringify(sanitizeSettingsDataForReport(raw), null, 2); + } catch (err) { + logError("[ReportIssue] failed to load settings for the report:", err); + return null; + } + } } async function resolveOpencodeLogPath(): Promise { diff --git a/src/settings/v2/components/AdvancedSettings.tsx b/src/settings/v2/components/AdvancedSettings.tsx index 2c9aa30d..2e598ec2 100644 --- a/src/settings/v2/components/AdvancedSettings.tsx +++ b/src/settings/v2/components/AdvancedSettings.tsx @@ -156,6 +156,7 @@ export const AdvancedSettings: React.FC = () => { getPlugin: (id: string) => { manifest?: { version?: string }; agentSessionManager?: { getActiveSession?: () => { backendId?: string } | null }; + loadData?: () => Promise; } | null; }; } @@ -170,6 +171,13 @@ export const AdvancedSettings: React.FC = () => { app, activeBackend, pluginVersion: copilotPlugin?.manifest?.version ?? "unknown", + // Raw loadData() is the true on-disk data.json (the modal sanitizes + // it), so the report reflects what is actually persisted rather than + // the in-memory settings object. + loadSettingsData: async () => { + const raw = await copilotPlugin?.loadData?.(); + return raw && typeof raw === "object" ? (raw as Record) : null; + }, // Resolve at capture time so we can close this Settings window and // reveal the agent pane first — the screenshot should be the chat // surface, not the settings dialog. Null when no agent pane is open. @@ -531,7 +539,7 @@ export const AdvancedSettings: React.FC = () => {