feat: include subagent trajectories in chat save and bug reports

This commit is contained in:
Aishanee Shah
2026-05-11 20:45:34 +00:00
parent 1a024f30a3
commit 413416d587
9 changed files with 214 additions and 6 deletions
@@ -157,6 +157,7 @@ describe('bugCommand', () => {
{ role: 'user', parts: [{ text: 'hello' }] },
{ role: 'model', parts: [{ text: 'hi' }] },
];
const mockGetSubagentTrajectories = vi.fn().mockResolvedValue({});
const mockContext = createMockCommandContext({
services: {
agentContext: {
@@ -173,6 +174,7 @@ describe('bugCommand', () => {
geminiClient: {
getChat: () => ({
getHistory: () => history,
getSubagentTrajectories: mockGetSubagentTrajectories,
}),
},
},
@@ -189,6 +191,7 @@ describe('bugCommand', () => {
expect(exportHistoryToFile).toHaveBeenCalledWith({
history,
filePath: expectedPath,
trajectories: {},
});
const addItemCall = vi.mocked(mockContext.ui.addItem).mock.calls[0];
+6 -1
View File
@@ -88,7 +88,12 @@ export const bugCommand: SlashCommand = {
const historyFileName = `bug-report-history-${Date.now()}.json`;
const historyFilePath = path.join(tempDir, historyFileName);
try {
await exportHistoryToFile({ history, filePath: historyFilePath });
const trajectories = await chat?.getSubagentTrajectories();
await exportHistoryToFile({
history,
filePath: historyFilePath,
trajectories,
});
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) {
@@ -63,6 +63,7 @@ describe('chatCommand', () => {
mockGetHistory = vi.fn().mockReturnValue([]);
mockGetChat = vi.fn().mockReturnValue({
getHistory: mockGetHistory,
getSubagentTrajectories: vi.fn().mockResolvedValue({}),
});
mockSaveCheckpoint = vi.fn().mockResolvedValue(undefined);
mockLoadCheckpoint = vi.fn().mockResolvedValue({ history: [] });
@@ -230,7 +231,7 @@ describe('chatCommand', () => {
expect(mockCheckpointExists).not.toHaveBeenCalled(); // Should skip existence check
expect(mockSaveCheckpoint).toHaveBeenCalledWith(
{ history, authType: AuthType.LOGIN_WITH_GOOGLE },
{ history, authType: AuthType.LOGIN_WITH_GOOGLE, trajectories: {} },
tag,
);
expect(result).toEqual({
@@ -465,6 +466,7 @@ describe('chatCommand', () => {
expect(mockExport).toHaveBeenCalledWith({
history: mockHistory,
filePath: expectedPath,
trajectories: {},
});
expect(result).toEqual({
type: 'message',
@@ -480,6 +482,7 @@ describe('chatCommand', () => {
expect(mockExport).toHaveBeenCalledWith({
history: mockHistory,
filePath: expectedPath,
trajectories: {},
});
expect(result).toEqual({
type: 'message',
@@ -495,6 +498,7 @@ describe('chatCommand', () => {
expect(mockExport).toHaveBeenCalledWith({
history: mockHistory,
filePath: expectedPath,
trajectories: {},
});
expect(result).toEqual({
type: 'message',
@@ -545,6 +549,7 @@ describe('chatCommand', () => {
expect(mockExport).toHaveBeenCalledWith({
history: mockHistory,
filePath: expectedPath,
trajectories: {},
});
});
@@ -555,6 +560,7 @@ describe('chatCommand', () => {
expect(mockExport).toHaveBeenCalledWith({
history: mockHistory,
filePath: expectedPath,
trajectories: {},
});
});
});
+4 -2
View File
@@ -139,7 +139,8 @@ const saveCommand: SlashCommand = {
const history = chat.getHistory();
if (history.length > INITIAL_HISTORY_LENGTH) {
const authType = config?.getContentGeneratorConfig()?.authType;
await logger.saveCheckpoint({ history, authType }, tag);
const trajectories = await chat.getSubagentTrajectories();
await logger.saveCheckpoint({ history, authType, trajectories }, tag);
return {
type: 'message',
messageType: 'info',
@@ -324,7 +325,8 @@ const shareCommand: SlashCommand = {
}
try {
await exportHistoryToFile({ history, filePath });
const trajectories = await chat.getSubagentTrajectories();
await exportHistoryToFile({ history, filePath, trajectories });
return {
type: 'message',
messageType: 'info',
@@ -7,6 +7,7 @@
import * as fsPromises from 'node:fs/promises';
import path from 'node:path';
import type { Content } from '@google/genai';
import type { ConversationRecord } from '@google/gemini-cli-core';
/**
* Serializes chat history to a Markdown string.
@@ -53,6 +54,7 @@ export function serializeHistoryToMarkdown(
export interface ExportHistoryOptions {
history: readonly Content[];
filePath: string;
trajectories?: Record<string, ConversationRecord>;
}
/**
@@ -61,12 +63,16 @@ export interface ExportHistoryOptions {
export async function exportHistoryToFile(
options: ExportHistoryOptions,
): Promise<void> {
const { history, filePath } = options;
const { history, filePath, trajectories } = options;
const extension = path.extname(filePath).toLowerCase();
let content: string;
if (extension === '.json') {
content = JSON.stringify(history, null, 2);
if (trajectories && Object.keys(trajectories).length > 0) {
content = JSON.stringify({ history, trajectories }, null, 2);
} else {
content = JSON.stringify(history, null, 2);
}
} else if (extension === '.md') {
content = serializeHistoryToMarkdown(history);
} else {