mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-02 13:40:46 +00:00
* feat(vscode-ide-companion): support /insight command Add ACP support for /insight progress streaming and report opening in the VSCode companion. Resolves #2023 * fix(cli): defer insight command runtime deps * test(cli): cover acp slash command allowlist * Revert "test(cli): cover acp slash command allowlist" This reverts commit3209274ab6. * Revert "fix(cli): defer insight command runtime deps" This reverts commit3b08491e46. * Reapply "fix(cli): defer insight command runtime deps" This reverts commit386c5c67d3. * Reapply "test(cli): cover acp slash command allowlist" This reverts commite2716140dd. * refactor(cli): simplify insight ACP integration - Replace `formatAcpInsightProgress` with `encodeAcpInsightProgress` using JSON payload - Move imports to top-level, no longer defer loading for non-ACP mode - Remove `INSIGHT_READY_MARKER` parsing from Session.ts as it's now handled by WebViewProvider * refactor: extract insight protocol markers to core package Move INSIGHT_PROGRESS_MARKER and INSIGHT_READY_MARKER from cli and vscode-ide-companion packages to @qwen-code/qwen-code-core for better shareability and to avoid duplication. Also extract ACP_ALLOWED_COMMANDS constant in Session.ts to improve readability and maintainability. * refactor(vscode-ide-companion): extract test helper to reduce webview mock duplication Introduce `setupAttachedProvider()` helper in WebViewProvider.test.ts to eliminate ~160 lines of repeated webview mock + provider setup code across 5 insight-related test cases. * feat(cli): 添加ACP执行模式到内置命令 当ACP启用时,将executionMode参数传递给所有内置命令, 使命令能够识别当前运行在ACP模式下并相应地调整行为。 test(cli): 为insight命令添加ACP进度消息流测试 新增测试验证insight命令在ACP模式下能够正确流式传输 进度消息,而不必等待生成完成。测试涵盖了从开始到完 成的整个进度更新过程。 refactor(core): 重构insight协议消息格式 将insight进度和就绪消息从基于标记字符串的格式 改为结构化的JSON格式,提供更好的类型安全和解析 可靠性。 feat(vscode-ide-companion): 支持新的insight消息协议 更新WebViewProvider以支持新的结构化insight消息协 议,能够正确解析和处理来自CLI的进度和就绪消息。 ``` * fix(vscode-ide-companion/insight): streamline insight progress handling Trim redundant CLI insight coverage around the ACP path. Keep the VS Code insight progress flow aligned with normalized slash commands and the updated progress layout. * fix(insight): restore slash commands after webview reload Cache available commands in the VS Code provider so webview restoration still exposes /insight without a manual login. Also remove the unused progress bar markup to keep the UI diff smaller. * Update packages/webui/src/index.ts Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> * fix(webui): remove duplicate insight card export --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
231 lines
6.1 KiB
TypeScript
231 lines
6.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen Code
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type { CommandContext, SlashCommand } from './types.js';
|
|
import { CommandKind } from './types.js';
|
|
import { MessageType } from '../types.js';
|
|
import type { HistoryItemInsightProgress } from '../types.js';
|
|
import { t } from '../../i18n/index.js';
|
|
import { join } from 'path';
|
|
import { StaticInsightGenerator } from '../../services/insight/generators/StaticInsightGenerator.js';
|
|
import {
|
|
createDebugLogger,
|
|
encodeInsightProgressMessage,
|
|
encodeInsightReadyMessage,
|
|
Storage,
|
|
} from '@qwen-code/qwen-code-core';
|
|
import open from 'open';
|
|
|
|
const logger = createDebugLogger('DataProcessor');
|
|
|
|
export const insightCommand: SlashCommand = {
|
|
name: 'insight',
|
|
get description() {
|
|
return t(
|
|
'generate personalized programming insights from your chat history',
|
|
);
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
action: async (context: CommandContext) => {
|
|
try {
|
|
context.ui.setDebugMessage(t('Generating insights...'));
|
|
|
|
const projectsDir = join(Storage.getRuntimeBaseDir(), 'projects');
|
|
if (!context.services.config) {
|
|
throw new Error('Config service is not available');
|
|
}
|
|
const insightGenerator = new StaticInsightGenerator(
|
|
context.services.config,
|
|
);
|
|
|
|
if (context.executionMode === 'acp') {
|
|
const pendingMessages: Array<{
|
|
messageType: 'info' | 'error';
|
|
content: string;
|
|
}> = [];
|
|
let isComplete = false;
|
|
let resume: (() => void) | null = null;
|
|
|
|
const flushResume = () => {
|
|
const resolve = resume;
|
|
if (!resolve) {
|
|
return;
|
|
}
|
|
resume = null;
|
|
resolve();
|
|
};
|
|
|
|
const pushMessage = (message: {
|
|
messageType: 'info' | 'error';
|
|
content: string;
|
|
}) => {
|
|
pendingMessages.push(message);
|
|
flushResume();
|
|
};
|
|
|
|
const streamMessages = async function* (): AsyncGenerator<
|
|
{ messageType: 'info' | 'error'; content: string },
|
|
void,
|
|
unknown
|
|
> {
|
|
while (!isComplete || pendingMessages.length > 0) {
|
|
if (pendingMessages.length === 0) {
|
|
await new Promise<void>((resolve) => {
|
|
resume = resolve;
|
|
});
|
|
}
|
|
|
|
while (pendingMessages.length > 0) {
|
|
const message = pendingMessages.shift();
|
|
if (message) {
|
|
yield message;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void (async () => {
|
|
try {
|
|
pushMessage({
|
|
messageType: 'info',
|
|
content: t('This may take a couple minutes. Sit tight!'),
|
|
});
|
|
pushMessage({
|
|
messageType: 'info',
|
|
content: encodeInsightProgressMessage(
|
|
t('Starting insight generation...'),
|
|
0,
|
|
),
|
|
});
|
|
|
|
const outputPath = await insightGenerator.generateStaticInsight(
|
|
projectsDir,
|
|
(stage, progress, detail) => {
|
|
pushMessage({
|
|
messageType: 'info',
|
|
content: encodeInsightProgressMessage(
|
|
stage,
|
|
progress,
|
|
detail,
|
|
),
|
|
});
|
|
},
|
|
);
|
|
|
|
pushMessage({
|
|
messageType: 'info',
|
|
content: encodeInsightReadyMessage(outputPath),
|
|
});
|
|
} catch (error) {
|
|
pushMessage({
|
|
messageType: 'error',
|
|
content: t('Failed to generate insights: {{error}}', {
|
|
error: (error as Error).message,
|
|
}),
|
|
});
|
|
logger.error('Insight generation error:', error);
|
|
} finally {
|
|
isComplete = true;
|
|
flushResume();
|
|
}
|
|
})();
|
|
|
|
return {
|
|
type: 'stream_messages',
|
|
messages: streamMessages(),
|
|
};
|
|
}
|
|
|
|
const updateProgress = (
|
|
stage: string,
|
|
progress: number,
|
|
detail?: string,
|
|
) => {
|
|
const progressItem: HistoryItemInsightProgress = {
|
|
type: MessageType.INSIGHT_PROGRESS,
|
|
progress: {
|
|
stage,
|
|
progress,
|
|
detail,
|
|
},
|
|
};
|
|
context.ui.setPendingItem(progressItem);
|
|
};
|
|
|
|
context.ui.addItem(
|
|
{
|
|
type: MessageType.INFO,
|
|
text: t('This may take a couple minutes. Sit tight!'),
|
|
},
|
|
Date.now(),
|
|
);
|
|
|
|
updateProgress(t('Starting insight generation...'), 0);
|
|
|
|
const outputPath = await insightGenerator.generateStaticInsight(
|
|
projectsDir,
|
|
updateProgress,
|
|
);
|
|
|
|
context.ui.setPendingItem(null);
|
|
|
|
context.ui.addItem(
|
|
{
|
|
type: MessageType.INFO,
|
|
text: t('Insight report generated successfully!'),
|
|
},
|
|
Date.now(),
|
|
);
|
|
|
|
try {
|
|
await open(outputPath);
|
|
|
|
context.ui.addItem(
|
|
{
|
|
type: MessageType.INFO,
|
|
text: t('Opening insights in your browser: {{path}}', {
|
|
path: outputPath,
|
|
}),
|
|
},
|
|
Date.now(),
|
|
);
|
|
} catch (browserError) {
|
|
logger.error('Failed to open browser automatically:', browserError);
|
|
|
|
context.ui.addItem(
|
|
{
|
|
type: MessageType.INFO,
|
|
text: t(
|
|
'Insights generated at: {{path}}. Please open this file in your browser.',
|
|
{
|
|
path: outputPath,
|
|
},
|
|
),
|
|
},
|
|
Date.now(),
|
|
);
|
|
}
|
|
|
|
context.ui.setDebugMessage(t('Insights ready.'));
|
|
return;
|
|
} catch (error) {
|
|
context.ui.setPendingItem(null);
|
|
|
|
context.ui.addItem(
|
|
{
|
|
type: MessageType.ERROR,
|
|
text: t('Failed to generate insights: {{error}}', {
|
|
error: (error as Error).message,
|
|
}),
|
|
},
|
|
Date.now(),
|
|
);
|
|
|
|
logger.error('Insight generation error:', error);
|
|
return;
|
|
}
|
|
},
|
|
};
|