-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Expand file tree
/
Copy pathbugCommand.ts
More file actions
194 lines (182 loc) · 7.14 KB
/
bugCommand.ts
File metadata and controls
194 lines (182 loc) · 7.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import open from 'open';
import process from 'node:process';
import {
type CommandContext,
type SlashCommand,
CommandKind,
} from './types.js';
import { MessageType } from '../types.js';
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
import { formatBytes } from '../utils/formatters.js';
import {
IdeClient,
getVersion,
INITIAL_HISTORY_LENGTH,
debugLogger,
} from '@google/gemini-cli-core';
import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js';
import { exportHistoryToFile } from '../utils/historyExportUtils.js';
import {
captureHeapSnapshot,
MEMORY_SNAPSHOT_AUTO_THRESHOLD_BYTES,
} from '../utils/memorySnapshot.js';
import { stat } from 'node:fs/promises';
import path from 'node:path';
export const bugCommand: SlashCommand = {
name: 'bug',
description: 'Submit a bug report',
kind: CommandKind.BUILT_IN,
autoExecute: false,
action: async (context: CommandContext, args?: string): Promise<void> => {
const bugDescription = (args || '').trim();
const agentContext = context.services.agentContext;
const config = agentContext?.config;
const osVersion = `${process.platform} ${process.version}`;
let sandboxEnv = 'no sandbox';
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
sandboxEnv = process.env['SANDBOX'].replace(/^gemini-(?:code-)?/, '');
} else if (process.env['SANDBOX'] === 'sandbox-exec') {
sandboxEnv = `sandbox-exec (${
process.env['SEATBELT_PROFILE'] || 'unknown'
})`;
}
const modelVersion = config?.getModel() || 'Unknown';
const cliVersion = await getVersion();
const memoryUsage = formatBytes(process.memoryUsage().rss);
const ideClient = await getIdeClientName(context);
const terminalName =
terminalCapabilityManager.getTerminalName() || 'Unknown';
const terminalBgColor =
terminalCapabilityManager.getTerminalBackgroundColor() || 'Unknown';
const kittyProtocol = terminalCapabilityManager.isKittyProtocolEnabled()
? 'Supported'
: 'Unsupported';
const authType = config?.getContentGeneratorConfig()?.authType || 'Unknown';
let info = `
* **CLI Version:** ${cliVersion}
* **Git Commit:** ${GIT_COMMIT_INFO}
* **Session ID:** ${config?.getSessionId() || 'Unknown'}
* **Operating System:** ${osVersion}
* **Sandbox Environment:** ${sandboxEnv}
* **Model Version:** ${modelVersion}
* **Auth Type:** ${authType}
* **Memory Usage:** ${memoryUsage}
* **Terminal Name:** ${terminalName}
* **Terminal Background:** ${terminalBgColor}
* **Kitty Keyboard Protocol:** ${kittyProtocol}
`;
if (ideClient) {
info += `* **IDE Client:** ${ideClient}\n`;
}
const chat = agentContext?.geminiClient?.getChat();
const history = chat?.getHistory() || [];
let historyFileMessage = '';
let problemValue = bugDescription;
if (history.length > INITIAL_HISTORY_LENGTH) {
const tempDir = config?.storage?.getProjectTempDir();
if (tempDir) {
const historyFileName = `bug-report-history-${Date.now()}.json`;
const historyFilePath = path.join(tempDir, historyFileName);
try {
await exportHistoryToFile({ history, filePath: historyFilePath });
historyFileMessage = `\n\n--------------------------------------------------------------------------------\n\n📄 **Chat History Exported**\nTo help us debug, we've exported your current chat history to:\n${historyFilePath}\n\nPlease consider attaching this file to your GitHub issue if you feel comfortable doing so.\n\n**Privacy Disclaimer:** Please do not upload any logs containing sensitive or private information that you are not comfortable sharing publicly.`;
problemValue += `\n\n[ACTION REQUIRED] 📎 PLEASE ATTACH THE EXPORTED CHAT HISTORY JSON FILE TO THIS ISSUE IF YOU FEEL COMFORTABLE SHARING IT.`;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
debugLogger.error(
`Failed to export chat history for bug report: ${errorMessage}`,
);
}
}
}
let bugReportUrl =
'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}&problem={problem}';
const bugCommandSettings = config?.getBugCommand();
if (bugCommandSettings?.urlTemplate) {
bugReportUrl = bugCommandSettings.urlTemplate;
}
bugReportUrl = bugReportUrl
.replace('{title}', encodeURIComponent(bugDescription))
.replace('{info}', encodeURIComponent(info))
.replace('{problem}', encodeURIComponent(problemValue));
context.ui.addItem(
{
type: MessageType.INFO,
text: `To submit your bug report, please open the following URL in your browser:\n${bugReportUrl}${historyFileMessage}`,
},
Date.now(),
);
try {
await open(bugReportUrl);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
context.ui.addItem(
{
type: MessageType.ERROR,
text: `Could not open URL in browser: ${errorMessage}`,
},
Date.now(),
);
}
const rss = process.memoryUsage().rss;
const tempDir = config?.storage?.getProjectTempDir();
if (rss >= MEMORY_SNAPSHOT_AUTO_THRESHOLD_BYTES && tempDir) {
const snapshotPath = path.join(
tempDir,
`bug-memory-${Date.now()}.heapsnapshot`,
);
context.ui.addItem(
{
type: MessageType.INFO,
text: `High memory usage detected (${formatBytes(rss)}). Capturing V8 heap snapshot to ${snapshotPath}.\nThis can take 20+ seconds and the CLI may be temporarily unresponsive; please do not exit.`,
},
Date.now(),
);
try {
const startedAt = Date.now();
await captureHeapSnapshot(snapshotPath);
const durationMs = Date.now() - startedAt;
let sizeText = '';
try {
const { size } = await stat(snapshotPath);
sizeText = ` (${formatBytes(size)})`;
} catch {
// Size reporting is best-effort; the snapshot itself was captured successfully.
}
context.ui.addItem(
{
type: MessageType.INFO,
text: `Heap snapshot saved${sizeText} in ${durationMs}ms:\n${snapshotPath}\n\nConsider attaching it to your bug report only if it does not contain sensitive information.`,
},
Date.now(),
);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
debugLogger.error(
`Failed to capture heap snapshot for bug report: ${errorMessage}`,
);
context.ui.addItem(
{
type: MessageType.ERROR,
text: `Failed to capture heap snapshot: ${errorMessage}`,
},
Date.now(),
);
}
}
},
};
async function getIdeClientName(context: CommandContext) {
if (!context.services.agentContext?.config.getIdeMode()) {
return '';
}
const ideClient = await IdeClient.getInstance();
return ideClient.getDetectedIdeDisplayName() ?? '';
}