clawsql 0.2.1 → 0.2.3
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/README.md +25 -5
- package/dist/bin/clawsql.js +0 -0
- package/dist/cli/agent/handler.d.ts +13 -31
- package/dist/cli/agent/handler.d.ts.map +1 -1
- package/dist/cli/agent/handler.js +111 -149
- package/dist/cli/agent/handler.js.map +1 -1
- package/dist/cli/agent/index.d.ts +2 -1
- package/dist/cli/agent/index.d.ts.map +1 -1
- package/dist/cli/agent/index.js +7 -1
- package/dist/cli/agent/index.js.map +1 -1
- package/dist/cli/agent/openclaw-integration.d.ts +92 -25
- package/dist/cli/agent/openclaw-integration.d.ts.map +1 -1
- package/dist/cli/agent/openclaw-integration.js +312 -195
- package/dist/cli/agent/openclaw-integration.js.map +1 -1
- package/dist/cli/commands/cleanup.d.ts.map +1 -1
- package/dist/cli/commands/cleanup.js +26 -15
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +0 -4
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +232 -581
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/openclaw.d.ts +9 -0
- package/dist/cli/commands/openclaw.d.ts.map +1 -0
- package/dist/cli/commands/openclaw.js +236 -0
- package/dist/cli/commands/openclaw.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +264 -8
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +7 -34
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/repl.js +1 -1
- package/dist/cli/repl.js.map +1 -1
- package/dist/cli/utils/ai-config.d.ts +32 -0
- package/dist/cli/utils/ai-config.d.ts.map +1 -0
- package/dist/cli/utils/ai-config.js +78 -0
- package/dist/cli/utils/ai-config.js.map +1 -0
- package/dist/cli/utils/docker-files.d.ts.map +1 -1
- package/dist/cli/utils/docker-files.js +2 -0
- package/dist/cli/utils/docker-files.js.map +1 -1
- package/docker/openclaw/entrypoint.sh +102 -0
- package/docker/openclaw/openclaw.json +4 -1
- package/docker/orchestrator/orchestrator-schema.sql +837 -0
- package/docker-compose.yml +22 -14
- package/init/metadata.sql +14 -2
- package/package.json +1 -1
|
@@ -39,7 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.isOpenClawGatewayReachable = exports.isDockerOpenClawContainerRunning = exports.OpenClawAgent = void 0;
|
|
42
|
+
exports.isOpenClawGatewayReachable = exports.isDockerOpenClawContainerRunning = exports.SUPPORTED_PROVIDERS = exports.OpenClawAgent = exports.CONFIG = void 0;
|
|
43
43
|
exports.isDockerOpenClawAvailable = isDockerOpenClawAvailable;
|
|
44
44
|
exports.isGatewayHealthy = isGatewayHealthy;
|
|
45
45
|
exports.isLocalOpenClawAvailable = isLocalOpenClawAvailable;
|
|
@@ -51,27 +51,45 @@ exports.scheduleCron = scheduleCron;
|
|
|
51
51
|
exports.sendNotification = sendNotification;
|
|
52
52
|
exports.writeToMemory = writeToMemory;
|
|
53
53
|
exports.createOpenClawAgent = createOpenClawAgent;
|
|
54
|
+
exports.getModelProviderInfo = getModelProviderInfo;
|
|
55
|
+
exports.configureModelProvider = configureModelProvider;
|
|
56
|
+
exports.testOpenClawConnection = testOpenClawConnection;
|
|
57
|
+
exports.getDetailedOpenClawStatus = getDetailedOpenClawStatus;
|
|
54
58
|
exports.ensureOpenClawRunning = ensureOpenClawRunning;
|
|
55
59
|
const child_process_1 = require("child_process");
|
|
56
60
|
const fs = __importStar(require("fs"));
|
|
57
61
|
const path = __importStar(require("path"));
|
|
58
62
|
const os = __importStar(require("os"));
|
|
63
|
+
const docker_prereq_js_1 = require("../utils/docker-prereq.js");
|
|
59
64
|
// ============================================================================
|
|
60
65
|
// Configuration
|
|
61
66
|
// ============================================================================
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
exports.CONFIG = {
|
|
68
|
+
gatewayUrl: process.env.OPENCLAW_GATEWAY_URL || 'ws://localhost:18789',
|
|
69
|
+
gatewayToken: process.env.OPENCLAW_GATEWAY_TOKEN || 'clawsql-openclaw-token',
|
|
70
|
+
sessionId: 'clawsql-session',
|
|
71
|
+
defaultTimeout: 120000,
|
|
72
|
+
healthCheckTimeout: 5000,
|
|
73
|
+
containerName: 'openclaw',
|
|
74
|
+
};
|
|
75
|
+
// Cached runtime detection
|
|
76
|
+
let cachedRuntime;
|
|
77
|
+
async function getContainerRuntime() {
|
|
78
|
+
if (cachedRuntime === undefined) {
|
|
79
|
+
cachedRuntime = await (0, docker_prereq_js_1.detectRuntime)();
|
|
80
|
+
}
|
|
81
|
+
return cachedRuntime;
|
|
82
|
+
}
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Process Execution Utilities
|
|
85
|
+
// ============================================================================
|
|
68
86
|
/**
|
|
69
87
|
* Execute a command and return the result
|
|
70
88
|
*/
|
|
71
89
|
function execCommand(command, args, options) {
|
|
72
90
|
return new Promise((resolve) => {
|
|
73
91
|
const proc = (0, child_process_1.spawn)(command, args, {
|
|
74
|
-
timeout: options?.timeout
|
|
92
|
+
timeout: options?.timeout ?? 30000,
|
|
75
93
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
76
94
|
env: options?.env ? { ...process.env, ...options.env } : process.env,
|
|
77
95
|
});
|
|
@@ -88,39 +106,146 @@ function execCommand(command, args, options) {
|
|
|
88
106
|
});
|
|
89
107
|
}
|
|
90
108
|
/**
|
|
91
|
-
*
|
|
109
|
+
* Get spawn configuration for OpenClaw CLI execution
|
|
110
|
+
* Automatically uses container exec when OpenClaw is running in Docker/Podman
|
|
92
111
|
*/
|
|
93
|
-
async function
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
async function getSpawnConfig(args) {
|
|
113
|
+
const isDocker = await isDockerOpenClawAvailable();
|
|
114
|
+
const runtime = isDocker ? await getContainerRuntime() : null;
|
|
115
|
+
// Environment variables to pass to OpenClaw
|
|
116
|
+
// Include all AI-related env vars for seamless integration
|
|
117
|
+
const env = {
|
|
118
|
+
OPENCLAW_GATEWAY_URL: exports.CONFIG.gatewayUrl,
|
|
119
|
+
OPENCLAW_GATEWAY_TOKEN: exports.CONFIG.gatewayToken,
|
|
120
|
+
// Anthropic/Claude configuration
|
|
121
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? '',
|
|
122
|
+
ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL ?? '',
|
|
123
|
+
ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL ?? '',
|
|
124
|
+
// OpenAI configuration
|
|
125
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '',
|
|
126
|
+
OPENAI_BASE_URL: process.env.OPENAI_BASE_URL ?? '',
|
|
127
|
+
OPENAI_MODEL: process.env.OPENAI_MODEL ?? '',
|
|
128
|
+
};
|
|
129
|
+
if (runtime) {
|
|
130
|
+
// Execute inside container - pass env vars via -e flags
|
|
131
|
+
return {
|
|
132
|
+
command: runtime,
|
|
133
|
+
args: [
|
|
134
|
+
'exec',
|
|
135
|
+
'-e', 'OPENCLAW_GATEWAY_URL',
|
|
136
|
+
'-e', 'OPENCLAW_GATEWAY_TOKEN',
|
|
137
|
+
'-e', 'ANTHROPIC_API_KEY',
|
|
138
|
+
'-e', 'ANTHROPIC_BASE_URL',
|
|
139
|
+
'-e', 'ANTHROPIC_MODEL',
|
|
140
|
+
'-e', 'OPENAI_API_KEY',
|
|
141
|
+
'-e', 'OPENAI_BASE_URL',
|
|
142
|
+
'-e', 'OPENAI_MODEL',
|
|
143
|
+
exports.CONFIG.containerName,
|
|
144
|
+
'openclaw',
|
|
145
|
+
...args,
|
|
146
|
+
],
|
|
147
|
+
env,
|
|
148
|
+
};
|
|
99
149
|
}
|
|
100
|
-
|
|
101
|
-
return
|
|
150
|
+
// Execute local CLI
|
|
151
|
+
return {
|
|
152
|
+
command: 'openclaw',
|
|
153
|
+
args,
|
|
154
|
+
env,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Spawn OpenClaw process with proper configuration
|
|
159
|
+
* Filters out internal log messages from stdout
|
|
160
|
+
*/
|
|
161
|
+
function spawnOpenClaw(args, options) {
|
|
162
|
+
return new Promise(async (resolve, reject) => {
|
|
163
|
+
const spawnConfig = await getSpawnConfig(args);
|
|
164
|
+
const timeout = options?.timeout ?? exports.CONFIG.defaultTimeout;
|
|
165
|
+
const proc = (0, child_process_1.spawn)(spawnConfig.command, spawnConfig.args, {
|
|
166
|
+
timeout,
|
|
167
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
168
|
+
env: spawnConfig.env,
|
|
169
|
+
});
|
|
170
|
+
let stdout = '';
|
|
171
|
+
let stderr = '';
|
|
172
|
+
/**
|
|
173
|
+
* Filter out OpenClaw internal log lines from output
|
|
174
|
+
* Log lines start with [bracket] pattern like [agents/...] or [xai-auth]
|
|
175
|
+
*/
|
|
176
|
+
const filterLogLines = (text) => {
|
|
177
|
+
return text.split('\n')
|
|
178
|
+
.filter(line => !line.trim().startsWith('['))
|
|
179
|
+
.join('\n');
|
|
180
|
+
};
|
|
181
|
+
proc.stdout?.on('data', (data) => {
|
|
182
|
+
const chunk = data.toString();
|
|
183
|
+
stdout += chunk;
|
|
184
|
+
// Filter log lines when streaming to user
|
|
185
|
+
const filtered = filterLogLines(chunk);
|
|
186
|
+
if (filtered) {
|
|
187
|
+
options?.onChunk?.(filtered);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
proc.stderr?.on('data', (data) => { stderr += data; });
|
|
191
|
+
// Abort signal handling
|
|
192
|
+
const abortHandler = () => {
|
|
193
|
+
proc.kill('SIGKILL');
|
|
194
|
+
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
195
|
+
};
|
|
196
|
+
if (options?.signal) {
|
|
197
|
+
if (options.signal.aborted) {
|
|
198
|
+
proc.kill('SIGKILL');
|
|
199
|
+
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
options.signal.addEventListener('abort', abortHandler);
|
|
203
|
+
}
|
|
204
|
+
proc.on('close', (code) => {
|
|
205
|
+
options?.signal?.removeEventListener('abort', abortHandler);
|
|
206
|
+
if (code === 0) {
|
|
207
|
+
// Filter log lines from final output
|
|
208
|
+
resolve(filterLogLines(stdout).trim());
|
|
209
|
+
}
|
|
210
|
+
else if (code === null) {
|
|
211
|
+
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
reject(new Error(`OpenClaw failed (exit ${code}): ${stderr || stdout}`));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
proc.on('error', (err) => {
|
|
218
|
+
options?.signal?.removeEventListener('abort', abortHandler);
|
|
219
|
+
reject(new Error(`Failed to spawn openclaw: ${err.message}`));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
102
222
|
}
|
|
103
223
|
// ============================================================================
|
|
104
224
|
// Detection Functions
|
|
105
225
|
// ============================================================================
|
|
106
226
|
/**
|
|
107
|
-
* Check if OpenClaw
|
|
227
|
+
* Check if OpenClaw container is running
|
|
108
228
|
*/
|
|
109
229
|
async function isDockerOpenClawAvailable() {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
230
|
+
const runtime = await getContainerRuntime();
|
|
231
|
+
if (!runtime)
|
|
232
|
+
return false;
|
|
233
|
+
const result = await execCommand(runtime, [
|
|
234
|
+
'ps',
|
|
235
|
+
'--filter', `name=${exports.CONFIG.containerName}`,
|
|
236
|
+
'--filter', 'status=running',
|
|
237
|
+
'--format', '{{.Names}}',
|
|
113
238
|
]);
|
|
114
|
-
return result.success && result.stdout.trim() ===
|
|
239
|
+
return result.success && result.stdout.trim() === exports.CONFIG.containerName;
|
|
115
240
|
}
|
|
116
241
|
/**
|
|
117
242
|
* Check if OpenClaw gateway health endpoint responds
|
|
118
243
|
*/
|
|
119
244
|
async function isGatewayHealthy() {
|
|
120
245
|
try {
|
|
121
|
-
const httpUrl =
|
|
246
|
+
const httpUrl = exports.CONFIG.gatewayUrl.replace('ws://', 'http://').replace('wss://', 'https://');
|
|
122
247
|
const response = await fetch(`${httpUrl}/health`, {
|
|
123
|
-
signal: AbortSignal.timeout(
|
|
248
|
+
signal: AbortSignal.timeout(exports.CONFIG.healthCheckTimeout),
|
|
124
249
|
});
|
|
125
250
|
return response.ok;
|
|
126
251
|
}
|
|
@@ -129,22 +254,16 @@ async function isGatewayHealthy() {
|
|
|
129
254
|
}
|
|
130
255
|
}
|
|
131
256
|
/**
|
|
132
|
-
* Check if local OpenClaw CLI
|
|
257
|
+
* Check if local OpenClaw CLI is running (not in container)
|
|
133
258
|
*/
|
|
134
259
|
async function isLocalOpenClawAvailable() {
|
|
135
|
-
|
|
136
|
-
if (await isDockerOpenClawAvailable()) {
|
|
260
|
+
if (await isDockerOpenClawAvailable())
|
|
137
261
|
return false;
|
|
138
|
-
|
|
139
|
-
// Gateway must be reachable
|
|
140
|
-
if (!(await isGatewayHealthy())) {
|
|
262
|
+
if (!(await isGatewayHealthy()))
|
|
141
263
|
return false;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const result = await execCommand('openclaw', ['status', '--json'], { timeout: 5000 });
|
|
145
|
-
if (!result.success) {
|
|
264
|
+
const result = await execCommand('openclaw', ['status', '--json'], { timeout: exports.CONFIG.healthCheckTimeout });
|
|
265
|
+
if (!result.success)
|
|
146
266
|
return false;
|
|
147
|
-
}
|
|
148
267
|
try {
|
|
149
268
|
const status = JSON.parse(result.stdout);
|
|
150
269
|
return status.gateway?.mode === 'local' && !!status.gateway?.url;
|
|
@@ -154,255 +273,253 @@ async function isLocalOpenClawAvailable() {
|
|
|
154
273
|
}
|
|
155
274
|
}
|
|
156
275
|
/**
|
|
157
|
-
* Check if any OpenClaw
|
|
276
|
+
* Check if any OpenClaw is available
|
|
277
|
+
* Optimized: checks gateway health first (fast HTTP), then container status
|
|
158
278
|
*/
|
|
159
279
|
async function isOpenClawAvailable() {
|
|
160
|
-
|
|
280
|
+
// Fast check: gateway health endpoint (no subprocess)
|
|
281
|
+
if (await isGatewayHealthy()) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
// Fallback: check if container is running (slower, spawns subprocess)
|
|
285
|
+
return isDockerOpenClawAvailable();
|
|
161
286
|
}
|
|
162
287
|
/**
|
|
163
|
-
* Get
|
|
288
|
+
* Get OpenClaw status summary
|
|
164
289
|
*/
|
|
165
290
|
async function getOpenClawStatus() {
|
|
166
291
|
const [isDocker, isLocal] = await Promise.all([
|
|
167
292
|
isDockerOpenClawAvailable(),
|
|
168
293
|
isLocalOpenClawAvailable(),
|
|
169
294
|
]);
|
|
170
|
-
return {
|
|
171
|
-
available: isDocker || isLocal,
|
|
172
|
-
isDocker,
|
|
173
|
-
isLocal,
|
|
174
|
-
};
|
|
295
|
+
return { available: isDocker || isLocal, isDocker, isLocal };
|
|
175
296
|
}
|
|
176
297
|
// ============================================================================
|
|
177
298
|
// Agent Functions
|
|
178
299
|
// ============================================================================
|
|
179
300
|
/**
|
|
180
|
-
*
|
|
301
|
+
* Build agent CLI arguments
|
|
181
302
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
303
|
+
function buildAgentArgs(message) {
|
|
304
|
+
return [
|
|
184
305
|
'agent',
|
|
185
|
-
'--
|
|
306
|
+
'--local',
|
|
307
|
+
'--session-id', exports.CONFIG.sessionId,
|
|
186
308
|
'--message', message,
|
|
187
309
|
'--thinking', 'minimal',
|
|
188
310
|
];
|
|
189
|
-
const timeout = options?.timeout || DEFAULT_TIMEOUT;
|
|
190
|
-
const gatewayUrl = options?.gatewayUrl || getGatewayUrl();
|
|
191
|
-
const gatewayToken = options?.gatewayToken || getGatewayToken();
|
|
192
|
-
return new Promise((resolve, reject) => {
|
|
193
|
-
const proc = (0, child_process_1.spawn)('openclaw', args, {
|
|
194
|
-
timeout,
|
|
195
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
196
|
-
env: {
|
|
197
|
-
...process.env,
|
|
198
|
-
OPENCLAW_GATEWAY_URL: gatewayUrl,
|
|
199
|
-
OPENCLAW_GATEWAY_TOKEN: gatewayToken,
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
let stdout = '';
|
|
203
|
-
let stderr = '';
|
|
204
|
-
proc.stdout?.on('data', (data) => { stdout += data; });
|
|
205
|
-
proc.stderr?.on('data', (data) => { stderr += data; });
|
|
206
|
-
// Handle abort signal
|
|
207
|
-
const abortHandler = () => {
|
|
208
|
-
proc.kill('SIGKILL');
|
|
209
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
210
|
-
};
|
|
211
|
-
if (options?.signal) {
|
|
212
|
-
if (options.signal.aborted) {
|
|
213
|
-
proc.kill('SIGKILL');
|
|
214
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
options.signal.addEventListener('abort', abortHandler);
|
|
218
|
-
}
|
|
219
|
-
proc.on('close', (code) => {
|
|
220
|
-
options?.signal?.removeEventListener('abort', abortHandler);
|
|
221
|
-
if (code === 0) {
|
|
222
|
-
resolve(stdout.trim());
|
|
223
|
-
}
|
|
224
|
-
else if (code === null) {
|
|
225
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
reject(new Error(`OpenClaw agent failed: ${stderr || stdout}`));
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
proc.on('error', (err) => {
|
|
232
|
-
options?.signal?.removeEventListener('abort', abortHandler);
|
|
233
|
-
reject(new Error(`Failed to run openclaw: ${err.message}`));
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
311
|
}
|
|
237
312
|
/**
|
|
238
|
-
* Send
|
|
313
|
+
* Send message to OpenClaw agent
|
|
314
|
+
*/
|
|
315
|
+
async function sendToOpenClaw(message, options) {
|
|
316
|
+
return spawnOpenClaw(buildAgentArgs(message), options);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Send message to OpenClaw with streaming output
|
|
239
320
|
*/
|
|
240
321
|
async function sendToOpenClawStream(message, onChunk, options) {
|
|
241
|
-
|
|
242
|
-
'agent',
|
|
243
|
-
'--session-id', SESSION_ID,
|
|
244
|
-
'--message', message,
|
|
245
|
-
'--thinking', 'minimal',
|
|
246
|
-
];
|
|
247
|
-
const timeout = options?.timeout || DEFAULT_TIMEOUT;
|
|
248
|
-
const gatewayUrl = options?.gatewayUrl || getGatewayUrl();
|
|
249
|
-
const gatewayToken = options?.gatewayToken || getGatewayToken();
|
|
250
|
-
return new Promise((resolve, reject) => {
|
|
251
|
-
const proc = (0, child_process_1.spawn)('openclaw', args, {
|
|
252
|
-
timeout,
|
|
253
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
254
|
-
env: {
|
|
255
|
-
...process.env,
|
|
256
|
-
OPENCLAW_GATEWAY_URL: gatewayUrl,
|
|
257
|
-
OPENCLAW_GATEWAY_TOKEN: gatewayToken,
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
let stdout = '';
|
|
261
|
-
let stderr = '';
|
|
262
|
-
proc.stdout?.on('data', (data) => {
|
|
263
|
-
const chunk = data.toString();
|
|
264
|
-
stdout += chunk;
|
|
265
|
-
onChunk(chunk);
|
|
266
|
-
});
|
|
267
|
-
proc.stderr?.on('data', (data) => { stderr += data; });
|
|
268
|
-
// Handle abort signal
|
|
269
|
-
const abortHandler = () => {
|
|
270
|
-
proc.kill('SIGKILL');
|
|
271
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
272
|
-
};
|
|
273
|
-
if (options?.signal) {
|
|
274
|
-
if (options.signal.aborted) {
|
|
275
|
-
proc.kill('SIGKILL');
|
|
276
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
options.signal.addEventListener('abort', abortHandler);
|
|
280
|
-
}
|
|
281
|
-
proc.on('close', (code) => {
|
|
282
|
-
options?.signal?.removeEventListener('abort', abortHandler);
|
|
283
|
-
if (code === 0) {
|
|
284
|
-
resolve(stdout.trim());
|
|
285
|
-
}
|
|
286
|
-
else if (code === null) {
|
|
287
|
-
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
reject(new Error(`OpenClaw agent failed: ${stderr || stdout}`));
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
proc.on('error', (err) => {
|
|
294
|
-
options?.signal?.removeEventListener('abort', abortHandler);
|
|
295
|
-
reject(new Error(`Failed to run openclaw: ${err.message}`));
|
|
296
|
-
});
|
|
297
|
-
});
|
|
322
|
+
return spawnOpenClaw(buildAgentArgs(message), { ...options, onChunk });
|
|
298
323
|
}
|
|
299
324
|
// ============================================================================
|
|
300
325
|
// Cron & Notifications
|
|
301
326
|
// ============================================================================
|
|
302
327
|
/**
|
|
303
|
-
* Schedule a cron job
|
|
328
|
+
* Schedule a cron job
|
|
304
329
|
*/
|
|
305
330
|
async function scheduleCron(name, schedule, prompt) {
|
|
306
|
-
const
|
|
331
|
+
const spawnConfig = await getSpawnConfig([
|
|
307
332
|
'cron', 'add',
|
|
308
333
|
'--name', name,
|
|
309
334
|
'--schedule', schedule,
|
|
310
335
|
'--prompt', prompt,
|
|
311
|
-
]
|
|
336
|
+
]);
|
|
337
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 10000, env: spawnConfig.env });
|
|
312
338
|
if (!result.success) {
|
|
313
339
|
throw new Error(`Failed to schedule cron: ${result.stderr || result.stdout}`);
|
|
314
340
|
}
|
|
315
341
|
return result.stdout.trim();
|
|
316
342
|
}
|
|
317
343
|
/**
|
|
318
|
-
* Send
|
|
344
|
+
* Send notification via OpenClaw
|
|
319
345
|
*/
|
|
320
346
|
async function sendNotification(to, message) {
|
|
321
|
-
const
|
|
347
|
+
const spawnConfig = await getSpawnConfig([
|
|
322
348
|
'message', 'send',
|
|
323
349
|
'--to', to,
|
|
324
350
|
'--message', message,
|
|
325
|
-
]
|
|
351
|
+
]);
|
|
352
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
|
|
326
353
|
if (!result.success) {
|
|
327
|
-
throw new Error(`Failed to send
|
|
354
|
+
throw new Error(`Failed to send notification: ${result.stderr || result.stdout}`);
|
|
328
355
|
}
|
|
329
356
|
return result.stdout.trim();
|
|
330
357
|
}
|
|
331
358
|
/**
|
|
332
|
-
* Write to OpenClaw memory
|
|
359
|
+
* Write to OpenClaw memory (local filesystem)
|
|
333
360
|
*/
|
|
334
361
|
async function writeToMemory(content, filename = 'clawsql-cluster-state.md') {
|
|
335
362
|
const memoryDir = path.join(os.homedir(), '.openclaw', 'memory');
|
|
336
363
|
try {
|
|
337
364
|
await fs.promises.mkdir(memoryDir, { recursive: true });
|
|
338
|
-
|
|
339
|
-
await fs.promises.appendFile(filePath, `\n\n---\n\n${content}`);
|
|
365
|
+
await fs.promises.appendFile(path.join(memoryDir, filename), `\n\n---\n\n${content}`);
|
|
340
366
|
}
|
|
341
367
|
catch {
|
|
342
|
-
//
|
|
368
|
+
// Silently ignore - memory is optional
|
|
343
369
|
}
|
|
344
370
|
}
|
|
345
371
|
// ============================================================================
|
|
346
|
-
//
|
|
372
|
+
// OpenClawAgent Class
|
|
347
373
|
// ============================================================================
|
|
348
374
|
/**
|
|
349
|
-
* OpenClaw-backed AI agent
|
|
375
|
+
* OpenClaw-backed AI agent for ClawSQL
|
|
350
376
|
*/
|
|
351
377
|
class OpenClawAgent {
|
|
352
378
|
context;
|
|
353
|
-
|
|
354
|
-
constructor(
|
|
355
|
-
this.context =
|
|
379
|
+
available = null;
|
|
380
|
+
constructor(context) {
|
|
381
|
+
this.context = context;
|
|
356
382
|
}
|
|
357
383
|
async isAvailable() {
|
|
358
|
-
if (this.
|
|
359
|
-
this.
|
|
384
|
+
if (this.available === null) {
|
|
385
|
+
this.available = await isOpenClawAvailable();
|
|
360
386
|
}
|
|
361
|
-
return this.
|
|
387
|
+
return this.available;
|
|
362
388
|
}
|
|
363
389
|
async process(input) {
|
|
364
390
|
if (!(await this.isAvailable())) {
|
|
365
|
-
throw new Error('OpenClaw
|
|
391
|
+
throw new Error('OpenClaw not running. Start with: clawsql /start');
|
|
366
392
|
}
|
|
367
|
-
|
|
368
|
-
return sendToOpenClaw(`${contextPrompt}\n\nUser query: ${input}`);
|
|
393
|
+
return sendToOpenClaw(`${this.buildContext()}\n\nUser: ${input}`);
|
|
369
394
|
}
|
|
370
|
-
async
|
|
371
|
-
return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health
|
|
395
|
+
async healthCheckCron(schedule = '0 * * * *') {
|
|
396
|
+
return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health. Report issues with instances, replication, or failover.');
|
|
372
397
|
}
|
|
373
398
|
async alert(channel, message) {
|
|
374
|
-
return sendNotification(channel, `🦞 ClawSQL
|
|
399
|
+
return sendNotification(channel, `🦞 ClawSQL: ${message}`);
|
|
375
400
|
}
|
|
376
|
-
|
|
401
|
+
buildContext() {
|
|
377
402
|
const { orchestrator, proxysql, failover } = this.context.settings;
|
|
378
|
-
return `
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
-
|
|
382
|
-
- ProxySQL: ${proxysql.host}:${proxysql.adminPort}
|
|
383
|
-
- Auto-failover: ${failover.autoFailoverEnabled ? 'enabled' : 'disabled'}
|
|
384
|
-
|
|
385
|
-
Use the clawsql skill commands to answer questions about the MySQL cluster.`;
|
|
403
|
+
return `ClawSQL assistant for MySQL cluster management.
|
|
404
|
+
Orchestrator: ${orchestrator.url}
|
|
405
|
+
ProxySQL: ${proxysql.host}:${proxysql.adminPort}
|
|
406
|
+
Auto-failover: ${failover.autoFailoverEnabled ? 'enabled' : 'disabled'}`;
|
|
386
407
|
}
|
|
387
408
|
}
|
|
388
409
|
exports.OpenClawAgent = OpenClawAgent;
|
|
389
|
-
/**
|
|
390
|
-
* Create an OpenClaw agent instance
|
|
391
|
-
*/
|
|
392
410
|
function createOpenClawAgent(ctx) {
|
|
393
411
|
return new OpenClawAgent(ctx);
|
|
394
412
|
}
|
|
395
413
|
// ============================================================================
|
|
396
|
-
//
|
|
414
|
+
// Model Provider Configuration
|
|
415
|
+
// ============================================================================
|
|
416
|
+
exports.SUPPORTED_PROVIDERS = [
|
|
417
|
+
{ id: 'anthropic', name: 'Anthropic (Claude)', envKey: 'ANTHROPIC_API_KEY' },
|
|
418
|
+
{ id: 'openai', name: 'OpenAI (GPT)', envKey: 'OPENAI_API_KEY' },
|
|
419
|
+
{ id: 'xai', name: 'xAI (Grok)', envKey: 'XAI_API_KEY' },
|
|
420
|
+
{ id: 'mistral', name: 'Mistral', envKey: 'MISTRAL_API_KEY' },
|
|
421
|
+
{ id: 'ollama', name: 'Ollama (Local)', envKey: null },
|
|
422
|
+
{ id: 'custom', name: 'Custom Provider', envKey: 'CUSTOM_API_KEY' },
|
|
423
|
+
];
|
|
424
|
+
/**
|
|
425
|
+
* Get current model provider info
|
|
426
|
+
*/
|
|
427
|
+
async function getModelProviderInfo() {
|
|
428
|
+
const spawnConfig = await getSpawnConfig(['config', 'get', 'agents.defaults.model']);
|
|
429
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: exports.CONFIG.healthCheckTimeout, env: spawnConfig.env });
|
|
430
|
+
if (!result.success || !result.stdout.trim()) {
|
|
431
|
+
return { provider: null, model: null, configured: false };
|
|
432
|
+
}
|
|
433
|
+
const model = result.stdout.trim();
|
|
434
|
+
const providerMatch = model.match(/^(\w+)\//);
|
|
435
|
+
return {
|
|
436
|
+
provider: providerMatch?.[1] ?? null,
|
|
437
|
+
model,
|
|
438
|
+
configured: !!model,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Configure model provider
|
|
443
|
+
*/
|
|
444
|
+
async function configureModelProvider(provider, apiKey, options) {
|
|
445
|
+
const providerInfo = exports.SUPPORTED_PROVIDERS.find(p => p.id === provider);
|
|
446
|
+
if (!providerInfo) {
|
|
447
|
+
return {
|
|
448
|
+
success: false,
|
|
449
|
+
message: `Unknown provider: ${provider}. Supported: ${exports.SUPPORTED_PROVIDERS.map(p => p.id).join(', ')}`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const args = ['onboard', '--non-interactive', '--accept-risk'];
|
|
453
|
+
if (provider === 'ollama') {
|
|
454
|
+
args.push('--auth-choice', 'ollama');
|
|
455
|
+
}
|
|
456
|
+
else if (provider === 'custom') {
|
|
457
|
+
args.push('--auth-choice', 'custom-api-key');
|
|
458
|
+
if (apiKey)
|
|
459
|
+
args.push('--custom-api-key', apiKey);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
args.push('--auth-choice', `${provider}-api-key`);
|
|
463
|
+
if (apiKey)
|
|
464
|
+
args.push(`--${provider}-api-key`, apiKey);
|
|
465
|
+
}
|
|
466
|
+
if (options?.baseUrl)
|
|
467
|
+
args.push('--custom-base-url', options.baseUrl);
|
|
468
|
+
if (options?.modelId)
|
|
469
|
+
args.push('--custom-model-id', options.modelId);
|
|
470
|
+
const spawnConfig = await getSpawnConfig(args);
|
|
471
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
|
|
472
|
+
return {
|
|
473
|
+
success: result.success,
|
|
474
|
+
message: result.success
|
|
475
|
+
? `Configured ${providerInfo.name}`
|
|
476
|
+
: `Failed: ${result.stderr || result.stdout}`,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Test OpenClaw connectivity
|
|
481
|
+
*/
|
|
482
|
+
async function testOpenClawConnection(query = 'Hello') {
|
|
483
|
+
const start = Date.now();
|
|
484
|
+
try {
|
|
485
|
+
const response = await sendToOpenClaw(query, { timeout: 30000 });
|
|
486
|
+
return { success: true, response, latencyMs: Date.now() - start };
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
error: error instanceof Error ? error.message : String(error),
|
|
492
|
+
latencyMs: Date.now() - start,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Get detailed status for display
|
|
498
|
+
*/
|
|
499
|
+
async function getDetailedOpenClawStatus() {
|
|
500
|
+
const status = await getOpenClawStatus();
|
|
501
|
+
const gatewayHealthy = status.available && await isGatewayHealthy();
|
|
502
|
+
const modelInfo = status.available ? await getModelProviderInfo() : { provider: null, model: null, configured: false };
|
|
503
|
+
return {
|
|
504
|
+
available: status.available,
|
|
505
|
+
mode: status.isDocker ? 'docker' : status.isLocal ? 'local' : 'unavailable',
|
|
506
|
+
gatewayHealthy,
|
|
507
|
+
modelInfo,
|
|
508
|
+
controlUI: 'http://localhost:18790',
|
|
509
|
+
gatewayUrl: exports.CONFIG.gatewayUrl,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
// ============================================================================
|
|
513
|
+
// Backwards Compatibility
|
|
397
514
|
// ============================================================================
|
|
398
|
-
/** @deprecated Use isDockerOpenClawAvailable
|
|
515
|
+
/** @deprecated Use isDockerOpenClawAvailable */
|
|
399
516
|
exports.isDockerOpenClawContainerRunning = isDockerOpenClawAvailable;
|
|
400
|
-
/** @deprecated Use isGatewayHealthy
|
|
517
|
+
/** @deprecated Use isGatewayHealthy */
|
|
401
518
|
exports.isOpenClawGatewayReachable = isGatewayHealthy;
|
|
402
|
-
/** @deprecated Use
|
|
519
|
+
/** @deprecated Use isGatewayHealthy() in a loop */
|
|
403
520
|
async function ensureOpenClawRunning(timeoutSeconds = 30) {
|
|
404
|
-
const
|
|
405
|
-
while (Date.now()
|
|
521
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
522
|
+
while (Date.now() < deadline) {
|
|
406
523
|
if (await isGatewayHealthy())
|
|
407
524
|
return true;
|
|
408
525
|
await new Promise(r => setTimeout(r, 2000));
|