openclaw-watcher 0.0.1 → 0.0.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +2 -85
  2. package/README.md +11 -16
  3. package/dist/ai/cli-client.js +1 -1
  4. package/dist/ai/cli-client.js.map +1 -1
  5. package/dist/setup/git-initializer.js +1 -1
  6. package/package.json +15 -2
  7. package/.claude/settings.local.json +0 -7
  8. package/.dockerignore +0 -21
  9. package/.env.example +0 -31
  10. package/.eslintrc.json +0 -26
  11. package/.prettierrc.json +0 -9
  12. package/Dockerfile +0 -47
  13. package/build.sh +0 -33
  14. package/docker-compose.yml +0 -43
  15. package/nodemon.json +0 -9
  16. package/scripts/setup.sh +0 -105
  17. package/src/ai/ai-orchestrator.ts +0 -95
  18. package/src/ai/cli-client.ts +0 -296
  19. package/src/cli.ts +0 -40
  20. package/src/commands/config.ts +0 -57
  21. package/src/commands/init.ts +0 -239
  22. package/src/commands/start.ts +0 -75
  23. package/src/commands/status.ts +0 -79
  24. package/src/config/default.ts +0 -25
  25. package/src/healthcheck/gateway-monitor.ts +0 -137
  26. package/src/healthcheck/health-checker.ts +0 -71
  27. package/src/index.ts +0 -48
  28. package/src/recovery/auto-fixer.ts +0 -184
  29. package/src/recovery/change-recorder.ts +0 -46
  30. package/src/setup/config-initializer.ts +0 -63
  31. package/src/setup/config-loader.ts +0 -25
  32. package/src/setup/git-initializer.ts +0 -203
  33. package/src/setup/safe-config-generator.ts +0 -100
  34. package/src/types/index.ts +0 -67
  35. package/src/utils/executor.ts +0 -75
  36. package/src/utils/git-manager.ts +0 -121
  37. package/src/utils/github-cli.ts +0 -37
  38. package/src/utils/logger.ts +0 -39
  39. package/src/utils/paths.ts +0 -25
  40. package/tsconfig.json +0 -29
  41. /package/{prompts → dist/prompts}/fix-openclaw.md +0 -0
@@ -1,95 +0,0 @@
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
- }
@@ -1,296 +0,0 @@
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
- }
package/src/cli.ts DELETED
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { initCommand } from './commands/init.js';
5
- import { startCommand } from './commands/start.js';
6
- import { statusCommand } from './commands/status.js';
7
- import { configCommand } from './commands/config.js';
8
-
9
- const program = new Command();
10
-
11
- program
12
- .name('openclaw-watcher')
13
- .description('AI-powered automated health monitoring and self-healing for OpenClaw Gateway')
14
- .version('2.0.0');
15
-
16
- program
17
- .command('init')
18
- .description('Initialize OpenClaw Watcher with interactive configuration')
19
- .option('--no-git', 'Skip Git repository initialization')
20
- .action(initCommand);
21
-
22
- program
23
- .command('start')
24
- .description('Start health monitoring and auto-repair service')
25
- .option('-d, --daemon', 'Run as daemon process')
26
- .action(startCommand);
27
-
28
- program
29
- .command('status')
30
- .description('Show current monitoring status')
31
- .action(statusCommand);
32
-
33
- program
34
- .command('config')
35
- .description('Manage configuration')
36
- .option('-s, --show', 'Show current configuration')
37
- .option('-e, --edit', 'Edit configuration file')
38
- .action(configCommand);
39
-
40
- program.parse();
@@ -1,57 +0,0 @@
1
- import chalk from 'chalk';
2
- import { exec } from 'child_process';
3
- import { promisify } from 'util';
4
- import { loadConfig, getConfigFilePath } from '@/setup/config-loader.js';
5
- import fs from 'fs/promises';
6
-
7
- const execAsync = promisify(exec);
8
-
9
- interface ConfigOptions {
10
- show?: boolean;
11
- edit?: boolean;
12
- }
13
-
14
- export async function configCommand(options: ConfigOptions) {
15
- const configPath = getConfigFilePath();
16
-
17
- if (options.show) {
18
- try {
19
- const config = await loadConfig();
20
- if (!config) {
21
- console.log(chalk.yellow('Configuration not found'));
22
- return;
23
- }
24
-
25
- console.log(chalk.bold.cyan('\nšŸ“‹ Current Configuration\n'));
26
- console.log(JSON.stringify(config, null, 2));
27
- console.log('');
28
- } catch (error: any) {
29
- console.error(chalk.red(`Error: ${error.message}`));
30
- process.exit(1);
31
- }
32
- } else if (options.edit) {
33
- try {
34
- const editor = process.env.EDITOR || 'vim';
35
- await execAsync(`${editor} ${configPath}`);
36
- console.log(chalk.green('āœ“ Configuration updated'));
37
- } catch (error: any) {
38
- console.error(chalk.red(`Error: ${error.message}`));
39
- process.exit(1);
40
- }
41
- } else {
42
- // Show config file path
43
- try {
44
- await fs.access(configPath);
45
- console.log(chalk.cyan('Configuration file:'), chalk.white(configPath));
46
- console.log('');
47
- console.log(chalk.gray('Use:'));
48
- console.log(chalk.white(' --show '), chalk.gray('Show current configuration'));
49
- console.log(chalk.white(' --edit '), chalk.gray('Edit configuration file'));
50
- } catch {
51
- console.log(chalk.yellow('Configuration not found'));
52
- console.log(
53
- chalk.gray('\nRun: npx openclaw-watcher init')
54
- );
55
- }
56
- }
57
- }