Skip to content

Commit 3d862e6

Browse files
author
catlog22
committed
feat: 添加更好的 SQLite3 模块加载和错误处理,更新相关组件以支持项目路径
1 parent 5cdbb43 commit 3d862e6

15 files changed

Lines changed: 541 additions & 38 deletions

File tree

.claude/settings.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"hooks": {
3+
"UserPromptSubmit": [
4+
{
5+
"hooks": [
6+
{
7+
"type": "command",
8+
"command": "node -e \"const p=JSON.parse(process.env.HOOK_INPUT||\\\"{}\\\");const prompt=(p.user_prompt||\\\"\\\").trim();if(/^ccw\\s+session\\s+init/i.test(prompt)||/^\\/workflow:session:start/i.test(prompt)||/^\\/workflow:session\\s+init/i.test(prompt)){const cp=require(\\\"child_process\\\");const payload=JSON.stringify({type:\\\"SESSION_CREATED\\\",prompt:prompt,timestamp:Date.now(),project:process.env.CLAUDE_PROJECT_DIR||process.cwd()});cp.spawnSync(\\\"curl\\\",[\\\"-s\\\",\\\"-X\\\",\\\"POST\\\",\\\"-H\\\",\\\"Content-Type: application/json\\\",\\\"-d\\\",payload,\\\"http://localhost:3456/api/hook\\\"],{stdio:\\\"inherit\\\",shell:true})}\""
9+
}
10+
]
11+
},
12+
{
13+
"hooks": [
14+
{
15+
"type": "command",
16+
"command": "node -e \"const p=JSON.parse(process.env.HOOK_INPUT||\\\"{}\\\");const prompt=(p.user_prompt||\\\"\\\").toLowerCase();if(prompt===\\\"status\\\"||prompt===\\\"ccw status\\\"||prompt.startsWith(\\\"/status\\\")){const cp=require(\\\"child_process\\\");cp.spawnSync(\\\"curl\\\",[\\\"-s\\\",\\\"http://localhost:3456/api/status/all\\\"],{stdio:\\\"inherit\\\"})}\""
17+
}
18+
]
19+
}
20+
]
21+
}
22+
}

FAQ.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,32 @@ dist/
520520

521521
## 🔧 Troubleshooting
522522

