mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 15:31:27 +00:00
feat: support /compress and /summary commands for non-interactive & ACP
integration
This commit is contained in:
parent
cebe0448d0
commit
8aceddffa2
12 changed files with 720 additions and 147 deletions
|
|
@ -26,6 +26,8 @@ export const summaryCommand: SlashCommand = {
|
|||
action: async (context): Promise<SlashCommandActionReturn> => {
|
||||
const { config } = context.services;
|
||||
const { ui } = context;
|
||||
const executionMode = context.executionMode ?? 'interactive';
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
|
@ -43,8 +45,8 @@ export const summaryCommand: SlashCommand = {
|
|||
};
|
||||
}
|
||||
|
||||
// Check if already generating summary
|
||||
if (ui.pendingItem) {
|
||||
// Check if already generating summary (interactive UI only)
|
||||
if (executionMode === 'interactive' && ui.pendingItem) {
|
||||
ui.addItem(
|
||||
{
|
||||
type: 'error' as const,
|
||||
|
|
@ -63,29 +65,15 @@ export const summaryCommand: SlashCommand = {
|
|||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const generateSummaryMarkdown = async (): Promise<string> => {
|
||||
// Get the current chat history
|
||||
const chat = geminiClient.getChat();
|
||||
const history = chat.getHistory();
|
||||
|
||||
if (history.length <= 2) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: t('No conversation found to summarize.'),
|
||||
};
|
||||
throw new Error(t('No conversation found to summarize.'));
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
const pendingMessage: HistoryItemSummary = {
|
||||
type: 'summary',
|
||||
summary: {
|
||||
isPending: true,
|
||||
stage: 'generating',
|
||||
},
|
||||
};
|
||||
ui.setPendingItem(pendingMessage);
|
||||
|
||||
// Build the conversation context for summary generation
|
||||
const conversationContext = history.map((message) => ({
|
||||
role: message.role,
|
||||
|
|
@ -121,19 +109,21 @@ export const summaryCommand: SlashCommand = {
|
|||
|
||||
if (!markdownSummary) {
|
||||
throw new Error(
|
||||
'Failed to generate summary - no text content received from LLM response',
|
||||
t(
|
||||
'Failed to generate summary - no text content received from LLM response',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Update loading message to show saving progress
|
||||
ui.setPendingItem({
|
||||
type: 'summary',
|
||||
summary: {
|
||||
isPending: true,
|
||||
stage: 'saving',
|
||||
},
|
||||
});
|
||||
return markdownSummary;
|
||||
};
|
||||
|
||||
const saveSummaryToDisk = async (
|
||||
markdownSummary: string,
|
||||
): Promise<{
|
||||
filePathForDisplay: string;
|
||||
fullPath: string;
|
||||
}> => {
|
||||
// Ensure .qwen directory exists
|
||||
const projectRoot = config.getProjectRoot();
|
||||
const qwenDir = path.join(projectRoot, '.qwen');
|
||||
|
|
@ -155,25 +145,46 @@ export const summaryCommand: SlashCommand = {
|
|||
|
||||
await fsPromises.writeFile(summaryPath, summaryContent, 'utf8');
|
||||
|
||||
// Clear pending item and show success message
|
||||
return {
|
||||
filePathForDisplay: '.qwen/PROJECT_SUMMARY.md',
|
||||
fullPath: summaryPath,
|
||||
};
|
||||
};
|
||||
|
||||
const emitInteractivePending = (stage: 'generating' | 'saving') => {
|
||||
if (executionMode !== 'interactive') {
|
||||
return;
|
||||
}
|
||||
const pendingMessage: HistoryItemSummary = {
|
||||
type: 'summary',
|
||||
summary: {
|
||||
isPending: true,
|
||||
stage,
|
||||
},
|
||||
};
|
||||
ui.setPendingItem(pendingMessage);
|
||||
};
|
||||
|
||||
const completeInteractive = (filePathForDisplay: string) => {
|
||||
if (executionMode !== 'interactive') {
|
||||
return;
|
||||
}
|
||||
ui.setPendingItem(null);
|
||||
const completedSummaryItem: HistoryItemSummary = {
|
||||
type: 'summary',
|
||||
summary: {
|
||||
isPending: false,
|
||||
stage: 'completed',
|
||||
filePath: '.qwen/PROJECT_SUMMARY.md',
|
||||
filePath: filePathForDisplay,
|
||||
},
|
||||
};
|
||||
ui.addItem(completedSummaryItem, Date.now());
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: '', // Empty content since we show the message in UI component
|
||||
};
|
||||
} catch (error) {
|
||||
// Clear pending item on error
|
||||
const failInteractive = (error: unknown) => {
|
||||
if (executionMode !== 'interactive') {
|
||||
return;
|
||||
}
|
||||
ui.setPendingItem(null);
|
||||
ui.addItem(
|
||||
{
|
||||
|
|
@ -187,6 +198,96 @@ export const summaryCommand: SlashCommand = {
|
|||
},
|
||||
Date.now(),
|
||||
);
|
||||
};
|
||||
|
||||
if (executionMode === 'acp') {
|
||||
const messages = async function* () {
|
||||
try {
|
||||
emitInteractivePending('generating');
|
||||
yield {
|
||||
messageType: 'info' as const,
|
||||
content: t('Generating project summary...'),
|
||||
};
|
||||
|
||||
const markdownSummary = await generateSummaryMarkdown();
|
||||
|
||||
yield {
|
||||
messageType: 'info' as const,
|
||||
content: t('Saving project summary...'),
|
||||
};
|
||||
const { filePathForDisplay } =
|
||||
await saveSummaryToDisk(markdownSummary);
|
||||
|
||||
completeInteractive(filePathForDisplay);
|
||||
yield {
|
||||
messageType: 'info' as const,
|
||||
content: t('Saved project summary to {{filePathForDisplay}}.', {
|
||||
filePathForDisplay,
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
failInteractive(error);
|
||||
yield {
|
||||
messageType: 'error' as const,
|
||||
content: t(
|
||||
'Failed to generate project context summary: {{error}}',
|
||||
{
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'stream_messages',
|
||||
messages: messages(),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
emitInteractivePending('generating');
|
||||
const markdownSummary = await generateSummaryMarkdown();
|
||||
emitInteractivePending('saving');
|
||||
const { filePathForDisplay } = await saveSummaryToDisk(markdownSummary);
|
||||
completeInteractive(filePathForDisplay);
|
||||
|
||||
if (executionMode === 'non_interactive') {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: `Saved project summary to ${filePathForDisplay}.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Interactive mode: UI components already display progress and completion.
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: '',
|
||||
};
|
||||
} catch (error) {
|
||||
// Convert "no conversation" into a clean info message for non-interactive / interactive modes.
|
||||
const msg =
|
||||
error instanceof Error ? error.message : t('Unknown error occurred.');
|
||||
|
||||
if (msg === t('No conversation found to summarize.')) {
|
||||
if (executionMode === 'interactive') {
|
||||
// Keep interactive behavior: show as a normal message.
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: msg,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: msg,
|
||||
};
|
||||
}
|
||||
|
||||
failInteractive(error);
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue