openclaw-watcher 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/.dockerignore +21 -0
- package/.env.example +31 -0
- package/.eslintrc.json +26 -0
- package/.prettierrc.json +9 -0
- package/CHANGELOG.md +93 -0
- package/Dockerfile +47 -0
- package/README.md +408 -0
- package/build.sh +33 -0
- package/dist/ai/ai-orchestrator.d.ts +11 -0
- package/dist/ai/ai-orchestrator.d.ts.map +1 -0
- package/dist/ai/ai-orchestrator.js +85 -0
- package/dist/ai/ai-orchestrator.js.map +1 -0
- package/dist/ai/cli-client.d.ts +17 -0
- package/dist/ai/cli-client.d.ts.map +1 -0
- package/dist/ai/cli-client.js +239 -0
- package/dist/ai/cli-client.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +52 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +205 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +49 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +48 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/default.d.ts +5 -0
- package/dist/config/default.d.ts.map +1 -0
- package/dist/config/default.js +22 -0
- package/dist/config/default.js.map +1 -0
- package/dist/healthcheck/gateway-monitor.d.ts +19 -0
- package/dist/healthcheck/gateway-monitor.d.ts.map +1 -0
- package/dist/healthcheck/gateway-monitor.js +116 -0
- package/dist/healthcheck/gateway-monitor.js.map +1 -0
- package/dist/healthcheck/health-checker.d.ts +11 -0
- package/dist/healthcheck/health-checker.d.ts.map +1 -0
- package/dist/healthcheck/health-checker.js +60 -0
- package/dist/healthcheck/health-checker.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/recovery/auto-fixer.d.ts +16 -0
- package/dist/recovery/auto-fixer.d.ts.map +1 -0
- package/dist/recovery/auto-fixer.js +162 -0
- package/dist/recovery/auto-fixer.js.map +1 -0
- package/dist/recovery/change-recorder.d.ts +8 -0
- package/dist/recovery/change-recorder.d.ts.map +1 -0
- package/dist/recovery/change-recorder.js +41 -0
- package/dist/recovery/change-recorder.js.map +1 -0
- package/dist/setup/config-initializer.d.ts +13 -0
- package/dist/setup/config-initializer.d.ts.map +1 -0
- package/dist/setup/config-initializer.js +46 -0
- package/dist/setup/config-initializer.js.map +1 -0
- package/dist/setup/config-loader.d.ts +9 -0
- package/dist/setup/config-loader.d.ts.map +1 -0
- package/dist/setup/config-loader.js +17 -0
- package/dist/setup/config-loader.js.map +1 -0
- package/dist/setup/git-initializer.d.ts +15 -0
- package/dist/setup/git-initializer.d.ts.map +1 -0
- package/dist/setup/git-initializer.js +189 -0
- package/dist/setup/git-initializer.js.map +1 -0
- package/dist/setup/safe-config-generator.d.ts +9 -0
- package/dist/setup/safe-config-generator.d.ts.map +1 -0
- package/dist/setup/safe-config-generator.js +85 -0
- package/dist/setup/safe-config-generator.js.map +1 -0
- package/dist/types/index.d.ts +60 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/executor.d.ts +17 -0
- package/dist/utils/executor.d.ts.map +1 -0
- package/dist/utils/executor.js +57 -0
- package/dist/utils/executor.js.map +1 -0
- package/dist/utils/git-manager.d.ts +14 -0
- package/dist/utils/git-manager.d.ts.map +1 -0
- package/dist/utils/git-manager.js +116 -0
- package/dist/utils/git-manager.js.map +1 -0
- package/dist/utils/github-cli.d.ts +9 -0
- package/dist/utils/github-cli.d.ts.map +1 -0
- package/dist/utils/github-cli.js +31 -0
- package/dist/utils/github-cli.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +26 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +19 -0
- package/dist/utils/paths.js.map +1 -0
- package/docker-compose.yml +43 -0
- package/nodemon.json +9 -0
- package/package.json +59 -0
- package/prompts/fix-openclaw.md +202 -0
- package/scripts/setup.sh +105 -0
- package/src/ai/ai-orchestrator.ts +95 -0
- package/src/ai/cli-client.ts +296 -0
- package/src/cli.ts +40 -0
- package/src/commands/config.ts +57 -0
- package/src/commands/init.ts +239 -0
- package/src/commands/start.ts +75 -0
- package/src/commands/status.ts +79 -0
- package/src/config/default.ts +25 -0
- package/src/healthcheck/gateway-monitor.ts +137 -0
- package/src/healthcheck/health-checker.ts +71 -0
- package/src/index.ts +48 -0
- package/src/recovery/auto-fixer.ts +184 -0
- package/src/recovery/change-recorder.ts +46 -0
- package/src/setup/config-initializer.ts +63 -0
- package/src/setup/config-loader.ts +25 -0
- package/src/setup/git-initializer.ts +203 -0
- package/src/setup/safe-config-generator.ts +100 -0
- package/src/types/index.ts +67 -0
- package/src/utils/executor.ts +75 -0
- package/src/utils/git-manager.ts +121 -0
- package/src/utils/github-cli.ts +37 -0
- package/src/utils/logger.ts +39 -0
- package/src/utils/paths.ts +25 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# OpenClaw Gateway Auto-Fix Agent
|
|
2
|
+
|
|
3
|
+
## Your Role
|
|
4
|
+
|
|
5
|
+
You are a professional OpenClaw Gateway troubleshooting and repair expert. When OpenClaw Gateway encounters issues, you need to:
|
|
6
|
+
|
|
7
|
+
1. **Diagnose the problem**: Analyze error logs and context to identify the root cause
|
|
8
|
+
2. **Fix it completely**: Use all available tools to directly fix the issue, don't just provide suggestions
|
|
9
|
+
3. **Verify the fix**: Ensure the problem is truly resolved
|
|
10
|
+
4. **Standardized summary**: Summarize the repair process in a standard format
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
### 1. Diagnosis Phase
|
|
15
|
+
|
|
16
|
+
- Read the provided error information and context
|
|
17
|
+
- Read relevant configuration files
|
|
18
|
+
- Use web search or other tools when necessary
|
|
19
|
+
- Check log files
|
|
20
|
+
- Analyze the root cause
|
|
21
|
+
|
|
22
|
+
### 2. Repair Phase
|
|
23
|
+
|
|
24
|
+
**IMPORTANT: Execute fixes directly, don't just give suggestions!**
|
|
25
|
+
|
|
26
|
+
- Use `Read` tool to read configuration files
|
|
27
|
+
- Use `Edit` or `Write` tool to modify configurations
|
|
28
|
+
- Use `Bash` tool to execute necessary commands
|
|
29
|
+
- If one attempt fails, continue iterating until the problem is resolved
|
|
30
|
+
|
|
31
|
+
### 3. Verification Phase
|
|
32
|
+
|
|
33
|
+
- Restart OpenClaw Gateway
|
|
34
|
+
- Check if it starts successfully
|
|
35
|
+
- Verify configuration is loaded correctly
|
|
36
|
+
- Confirm the issue is completely resolved
|
|
37
|
+
|
|
38
|
+
### 4. Summary Phase
|
|
39
|
+
|
|
40
|
+
After completion, output a summary in JSON format, **MUST be enclosed between `===FIX_SUMMARY_START===` and `===FIX_SUMMARY_END===` markers**:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
===FIX_SUMMARY_START===
|
|
44
|
+
{
|
|
45
|
+
"issue": "Brief description of the problem",
|
|
46
|
+
"rootCause": "Root cause analysis",
|
|
47
|
+
"fixApplied": "Actual repair measures taken (concise description)",
|
|
48
|
+
"filesModified": ["Modified file paths"],
|
|
49
|
+
"commandsExecuted": ["Key commands executed"],
|
|
50
|
+
"verificationResult": "Verification result description",
|
|
51
|
+
"success": true
|
|
52
|
+
}
|
|
53
|
+
===FIX_SUMMARY_END===
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Common Problem Repair Guide
|
|
57
|
+
|
|
58
|
+
### Configuration File Errors
|
|
59
|
+
|
|
60
|
+
**Symptoms**: Gateway fails to start, reports configuration parsing error
|
|
61
|
+
|
|
62
|
+
**Diagnosis**:
|
|
63
|
+
```bash
|
|
64
|
+
openclaw gateway restart
|
|
65
|
+
# Check error output
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Repair**:
|
|
69
|
+
1. Read the configuration file
|
|
70
|
+
2. Locate the incorrect configuration item
|
|
71
|
+
3. Correct to the proper value
|
|
72
|
+
4. Restart and verify
|
|
73
|
+
|
|
74
|
+
### Port Conflict
|
|
75
|
+
|
|
76
|
+
**Symptoms**: Gateway fails to start, reports port already in use
|
|
77
|
+
|
|
78
|
+
**Diagnosis**:
|
|
79
|
+
```bash
|
|
80
|
+
lsof -i :10002
|
|
81
|
+
netstat -an | grep 10002
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Repair**:
|
|
85
|
+
1. Modify the port in configuration file
|
|
86
|
+
2. Or terminate the process occupying the port
|
|
87
|
+
3. Restart and verify
|
|
88
|
+
|
|
89
|
+
### Dependency Service Unavailable
|
|
90
|
+
|
|
91
|
+
**Symptoms**: Gateway cannot connect to database, Redis, etc.
|
|
92
|
+
|
|
93
|
+
**Diagnosis**:
|
|
94
|
+
```bash
|
|
95
|
+
# Check service status
|
|
96
|
+
systemctl status mongodb
|
|
97
|
+
systemctl status redis
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Repair**:
|
|
101
|
+
1. Start the dependency service
|
|
102
|
+
2. Check connection address in configuration
|
|
103
|
+
3. Correct connection configuration
|
|
104
|
+
4. Restart and verify
|
|
105
|
+
|
|
106
|
+
### Permission Issues
|
|
107
|
+
|
|
108
|
+
**Symptoms**: Gateway cannot read/write files
|
|
109
|
+
|
|
110
|
+
**Diagnosis**:
|
|
111
|
+
```bash
|
|
112
|
+
ls -la /path/to/files
|
|
113
|
+
# Check file permissions
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Repair**:
|
|
117
|
+
```bash
|
|
118
|
+
chmod 755 /path/to/dir
|
|
119
|
+
chown openclaw:openclaw /path/to/files
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Missing Configuration
|
|
123
|
+
|
|
124
|
+
**Symptoms**: Gateway fails to start, missing required configuration
|
|
125
|
+
|
|
126
|
+
**Repair**:
|
|
127
|
+
1. Read the configuration file
|
|
128
|
+
2. Add missing configuration items (use reasonable defaults)
|
|
129
|
+
3. Restart and verify
|
|
130
|
+
|
|
131
|
+
## Important Constraints
|
|
132
|
+
|
|
133
|
+
1. **MUST fix completely**: Don't just diagnose, must actually fix
|
|
134
|
+
2. **MUST verify**: Must verify success after fixing
|
|
135
|
+
3. **MUST summarize**: Must output standard JSON summary when complete
|
|
136
|
+
4. **Safety first**: Backup before modifying configuration (use `cp` command)
|
|
137
|
+
5. **Minimal changes**: Only modify necessary parts, don't refactor entire config
|
|
138
|
+
6. **Preserve comments**: Preserve original comments when editing config files
|
|
139
|
+
|
|
140
|
+
## Example Interaction
|
|
141
|
+
|
|
142
|
+
**Input**:
|
|
143
|
+
```
|
|
144
|
+
Error: Gateway failed to start
|
|
145
|
+
Context:
|
|
146
|
+
Gateway Status Error:
|
|
147
|
+
Error: Cannot parse config file: Unexpected token
|
|
148
|
+
|
|
149
|
+
Restart Error:
|
|
150
|
+
Failed to start gateway: Invalid JSON syntax in openclaw.json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Your Actions**:
|
|
154
|
+
|
|
155
|
+
1. Read the configuration file:
|
|
156
|
+
```bash
|
|
157
|
+
Read ~/.openclaw/openclaw.json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
2. Identify the problem: JSON syntax error at line 15
|
|
161
|
+
|
|
162
|
+
3. Backup configuration:
|
|
163
|
+
```bash
|
|
164
|
+
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
4. Fix configuration:
|
|
168
|
+
```bash
|
|
169
|
+
Edit openclaw.json, correct syntax error at line 15
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
5. Restart and verify:
|
|
173
|
+
```bash
|
|
174
|
+
openclaw gateway restart
|
|
175
|
+
openclaw gateway status
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
6. Output summary:
|
|
179
|
+
```
|
|
180
|
+
===FIX_SUMMARY_START===
|
|
181
|
+
{
|
|
182
|
+
"issue": "Gateway failed to start - JSON configuration file syntax error",
|
|
183
|
+
"rootCause": "openclaw.json line 15 has incorrect JSON syntax, causing parse failure",
|
|
184
|
+
"fixApplied": "Corrected JSON syntax at line 15 in openclaw.json (added missing comma)",
|
|
185
|
+
"filesModified": ["~/.openclaw/openclaw.json"],
|
|
186
|
+
"commandsExecuted": ["openclaw gateway restart"],
|
|
187
|
+
"verificationResult": "Gateway successfully restarted, status check normal, configuration loaded correctly",
|
|
188
|
+
"success": true
|
|
189
|
+
}
|
|
190
|
+
===FIX_SUMMARY_END===
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Start Working
|
|
194
|
+
|
|
195
|
+
Upon receiving a problem, immediately begin diagnosis and repair. Remember:
|
|
196
|
+
|
|
197
|
+
- ✅ Fix it directly
|
|
198
|
+
- ✅ Iterate until successful
|
|
199
|
+
- ✅ Verify the results
|
|
200
|
+
- ✅ Output standard summary
|
|
201
|
+
- ❌ Don't just give suggestions
|
|
202
|
+
- ❌ Don't leave half-finished work
|
package/scripts/setup.sh
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
echo "🚀 Setting up OpenClaw HealthCheck..."
|
|
6
|
+
|
|
7
|
+
# Check Node.js version
|
|
8
|
+
echo "Checking Node.js version..."
|
|
9
|
+
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
|
10
|
+
if [ "$NODE_VERSION" -lt 22 ]; then
|
|
11
|
+
echo "❌ Node.js version 22 or higher is required"
|
|
12
|
+
echo "Current version: $(node -v)"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
echo "✅ Node.js version: $(node -v)"
|
|
16
|
+
|
|
17
|
+
# Check pnpm
|
|
18
|
+
echo "Checking pnpm..."
|
|
19
|
+
if ! command -v pnpm &> /dev/null; then
|
|
20
|
+
echo "📦 Installing pnpm..."
|
|
21
|
+
npm install -g pnpm
|
|
22
|
+
fi
|
|
23
|
+
echo "✅ pnpm version: $(pnpm -v)"
|
|
24
|
+
|
|
25
|
+
# Install dependencies
|
|
26
|
+
echo "📦 Installing dependencies..."
|
|
27
|
+
pnpm install
|
|
28
|
+
|
|
29
|
+
# Setup environment
|
|
30
|
+
if [ ! -f .env ]; then
|
|
31
|
+
echo "📝 Creating .env file..."
|
|
32
|
+
cp .env.example .env
|
|
33
|
+
echo "⚠️ Please edit .env file with your configuration"
|
|
34
|
+
else
|
|
35
|
+
echo "✅ .env file already exists"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Create necessary directories
|
|
39
|
+
echo "📁 Creating directories..."
|
|
40
|
+
mkdir -p logs config-backups
|
|
41
|
+
|
|
42
|
+
# Set executable permissions
|
|
43
|
+
echo "🔐 Setting permissions..."
|
|
44
|
+
chmod +x build.sh
|
|
45
|
+
chmod +x scripts/*.sh 2>/dev/null || true
|
|
46
|
+
|
|
47
|
+
# Check OpenClaw
|
|
48
|
+
echo "Checking OpenClaw installation..."
|
|
49
|
+
if command -v openclaw &> /dev/null; then
|
|
50
|
+
echo "✅ OpenClaw found: $(which openclaw)"
|
|
51
|
+
OPENCLAW_VERSION=$(openclaw version 2>/dev/null || echo "unknown")
|
|
52
|
+
echo " Version: $OPENCLAW_VERSION"
|
|
53
|
+
else
|
|
54
|
+
echo "⚠️ OpenClaw not found in PATH"
|
|
55
|
+
echo " Please ensure OpenClaw is installed and accessible"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Check AI providers
|
|
59
|
+
echo "Checking AI providers..."
|
|
60
|
+
CLAUDE_FOUND=false
|
|
61
|
+
KIMI_FOUND=false
|
|
62
|
+
|
|
63
|
+
if command -v claude &> /dev/null; then
|
|
64
|
+
echo "✅ Claude Code CLI found: $(which claude)"
|
|
65
|
+
CLAUDE_FOUND=true
|
|
66
|
+
else
|
|
67
|
+
echo "⚠️ Claude Code CLI not found"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if command -v kimi &> /dev/null; then
|
|
71
|
+
echo "✅ Kimi Code CLI found: $(which kimi)"
|
|
72
|
+
KIMI_FOUND=true
|
|
73
|
+
else
|
|
74
|
+
echo "⚠️ Kimi Code CLI not found"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ "$CLAUDE_FOUND" = false ] && [ "$KIMI_FOUND" = false ]; then
|
|
78
|
+
echo ""
|
|
79
|
+
echo "❌ ERROR: No AI CLI tool found!"
|
|
80
|
+
echo " You must install at least one:"
|
|
81
|
+
echo " - Claude Code CLI: https://github.com/anthropics/claude-code"
|
|
82
|
+
echo " - Kimi Code CLI: https://platform.moonshot.cn/docs/code"
|
|
83
|
+
echo ""
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
echo ""
|
|
88
|
+
echo "✅ Setup completed!"
|
|
89
|
+
echo ""
|
|
90
|
+
echo "Next steps:"
|
|
91
|
+
echo "1. Edit .env file with your configuration:"
|
|
92
|
+
echo " vim .env"
|
|
93
|
+
echo ""
|
|
94
|
+
echo "2. Update these important settings:"
|
|
95
|
+
echo " - OPENCLAW_GATEWAY_URL"
|
|
96
|
+
echo " - OPENCLAW_CONFIG_PATH"
|
|
97
|
+
echo " - AI_PROVIDER and API keys"
|
|
98
|
+
echo ""
|
|
99
|
+
echo "3. Run in development mode:"
|
|
100
|
+
echo " pnpm run dev"
|
|
101
|
+
echo ""
|
|
102
|
+
echo "4. Or build and run in production:"
|
|
103
|
+
echo " ./build.sh"
|
|
104
|
+
echo " pnpm start"
|
|
105
|
+
echo ""
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CLIClient, AIProvider } from './cli-client.js';
|
|
2
|
+
import logger from '@/utils/logger.js';
|
|
3
|
+
import { AIClientConfig, DiagnosisResult, RecoveryConfig } from '@/types';
|
|
4
|
+
|
|
5
|
+
export class AIOrchestrator {
|
|
6
|
+
private primaryClient: CLIClient;
|
|
7
|
+
private fallbackClient?: CLIClient;
|
|
8
|
+
private configPath: string;
|
|
9
|
+
|
|
10
|
+
constructor(aiConfig: AIClientConfig, recoveryConfig: RecoveryConfig) {
|
|
11
|
+
this.configPath = recoveryConfig.openclawConfigPath;
|
|
12
|
+
|
|
13
|
+
// Primary AI client
|
|
14
|
+
this.primaryClient = new CLIClient(aiConfig.provider as AIProvider, aiConfig.timeout);
|
|
15
|
+
|
|
16
|
+
// Fallback to alternative provider
|
|
17
|
+
const fallbackProvider: AIProvider = aiConfig.provider === 'claude' ? 'kimi' : 'claude';
|
|
18
|
+
this.fallbackClient = new CLIClient(fallbackProvider, aiConfig.timeout);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async initialize(): Promise<void> {
|
|
22
|
+
logger.info('Initializing AI orchestrator');
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await this.primaryClient.initialize();
|
|
26
|
+
logger.info('Primary AI client initialized');
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
logger.error('Failed to initialize primary AI client', {
|
|
29
|
+
error: error.message,
|
|
30
|
+
});
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Try to initialize fallback
|
|
35
|
+
try {
|
|
36
|
+
if (this.fallbackClient) {
|
|
37
|
+
await this.fallbackClient.initialize();
|
|
38
|
+
logger.info('Fallback AI client initialized');
|
|
39
|
+
}
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
logger.warn('Failed to initialize fallback AI client', {
|
|
42
|
+
error: error.message,
|
|
43
|
+
});
|
|
44
|
+
// Non-critical, continue
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async diagnoseAndFix(error: string): Promise<DiagnosisResult> {
|
|
49
|
+
try {
|
|
50
|
+
// Check if primary client is available
|
|
51
|
+
const isPrimaryAvailable = await this.primaryClient.testConnection();
|
|
52
|
+
|
|
53
|
+
if (isPrimaryAvailable) {
|
|
54
|
+
return await this.primaryClient.diagnoseAndFix(error, this.configPath);
|
|
55
|
+
} else {
|
|
56
|
+
logger.warn('Primary AI CLI not available, trying fallback');
|
|
57
|
+
return await this.fallbackDiagnosis(error);
|
|
58
|
+
}
|
|
59
|
+
} catch (primaryError: any) {
|
|
60
|
+
logger.error('Primary AI diagnosis and fix failed, attempting fallback', {
|
|
61
|
+
error: primaryError.message,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return await this.fallbackDiagnosis(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async fallbackDiagnosis(error: string): Promise<DiagnosisResult> {
|
|
69
|
+
if (!this.fallbackClient) {
|
|
70
|
+
throw new Error('No fallback AI client available');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const isFallbackAvailable = await this.fallbackClient.testConnection();
|
|
75
|
+
|
|
76
|
+
if (!isFallbackAvailable) {
|
|
77
|
+
throw new Error('Fallback AI CLI not available');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
logger.info('Using fallback AI for diagnosis and fix');
|
|
81
|
+
return await this.fallbackClient.diagnoseAndFix(error, this.configPath);
|
|
82
|
+
} catch (fallbackError: any) {
|
|
83
|
+
logger.error('Fallback AI also failed', { error: fallbackError.message });
|
|
84
|
+
|
|
85
|
+
// Return basic diagnosis if all else fails
|
|
86
|
+
return {
|
|
87
|
+
issue: 'AI diagnosis and fix unavailable',
|
|
88
|
+
rootCause: `All AI providers failed. Manual intervention required.`,
|
|
89
|
+
suggestedFix: 'Attempt basic restart and check logs manually',
|
|
90
|
+
commands: ['openclaw gateway restart', 'openclaw gateway status'],
|
|
91
|
+
confidence: 0.1,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { exec, spawn } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import logger from '@/utils/logger.js';
|
|
7
|
+
import { DiagnosisResult } from '@/types';
|
|
8
|
+
import { executor } from '@/utils/executor.js';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
interface FixSummary {
|
|
15
|
+
issue: string;
|
|
16
|
+
rootCause: string;
|
|
17
|
+
fixApplied: string;
|
|
18
|
+
filesModified: string[];
|
|
19
|
+
commandsExecuted: string[];
|
|
20
|
+
verificationResult: string;
|
|
21
|
+
success: boolean;
|
|
22
|
+
confidence: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type AIProvider = 'claude' | 'kimi';
|
|
26
|
+
|
|
27
|
+
export class CLIClient {
|
|
28
|
+
private provider: AIProvider;
|
|
29
|
+
private timeout: number;
|
|
30
|
+
private systemPrompt: string = '';
|
|
31
|
+
|
|
32
|
+
constructor(provider: AIProvider, timeout: number = 300000) {
|
|
33
|
+
this.provider = provider;
|
|
34
|
+
this.timeout = timeout;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async initialize(): Promise<void> {
|
|
38
|
+
const promptPath = path.join(__dirname, '../../prompts/fix-openclaw.md');
|
|
39
|
+
try {
|
|
40
|
+
this.systemPrompt = await fs.readFile(promptPath, 'utf-8');
|
|
41
|
+
logger.info('System prompt loaded', { provider: this.provider });
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
logger.error('Failed to load system prompt', { error: error.message });
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async diagnoseAndFix(error: string, configPath: string): Promise<DiagnosisResult> {
|
|
49
|
+
logger.info('Starting AI-powered diagnosis and fix', {
|
|
50
|
+
provider: this.provider,
|
|
51
|
+
configPath,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Gather context
|
|
56
|
+
const context = await this.gatherContext(error);
|
|
57
|
+
|
|
58
|
+
// Create task description
|
|
59
|
+
const taskDescription = this.createTaskDescription(error, context, configPath);
|
|
60
|
+
|
|
61
|
+
// Execute AI CLI
|
|
62
|
+
const output = await this.executeAICLI(taskDescription, configPath);
|
|
63
|
+
|
|
64
|
+
// Parse fix summary
|
|
65
|
+
const summary = this.parseFixSummary(output);
|
|
66
|
+
|
|
67
|
+
// Convert to DiagnosisResult format
|
|
68
|
+
const diagnosis = this.convertToDiagnosisResult(summary);
|
|
69
|
+
|
|
70
|
+
logger.info('AI diagnosis and fix completed', diagnosis);
|
|
71
|
+
return diagnosis;
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
logger.error('AI diagnosis and fix failed', { error: error.message });
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async gatherContext(_error: string): Promise<string> {
|
|
79
|
+
const parts: string[] = [];
|
|
80
|
+
|
|
81
|
+
// Get gateway status
|
|
82
|
+
const statusResult = await executor.getGatewayStatus();
|
|
83
|
+
if (statusResult.stdout) {
|
|
84
|
+
parts.push(`Gateway Status:\n${statusResult.stdout}`);
|
|
85
|
+
}
|
|
86
|
+
if (statusResult.stderr) {
|
|
87
|
+
parts.push(`Gateway Status Error:\n${statusResult.stderr}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get recent logs
|
|
91
|
+
const logsResult = await executor.getGatewayLogs(50);
|
|
92
|
+
if (logsResult.stdout) {
|
|
93
|
+
parts.push(`Recent Logs:\n${logsResult.stdout}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Attempt restart to get detailed error
|
|
97
|
+
const restartResult = await executor.restartGateway();
|
|
98
|
+
if (restartResult.stderr) {
|
|
99
|
+
parts.push(`Restart Error:\n${restartResult.stderr}`);
|
|
100
|
+
}
|
|
101
|
+
if (restartResult.stdout) {
|
|
102
|
+
parts.push(`Restart Output:\n${restartResult.stdout}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return parts.join('\n\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private createTaskDescription(
|
|
109
|
+
error: string,
|
|
110
|
+
context: string,
|
|
111
|
+
configPath: string
|
|
112
|
+
): string {
|
|
113
|
+
return `${this.systemPrompt}
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 当前问题
|
|
118
|
+
|
|
119
|
+
**错误信息**: ${error}
|
|
120
|
+
|
|
121
|
+
**OpenClaw 配置路径**: ${configPath}
|
|
122
|
+
|
|
123
|
+
**上下文信息**:
|
|
124
|
+
${context}
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
请立即开始诊断和修复这个问题。记住:
|
|
129
|
+
|
|
130
|
+
1. 直接修复,不要只给建议
|
|
131
|
+
2. 使用工具读取、编辑配置文件
|
|
132
|
+
3. 执行必要的命令
|
|
133
|
+
4. 验证修复是否成功
|
|
134
|
+
5. 完成后输出标准格式的 JSON 摘要
|
|
135
|
+
|
|
136
|
+
开始工作!`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async executeAICLI(task: string, workingDir: string): Promise<string> {
|
|
140
|
+
const cliCommand = this.provider === 'claude' ? 'claude' : 'kimi';
|
|
141
|
+
|
|
142
|
+
logger.info('Executing AI CLI', {
|
|
143
|
+
provider: this.provider,
|
|
144
|
+
workingDir,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return new Promise<string>((resolve, reject) => {
|
|
148
|
+
const child = spawn(cliCommand, ['--dangerously-skip-permissions'], {
|
|
149
|
+
cwd: workingDir,
|
|
150
|
+
env: {
|
|
151
|
+
...process.env,
|
|
152
|
+
FORCE_COLOR: '0',
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const stdoutChunks: Buffer[] = [];
|
|
157
|
+
const stderrChunks: Buffer[] = [];
|
|
158
|
+
let settled = false;
|
|
159
|
+
|
|
160
|
+
const timer = setTimeout(() => {
|
|
161
|
+
if (!settled) {
|
|
162
|
+
settled = true;
|
|
163
|
+
child.kill('SIGTERM');
|
|
164
|
+
reject(new Error(`${this.provider} CLI timed out after ${this.timeout}ms`));
|
|
165
|
+
}
|
|
166
|
+
}, this.timeout);
|
|
167
|
+
|
|
168
|
+
child.stdout.on('data', (chunk) => stdoutChunks.push(chunk));
|
|
169
|
+
child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
|
|
170
|
+
|
|
171
|
+
child.on('error', (err) => {
|
|
172
|
+
if (!settled) {
|
|
173
|
+
settled = true;
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
reject(new Error(`${this.provider} CLI spawn error: ${err.message}`));
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
child.on('close', (code) => {
|
|
180
|
+
if (!settled) {
|
|
181
|
+
settled = true;
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
184
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
185
|
+
const output = stdout + stderr;
|
|
186
|
+
|
|
187
|
+
if (code !== 0) {
|
|
188
|
+
logger.error('AI CLI execution failed', {
|
|
189
|
+
exitCode: code,
|
|
190
|
+
stderrPreview: stderr.substring(0, 200),
|
|
191
|
+
});
|
|
192
|
+
reject(new Error(`${this.provider} CLI exited with code ${code}`));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
logger.info('AI CLI execution completed', {
|
|
197
|
+
outputLength: output.length,
|
|
198
|
+
hasStderr: !!stderr,
|
|
199
|
+
});
|
|
200
|
+
resolve(output);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Pipe task via stdin instead of embedding in command
|
|
205
|
+
child.stdin.write(task);
|
|
206
|
+
child.stdin.end();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private parseFixSummary(output: string): FixSummary {
|
|
211
|
+
try {
|
|
212
|
+
// Extract JSON summary between markers
|
|
213
|
+
const startMarker = '===FIX_SUMMARY_START===';
|
|
214
|
+
const endMarker = '===FIX_SUMMARY_END===';
|
|
215
|
+
|
|
216
|
+
const startIndex = output.indexOf(startMarker);
|
|
217
|
+
const endIndex = output.indexOf(endMarker);
|
|
218
|
+
|
|
219
|
+
if (startIndex === -1 || endIndex === -1) {
|
|
220
|
+
throw new Error('Fix summary markers not found in AI output');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const jsonStr = output
|
|
224
|
+
.substring(startIndex + startMarker.length, endIndex)
|
|
225
|
+
.trim();
|
|
226
|
+
|
|
227
|
+
// Remove markdown code blocks if present
|
|
228
|
+
const cleanJson = jsonStr.replace(/^```json\n?/, '').replace(/\n?```$/, '');
|
|
229
|
+
|
|
230
|
+
const summary: FixSummary = JSON.parse(cleanJson);
|
|
231
|
+
|
|
232
|
+
// Validate required fields
|
|
233
|
+
if (
|
|
234
|
+
!summary.issue ||
|
|
235
|
+
!summary.rootCause ||
|
|
236
|
+
!summary.fixApplied ||
|
|
237
|
+
typeof summary.success !== 'boolean'
|
|
238
|
+
) {
|
|
239
|
+
throw new Error('Fix summary missing required fields');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
logger.info('Fix summary parsed successfully', { success: summary.success });
|
|
243
|
+
return summary;
|
|
244
|
+
} catch (error: any) {
|
|
245
|
+
logger.error('Failed to parse fix summary', {
|
|
246
|
+
error: error.message,
|
|
247
|
+
outputPreview: output.substring(0, 500),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Return a fallback summary
|
|
251
|
+
return {
|
|
252
|
+
issue: 'Parse error',
|
|
253
|
+
rootCause: `Failed to parse AI output: ${error.message}`,
|
|
254
|
+
fixApplied: 'AI attempted fixes but summary parsing failed',
|
|
255
|
+
filesModified: [],
|
|
256
|
+
commandsExecuted: [],
|
|
257
|
+
verificationResult: 'Unknown - could not parse verification result',
|
|
258
|
+
success: false,
|
|
259
|
+
confidence: 0.3,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private convertToDiagnosisResult(summary: FixSummary): DiagnosisResult {
|
|
265
|
+
return {
|
|
266
|
+
issue: summary.issue,
|
|
267
|
+
rootCause: summary.rootCause,
|
|
268
|
+
suggestedFix: summary.fixApplied,
|
|
269
|
+
commands: summary.commandsExecuted,
|
|
270
|
+
configChanges: summary.filesModified.map((file) => ({
|
|
271
|
+
file,
|
|
272
|
+
path: 'auto-modified',
|
|
273
|
+
oldValue: 'see git diff',
|
|
274
|
+
newValue: 'see git diff',
|
|
275
|
+
reason: summary.fixApplied,
|
|
276
|
+
})),
|
|
277
|
+
confidence: summary.confidence,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async testConnection(): Promise<boolean> {
|
|
282
|
+
try {
|
|
283
|
+
const command =
|
|
284
|
+
this.provider === 'claude' ? 'claude --version' : 'kimi --version';
|
|
285
|
+
|
|
286
|
+
await execAsync(command, { timeout: 5000 });
|
|
287
|
+
logger.info(`${this.provider} CLI is available`);
|
|
288
|
+
return true;
|
|
289
|
+
} catch (error: any) {
|
|
290
|
+
logger.warn(`${this.provider} CLI is not available`, {
|
|
291
|
+
error: error.message,
|
|
292
|
+
});
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|