@openclaw-cloud/agent-controller 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.
- package/Dockerfile +9 -0
- package/__tests__/backup.test.ts +145 -0
- package/__tests__/connection.test.ts +111 -0
- package/__tests__/handlers.test.ts +150 -0
- package/__tests__/heartbeat.test.ts +80 -0
- package/bin/agent-controller.js +5 -0
- package/dist/connection.d.ts +11 -0
- package/dist/connection.js +117 -0
- package/dist/connection.js.map +1 -0
- package/dist/handlers/backup.d.ts +2 -0
- package/dist/handlers/backup.js +81 -0
- package/dist/handlers/backup.js.map +1 -0
- package/dist/handlers/config.d.ts +2 -0
- package/dist/handlers/config.js +43 -0
- package/dist/handlers/config.js.map +1 -0
- package/dist/handlers/deploy.d.ts +2 -0
- package/dist/handlers/deploy.js +30 -0
- package/dist/handlers/deploy.js.map +1 -0
- package/dist/handlers/exec.d.ts +2 -0
- package/dist/handlers/exec.js +29 -0
- package/dist/handlers/exec.js.map +1 -0
- package/dist/handlers/pair.d.ts +2 -0
- package/dist/handlers/pair.js +23 -0
- package/dist/handlers/pair.js.map +1 -0
- package/dist/handlers/restart.d.ts +2 -0
- package/dist/handlers/restart.js +18 -0
- package/dist/handlers/restart.js.map +1 -0
- package/dist/handlers/stop.d.ts +2 -0
- package/dist/handlers/stop.js +14 -0
- package/dist/handlers/stop.js.map +1 -0
- package/dist/heartbeat.d.ts +9 -0
- package/dist/heartbeat.js +60 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/jest.config.ts +16 -0
- package/package.json +26 -0
- package/src/connection.ts +135 -0
- package/src/handlers/backup.ts +97 -0
- package/src/handlers/config.ts +48 -0
- package/src/handlers/deploy.ts +32 -0
- package/src/handlers/exec.ts +32 -0
- package/src/handlers/pair.ts +26 -0
- package/src/handlers/restart.ts +19 -0
- package/src/handlers/stop.ts +17 -0
- package/src/heartbeat.ts +73 -0
- package/src/index.ts +47 -0
- package/src/types.ts +32 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const WORKSPACE_DIR = '/home/node/.openclaw/workspace';
|
|
5
|
+
const BACKUP_DIR = '/tmp';
|
|
6
|
+
const TAR_TIMEOUT = 120_000;
|
|
7
|
+
const UPLOAD_TIMEOUT = 120_000;
|
|
8
|
+
export function handleBackup(command) {
|
|
9
|
+
const uploadUrl = command.payload.uploadUrl;
|
|
10
|
+
if (!uploadUrl) {
|
|
11
|
+
return Promise.resolve({
|
|
12
|
+
id: command.id,
|
|
13
|
+
type: 'backup',
|
|
14
|
+
success: false,
|
|
15
|
+
error: 'Missing "uploadUrl" in payload',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const timestamp = Date.now();
|
|
19
|
+
const filename = `backup-${timestamp}.tar.gz`;
|
|
20
|
+
const archivePath = path.join(BACKUP_DIR, filename);
|
|
21
|
+
return createArchive(archivePath)
|
|
22
|
+
.then(() => uploadArchive(archivePath, uploadUrl))
|
|
23
|
+
.then(async (size) => {
|
|
24
|
+
await cleanup(archivePath);
|
|
25
|
+
return {
|
|
26
|
+
id: command.id,
|
|
27
|
+
type: 'backup',
|
|
28
|
+
success: true,
|
|
29
|
+
data: { size, filename },
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
.catch(async (err) => {
|
|
33
|
+
await cleanup(archivePath);
|
|
34
|
+
return {
|
|
35
|
+
id: command.id,
|
|
36
|
+
type: 'backup',
|
|
37
|
+
success: false,
|
|
38
|
+
error: err instanceof Error ? err.message : String(err),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function createArchive(archivePath) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
exec(`tar -czf ${archivePath} -C ${path.dirname(WORKSPACE_DIR)} ${path.basename(WORKSPACE_DIR)}`, { timeout: TAR_TIMEOUT }, (error, _stdout, stderr) => {
|
|
45
|
+
if (error) {
|
|
46
|
+
reject(new Error(`tar failed: ${stderr.toString() || error.message}`));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
resolve();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function uploadArchive(archivePath, uploadUrl) {
|
|
55
|
+
const stat = await fs.stat(archivePath);
|
|
56
|
+
const fileBuffer = await fs.readFile(archivePath);
|
|
57
|
+
const boundary = `----backup-${Date.now()}`;
|
|
58
|
+
const filename = path.basename(archivePath);
|
|
59
|
+
const header = Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: application/gzip\r\n\r\n`);
|
|
60
|
+
const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
61
|
+
const body = Buffer.concat([header, fileBuffer, footer]);
|
|
62
|
+
const response = await fetch(uploadUrl, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` },
|
|
65
|
+
body,
|
|
66
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT),
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
return stat.size;
|
|
72
|
+
}
|
|
73
|
+
async function cleanup(archivePath) {
|
|
74
|
+
try {
|
|
75
|
+
await fs.unlink(archivePath);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// ignore cleanup errors
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/handlers/backup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
const CONFIG_DIR = '/etc/openclaw';
|
|
5
|
+
export function handleConfig(command) {
|
|
6
|
+
const filename = command.payload.filename;
|
|
7
|
+
const content = command.payload.content;
|
|
8
|
+
if (!filename || content === undefined) {
|
|
9
|
+
return Promise.resolve({
|
|
10
|
+
id: command.id,
|
|
11
|
+
type: 'config',
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'Missing "filename" or "content" in payload',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const configPath = path.join(CONFIG_DIR, path.basename(filename));
|
|
17
|
+
return fs.mkdir(CONFIG_DIR, { recursive: true })
|
|
18
|
+
.then(() => fs.writeFile(configPath, content, 'utf-8'))
|
|
19
|
+
.then(() => {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
exec('openclaw gateway restart', { timeout: 30_000 }, (error, stdout, stderr) => {
|
|
22
|
+
resolve({
|
|
23
|
+
id: command.id,
|
|
24
|
+
type: 'config',
|
|
25
|
+
success: !error,
|
|
26
|
+
data: {
|
|
27
|
+
path: configPath,
|
|
28
|
+
stdout: stdout.toString(),
|
|
29
|
+
stderr: stderr.toString(),
|
|
30
|
+
},
|
|
31
|
+
...(error ? { error: error.message } : {}),
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
})
|
|
36
|
+
.catch((err) => ({
|
|
37
|
+
id: command.id,
|
|
38
|
+
type: 'config',
|
|
39
|
+
success: false,
|
|
40
|
+
error: err instanceof Error ? err.message : String(err),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/handlers/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAG1C,MAAM,UAAU,GAAG,eAAe,CAAC;AAEnC,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAkB,CAAC;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAiB,CAAC;IAElD,IAAI,CAAC,QAAQ,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,4CAA4C;SACpD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElE,OAAO,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;SAC7C,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;SACtD,IAAI,CAAC,GAAG,EAAE;QACT,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YAC5C,IAAI,CAAC,0BAA0B,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;gBAC9E,OAAO,CAAC;oBACN,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,CAAC,KAAK;oBACf,IAAI,EAAE;wBACJ,IAAI,EAAE,UAAU;wBAChB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;wBACzB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;qBAC1B;oBACD,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC3C,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACf,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;KACxD,CAAC,CAAC,CAAC;AACR,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
export function handleDeploy(command) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
exec('npm i -g openclaw', { timeout: 120_000 }, (installErr, installOut, installStderr) => {
|
|
5
|
+
if (installErr) {
|
|
6
|
+
resolve({
|
|
7
|
+
id: command.id,
|
|
8
|
+
type: 'deploy',
|
|
9
|
+
success: false,
|
|
10
|
+
data: { stdout: installOut.toString(), stderr: installStderr.toString() },
|
|
11
|
+
error: `Install failed: ${installErr.message}`,
|
|
12
|
+
});
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
exec('openclaw gateway restart', { timeout: 30_000 }, (restartErr, restartOut, restartStderr) => {
|
|
16
|
+
resolve({
|
|
17
|
+
id: command.id,
|
|
18
|
+
type: 'deploy',
|
|
19
|
+
success: !restartErr,
|
|
20
|
+
data: {
|
|
21
|
+
install: { stdout: installOut.toString(), stderr: installStderr.toString() },
|
|
22
|
+
restart: { stdout: restartOut.toString(), stderr: restartStderr.toString() },
|
|
23
|
+
},
|
|
24
|
+
...(restartErr ? { error: `Restart failed: ${restartErr.message}` } : {}),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/handlers/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAG1C,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE;YACxF,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,CAAC;oBACN,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,KAAK;oBACd,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;oBACzE,KAAK,EAAE,mBAAmB,UAAU,CAAC,OAAO,EAAE;iBAC/C,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,IAAI,CAAC,0BAA0B,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE;gBAC9F,OAAO,CAAC;oBACN,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,CAAC,UAAU;oBACpB,IAAI,EAAE;wBACJ,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;wBAC5E,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;qBAC7E;oBACD,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,mBAAmB,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC1E,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
const EXEC_TIMEOUT = 60_000;
|
|
3
|
+
export function handleExec(command) {
|
|
4
|
+
const cmd = command.payload.command;
|
|
5
|
+
if (!cmd) {
|
|
6
|
+
return Promise.resolve({
|
|
7
|
+
id: command.id,
|
|
8
|
+
type: 'exec',
|
|
9
|
+
success: false,
|
|
10
|
+
error: 'Missing "command" in payload',
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
exec(cmd, { timeout: EXEC_TIMEOUT }, (error, stdout, stderr) => {
|
|
15
|
+
resolve({
|
|
16
|
+
id: command.id,
|
|
17
|
+
type: 'exec',
|
|
18
|
+
success: !error,
|
|
19
|
+
data: {
|
|
20
|
+
exitCode: error ? error.code ?? 1 : 0,
|
|
21
|
+
stdout: stdout.toString(),
|
|
22
|
+
stderr: stderr.toString(),
|
|
23
|
+
},
|
|
24
|
+
...(error ? { error: error.message } : {}),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/handlers/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAG1C,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,MAAM,UAAU,UAAU,CAAC,OAAqB;IAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAiB,CAAC;IAC9C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,8BAA8B;SACtC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YAC7D,OAAO,CAAC;gBACN,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,CAAC,KAAK;gBACf,IAAI,EAAE;oBACJ,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAE,KAAmD,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;oBACpF,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;oBACzB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;iBAC1B;gBACD,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function handlePair(command) {
|
|
2
|
+
const pairToken = command.payload.token;
|
|
3
|
+
const targetId = command.payload.targetId;
|
|
4
|
+
if (!pairToken || !targetId) {
|
|
5
|
+
return Promise.resolve({
|
|
6
|
+
id: command.id,
|
|
7
|
+
type: 'pair',
|
|
8
|
+
success: false,
|
|
9
|
+
error: 'Missing "token" or "targetId" in payload',
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return Promise.resolve({
|
|
13
|
+
id: command.id,
|
|
14
|
+
type: 'pair',
|
|
15
|
+
success: true,
|
|
16
|
+
data: {
|
|
17
|
+
paired: true,
|
|
18
|
+
targetId,
|
|
19
|
+
agentId: process.env.AGENT_ID,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=pair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair.js","sourceRoot":"","sources":["../../src/handlers/pair.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,OAAqB;IAC9C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,KAAe,CAAC;IAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAkB,CAAC;IAEpD,IAAI,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,0CAA0C;SAClD,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;QACrB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,IAAI;QACb,IAAI,EAAE;YACJ,MAAM,EAAE,IAAI;YACZ,QAAQ;YACR,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ;SAC9B;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
export function handleRestart(command) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
exec('openclaw gateway restart', { timeout: 30_000 }, (error, stdout, stderr) => {
|
|
5
|
+
resolve({
|
|
6
|
+
id: command.id,
|
|
7
|
+
type: 'restart',
|
|
8
|
+
success: !error,
|
|
9
|
+
data: {
|
|
10
|
+
stdout: stdout.toString(),
|
|
11
|
+
stderr: stderr.toString(),
|
|
12
|
+
},
|
|
13
|
+
...(error ? { error: error.message } : {}),
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=restart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restart.js","sourceRoot":"","sources":["../../src/handlers/restart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAG1C,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,CAAC,0BAA0B,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YAC9E,OAAO,CAAC;gBACN,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,CAAC,KAAK;gBACf,IAAI,EAAE;oBACJ,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;oBACzB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;iBAC1B;gBACD,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function handleStop(command) {
|
|
2
|
+
const response = {
|
|
3
|
+
id: command.id,
|
|
4
|
+
type: 'stop',
|
|
5
|
+
success: true,
|
|
6
|
+
data: { message: 'Shutting down gracefully' },
|
|
7
|
+
};
|
|
8
|
+
setTimeout(() => {
|
|
9
|
+
console.log('Agent stopping gracefully...');
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}, 500);
|
|
12
|
+
return Promise.resolve(response);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=stop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.js","sourceRoot":"","sources":["../../src/handlers/stop.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,OAAqB;IAC9C,MAAM,QAAQ,GAAkB;QAC9B,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,EAAE,OAAO,EAAE,0BAA0B,EAAE;KAC9C,CAAC;IAEF,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,EAAE,GAAG,CAAC,CAAC;IAER,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HeartbeatPayload } from './types.js';
|
|
2
|
+
export interface HeartbeatOptions {
|
|
3
|
+
centrifugoInternalUrl: string;
|
|
4
|
+
centrifugoApiKey: string;
|
|
5
|
+
agentId: string;
|
|
6
|
+
}
|
|
7
|
+
declare function getMetrics(): HeartbeatPayload['metrics'];
|
|
8
|
+
export declare function startHeartbeat(opts: HeartbeatOptions): NodeJS.Timeout;
|
|
9
|
+
export { getMetrics };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
const HEARTBEAT_INTERVAL = 30_000;
|
|
3
|
+
const HEARTBEAT_CHANNEL = 'heartbeat:data';
|
|
4
|
+
function getMetrics() {
|
|
5
|
+
const cpus = os.cpus();
|
|
6
|
+
const cpuUsage = cpus.reduce((acc, cpu) => {
|
|
7
|
+
const total = Object.values(cpu.times).reduce((a, b) => a + b, 0);
|
|
8
|
+
return acc + (1 - cpu.times.idle / total);
|
|
9
|
+
}, 0) / cpus.length;
|
|
10
|
+
const totalMem = os.totalmem();
|
|
11
|
+
const freeMem = os.freemem();
|
|
12
|
+
return {
|
|
13
|
+
cpu: Math.round(cpuUsage * 100) / 100,
|
|
14
|
+
mem: Math.round(((totalMem - freeMem) / totalMem) * 100) / 100,
|
|
15
|
+
uptime: os.uptime(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function publishHeartbeat(opts) {
|
|
19
|
+
const payload = {
|
|
20
|
+
type: 'heartbeat',
|
|
21
|
+
agentId: opts.agentId,
|
|
22
|
+
ts: Date.now(),
|
|
23
|
+
metrics: getMetrics(),
|
|
24
|
+
};
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`${opts.centrifugoInternalUrl}/api/publish`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'Authorization': `apikey ${opts.centrifugoApiKey}`,
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({ channel: HEARTBEAT_CHANNEL, data: payload }),
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const body = await res.text().catch(() => '');
|
|
39
|
+
console.error(`Heartbeat publish failed (HTTP ${res.status}): ${body}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err.name === 'AbortError') {
|
|
44
|
+
console.error('Heartbeat publish timed out');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.error('Heartbeat publish failed:', err instanceof Error ? err.message : err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function startHeartbeat(opts) {
|
|
55
|
+
const send = () => { publishHeartbeat(opts).catch(() => { }); };
|
|
56
|
+
send();
|
|
57
|
+
return setInterval(send, HEARTBEAT_INTERVAL);
|
|
58
|
+
}
|
|
59
|
+
export { getMetrics };
|
|
60
|
+
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createConnection } from './connection.js';
|
|
2
|
+
import { startHeartbeat } from './heartbeat.js';
|
|
3
|
+
function requireEnv(name) {
|
|
4
|
+
const value = process.env[name];
|
|
5
|
+
if (!value) {
|
|
6
|
+
console.error(`Missing required environment variable: ${name}`);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export function main() {
|
|
12
|
+
const rawUrl = requireEnv('CENTRIFUGO_URL');
|
|
13
|
+
// Centrifuge client requires ws:// scheme
|
|
14
|
+
const url = rawUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://') + '/connection/websocket';
|
|
15
|
+
const token = requireEnv('AGENT_TOKEN');
|
|
16
|
+
const agentId = requireEnv('AGENT_ID');
|
|
17
|
+
const backendUrl = process.env.BACKEND_INTERNAL_URL || '';
|
|
18
|
+
const centrifugoInternalUrl = process.env.CENTRIFUGO_INTERNAL_URL || '';
|
|
19
|
+
const centrifugoApiKey = process.env.CENTRIFUGO_API_KEY || '';
|
|
20
|
+
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
21
|
+
if (backendUrl) {
|
|
22
|
+
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
23
|
+
}
|
|
24
|
+
const { client } = createConnection({ url, token, agentId, backendUrl: backendUrl || undefined });
|
|
25
|
+
let heartbeatTimer;
|
|
26
|
+
if (centrifugoInternalUrl && centrifugoApiKey) {
|
|
27
|
+
console.log(`Heartbeat via Centrifugo internal API: ${centrifugoInternalUrl}`);
|
|
28
|
+
heartbeatTimer = startHeartbeat({ centrifugoInternalUrl, centrifugoApiKey, agentId });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.warn('CENTRIFUGO_INTERNAL_URL or CENTRIFUGO_API_KEY not set, heartbeat disabled');
|
|
32
|
+
}
|
|
33
|
+
const shutdown = () => {
|
|
34
|
+
console.log('Shutting down...');
|
|
35
|
+
if (heartbeatTimer)
|
|
36
|
+
clearInterval(heartbeatTimer);
|
|
37
|
+
client.disconnect();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
};
|
|
40
|
+
process.on('SIGINT', shutdown);
|
|
41
|
+
process.on('SIGTERM', shutdown);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,MAAM,MAAM,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC5C,0CAA0C;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,GAAG,uBAAuB,CAAC;IAC7G,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC;IAC1D,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,EAAE,CAAC;IACxE,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAE9D,OAAO,CAAC,GAAG,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;IAC/D,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,gBAAgB,UAAU,aAAa,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,SAAS,EAAE,CAAC,CAAC;IAElG,IAAI,cAA0C,CAAC;IAC/C,IAAI,qBAAqB,IAAI,gBAAgB,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,0CAA0C,qBAAqB,EAAE,CAAC,CAAC;QAC/E,cAAc,GAAG,cAAc,CAAC,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC;IACxF,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAChC,IAAI,cAAc;YAAE,aAAa,CAAC,cAAc,CAAC,CAAC;QAClD,MAAM,CAAC,UAAU,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centrifugo channel layout:
|
|
3
|
+
* agent:<agentId> — commands from backend, responses from agent
|
|
4
|
+
* heartbeat:<agentId> — heartbeat publications from agent
|
|
5
|
+
*/
|
|
6
|
+
export type CommandType = 'exec' | 'restart' | 'deploy' | 'config' | 'pair' | 'stop' | 'backup';
|
|
7
|
+
export interface AgentCommand {
|
|
8
|
+
id: string;
|
|
9
|
+
type: CommandType;
|
|
10
|
+
payload: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentResponse {
|
|
13
|
+
id: string;
|
|
14
|
+
type: CommandType;
|
|
15
|
+
success: boolean;
|
|
16
|
+
data?: Record<string, unknown>;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface HeartbeatPayload {
|
|
20
|
+
type: 'heartbeat';
|
|
21
|
+
agentId: string;
|
|
22
|
+
ts: number;
|
|
23
|
+
metrics: {
|
|
24
|
+
cpu: number;
|
|
25
|
+
mem: number;
|
|
26
|
+
uptime: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Config } from 'jest';
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
preset: 'ts-jest',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
roots: ['<rootDir>/__tests__'],
|
|
7
|
+
moduleFileExtensions: ['ts', 'js', 'json'],
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.ts$': ['ts-jest', { useESM: false, tsconfig: { module: 'commonjs' } }],
|
|
10
|
+
},
|
|
11
|
+
moduleNameMapper: {
|
|
12
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openclaw-cloud/agent-controller",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-controller": "bin/agent-controller.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
|
12
|
+
"start": "node bin/agent-controller.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"centrifuge": "^5.5.3",
|
|
16
|
+
"ws": "^8.19.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/jest": "^30.0.0",
|
|
20
|
+
"@types/node": "^25.2.3",
|
|
21
|
+
"@types/ws": "^8.18.1",
|
|
22
|
+
"jest": "^30.2.0",
|
|
23
|
+
"ts-jest": "^29.4.6",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
}
|
|
26
|
+
}
|