From f75c2ebbee669ffb6f1417471100913d26136d7a Mon Sep 17 00:00:00 2001 From: Logan Yang Date: Mon, 15 Jun 2026 16:43:26 -0700 Subject: [PATCH] feat(agent-mode): one-click "Report an Issue" flow with screenshot + frame log (#2614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(agent-mode): one-click "Report an Issue" flow with screenshot + frame log Reporting an Agent Mode bug was high-friction: the diagnostic frame log defaulted off (so it usually wasn't capturing when a problem hit), and there was no in-product path to assemble a report. - Default `agentMode.debugFullFrames` to ON for new installs so the frame log is already capturing when a bug occurs. The existing sanitize migration preserves an explicit prior choice (a user who turned it off stays off). - Add a "Report an Issue" button to the Agent Mode control bar. It opens a modal for a note, captures a screenshot of the chat surface (Electron capturePage, popout-aware, degrades gracefully), bundles it with the current frame log into a timestamped folder, reveals the folder, and opens a prefilled GitHub issue. - Optionally include the OpenCode log when that backend is active, and show an in-flow privacy disclosure shown only when the user chooses to share. Desktop-only; mobile and capture failures degrade to a report without a screenshot. Adds unit tests for the new default, the preserve-explicit-choice migration, the bundle assembler, and the opencode-log locator. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(agent-mode): make opencode log attachment opt-in by default The Report-issue flow bundles opencode's newest *global* log, which may belong to an unrelated CLI/Desktop session for another project. Defaulting the checkbox to checked could silently attach that log, exposing unrelated prompts and tool output. Default it to unchecked so the user opts in. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(agent-mode): cap report issue URL length and document the flow - buildReportIssueUrl: truncate the prefilled body so the assembled URL stays under Electron's ~2081-char openExternal limit on Windows. Without this, a long note made openExternal reject silently while the success notice still claimed the issue page opened. The full report is preserved in report.md on disk, and the truncated body points the user there. - Document the Report an Issue flow (bundle contents, drag-drop attach, privacy note, opt-in OpenCode log) in docs/agent-mode-and-tools.md per DOCS_GUIDE. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(agent-mode): skip frame log in report when logging is disabled If a user turns off "Log Full Agent Mode Frames" but a stale acp-frames.ndjson still exists in the temp dir, the report assembler would copy that old file into the bundle, leaking plaintext prompts/tool output despite logging being opted out. Only bundle the frame log when debugFullFrames is currently enabled. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(agent-mode): file reports to public repo; clarify report UX and settings Addresses review feedback on the Report an Issue flow: - File user reports to the PUBLIC logancyang/obsidian-copilot repo (label "bug"), never the private preview repo, which users can't see. - Report modal: state plainly that the screenshot is of the Agent Mode chat pane (not the whole screen), spell out what "Prepare report" does (save files to a folder, open it, open a prefilled GitHub issue to attach them), and replace the obscure muted disclaimer with a prominent warning callout. - Advanced settings: split Agent Mode logging into its own "Agent Mode debugging" section with plain-language copy (renamed to "Keep an Agent Mode activity log" / "Agent Mode activity log file"), and clarify that the legacy Debug Mode / Create Log File tools are for the regular chat, not Agent Mode. - Update docs to match. Co-Authored-By: Claude Opus 4.8 (1M context) * feat(agent-mode): move Report an Issue to the Advanced settings section Per UX feedback, the entry point moves out of the agent chat control bar into Settings → Advanced → Agent Mode debugging, alongside the activity-log controls it relates to. - Remove the bug-icon button and its wiring from AgentChatControls/AgentHome. - Add a "Report an Issue" button to the Agent Mode debugging settings section. - ReportIssueModal now resolves its screenshot target lazily via resolveCaptureTarget(): the settings caller closes the Settings window and reveals the agent pane first, so the screenshot is still the chat surface (not the dialog). No agent pane open -> screenshot is skipped gracefully. - Export ReportIssueModal from the agentMode barrel so host code can reach it without crossing the ui boundary. - Update docs to point at the new location. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(agent-mode): honor opencode env overrides when finding its log OpencodeBackend spawns opencode with `{ ...process.env, ...envOverrides }`, so a user who relocates opencode's data dir via XDG_DATA_HOME/HOME overrides has its logs written there. The report's opencode-log lookup only consulted ambient process.env/homedir, so it could attach an unrelated global log or miss the active session's. Resolve the log path from the same merged env + HOME override. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(agent-mode): report uses active session backend; gate import on desktop Two review fixes for the settings-based Report an Issue button: - Read the backend from the active Agent Mode session, not the persisted default. Switching tabs across backends changes the active session without touching settings.agentMode.activeBackend, so the modal could hide/show the OpenCode-log option for the wrong tab and name the wrong backend in the env block. - Check isDesktopRuntime() before importing the @/agentMode barrel. On mobile the barrel evaluates Node-only modules; importing before the modal's own desktop guard could reject during module load instead of showing the desktop-only notice. Mirrors the existing frame-log buttons. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- docs/agent-mode-and-tools.md | 23 ++ eslint.config.mjs | 2 + src/agentMode/index.ts | 2 + src/agentMode/ui/ReportIssueModal.tsx | 273 ++++++++++++++++++ src/constants.ts | 8 +- src/settings/model.test.ts | 48 ++- .../v2/components/AdvancedSettings.tsx | 80 ++++- src/utils/captureViewScreenshot.ts | 105 +++++++ src/utils/issueReport.test.ts | 164 +++++++++++ src/utils/issueReport.ts | 193 +++++++++++++ src/utils/opencodeLog.test.ts | 84 ++++++ src/utils/opencodeLog.ts | 77 +++++ 12 files changed, 1051 insertions(+), 8 deletions(-) create mode 100644 src/agentMode/ui/ReportIssueModal.tsx create mode 100644 src/utils/captureViewScreenshot.ts create mode 100644 src/utils/issueReport.test.ts create mode 100644 src/utils/issueReport.ts create mode 100644 src/utils/opencodeLog.test.ts create mode 100644 src/utils/opencodeLog.ts diff --git a/docs/agent-mode-and-tools.md b/docs/agent-mode-and-tools.md index ada0b2ba..2f9ce247 100644 --- a/docs/agent-mode-and-tools.md +++ b/docs/agent-mode-and-tools.md @@ -183,6 +183,29 @@ If you trust the agent and don't want to review every file change, enable **Auto --- +## Reporting an Issue + +When something goes wrong in Agent Mode, the **Report an Issue** button under **Settings → Copilot → Advanced → Agent Mode debugging** bundles everything a maintainer needs to diagnose it. + +Clicking it opens a short form where you describe what happened. When you submit, Copilot: + +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. + +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. + +### Including the OpenCode log + +When the **OpenCode** backend is active, the form shows an optional checkbox to include OpenCode's own log. It is **off by default** because OpenCode's log is shared across all of your OpenCode sessions, so it may contain activity from unrelated projects. Turn it on only when the issue involves the OpenCode backend itself. + +This feature is available on desktop only. + +--- + ## Related - [Copilot Plus and Self-Host](copilot-plus-and-self-host.md) — Licensing and memory diff --git a/eslint.config.mjs b/eslint.config.mjs index bc506a83..80af4c7d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -258,6 +258,8 @@ export default [ "src/utils/binaryPath.ts", "src/utils/nodeToolBinDirs.ts", "src/utils/rendererEventsShim.ts", + "src/utils/issueReport.ts", + "src/utils/opencodeLog.ts", ], rules: { "import/no-nodejs-modules": "off", diff --git a/src/agentMode/index.ts b/src/agentMode/index.ts index 74a297c9..355d9c03 100644 --- a/src/agentMode/index.ts +++ b/src/agentMode/index.ts @@ -68,6 +68,8 @@ export { ModelEnableList } from "./ui/ModelEnableList"; export type { ModelEnableGroup, ModelEnableRow } from "./ui/ModelEnableList"; export { PlanPreviewView, PLAN_PREVIEW_VIEW_TYPE } from "./ui/PlanPreviewView"; export type { PlanPreviewViewState } from "./ui/PlanPreviewView"; +export { ReportIssueModal } from "./ui/ReportIssueModal"; +export type { ReportIssueModalParams } from "./ui/ReportIssueModal"; export { getActiveBackendDescriptor, listBackendDescriptors } from "./backends/registry"; export { frameSink as acpFrameSink, setFrameSinkVaultBasePath } from "./session/debugSink"; export { getManagedSkills, SkillManager, SkillsSettings, useManagedSkills } from "./skills"; diff --git a/src/agentMode/ui/ReportIssueModal.tsx b/src/agentMode/ui/ReportIssueModal.tsx new file mode 100644 index 00000000..f8dab406 --- /dev/null +++ b/src/agentMode/ui/ReportIssueModal.tsx @@ -0,0 +1,273 @@ +import { frameSink } from "@/agentMode/session/debugSink"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Textarea } from "@/components/ui/textarea"; +import { logError, logInfo } from "@/logger"; +import { captureViewScreenshot } from "@/utils/captureViewScreenshot"; +import { isDesktopRuntime } from "@/utils/desktopRuntime"; +import { assembleReportBundle, type ReportEnvInfo } from "@/utils/issueReport"; +import { findLatestOpencodeLog } from "@/utils/opencodeLog"; +import { createPluginRoot } from "@/utils/react/createPluginRoot"; +import { cn } from "@/lib/utils"; +import { getSettings } from "@/settings/model"; +import { AlertTriangle } from "lucide-react"; +import { App, Modal, Notice, apiVersion } from "obsidian"; +import React from "react"; +import { Root } from "react-dom/client"; + +const OPENCODE_BACKEND_ID = "opencode"; + +export interface ReportIssueModalParams { + app: App; + /** + * Resolves the element to screenshot, called after this modal closes and just + * before capture. Lets the caller first dismiss any overlay (e.g. the Settings + * window) and reveal the Agent Mode pane so the shot is the chat surface, not + * the dialog. Returns `null` to skip the screenshot (e.g. no agent pane open). + */ + resolveCaptureTarget: () => Promise | HTMLElement | null; + /** Active backend id — gates the opencode-log option. */ + activeBackend: string; + /** Plugin version for the report's environment block. */ + pluginVersion: string; +} + +interface ElectronShell { + openPath?: (path: string) => Promise; + openExternal?: (url: string) => Promise; + showItemInFolder?: (path: string) => void; +} + +function getElectronShell(): ElectronShell | null { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const electron = require("electron") as + | { shell?: ElectronShell; remote?: { shell?: ElectronShell } } + | undefined; + return electron?.shell ?? electron?.remote?.shell ?? null; + } catch { + return null; + } +} + +function reportsRootDir(): string | null { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const os = require("node:os") as typeof import("node:os"); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const path = require("node:path") as typeof import("node:path"); + return path.join(os.tmpdir(), "obsidian-copilot", "reports"); + } catch { + return null; + } +} + +/** `YYYYMMDD-HHmmss` in local time, for a sortable, filesystem-safe folder name. */ +function formatTimestamp(date: Date): string { + const pad = (n: number) => String(n).padStart(2, "0"); + return ( + `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}` + + `-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}` + ); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => window.setTimeout(resolve, ms)); +} + +interface ReportIssueContentProps { + showOpencodeOption: boolean; + onSubmit: (note: string, includeOpencodeLog: boolean) => void; + onCancel: () => void; +} + +function ReportIssueContent({ showOpencodeOption, onSubmit, onCancel }: ReportIssueContentProps) { + const [note, setNote] = React.useState(""); + // Opt-in by default: the bundled log is opencode's newest *global* log, which + // may belong to an unrelated CLI/Desktop session, so don't attach it silently. + const [includeOpencodeLog, setIncludeOpencodeLog] = React.useState(false); + + return ( +
+
+ What went wrong? +