clawdoctor 0.1.0

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 (78) hide show
  1. package/README.md +112 -0
  2. package/dist/alerters/telegram.d.ts +21 -0
  3. package/dist/alerters/telegram.d.ts.map +1 -0
  4. package/dist/alerters/telegram.js +87 -0
  5. package/dist/alerters/telegram.js.map +1 -0
  6. package/dist/config.d.ts +42 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +77 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/daemon.d.ts +21 -0
  11. package/dist/daemon.d.ts.map +1 -0
  12. package/dist/daemon.js +172 -0
  13. package/dist/daemon.js.map +1 -0
  14. package/dist/healers/base.d.ts +15 -0
  15. package/dist/healers/base.d.ts.map +1 -0
  16. package/dist/healers/base.js +25 -0
  17. package/dist/healers/base.js.map +1 -0
  18. package/dist/healers/cron.d.ts +6 -0
  19. package/dist/healers/cron.d.ts.map +1 -0
  20. package/dist/healers/cron.js +26 -0
  21. package/dist/healers/cron.js.map +1 -0
  22. package/dist/healers/process.d.ts +6 -0
  23. package/dist/healers/process.d.ts.map +1 -0
  24. package/dist/healers/process.js +67 -0
  25. package/dist/healers/process.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +283 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/store.d.ts +22 -0
  31. package/dist/store.d.ts.map +1 -0
  32. package/dist/store.js +97 -0
  33. package/dist/store.js.map +1 -0
  34. package/dist/test/config.test.d.ts +2 -0
  35. package/dist/test/config.test.d.ts.map +1 -0
  36. package/dist/test/config.test.js +90 -0
  37. package/dist/test/config.test.js.map +1 -0
  38. package/dist/test/store.test.d.ts +2 -0
  39. package/dist/test/store.test.d.ts.map +1 -0
  40. package/dist/test/store.test.js +89 -0
  41. package/dist/test/store.test.js.map +1 -0
  42. package/dist/test/telegram.test.d.ts +2 -0
  43. package/dist/test/telegram.test.d.ts.map +1 -0
  44. package/dist/test/telegram.test.js +107 -0
  45. package/dist/test/telegram.test.js.map +1 -0
  46. package/dist/test/watchers.test.d.ts +2 -0
  47. package/dist/test/watchers.test.d.ts.map +1 -0
  48. package/dist/test/watchers.test.js +194 -0
  49. package/dist/test/watchers.test.js.map +1 -0
  50. package/dist/utils.d.ts +18 -0
  51. package/dist/utils.d.ts.map +1 -0
  52. package/dist/utils.js +62 -0
  53. package/dist/utils.js.map +1 -0
  54. package/dist/watchers/auth.d.ts +10 -0
  55. package/dist/watchers/auth.d.ts.map +1 -0
  56. package/dist/watchers/auth.js +79 -0
  57. package/dist/watchers/auth.js.map +1 -0
  58. package/dist/watchers/base.d.ts +22 -0
  59. package/dist/watchers/base.d.ts.map +1 -0
  60. package/dist/watchers/base.js +39 -0
  61. package/dist/watchers/base.js.map +1 -0
  62. package/dist/watchers/cost.d.ts +9 -0
  63. package/dist/watchers/cost.d.ts.map +1 -0
  64. package/dist/watchers/cost.js +151 -0
  65. package/dist/watchers/cost.js.map +1 -0
  66. package/dist/watchers/cron.d.ts +7 -0
  67. package/dist/watchers/cron.d.ts.map +1 -0
  68. package/dist/watchers/cron.js +79 -0
  69. package/dist/watchers/cron.js.map +1 -0
  70. package/dist/watchers/gateway.d.ts +7 -0
  71. package/dist/watchers/gateway.d.ts.map +1 -0
  72. package/dist/watchers/gateway.js +33 -0
  73. package/dist/watchers/gateway.js.map +1 -0
  74. package/dist/watchers/session.d.ts +7 -0
  75. package/dist/watchers/session.d.ts.map +1 -0
  76. package/dist/watchers/session.js +112 -0
  77. package/dist/watchers/session.js.map +1 -0
  78. package/package.json +46 -0
@@ -0,0 +1,22 @@
1
+ import { ClawDoctorConfig } from '../config.js';
2
+ import { Severity } from '../store.js';
3
+ export interface WatchResult {
4
+ ok: boolean;
5
+ severity: Severity;
6
+ event_type: string;
7
+ message: string;
8
+ details?: Record<string, unknown>;
9
+ }
10
+ export declare abstract class BaseWatcher {
11
+ abstract readonly name: string;
12
+ abstract readonly defaultInterval: number;
13
+ protected config: ClawDoctorConfig;
14
+ constructor(config: ClawDoctorConfig);
15
+ abstract check(): Promise<WatchResult[]>;
16
+ run(): Promise<WatchResult[]>;
17
+ protected ok(message: string, event_type?: string): WatchResult;
18
+ protected warn(message: string, event_type: string, details?: Record<string, unknown>): WatchResult;
19
+ protected error(message: string, event_type: string, details?: Record<string, unknown>): WatchResult;
20
+ protected critical(message: string, event_type: string, details?: Record<string, unknown>): WatchResult;
21
+ }
22
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/watchers/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAe,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGpD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,8BAAsB,WAAW;IAC/B,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAE1C,SAAS,CAAC,MAAM,EAAE,gBAAgB,CAAC;gBAEvB,MAAM,EAAE,gBAAgB;IAIpC,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAElC,GAAG,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAenC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAa,GAAG,WAAW;IAInE,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;IAInG,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;IAIpG,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;CAGxG"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseWatcher = void 0;
4
+ const store_js_1 = require("../store.js");
5
+ const utils_js_1 = require("../utils.js");
6
+ class BaseWatcher {
7
+ config;
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+ async run() {
12
+ const results = await this.check();
13
+ for (const result of results) {
14
+ (0, store_js_1.insertEvent)({
15
+ timestamp: (0, utils_js_1.nowIso)(),
16
+ watcher: this.name,
17
+ severity: result.severity,
18
+ event_type: result.event_type,
19
+ message: result.message,
20
+ details: result.details ? JSON.stringify(result.details) : undefined,
21
+ });
22
+ }
23
+ return results;
24
+ }
25
+ ok(message, event_type = 'check_ok') {
26
+ return { ok: true, severity: 'info', event_type, message };
27
+ }
28
+ warn(message, event_type, details) {
29
+ return { ok: false, severity: 'warning', event_type, message, details };
30
+ }
31
+ error(message, event_type, details) {
32
+ return { ok: false, severity: 'error', event_type, message, details };
33
+ }
34
+ critical(message, event_type, details) {
35
+ return { ok: false, severity: 'critical', event_type, message, details };
36
+ }
37
+ }
38
+ exports.BaseWatcher = BaseWatcher;
39
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/watchers/base.ts"],"names":[],"mappings":";;;AACA,0CAAoD;AACpD,0CAAqC;AAUrC,MAAsB,WAAW;IAIrB,MAAM,CAAmB;IAEnC,YAAY,MAAwB;QAClC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAID,KAAK,CAAC,GAAG;QACP,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAA,sBAAW,EAAC;gBACV,SAAS,EAAE,IAAA,iBAAM,GAAE;gBACnB,OAAO,EAAE,IAAI,CAAC,IAAI;gBAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;aACrE,CAAC,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAES,EAAE,CAAC,OAAe,EAAE,UAAU,GAAG,UAAU;QACnD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IAC7D,CAAC;IAES,IAAI,CAAC,OAAe,EAAE,UAAkB,EAAE,OAAiC;QACnF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC1E,CAAC;IAES,KAAK,CAAC,OAAe,EAAE,UAAkB,EAAE,OAAiC;QACpF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IACxE,CAAC;IAES,QAAQ,CAAC,OAAe,EAAE,UAAkB,EAAE,OAAiC;QACvF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC3E,CAAC;CACF;AA1CD,kCA0CC"}
@@ -0,0 +1,9 @@
1
+ import { BaseWatcher, WatchResult } from './base.js';
2
+ export declare class CostWatcher extends BaseWatcher {
3
+ readonly name = "CostWatcher";
4
+ readonly defaultInterval = 300;
5
+ check(): Promise<WatchResult[]>;
6
+ private collectSessionCosts;
7
+ private extractSessionCost;
8
+ }
9
+ //# sourceMappingURL=cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/watchers/cost.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAyBrD,qBAAa,WAAY,SAAQ,WAAW;IAC1C,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,eAAe,OAAO;IAEzB,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAuErC,OAAO,CAAC,mBAAmB;IAyC3B,OAAO,CAAC,kBAAkB;CAgC3B"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CostWatcher = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const base_js_1 = require("./base.js");
10
+ const utils_js_1 = require("../utils.js");
11
+ const ROLLING_WINDOW = 20;
12
+ const ANOMALY_MULTIPLIER = 3;
13
+ const RECENT_HOURS = 1;
14
+ class CostWatcher extends base_js_1.BaseWatcher {
15
+ name = 'CostWatcher';
16
+ defaultInterval = 300;
17
+ async check() {
18
+ const agentsDir = path_1.default.join(this.config.openclawPath, 'agents');
19
+ const results = [];
20
+ if (!fs_1.default.existsSync(agentsDir)) {
21
+ results.push(this.ok('No agents directory found', 'cost_no_agents_dir'));
22
+ return results;
23
+ }
24
+ const sessionCosts = this.collectSessionCosts(agentsDir);
25
+ if (sessionCosts.length < 2) {
26
+ results.push(this.ok('Not enough sessions to calculate cost baseline', 'cost_no_baseline'));
27
+ return results;
28
+ }
29
+ // Sort by timestamp, newest last
30
+ sessionCosts.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
31
+ // Check recent sessions against rolling average
32
+ const recentCutoff = new Date(Date.now() - RECENT_HOURS * 3600 * 1000);
33
+ const recentSessions = sessionCosts.filter(s => s.timestamp > recentCutoff);
34
+ if (recentSessions.length === 0) {
35
+ results.push(this.ok('No recent sessions to check for cost anomalies', 'cost_ok'));
36
+ return results;
37
+ }
38
+ // Calculate rolling average from last ROLLING_WINDOW sessions (excluding recent ones)
39
+ const historicalSessions = sessionCosts
40
+ .filter(s => s.timestamp <= recentCutoff)
41
+ .slice(-ROLLING_WINDOW);
42
+ if (historicalSessions.length === 0) {
43
+ results.push(this.ok('No historical sessions for cost baseline yet', 'cost_no_baseline'));
44
+ return results;
45
+ }
46
+ const avgCost = historicalSessions.reduce((sum, s) => sum + s.cost, 0) / historicalSessions.length;
47
+ const threshold = avgCost * ANOMALY_MULTIPLIER;
48
+ const anomalies = recentSessions.filter(s => s.cost > threshold && s.cost > 0.01);
49
+ if (anomalies.length === 0) {
50
+ results.push(this.ok(`Cost normal — avg $${avgCost.toFixed(4)}/session, ${recentSessions.length} recent session(s) checked`, 'cost_ok'));
51
+ }
52
+ else {
53
+ for (const anomaly of anomalies) {
54
+ results.push(this.error(`Cost anomaly in agent '${anomaly.agent}': $${anomaly.cost.toFixed(4)} (${Math.round(anomaly.cost / avgCost)}x avg of $${avgCost.toFixed(4)})`, 'cost_anomaly', {
55
+ agent: anomaly.agent,
56
+ session: anomaly.session,
57
+ cost: anomaly.cost,
58
+ avgCost,
59
+ multiplier: anomaly.cost / avgCost,
60
+ }));
61
+ }
62
+ }
63
+ return results;
64
+ }
65
+ collectSessionCosts(agentsDir) {
66
+ const costs = [];
67
+ let agentDirs;
68
+ try {
69
+ agentDirs = fs_1.default.readdirSync(agentsDir).filter(d => {
70
+ try {
71
+ return fs_1.default.statSync(path_1.default.join(agentsDir, d)).isDirectory();
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ });
77
+ }
78
+ catch {
79
+ return costs;
80
+ }
81
+ for (const agentDir of agentDirs) {
82
+ const sessionsDir = path_1.default.join(agentsDir, agentDir, 'sessions');
83
+ if (!fs_1.default.existsSync(sessionsDir))
84
+ continue;
85
+ let sessionFiles;
86
+ try {
87
+ sessionFiles = fs_1.default.readdirSync(sessionsDir)
88
+ .filter(f => f.endsWith('.jsonl'))
89
+ .map(f => {
90
+ const stat = fs_1.default.statSync(path_1.default.join(sessionsDir, f));
91
+ return { name: f, mtime: stat.mtime };
92
+ })
93
+ .filter(f => (0, utils_js_1.fileAgeSeconds)(f.mtime) < 7 * 24 * 3600) // last 7 days
94
+ .sort((a, b) => a.mtime.getTime() - b.mtime.getTime())
95
+ .map(f => f.name);
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ for (const sessionFile of sessionFiles) {
101
+ const sessionPath = path_1.default.join(sessionsDir, sessionFile);
102
+ const cost = this.extractSessionCost(sessionPath);
103
+ if (cost !== null) {
104
+ const stat = fs_1.default.statSync(sessionPath);
105
+ costs.push({ agent: agentDir, session: sessionFile, cost, timestamp: stat.mtime });
106
+ }
107
+ }
108
+ }
109
+ return costs;
110
+ }
111
+ extractSessionCost(filePath) {
112
+ try {
113
+ const lines = fs_1.default.readFileSync(filePath, 'utf-8').split('\n').filter(l => l.trim());
114
+ let totalCost = 0;
115
+ let found = false;
116
+ for (const line of lines) {
117
+ try {
118
+ const entry = JSON.parse(line);
119
+ if (entry.usage?.cost_usd != null) {
120
+ totalCost += entry.usage.cost_usd;
121
+ found = true;
122
+ }
123
+ else if (entry.cost != null) {
124
+ totalCost = entry.cost; // use last total_cost if available
125
+ found = true;
126
+ }
127
+ else if (entry.total_cost != null) {
128
+ totalCost = entry.total_cost;
129
+ found = true;
130
+ }
131
+ else if (entry.usage?.input_tokens != null || entry.usage?.output_tokens != null) {
132
+ // Estimate cost: ~$3/M input, ~$15/M output (Claude Sonnet ballpark)
133
+ const inputTokens = entry.usage.input_tokens ?? 0;
134
+ const outputTokens = entry.usage.output_tokens ?? 0;
135
+ totalCost += (inputTokens * 3 + outputTokens * 15) / 1_000_000;
136
+ found = true;
137
+ }
138
+ }
139
+ catch {
140
+ continue;
141
+ }
142
+ }
143
+ return found ? totalCost : null;
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }
149
+ }
150
+ exports.CostWatcher = CostWatcher;
151
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/watchers/cost.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { BaseWatcher, WatchResult } from './base.js';
2
+ export declare class CronWatcher extends BaseWatcher {
3
+ readonly name = "CronWatcher";
4
+ readonly defaultInterval = 60;
5
+ check(): Promise<WatchResult[]>;
6
+ }
7
+ //# sourceMappingURL=cron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/watchers/cron.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AA2BrD,qBAAa,WAAY,SAAQ,WAAW;IAC1C,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,eAAe,MAAM;IAExB,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;CAoGtC"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CronWatcher = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const base_js_1 = require("./base.js");
10
+ class CronWatcher extends base_js_1.BaseWatcher {
11
+ name = 'CronWatcher';
12
+ defaultInterval = 60;
13
+ async check() {
14
+ const jobsFile = path_1.default.join(this.config.openclawPath, 'cron', 'jobs.json');
15
+ const results = [];
16
+ if (!fs_1.default.existsSync(jobsFile)) {
17
+ results.push(this.ok('No cron jobs file found', 'cron_no_file'));
18
+ return results;
19
+ }
20
+ let data;
21
+ try {
22
+ const raw = fs_1.default.readFileSync(jobsFile, 'utf-8');
23
+ data = JSON.parse(raw);
24
+ }
25
+ catch {
26
+ results.push(this.warn(`Cannot parse cron jobs file: ${jobsFile}`, 'cron_parse_error'));
27
+ return results;
28
+ }
29
+ const jobs = data.jobs ?? [];
30
+ const enabledJobs = jobs.filter(j => j.enabled);
31
+ if (enabledJobs.length === 0) {
32
+ results.push(this.ok('No enabled cron jobs', 'cron_none_enabled'));
33
+ return results;
34
+ }
35
+ let healthy = 0;
36
+ let errored = 0;
37
+ let overdue = 0;
38
+ for (const job of enabledJobs) {
39
+ const state = job.state ?? {};
40
+ const name = job.name ?? job.id;
41
+ // Check consecutive errors
42
+ if ((state.consecutiveErrors ?? 0) >= 3) {
43
+ results.push(this.error(`Cron '${name}' has ${state.consecutiveErrors} consecutive errors`, 'cron_consecutive_errors', { cronName: name, jobId: job.id, consecutiveErrors: state.consecutiveErrors, lastStatus: state.lastRunStatus }));
44
+ errored++;
45
+ continue;
46
+ }
47
+ // Check last run status
48
+ if (state.lastRunStatus && state.lastRunStatus !== 'ok') {
49
+ results.push(this.warn(`Cron '${name}' last run status: ${state.lastRunStatus}`, 'cron_last_error', { cronName: name, jobId: job.id, lastRunStatus: state.lastRunStatus }));
50
+ errored++;
51
+ continue;
52
+ }
53
+ // Check if overdue (should have run but nextRunAtMs is in the past by >2x the expected interval)
54
+ if (state.nextRunAtMs) {
55
+ const now = Date.now();
56
+ const overdueMs = now - state.nextRunAtMs;
57
+ // Consider overdue if more than 30 minutes past expected next run
58
+ if (overdueMs > 30 * 60 * 1000) {
59
+ results.push(this.warn(`Cron '${name}' overdue — was expected ${Math.round(overdueMs / 60000)}m ago`, 'cron_overdue', { cronName: name, jobId: job.id, nextRunAtMs: state.nextRunAtMs, overdueMs }));
60
+ overdue++;
61
+ continue;
62
+ }
63
+ }
64
+ // Check delivery status
65
+ if (state.lastDeliveryStatus && state.lastDeliveryStatus !== 'delivered') {
66
+ results.push(this.warn(`Cron '${name}' last delivery failed: ${state.lastDeliveryStatus}`, 'cron_delivery_failed', { cronName: name, jobId: job.id, lastDeliveryStatus: state.lastDeliveryStatus }));
67
+ continue;
68
+ }
69
+ healthy++;
70
+ }
71
+ // Summary result
72
+ if (errored === 0 && overdue === 0) {
73
+ results.push(this.ok(`${healthy} cron job(s) healthy`, 'cron_all_ok'));
74
+ }
75
+ return results;
76
+ }
77
+ }
78
+ exports.CronWatcher = CronWatcher;
79
+ //# sourceMappingURL=cron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/watchers/cron.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { BaseWatcher, WatchResult } from './base.js';
2
+ export declare class GatewayWatcher extends BaseWatcher {
3
+ readonly name = "GatewayWatcher";
4
+ readonly defaultInterval = 30;
5
+ check(): Promise<WatchResult[]>;
6
+ }
7
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/watchers/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGrD,qBAAa,cAAe,SAAQ,WAAW;IAC7C,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,eAAe,MAAM;IAExB,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;CA6BtC"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GatewayWatcher = void 0;
4
+ const base_js_1 = require("./base.js");
5
+ const utils_js_1 = require("../utils.js");
6
+ class GatewayWatcher extends base_js_1.BaseWatcher {
7
+ name = 'GatewayWatcher';
8
+ defaultInterval = 30;
9
+ async check() {
10
+ // Check openclaw gateway status directly
11
+ const status = (0, utils_js_1.runShell)('openclaw gateway status 2>/dev/null');
12
+ if (status.ok && /running|active|started/i.test(status.stdout)) {
13
+ return [this.ok('Gateway running', 'gateway_running')];
14
+ }
15
+ // Fallback: check systemd service
16
+ const systemctl = (0, utils_js_1.runShell)('systemctl is-active openclaw-gateway 2>/dev/null');
17
+ if (systemctl.ok && systemctl.stdout.trim() === 'active') {
18
+ return [this.ok('Gateway systemd service active', 'gateway_running')];
19
+ }
20
+ // Fallback: check if any openclaw gateway process exists
21
+ // Use more specific match to avoid matching unrelated openclaw processes
22
+ const pgrep = (0, utils_js_1.runShell)('pgrep -f "openclaw.*gateway" 2>/dev/null');
23
+ if (pgrep.ok && pgrep.stdout.trim().length > 0) {
24
+ const pid = pgrep.stdout.trim().split('\n')[0]; // Just show first PID
25
+ return [this.ok(`Gateway process running (PID: ${pid})`, 'gateway_running')];
26
+ }
27
+ return [
28
+ this.critical('Gateway process not found', 'gateway_down', { statusOutput: status.stdout?.substring(0, 200), statusErr: status.stderr?.substring(0, 200) }),
29
+ ];
30
+ }
31
+ }
32
+ exports.GatewayWatcher = GatewayWatcher;
33
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/watchers/gateway.ts"],"names":[],"mappings":";;;AAAA,uCAAqD;AACrD,0CAAuC;AAEvC,MAAa,cAAe,SAAQ,qBAAW;IACpC,IAAI,GAAG,gBAAgB,CAAC;IACxB,eAAe,GAAG,EAAE,CAAC;IAE9B,KAAK,CAAC,KAAK;QACT,yCAAyC;QACzC,MAAM,MAAM,GAAG,IAAA,mBAAQ,EAAC,qCAAqC,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,EAAE,IAAI,yBAAyB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACzD,CAAC;QAED,kCAAkC;QAClC,MAAM,SAAS,GAAG,IAAA,mBAAQ,EAAC,kDAAkD,CAAC,CAAC;QAC/E,IAAI,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACzD,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,gCAAgC,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACxE,CAAC;QAED,yDAAyD;QACzD,yEAAyE;QACzE,MAAM,KAAK,GAAG,IAAA,mBAAQ,EAAC,0CAA0C,CAAC,CAAC;QACnE,IAAI,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;YACtE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,iCAAiC,GAAG,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC/E,CAAC;QAED,OAAO;YACL,IAAI,CAAC,QAAQ,CACX,2BAA2B,EAC3B,cAAc,EACd,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAChG;SACF,CAAC;IACJ,CAAC;CACF;AAjCD,wCAiCC"}
@@ -0,0 +1,7 @@
1
+ import { BaseWatcher, WatchResult } from './base.js';
2
+ export declare class SessionWatcher extends BaseWatcher {
3
+ readonly name = "SessionWatcher";
4
+ readonly defaultInterval = 60;
5
+ check(): Promise<WatchResult[]>;
6
+ }
7
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/watchers/session.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAcrD,qBAAa,cAAe,SAAQ,WAAW;IAC7C,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,eAAe,MAAM;IAExB,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;CA8GtC"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SessionWatcher = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const base_js_1 = require("./base.js");
10
+ const utils_js_1 = require("../utils.js");
11
+ const MAX_SESSION_AGE_HOURS = 4;
12
+ const SESSION_TIMEOUT_SECONDS = MAX_SESSION_AGE_HOURS * 3600;
13
+ class SessionWatcher extends base_js_1.BaseWatcher {
14
+ name = 'SessionWatcher';
15
+ defaultInterval = 60;
16
+ async check() {
17
+ const agentsDir = path_1.default.join(this.config.openclawPath, 'agents');
18
+ const results = [];
19
+ if (!fs_1.default.existsSync(agentsDir)) {
20
+ results.push(this.ok('No agents directory found', 'session_no_agents_dir'));
21
+ return results;
22
+ }
23
+ let agentDirs;
24
+ try {
25
+ agentDirs = fs_1.default.readdirSync(agentsDir).filter(d => {
26
+ try {
27
+ return fs_1.default.statSync(path_1.default.join(agentsDir, d)).isDirectory();
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ });
33
+ }
34
+ catch {
35
+ results.push(this.warn(`Cannot read agents dir: ${agentsDir}`, 'session_agents_unreadable'));
36
+ return results;
37
+ }
38
+ let checkedSessions = 0;
39
+ for (const agentDir of agentDirs) {
40
+ const sessionsDir = path_1.default.join(agentsDir, agentDir, 'sessions');
41
+ if (!fs_1.default.existsSync(sessionsDir))
42
+ continue;
43
+ let sessionFiles;
44
+ try {
45
+ sessionFiles = fs_1.default.readdirSync(sessionsDir)
46
+ .filter(f => f.endsWith('.jsonl'))
47
+ .map(f => ({ name: f, mtime: fs_1.default.statSync(path_1.default.join(sessionsDir, f)).mtime }))
48
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
49
+ .slice(0, 5) // newest 5 sessions per agent
50
+ .map(f => f.name);
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ for (const sessionFile of sessionFiles) {
56
+ const sessionPath = path_1.default.join(sessionsDir, sessionFile);
57
+ checkedSessions++;
58
+ try {
59
+ const stat = fs_1.default.statSync(sessionPath);
60
+ const ageSec = (0, utils_js_1.fileAgeSeconds)(stat.mtime);
61
+ // Only check recent sessions (< 4 hours old)
62
+ if (ageSec > SESSION_TIMEOUT_SECONDS)
63
+ continue;
64
+ const lines = fs_1.default.readFileSync(sessionPath, 'utf-8')
65
+ .split('\n')
66
+ .filter(l => l.trim())
67
+ .slice(-50); // last 50 entries
68
+ const entries = lines.map(line => {
69
+ try {
70
+ return JSON.parse(line);
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }).filter((e) => e !== null);
76
+ // Check for errors
77
+ const errorEntries = entries.filter(e => e.type === 'error' || e.error || e.status === 'error');
78
+ if (errorEntries.length > 0) {
79
+ const lastError = errorEntries[errorEntries.length - 1];
80
+ results.push(this.error(`Session error in agent '${agentDir}': ${lastError.error ?? lastError.type ?? 'unknown error'}`, 'session_error', { agent: agentDir, session: sessionFile, error: lastError.error }));
81
+ continue;
82
+ }
83
+ // Check for aborted sessions
84
+ const abortedEntries = entries.filter(e => e.status === 'aborted' || e.type === 'abort');
85
+ if (abortedEntries.length > 0) {
86
+ results.push(this.warn(`Session aborted in agent '${agentDir}'`, 'session_aborted', { agent: agentDir, session: sessionFile }));
87
+ continue;
88
+ }
89
+ // Check for sessions that ran too long (still active but file is old)
90
+ const lastEntry = entries[entries.length - 1];
91
+ if (lastEntry?.type !== 'end' && lastEntry?.status !== 'complete' && ageSec > 3600) {
92
+ results.push(this.warn(`Session in agent '${agentDir}' may be stuck (running for ${Math.round(ageSec / 60)}m)`, 'session_stuck', { agent: agentDir, session: sessionFile, ageSec }));
93
+ }
94
+ }
95
+ catch {
96
+ continue;
97
+ }
98
+ }
99
+ }
100
+ if (results.length === 0) {
101
+ if (checkedSessions > 0) {
102
+ results.push(this.ok(`${checkedSessions} recent session(s) healthy`, 'sessions_ok'));
103
+ }
104
+ else {
105
+ results.push(this.ok('No recent sessions to check', 'sessions_ok'));
106
+ }
107
+ }
108
+ return results;
109
+ }
110
+ }
111
+ exports.SessionWatcher = SessionWatcher;
112
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/watchers/session.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "clawdoctor",
3
+ "version": "0.1.0",
4
+ "description": "Self-healing doctor for OpenClaw. Watches gateway, crons, and sessions, sends Telegram alerts, and auto-fixes what it can.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "clawdoctor": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "test": "node --test dist/test/**/*.js",
13
+ "test:src": "npx ts-node --test src/test/**/*.ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "openclaw",
18
+ "monitor",
19
+ "cli",
20
+ "agent",
21
+ "self-healing"
22
+ ],
23
+ "author": "Matt Turley",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "better-sqlite3": "^9.4.3",
27
+ "commander": "^12.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/better-sqlite3": "^7.6.8",
31
+ "@types/node": "^20.12.7",
32
+ "typescript": "^5.4.5"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "files": [
38
+ "dist/**/*",
39
+ "README.md"
40
+ ],
41
+ "homepage": "https://clawdoctor.dev",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/turleydesigns/clawdoctor.git"
45
+ }
46
+ }