523+
### `better-sqlite3` NODE_MODULE_VERSION mismatch
524+
525+
**Error message**:
526+
```
527+
Error: The module '.../better_sqlite3.node' was compiled against a different Node.js version
528+
using NODE_MODULE_VERSION XX. This version of Node.js requires NODE_MODULE_VERSION YY.
529+
```
530+
531+
**Cause**: The `better-sqlite3` native module was compiled for a different Node.js version than the one you're running. This commonly happens when:
532+
- You installed dependencies with one Node.js version and later switched versions
533+
- Prebuilt binaries don't match your Node.js version
534+
535+
**Solution**:
536+
```bash
537+
# Option 1: Rebuild the native module (recommended)
538+
npm rebuild better-sqlite3
539+
540+
# Option 2: Rebuild from source
541+
npm install better-sqlite3 --build-from-source
542+
543+
# Option 3: Reinstall all dependencies
544+
rm -rf node_modules && npm install
545+
```
546+
547+
> **Note**: Building from source requires C++ build tools. On macOS run `xcode-select --install`, on Ubuntu run `sudo apt install build-essential`, on Windows install [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
548+
523549
### "No active session found" error
524550

525551
**Cause**: No workflow session is currently active.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ npm install -g claude-code-workflow
8181
ccw install -m Global
8282
```
8383

84+
> **Troubleshooting**: If you see `NODE_MODULE_VERSION` mismatch errors for `better-sqlite3`, run `npm rebuild better-sqlite3`. See [FAQ - Troubleshooting](FAQ.md#better-sqlite3-node_module_version-mismatch) for details.
85+
8486
### Choose Your Workflow Level
8587

8688
<div align="center">

ccw/frontend/src/components/mcp/CrossCliCopyButton.tsx

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import {
1717
import { Checkbox } from '@/components/ui/Checkbox';
1818
import { Badge } from '@/components/ui/Badge';
1919
import { useMcpServers } from '@/hooks';
20-
import { crossCliCopy } from '@/lib/api';
20+
import { crossCliCopy, fetchCodexMcpServers } from '@/lib/api';
2121
import { cn } from '@/lib/utils';
22+
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
2223

2324
// ========== Types ==========
2425

@@ -69,13 +70,11 @@ export function CrossCliCopyButton({
6970
const [serverItems, setServerItems] = useState<ServerCheckboxItem[]>([]);
7071

7172
const { servers } = useMcpServers();
73+
const projectPath = useWorkflowStore(selectProjectPath);
7274
const [isCopying, setIsCopying] = useState(false);
7375

74-
// Initialize server items when dialog opens
75-
const handleOpenChange = (open: boolean) => {
76-
setIsOpen(open);
77-
if (open) {
78-
setDirection(currentMode === 'claude' ? 'claude-to-codex' : 'codex-to-claude');
76+
const loadServerItems = async (nextDirection: CopyDirection) => {
77+
if (nextDirection === 'claude-to-codex') {
7978
setServerItems(
8079
servers.map((s) => ({
8180
name: s.name,
@@ -84,6 +83,34 @@ export function CrossCliCopyButton({
8483
selected: false,
8584
}))
8685
);
86+
return;
87+
}
88+
89+
try {
90+
const codex = await fetchCodexMcpServers();
91+
setServerItems(
92+
(codex.servers ?? []).map((s) => ({
93+
name: s.name,
94+
command: s.command,
95+
enabled: s.enabled,
96+
selected: false,
97+
}))
98+
);
99+
} catch (error) {
100+
console.error('Failed to load Codex MCP servers:', error);
101+
setServerItems([]);
102+
}
103+
};
104+
105+
// Initialize server items when dialog opens
106+
const handleOpenChange = (open: boolean) => {
107+
setIsOpen(open);
108+
if (open) {
109+
const nextDirection = currentMode === 'claude' ? 'claude-to-codex' : 'codex-to-claude';
110+
setDirection(nextDirection);
111+
void loadServerItems(nextDirection);
112+
} else {
113+
setServerItems([]);
87114
}
88115
};
89116

@@ -93,10 +120,9 @@ export function CrossCliCopyButton({
93120

94121
// Toggle direction
95122
const handleToggleDirection = () => {
96-
setDirection((prev) =>
97-
prev === 'claude-to-codex' ? 'codex-to-claude' : 'claude-to-codex'
98-
);
99-
setServerItems((prev) => prev.map((item) => ({ ...item, selected: false })));
123+
const next = direction === 'claude-to-codex' ? 'codex-to-claude' : 'claude-to-codex';
124+
setDirection(next);
125+
void loadServerItems(next);
100126
};
101127

102128
// Toggle server selection
@@ -124,10 +150,15 @@ export function CrossCliCopyButton({
124150

125151
setIsCopying(true);
126152
try {
153+
if (targetCli === 'claude' && !projectPath) {
154+
throw new Error('Project path is required to copy servers into Claude project');
155+
}
156+
127157
const result = await crossCliCopy({
128158
source: sourceCli,
129159
target: targetCli,
130160
serverNames: selectedServers,
161+
projectPath: projectPath ?? undefined,
131162
});
132163

133164
if (result.success) {

ccw/frontend/src/components/mcp/McpServerDialog.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ import {
2828
updateMcpServer,
2929
fetchMcpServers,
3030
type McpServer,
31+
type McpProjectConfigType,
3132
} from '@/lib/api';
3233
import { mcpServersKeys, useMcpTemplates } from '@/hooks';
3334
import { cn } from '@/lib/utils';
3435
import { ConfigTypeToggle, type McpConfigType } from './ConfigTypeToggle';
36+
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
3537

3638
// ========== Types ==========
3739

@@ -73,6 +75,7 @@ export function McpServerDialog({
7375
}: McpServerDialogProps) {
7476
const { formatMessage } = useIntl();
7577
const queryClient = useQueryClient();
78+
const projectPath = useWorkflowStore(selectProjectPath);
7679

7780
// Fetch templates from backend
7881
const { templates, isLoading: templatesLoading } = useMcpTemplates();
@@ -92,6 +95,7 @@ export function McpServerDialog({
9295
const [argsInput, setArgsInput] = useState('');
9396
const [envInput, setEnvInput] = useState('');
9497
const [configType, setConfigType] = useState<McpConfigType>('mcp-json');
98+
const projectConfigType: McpProjectConfigType = configType === 'claude-json' ? 'claude' : 'mcp';
9599

96100
// Initialize form from server prop (edit mode)
97101
useEffect(() => {
@@ -129,7 +133,8 @@ export function McpServerDialog({
129133

130134
// Mutations
131135
const createMutation = useMutation({
132-
mutationFn: (data: Omit<McpServer, 'name'>) => createMcpServer(data),
136+
mutationFn: ({ server, configType }: { server: McpServer; configType?: McpProjectConfigType }) =>
137+
createMcpServer(server, { projectPath: projectPath ?? undefined, configType }),
133138
onSuccess: () => {
134139
queryClient.invalidateQueries({ queryKey: mcpServersKeys.all });
135140
handleClose();
@@ -138,8 +143,8 @@ export function McpServerDialog({
138143
});
139144

140145
const updateMutation = useMutation({
141-
mutationFn: ({ serverName, config }: { serverName: string; config: Partial<McpServer> }) =>
142-
updateMcpServer(serverName, config),
146+
mutationFn: ({ serverName, config, configType }: { serverName: string; config: Partial<McpServer>; configType?: McpProjectConfigType }) =>
147+
updateMcpServer(serverName, config, { projectPath: projectPath ?? undefined, configType }),
143148
onSuccess: () => {
144149
queryClient.invalidateQueries({ queryKey: mcpServersKeys.all });
145150
handleClose();
@@ -234,7 +239,7 @@ export function McpServerDialog({
234239

235240
const checkNameExists = async (name: string): Promise<boolean> => {
236241
try {
237-
const data = await fetchMcpServers();
242+
const data = await fetchMcpServers(projectPath ?? undefined);
238243
const allServers = [...data.project, ...data.global];
239244
// In edit mode, exclude current server
240245
return allServers.some(
@@ -258,11 +263,15 @@ export function McpServerDialog({
258263

259264
if (mode === 'add') {
260265
createMutation.mutate({
261-
command: formData.command,
262-
args: formData.args,
263-
env: formData.env,
264-
scope: formData.scope,
265-
enabled: formData.enabled,
266+
server: {
267+
name: formData.name,
268+
command: formData.command,
269+
args: formData.args,
270+
env: formData.env,
271+
scope: formData.scope,
272+
enabled: formData.enabled,
273+
},
274+
configType: formData.scope === 'project' ? projectConfigType : undefined,
266275
});
267276
} else {
268277
updateMutation.mutate({
@@ -274,6 +283,7 @@ export function McpServerDialog({
274283
scope: formData.scope,
275284
enabled: formData.enabled,
276285
},
286+
configType: formData.scope === 'project' ? projectConfigType : undefined,
277287
});
278288
}
279289
};
@@ -441,6 +451,7 @@ export function McpServerDialog({
441451
checked={formData.scope === 'project'}
442452
onChange={(e) => handleFieldChange('scope', e.target.value as 'project' | 'global')}
443453
className="w-4 h-4"
454+
disabled={mode === 'edit'}
444455
/>
445456
<span className="text-sm">
446457
{formatMessage({ id: 'mcp.scope.project' })}
@@ -454,6 +465,7 @@ export function McpServerDialog({
454465
checked={formData.scope === 'global'}
455466
onChange={(e) => handleFieldChange('scope', e.target.value as 'project' | 'global')}
456467
className="w-4 h-4"
468+
disabled={mode === 'edit'}
457469
/>
458470
<span className="text-sm">
459471
{formatMessage({ id: 'mcp.scope.global' })}

ccw/frontend/src/components/mcp/OtherProjectsSection.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ export function OtherProjectsSection({
6363

6464
for (const [path, serverList] of Object.entries(response.servers)) {
6565
const projectName = path.split(/[/\\]/).filter(Boolean).pop() || path;
66-
for (const server of (serverList as McpServer[])) {
66+
for (const server of (serverList as Omit<McpServer, 'scope'>[])) {
6767
servers.push({
6868
...server,
69+
scope: 'project',
6970
projectPath: path,
7071
projectName,
7172
});
@@ -88,6 +89,7 @@ export function OtherProjectsSection({
8889
const uniqueName = `${server.projectName}-${server.name}`.toLowerCase().replace(/\s+/g, '-');
8990

9091
await createServer({
92+
name: uniqueName,
9193
command: server.command,
9294
args: server.args,
9395
env: server.env,

ccw/frontend/src/components/mcp/RecommendedMcpWizard.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { mcpServersKeys } from '@/hooks';
2626
import { useNotifications } from '@/hooks/useNotifications';
2727
import { cn } from '@/lib/utils';
28+
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
2829

2930
// Icon map for MCP definitions
3031
const ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {
@@ -96,6 +97,7 @@ export function RecommendedMcpWizard({
9697
const { formatMessage } = useIntl();
9798
const queryClient = useQueryClient();
9899
const { success: showSuccess, error: showError } = useNotifications();
100+
const projectPath = useWorkflowStore(selectProjectPath);
99101

100102
// State for field values
101103
const [fieldValues, setFieldValues] = useState<Record<string, any>>({});
@@ -138,7 +140,10 @@ export function RecommendedMcpWizard({
138140
if (selectedScope === 'global') {
139141
return addGlobalMcpServer(mcpDefinition.id, serverConfig);
140142
} else {
141-
return copyMcpServerToProject(mcpDefinition.id, serverConfig);
143+
if (!projectPath) {
144+
throw new Error('Project path is required to install to project scope');
145+
}
146+
return copyMcpServerToProject(mcpDefinition.id, serverConfig, projectPath);
142147
}
143148
},
144149
onSuccess: (result) => {

ccw/frontend/src/hooks/useMcpServers.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,18 @@ export function useProjectOperations(): UseProjectOperationsReturn {
524524
isLoading: projectsQuery.isLoading,
525525
error: projectsQuery.error,
526526
refetch,
527-
copyToCodex: (request) => copyMutation.mutateAsync({ ...request, source: 'claude', target: 'codex' }),
528-
copyFromCodex: (request) => copyMutation.mutateAsync({ ...request, source: 'codex', target: 'claude' }),
527+
copyToCodex: (request) => copyMutation.mutateAsync({
528+
...request,
529+
source: 'claude',
530+
target: 'codex',
531+
projectPath: request.projectPath ?? projectPath ?? undefined,
532+
}),
533+
copyFromCodex: (request) => copyMutation.mutateAsync({
534+
...request,
535+
source: 'codex',
536+
target: 'claude',
537+
projectPath: request.projectPath ?? projectPath ?? undefined,
538+
}),
529539
isCopying: copyMutation.isPending,
530540
fetchOtherServers,
531541
isFetchingServers: serversQuery.isFetching,

0 commit comments

Comments
 (0)