mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 23:42:03 +00:00
Merge branch 'main' into fix/pr2371-btw-complete
This commit is contained in:
commit
905f2c3f36
108 changed files with 5064 additions and 965 deletions
|
|
@ -268,7 +268,7 @@ describe('AppContainer State Management', () => {
|
|||
// Mock config's getTargetDir to return consistent workspace directory
|
||||
vi.spyOn(mockConfig, 'getTargetDir').mockReturnValue('/test/workspace');
|
||||
|
||||
// Mock GeminiClient to prevent unhandled errors from TaskTool.refreshSubagents
|
||||
// Mock GeminiClient to prevent unhandled errors from AgentTool.refreshSubagents
|
||||
const mockGeminiClient: Partial<GeminiClient> = {
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
setTools: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -278,7 +278,7 @@ describe('AppContainer State Management', () => {
|
|||
mockGeminiClient as GeminiClient,
|
||||
);
|
||||
|
||||
// Mock SubagentManager to prevent errors during TaskTool initialization
|
||||
// Mock SubagentManager to prevent errors during AgentTool initialization
|
||||
const mockSubagentManager: Partial<SubagentManager> = {
|
||||
listSubagents: vi.fn().mockResolvedValue([]),
|
||||
addChangeListener: vi.fn(),
|
||||
|
|
|
|||
66
packages/cli/src/ui/commands/insightCommand.test.ts
Normal file
66
packages/cli/src/ui/commands/insightCommand.test.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Code
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import path from 'path';
|
||||
import open from 'open';
|
||||
import { Storage } from '@qwen-code/qwen-code-core';
|
||||
import { insightCommand } from './insightCommand.js';
|
||||
import type { CommandContext } from './types.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
|
||||
const mockGenerateStaticInsight = vi.fn();
|
||||
|
||||
vi.mock('../../services/insight/generators/StaticInsightGenerator.js', () => ({
|
||||
StaticInsightGenerator: vi.fn(() => ({
|
||||
generateStaticInsight: mockGenerateStaticInsight,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('open', () => ({
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('insightCommand', () => {
|
||||
let mockContext: CommandContext;
|
||||
|
||||
beforeEach(() => {
|
||||
Storage.setRuntimeBaseDir(path.resolve('runtime-output'));
|
||||
mockGenerateStaticInsight.mockResolvedValue(
|
||||
path.resolve('runtime-output', 'insights', 'insight-2026-03-05.html'),
|
||||
);
|
||||
vi.mocked(open).mockResolvedValue(undefined as never);
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {} as CommandContext['services']['config'],
|
||||
},
|
||||
ui: {
|
||||
addItem: vi.fn(),
|
||||
setPendingItem: vi.fn(),
|
||||
setDebugMessage: vi.fn(),
|
||||
},
|
||||
} as unknown as CommandContext);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Storage.setRuntimeBaseDir(null);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses runtime base dir to locate projects directory', async () => {
|
||||
if (!insightCommand.action) {
|
||||
throw new Error('insight command must have action');
|
||||
}
|
||||
|
||||
await insightCommand.action(mockContext, '');
|
||||
|
||||
expect(mockGenerateStaticInsight).toHaveBeenCalledWith(
|
||||
path.join(Storage.getRuntimeBaseDir(), 'projects'),
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -10,9 +10,8 @@ import { MessageType } from '../types.js';
|
|||
import type { HistoryItemInsightProgress } from '../types.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { join } from 'path';
|
||||
import os from 'os';
|
||||
import { StaticInsightGenerator } from '../../services/insight/generators/StaticInsightGenerator.js';
|
||||
import { createDebugLogger } from '@qwen-code/qwen-code-core';
|
||||
import { createDebugLogger, Storage } from '@qwen-code/qwen-code-core';
|
||||
import open from 'open';
|
||||
|
||||
const logger = createDebugLogger('DataProcessor');
|
||||
|
|
@ -29,7 +28,7 @@ export const insightCommand: SlashCommand = {
|
|||
try {
|
||||
context.ui.setDebugMessage(t('Generating insights...'));
|
||||
|
||||
const projectsDir = join(os.homedir(), '.qwen', 'projects');
|
||||
const projectsDir = join(Storage.getRuntimeBaseDir(), 'projects');
|
||||
if (!context.services.config) {
|
||||
throw new Error('Config service is not available');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
|||
import { TodoDisplay } from '../TodoDisplay.js';
|
||||
import type {
|
||||
TodoResultDisplay,
|
||||
TaskResultDisplay,
|
||||
AgentResultDisplay,
|
||||
PlanResultDisplay,
|
||||
AnsiOutput,
|
||||
Config,
|
||||
|
|
@ -50,7 +50,7 @@ type DisplayRendererResult =
|
|||
| { type: 'plan'; data: PlanResultDisplay }
|
||||
| { type: 'string'; data: string }
|
||||
| { type: 'diff'; data: { fileDiff: string; fileName: string } }
|
||||
| { type: 'task'; data: TaskResultDisplay }
|
||||
| { type: 'task'; data: AgentResultDisplay }
|
||||
| { type: 'ansi'; data: AnsiOutput };
|
||||
|
||||
/**
|
||||
|
|
@ -98,7 +98,7 @@ const useResultDisplayRenderer = (
|
|||
) {
|
||||
return {
|
||||
type: 'task',
|
||||
data: resultDisplay as TaskResultDisplay,
|
||||
data: resultDisplay as AgentResultDisplay,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ const PlanResultRenderer: React.FC<{
|
|||
* Component to render subagent execution results
|
||||
*/
|
||||
const SubagentExecutionRenderer: React.FC<{
|
||||
data: TaskResultDisplay;
|
||||
data: AgentResultDisplay;
|
||||
availableHeight?: number;
|
||||
childWidth: number;
|
||||
config: Config;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import type {
|
||||
TaskResultDisplay,
|
||||
AgentResultDisplay,
|
||||
AgentStatsSummary,
|
||||
Config,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
|
|
@ -20,7 +20,7 @@ import { ToolConfirmationMessage } from '../../messages/ToolConfirmationMessage.
|
|||
export type DisplayMode = 'compact' | 'default' | 'verbose';
|
||||
|
||||
export interface AgentExecutionDisplayProps {
|
||||
data: TaskResultDisplay;
|
||||
data: AgentResultDisplay;
|
||||
availableHeight?: number;
|
||||
childWidth: number;
|
||||
config: Config;
|
||||
|
|
@ -28,7 +28,7 @@ export interface AgentExecutionDisplayProps {
|
|||
|
||||
const getStatusColor = (
|
||||
status:
|
||||
| TaskResultDisplay['status']
|
||||
| AgentResultDisplay['status']
|
||||
| 'executing'
|
||||
| 'success'
|
||||
| 'awaiting_approval',
|
||||
|
|
@ -50,7 +50,7 @@ const getStatusColor = (
|
|||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: TaskResultDisplay['status']) => {
|
||||
const getStatusText = (status: AgentResultDisplay['status']) => {
|
||||
switch (status) {
|
||||
case 'running':
|
||||
return 'Running';
|
||||
|
|
@ -301,7 +301,7 @@ const TaskPromptSection: React.FC<{
|
|||
* Status dot component with similar height as text
|
||||
*/
|
||||
const StatusDot: React.FC<{
|
||||
status: TaskResultDisplay['status'];
|
||||
status: AgentResultDisplay['status'];
|
||||
}> = ({ status }) => (
|
||||
<Box marginLeft={1} marginRight={1}>
|
||||
<Text color={getStatusColor(status)}>●</Text>
|
||||
|
|
@ -312,7 +312,7 @@ const StatusDot: React.FC<{
|
|||
* Status indicator component
|
||||
*/
|
||||
const StatusIndicator: React.FC<{
|
||||
status: TaskResultDisplay['status'];
|
||||
status: AgentResultDisplay['status'];
|
||||
}> = ({ status }) => {
|
||||
const color = getStatusColor(status);
|
||||
const text = getStatusText(status);
|
||||
|
|
@ -323,7 +323,7 @@ const StatusIndicator: React.FC<{
|
|||
* Tool calls list - format consistent with ToolInfo in ToolMessage.tsx
|
||||
*/
|
||||
const ToolCallsList: React.FC<{
|
||||
toolCalls: TaskResultDisplay['toolCalls'];
|
||||
toolCalls: AgentResultDisplay['toolCalls'];
|
||||
displayMode: DisplayMode;
|
||||
}> = ({ toolCalls, displayMode }) => {
|
||||
const calls = toolCalls || [];
|
||||
|
|
@ -435,7 +435,7 @@ const ToolCallItem: React.FC<{
|
|||
* Execution summary details component
|
||||
*/
|
||||
const ExecutionSummaryDetails: React.FC<{
|
||||
data: TaskResultDisplay;
|
||||
data: AgentResultDisplay;
|
||||
displayMode: DisplayMode;
|
||||
}> = ({ data, displayMode: _displayMode }) => {
|
||||
const stats = data.executionSummary;
|
||||
|
|
@ -505,7 +505,7 @@ const ToolUsageStats: React.FC<{
|
|||
* Results section for completed executions - matches the clean layout from the image
|
||||
*/
|
||||
const ResultsSection: React.FC<{
|
||||
data: TaskResultDisplay;
|
||||
data: AgentResultDisplay;
|
||||
displayMode: DisplayMode;
|
||||
}> = ({ data, displayMode }) => (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
|
|
|
|||
|
|
@ -181,10 +181,7 @@ function calculateFileStats(records: ChatRecord[]): FileOperationStats {
|
|||
let filePath: string;
|
||||
if (typeof display.fileName === 'string') {
|
||||
// Prefer args.file_path for full path, fallback to fileName (which may be basename)
|
||||
filePath =
|
||||
(args?.['file_path'] as string) ||
|
||||
(args?.['absolute_path'] as string) ||
|
||||
display.fileName;
|
||||
filePath = (args?.['file_path'] as string) || display.fileName;
|
||||
} else {
|
||||
// Fallback if fileName is not a string
|
||||
filePath = 'unknown';
|
||||
|
|
|
|||
|
|
@ -100,6 +100,10 @@ function formatToolDescription(
|
|||
const invocation = tool.build(args);
|
||||
return invocation.getDescription();
|
||||
} catch {
|
||||
// Fallback: use the description arg directly if available
|
||||
if (typeof args['description'] === 'string') {
|
||||
return args['description'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue