clawsql 0.2.0 → 0.2.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.
- package/README.md +25 -5
- package/dist/bin/clawsql.d.ts +1 -0
- package/dist/bin/clawsql.d.ts.map +1 -1
- package/dist/bin/clawsql.js +18 -2
- package/dist/bin/clawsql.js.map +1 -1
- 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 +107 -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 +83 -25
- package/dist/cli/agent/openclaw-integration.d.ts.map +1 -1
- package/dist/cli/agent/openclaw-integration.js +305 -194
- 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 +330 -469
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install.d.ts +13 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +286 -0
- package/dist/cli/commands/install.js.map +1 -0
- 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 +342 -16
- 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 +71 -25
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -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/command-executor.d.ts.map +1 -1
- package/dist/cli/utils/command-executor.js +60 -23
- package/dist/cli/utils/command-executor.js.map +1 -1
- 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/dist/cli/utils/docker-prereq.d.ts +23 -0
- package/dist/cli/utils/docker-prereq.d.ts.map +1 -1
- package/dist/cli/utils/docker-prereq.js +70 -1
- package/dist/cli/utils/docker-prereq.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 = 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
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const 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: CONFIG.gatewayUrl,
|
|
119
|
+
OPENCLAW_GATEWAY_TOKEN: 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
|
+
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 ?? 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=${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() === 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 = CONFIG.gatewayUrl.replace('ws://', 'http://').replace('wss://', 'https://');
|
|
122
247
|
const response = await fetch(`${httpUrl}/health`, {
|
|
123
|
-
signal: AbortSignal.timeout(
|
|
248
|
+
signal: AbortSignal.timeout(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: 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,247 @@ async function isLocalOpenClawAvailable() {
|
|
|
154
273
|
}
|
|
155
274
|
}
|
|
156
275
|
/**
|
|
157
|
-
* Check if any OpenClaw
|
|
276
|
+
* Check if any OpenClaw is available
|
|
158
277
|
*/
|
|
159
278
|
async function isOpenClawAvailable() {
|
|
160
279
|
return (await isDockerOpenClawAvailable()) || (await isLocalOpenClawAvailable());
|
|
161
280
|
}
|
|
162
281
|
/**
|
|
163
|
-
* Get
|
|
282
|
+
* Get OpenClaw status summary
|
|
164
283
|
*/
|
|
165
284
|
async function getOpenClawStatus() {
|
|
166
285
|
const [isDocker, isLocal] = await Promise.all([
|
|
167
286
|
isDockerOpenClawAvailable(),
|
|
168
287
|
isLocalOpenClawAvailable(),
|
|
169
288
|
]);
|
|
170
|
-
return {
|
|
171
|
-
available: isDocker || isLocal,
|
|
172
|
-
isDocker,
|
|
173
|
-
isLocal,
|
|
174
|
-
};
|
|
289
|
+
return { available: isDocker || isLocal, isDocker, isLocal };
|
|
175
290
|
}
|
|
176
291
|
// ============================================================================
|
|
177
292
|
// Agent Functions
|
|
178
293
|
// ============================================================================
|
|
179
294
|
/**
|
|
180
|
-
*
|
|
295
|
+
* Build agent CLI arguments
|
|
181
296
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
297
|
+
function buildAgentArgs(message) {
|
|
298
|
+
return [
|
|
184
299
|
'agent',
|
|
185
|
-
'--
|
|
300
|
+
'--local',
|
|
301
|
+
'--session-id', CONFIG.sessionId,
|
|
186
302
|
'--message', message,
|
|
187
303
|
'--thinking', 'minimal',
|
|
188
304
|
];
|
|
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
305
|
}
|
|
237
306
|
/**
|
|
238
|
-
* Send
|
|
307
|
+
* Send message to OpenClaw agent
|
|
308
|
+
*/
|
|
309
|
+
async function sendToOpenClaw(message, options) {
|
|
310
|
+
return spawnOpenClaw(buildAgentArgs(message), options);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Send message to OpenClaw with streaming output
|
|
239
314
|
*/
|
|
240
315
|
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
|
-
});
|
|
316
|
+
return spawnOpenClaw(buildAgentArgs(message), { ...options, onChunk });
|
|
298
317
|
}
|
|
299
318
|
// ============================================================================
|
|
300
319
|
// Cron & Notifications
|
|
301
320
|
// ============================================================================
|
|
302
321
|
/**
|
|
303
|
-
* Schedule a cron job
|
|
322
|
+
* Schedule a cron job
|
|
304
323
|
*/
|
|
305
324
|
async function scheduleCron(name, schedule, prompt) {
|
|
306
|
-
const
|
|
325
|
+
const spawnConfig = await getSpawnConfig([
|
|
307
326
|
'cron', 'add',
|
|
308
327
|
'--name', name,
|
|
309
328
|
'--schedule', schedule,
|
|
310
329
|
'--prompt', prompt,
|
|
311
|
-
]
|
|
330
|
+
]);
|
|
331
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 10000, env: spawnConfig.env });
|
|
312
332
|
if (!result.success) {
|
|
313
333
|
throw new Error(`Failed to schedule cron: ${result.stderr || result.stdout}`);
|
|
314
334
|
}
|
|
315
335
|
return result.stdout.trim();
|
|
316
336
|
}
|
|
317
337
|
/**
|
|
318
|
-
* Send
|
|
338
|
+
* Send notification via OpenClaw
|
|
319
339
|
*/
|
|
320
340
|
async function sendNotification(to, message) {
|
|
321
|
-
const
|
|
341
|
+
const spawnConfig = await getSpawnConfig([
|
|
322
342
|
'message', 'send',
|
|
323
343
|
'--to', to,
|
|
324
344
|
'--message', message,
|
|
325
|
-
]
|
|
345
|
+
]);
|
|
346
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
|
|
326
347
|
if (!result.success) {
|
|
327
|
-
throw new Error(`Failed to send
|
|
348
|
+
throw new Error(`Failed to send notification: ${result.stderr || result.stdout}`);
|
|
328
349
|
}
|
|
329
350
|
return result.stdout.trim();
|
|
330
351
|
}
|
|
331
352
|
/**
|
|
332
|
-
* Write to OpenClaw memory
|
|
353
|
+
* Write to OpenClaw memory (local filesystem)
|
|
333
354
|
*/
|
|
334
355
|
async function writeToMemory(content, filename = 'clawsql-cluster-state.md') {
|
|
335
356
|
const memoryDir = path.join(os.homedir(), '.openclaw', 'memory');
|
|
336
357
|
try {
|
|
337
358
|
await fs.promises.mkdir(memoryDir, { recursive: true });
|
|
338
|
-
|
|
339
|
-
await fs.promises.appendFile(filePath, `\n\n---\n\n${content}`);
|
|
359
|
+
await fs.promises.appendFile(path.join(memoryDir, filename), `\n\n---\n\n${content}`);
|
|
340
360
|
}
|
|
341
361
|
catch {
|
|
342
|
-
//
|
|
362
|
+
// Silently ignore - memory is optional
|
|
343
363
|
}
|
|
344
364
|
}
|
|
345
365
|
// ============================================================================
|
|
346
|
-
//
|
|
366
|
+
// OpenClawAgent Class
|
|
347
367
|
// ============================================================================
|
|
348
368
|
/**
|
|
349
|
-
* OpenClaw-backed AI agent
|
|
369
|
+
* OpenClaw-backed AI agent for ClawSQL
|
|
350
370
|
*/
|
|
351
371
|
class OpenClawAgent {
|
|
352
372
|
context;
|
|
353
|
-
|
|
354
|
-
constructor(
|
|
355
|
-
this.context =
|
|
373
|
+
available = null;
|
|
374
|
+
constructor(context) {
|
|
375
|
+
this.context = context;
|
|
356
376
|
}
|
|
357
377
|
async isAvailable() {
|
|
358
|
-
if (this.
|
|
359
|
-
this.
|
|
378
|
+
if (this.available === null) {
|
|
379
|
+
this.available = await isOpenClawAvailable();
|
|
360
380
|
}
|
|
361
|
-
return this.
|
|
381
|
+
return this.available;
|
|
362
382
|
}
|
|
363
383
|
async process(input) {
|
|
364
384
|
if (!(await this.isAvailable())) {
|
|
365
|
-
throw new Error('OpenClaw
|
|
385
|
+
throw new Error('OpenClaw not running. Start with: clawsql /start');
|
|
366
386
|
}
|
|
367
|
-
|
|
368
|
-
return sendToOpenClaw(`${contextPrompt}\n\nUser query: ${input}`);
|
|
387
|
+
return sendToOpenClaw(`${this.buildContext()}\n\nUser: ${input}`);
|
|
369
388
|
}
|
|
370
|
-
async
|
|
371
|
-
return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health
|
|
389
|
+
async healthCheckCron(schedule = '0 * * * *') {
|
|
390
|
+
return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health. Report issues with instances, replication, or failover.');
|
|
372
391
|
}
|
|
373
392
|
async alert(channel, message) {
|
|
374
|
-
return sendNotification(channel, `🦞 ClawSQL
|
|
393
|
+
return sendNotification(channel, `🦞 ClawSQL: ${message}`);
|
|
375
394
|
}
|
|
376
|
-
|
|
395
|
+
buildContext() {
|
|
377
396
|
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.`;
|
|
397
|
+
return `ClawSQL assistant for MySQL cluster management.
|
|
398
|
+
Orchestrator: ${orchestrator.url}
|
|
399
|
+
ProxySQL: ${proxysql.host}:${proxysql.adminPort}
|
|
400
|
+
Auto-failover: ${failover.autoFailoverEnabled ? 'enabled' : 'disabled'}`;
|
|
386
401
|
}
|
|
387
402
|
}
|
|
388
403
|
exports.OpenClawAgent = OpenClawAgent;
|
|
389
|
-
/**
|
|
390
|
-
* Create an OpenClaw agent instance
|
|
391
|
-
*/
|
|
392
404
|
function createOpenClawAgent(ctx) {
|
|
393
405
|
return new OpenClawAgent(ctx);
|
|
394
406
|
}
|
|
395
407
|
// ============================================================================
|
|
396
|
-
//
|
|
408
|
+
// Model Provider Configuration
|
|
409
|
+
// ============================================================================
|
|
410
|
+
exports.SUPPORTED_PROVIDERS = [
|
|
411
|
+
{ id: 'anthropic', name: 'Anthropic (Claude)', envKey: 'ANTHROPIC_API_KEY' },
|
|
412
|
+
{ id: 'openai', name: 'OpenAI (GPT)', envKey: 'OPENAI_API_KEY' },
|
|
413
|
+
{ id: 'xai', name: 'xAI (Grok)', envKey: 'XAI_API_KEY' },
|
|
414
|
+
{ id: 'mistral', name: 'Mistral', envKey: 'MISTRAL_API_KEY' },
|
|
415
|
+
{ id: 'ollama', name: 'Ollama (Local)', envKey: null },
|
|
416
|
+
{ id: 'custom', name: 'Custom Provider', envKey: 'CUSTOM_API_KEY' },
|
|
417
|
+
];
|
|
418
|
+
/**
|
|
419
|
+
* Get current model provider info
|
|
420
|
+
*/
|
|
421
|
+
async function getModelProviderInfo() {
|
|
422
|
+
const spawnConfig = await getSpawnConfig(['config', 'get', 'agents.defaults.model']);
|
|
423
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: CONFIG.healthCheckTimeout, env: spawnConfig.env });
|
|
424
|
+
if (!result.success || !result.stdout.trim()) {
|
|
425
|
+
return { provider: null, model: null, configured: false };
|
|
426
|
+
}
|
|
427
|
+
const model = result.stdout.trim();
|
|
428
|
+
const providerMatch = model.match(/^(\w+)\//);
|
|
429
|
+
return {
|
|
430
|
+
provider: providerMatch?.[1] ?? null,
|
|
431
|
+
model,
|
|
432
|
+
configured: !!model,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Configure model provider
|
|
437
|
+
*/
|
|
438
|
+
async function configureModelProvider(provider, apiKey, options) {
|
|
439
|
+
const providerInfo = exports.SUPPORTED_PROVIDERS.find(p => p.id === provider);
|
|
440
|
+
if (!providerInfo) {
|
|
441
|
+
return {
|
|
442
|
+
success: false,
|
|
443
|
+
message: `Unknown provider: ${provider}. Supported: ${exports.SUPPORTED_PROVIDERS.map(p => p.id).join(', ')}`,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const args = ['onboard', '--non-interactive', '--accept-risk'];
|
|
447
|
+
if (provider === 'ollama') {
|
|
448
|
+
args.push('--auth-choice', 'ollama');
|
|
449
|
+
}
|
|
450
|
+
else if (provider === 'custom') {
|
|
451
|
+
args.push('--auth-choice', 'custom-api-key');
|
|
452
|
+
if (apiKey)
|
|
453
|
+
args.push('--custom-api-key', apiKey);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
args.push('--auth-choice', `${provider}-api-key`);
|
|
457
|
+
if (apiKey)
|
|
458
|
+
args.push(`--${provider}-api-key`, apiKey);
|
|
459
|
+
}
|
|
460
|
+
if (options?.baseUrl)
|
|
461
|
+
args.push('--custom-base-url', options.baseUrl);
|
|
462
|
+
if (options?.modelId)
|
|
463
|
+
args.push('--custom-model-id', options.modelId);
|
|
464
|
+
const spawnConfig = await getSpawnConfig(args);
|
|
465
|
+
const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
|
|
466
|
+
return {
|
|
467
|
+
success: result.success,
|
|
468
|
+
message: result.success
|
|
469
|
+
? `Configured ${providerInfo.name}`
|
|
470
|
+
: `Failed: ${result.stderr || result.stdout}`,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Test OpenClaw connectivity
|
|
475
|
+
*/
|
|
476
|
+
async function testOpenClawConnection(query = 'Hello') {
|
|
477
|
+
const start = Date.now();
|
|
478
|
+
try {
|
|
479
|
+
const response = await sendToOpenClaw(query, { timeout: 30000 });
|
|
480
|
+
return { success: true, response, latencyMs: Date.now() - start };
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
return {
|
|
484
|
+
success: false,
|
|
485
|
+
error: error instanceof Error ? error.message : String(error),
|
|
486
|
+
latencyMs: Date.now() - start,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Get detailed status for display
|
|
492
|
+
*/
|
|
493
|
+
async function getDetailedOpenClawStatus() {
|
|
494
|
+
const status = await getOpenClawStatus();
|
|
495
|
+
const gatewayHealthy = status.available && await isGatewayHealthy();
|
|
496
|
+
const modelInfo = status.available ? await getModelProviderInfo() : { provider: null, model: null, configured: false };
|
|
497
|
+
return {
|
|
498
|
+
available: status.available,
|
|
499
|
+
mode: status.isDocker ? 'docker' : status.isLocal ? 'local' : 'unavailable',
|
|
500
|
+
gatewayHealthy,
|
|
501
|
+
modelInfo,
|
|
502
|
+
controlUI: 'http://localhost:18790',
|
|
503
|
+
gatewayUrl: CONFIG.gatewayUrl,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
// ============================================================================
|
|
507
|
+
// Backwards Compatibility
|
|
397
508
|
// ============================================================================
|
|
398
|
-
/** @deprecated Use isDockerOpenClawAvailable
|
|
509
|
+
/** @deprecated Use isDockerOpenClawAvailable */
|
|
399
510
|
exports.isDockerOpenClawContainerRunning = isDockerOpenClawAvailable;
|
|
400
|
-
/** @deprecated Use isGatewayHealthy
|
|
511
|
+
/** @deprecated Use isGatewayHealthy */
|
|
401
512
|
exports.isOpenClawGatewayReachable = isGatewayHealthy;
|
|
402
|
-
/** @deprecated Use
|
|
513
|
+
/** @deprecated Use isGatewayHealthy() in a loop */
|
|
403
514
|
async function ensureOpenClawRunning(timeoutSeconds = 30) {
|
|
404
|
-
const
|
|
405
|
-
while (Date.now()
|
|
515
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
516
|
+
while (Date.now() < deadline) {
|
|
406
517
|
if (await isGatewayHealthy())
|
|
407
518
|
return true;
|
|
408
519
|
await new Promise(r => setTimeout(r, 2000));
|