fix(tests): add test for disabling all tools in CcwToolsMcpCard component

fix(api): handle empty enabledTools array and improve default tool logic
fix(queueScheduler): ignore network errors in loadInitialState
fix(auth): ensure token generation handles max session capacity
chore(dependencies): update package requirements to use compatible version specifiers
chore(tests): add new test cases for incremental indexer and migrations
This commit is contained in:
catlog22
2026-03-02 11:26:15 +08:00
parent b36a46d59d
commit 8ad283086b
7 changed files with 159 additions and 84 deletions

View File

@@ -41,6 +41,43 @@ describe('CcwToolsMcpCard', () => {
vi.clearAllMocks();
});
it('disables all tools when clicking "Disable All" button', async () => {
const { CcwToolsMcpCard } = await import('./CcwToolsMcpCard');
const onUpdateConfigMock = vi.fn();
render(
<CcwToolsMcpCard
target="codex"
isInstalled={true}
enabledTools={['write_file', 'read_file', 'edit_file']}
onToggleTool={vi.fn()}
onUpdateConfig={onUpdateConfigMock}
onInstall={vi.fn()}
/>,
{ locale: 'en' }
);
const user = userEvent.setup();
// Expand the card
await act(async () => {
await user.click(screen.getByText(/CCW MCP Server|mcp\.ccw\.title/i));
});
// Find and click "Disable All" button
const disableAllButton = screen.getByRole('button', {
name: /Disable All|mcp\.ccw\.actions\.disableAll/i,
});
expect(disableAllButton).toBeEnabled();
await act(async () => {
await user.click(disableAllButton);
});
// Verify onUpdateConfig was called with empty enabledTools array
await waitFor(() => {
expect(onUpdateConfigMock).toHaveBeenCalledWith({ enabledTools: [] });
});
});
it('preserves enabledTools when saving config (Codex)', async () => {
const { CcwToolsMcpCard } = await import('./CcwToolsMcpCard');
const updateCodexMock = vi.mocked(apiMock.updateCcwConfigForCodex);

View File

@@ -4371,7 +4371,9 @@ function buildCcwMcpServerConfig(config: {
}): { command: string; args: string[]; env: Record<string, string> } {
const env: Record<string, string> = {};
if (config.enabledTools && config.enabledTools.length > 0) {
// Only use default when enabledTools is undefined (not provided)
// When enabledTools is an empty array, set to empty string to disable all tools
if (config.enabledTools !== undefined) {
env.CCW_ENABLED_TOOLS = config.enabledTools.join(',');
} else {
env.CCW_ENABLED_TOOLS = 'write_file,edit_file,read_file,core_memory,ask_question,smart_search';
@@ -4455,11 +4457,20 @@ export async function fetchCcwMcpConfig(currentProjectPath?: string): Promise<Cc
}
// Parse enabled tools from env
// Note: CCW_ENABLED_TOOLS can be empty string (all tools disabled), 'all' (default set), or comma-separated list
const env = ccwServer.env || {};
const enabledToolsStr = env.CCW_ENABLED_TOOLS || 'all';
const enabledTools = enabledToolsStr === 'all'
? ['write_file', 'edit_file', 'read_file', 'core_memory', 'ask_question']
: enabledToolsStr.split(',').map((t: string) => t.trim());
const enabledToolsStr = env.CCW_ENABLED_TOOLS;
let enabledTools: string[];
if (enabledToolsStr === undefined || enabledToolsStr === null) {
// No setting = use default tools
enabledTools = ['write_file', 'edit_file', 'read_file', 'core_memory', 'ask_question', 'smart_search'];
} else if (enabledToolsStr === '' || enabledToolsStr === 'all') {
// Empty string = all tools disabled, 'all' = default set (for backward compatibility)
enabledTools = enabledToolsStr === '' ? [] : ['write_file', 'edit_file', 'read_file', 'core_memory', 'ask_question', 'smart_search'];
} else {
// Comma-separated list
enabledTools = enabledToolsStr.split(',').map((t: string) => t.trim()).filter(Boolean);
}
return {
isInstalled: true,
@@ -4601,10 +4612,19 @@ export async function fetchCcwMcpConfigForCodex(): Promise<CcwMcpConfig> {
}
const env = ccwServer.env || {};
const enabledToolsStr = env.CCW_ENABLED_TOOLS || 'all';
const enabledTools = enabledToolsStr === 'all'
? ['write_file', 'edit_file', 'read_file', 'core_memory', 'ask_question', 'smart_search']
: enabledToolsStr.split(',').map((t: string) => t.trim());
// Note: CCW_ENABLED_TOOLS can be empty string (all tools disabled), 'all' (default set), or comma-separated list
const enabledToolsStr = env.CCW_ENABLED_TOOLS;
let enabledTools: string[];
if (enabledToolsStr === undefined || enabledToolsStr === null) {
// No setting = use default tools
enabledTools = ['write_file', 'edit_file', 'read_file', 'core_memory', 'ask_question', 'smart_search'];
} else if (enabledToolsStr === '' || enabledToolsStr === 'all') {
// Empty string = all tools disabled, 'all' = default set (for backward compatibility)
enabledTools = enabledToolsStr === '' ? [] : ['write_file', 'edit_file', 'read_file', 'core_memory', 'ask_question', 'smart_search'];
} else {
// Comma-separated list
enabledTools = enabledToolsStr.split(',').map((t: string) => t.trim()).filter(Boolean);
}
return {
isInstalled: true,
@@ -4630,7 +4650,9 @@ function buildCcwMcpServerConfigForCodex(config: {
}): { command: string; args: string[]; env: Record<string, string> } {
const env: Record<string, string> = {};
if (config.enabledTools && config.enabledTools.length > 0) {
// Only use default when enabledTools is undefined (not provided)
// When enabledTools is an empty array, set to empty string to disable all tools
if (config.enabledTools !== undefined) {
env.CCW_ENABLED_TOOLS = config.enabledTools.join(',');
} else {
env.CCW_ENABLED_TOOLS = 'write_file,edit_file,read_file,core_memory,ask_question,smart_search';

View File

@@ -195,9 +195,21 @@ export const useQueueSchedulerStore = create<QueueSchedulerStore>()(
'loadInitialState'
);
} catch (error) {
// Silently ignore network errors (backend not connected)
// Only log non-network errors
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('[QueueScheduler] loadInitialState error:', message);
set({ error: message }, false, 'loadInitialState/error');
const isNetworkError =
message.includes('Failed to fetch') ||
message.includes('NetworkError') ||
message.includes('Network request failed') ||
message.includes('ERR_CONNECTION_REFUSED') ||
message.includes('ERR_CONNECTION_RESET');
if (!isNetworkError) {
console.error('[QueueScheduler] loadInitialState error:', message);
set({ error: message }, false, 'loadInitialState/error');
}
// For network errors, keep state as-is without showing error
}
},

View File

@@ -100,14 +100,16 @@ export async function handleAuthRoutes(ctx: RouteContext): Promise<boolean> {
} else {
// Batch token response (pool pattern)
const tokens = tokenManager.generateTokens(sessionId, count);
const firstToken = tokens[0];
// If no tokens generated (session at max capacity), force generate one
const firstToken = tokens.length > 0 ? tokens[0] : tokenManager.generateToken(sessionId);
// Set header and cookie with first token for compatibility
res.setHeader('X-CSRF-Token', firstToken);
setCsrfCookie(res, firstToken, 15 * 60);
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({
tokens,
tokens: tokens.length > 0 ? tokens : [firstToken],
expiresIn: 15 * 60, // seconds
}));
}