@openclaw-cloud/agent-controller 0.2.5 → 0.2.7
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/bin/agent-controller.js +5 -0
- package/dist/commands/install.js +3 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/config-file.d.ts +9 -0
- package/dist/config-file.js +47 -0
- package/dist/config-file.js.map +1 -0
- package/dist/connection.d.ts +1 -0
- package/dist/connection.js +27 -13
- package/dist/connection.js.map +1 -1
- package/dist/handlers/backup.js +7 -2
- package/dist/handlers/backup.js.map +1 -1
- package/dist/handlers/knowledge-sync.d.ts +2 -0
- package/dist/handlers/knowledge-sync.js +51 -0
- package/dist/handlers/knowledge-sync.js.map +1 -0
- package/dist/heartbeat.d.ts +1 -0
- package/dist/heartbeat.js +30 -0
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/package.json +6 -1
- package/.claude/cc-notify.sh +0 -32
- package/.claude/settings.json +0 -31
- package/.husky/pre-commit +0 -1
- package/BIZPLAN.md +0 -530
- package/CLAUDE.md +0 -172
- package/Dockerfile +0 -9
- package/__tests__/api.test.ts +0 -183
- package/__tests__/backup.test.ts +0 -145
- package/__tests__/board-handler.test.ts +0 -323
- package/__tests__/chat.test.ts +0 -191
- package/__tests__/config.test.ts +0 -100
- package/__tests__/connection.test.ts +0 -289
- package/__tests__/file-delete.test.ts +0 -90
- package/__tests__/file-write.test.ts +0 -119
- package/__tests__/gateway-adapter.test.ts +0 -366
- package/__tests__/gateway-client.test.ts +0 -272
- package/__tests__/handlers.test.ts +0 -150
- package/__tests__/heartbeat.test.ts +0 -124
- package/__tests__/onboarding.test.ts +0 -55
- package/__tests__/package-install.test.ts +0 -109
- package/__tests__/pair.test.ts +0 -60
- package/__tests__/self-update.test.ts +0 -123
- package/__tests__/stop.test.ts +0 -38
- package/jest.config.ts +0 -16
- package/src/api.ts +0 -62
- package/src/commands/install.ts +0 -64
- package/src/commands/self-update.ts +0 -43
- package/src/commands/uninstall.ts +0 -19
- package/src/connection.ts +0 -203
- package/src/debug.ts +0 -11
- package/src/handlers/backup.ts +0 -101
- package/src/handlers/board-handler.ts +0 -155
- package/src/handlers/chat.ts +0 -79
- package/src/handlers/config.ts +0 -48
- package/src/handlers/deploy.ts +0 -32
- package/src/handlers/exec.ts +0 -32
- package/src/handlers/file-delete.ts +0 -46
- package/src/handlers/file-write.ts +0 -65
- package/src/handlers/knowledge-sync.ts +0 -53
- package/src/handlers/onboarding.ts +0 -19
- package/src/handlers/package-install.ts +0 -69
- package/src/handlers/pair.ts +0 -26
- package/src/handlers/restart.ts +0 -19
- package/src/handlers/stop.ts +0 -17
- package/src/heartbeat.ts +0 -110
- package/src/index.ts +0 -97
- package/src/openclaw/gateway-adapter.ts +0 -129
- package/src/openclaw/gateway-client.ts +0 -131
- package/src/openclaw/index.ts +0 -17
- package/src/openclaw/types.ts +0 -41
- package/src/platform/linux.ts +0 -108
- package/src/platform/macos.ts +0 -122
- package/src/platform/windows.ts +0 -92
- package/src/types.ts +0 -94
- package/tsconfig.json +0 -18
package/src/handlers/chat.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { AgentCommand } from '../types.js';
|
|
2
|
-
import { getChatProvider } from '../openclaw/index.js';
|
|
3
|
-
|
|
4
|
-
export async function handleChatListSessions(
|
|
5
|
-
command: AgentCommand,
|
|
6
|
-
publish: (data: unknown) => Promise<void>,
|
|
7
|
-
): Promise<void> {
|
|
8
|
-
const provider = getChatProvider();
|
|
9
|
-
if (!provider) {
|
|
10
|
-
await publish({ type: 'chat_sessions_response', correlationId: command.id, sessions: [], error: 'Chat provider not initialized' });
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const sessions = await provider.listSessions();
|
|
15
|
-
await publish({ type: 'chat_sessions_response', correlationId: command.id, sessions });
|
|
16
|
-
} catch (err) {
|
|
17
|
-
await publish({ type: 'chat_sessions_response', correlationId: command.id, sessions: [], error: err instanceof Error ? err.message : String(err) });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function handleChatHistory(
|
|
22
|
-
command: AgentCommand,
|
|
23
|
-
publish: (data: unknown) => Promise<void>,
|
|
24
|
-
): Promise<void> {
|
|
25
|
-
const { sessionKey, limit } = command.payload as { sessionKey: string; limit?: number };
|
|
26
|
-
const provider = getChatProvider();
|
|
27
|
-
if (!provider) {
|
|
28
|
-
await publish({ type: 'chat_history_response', correlationId: command.id, messages: [], error: 'Chat provider not initialized' });
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const messages = await provider.getHistory(sessionKey, limit ?? 200);
|
|
33
|
-
await publish({ type: 'chat_history_response', correlationId: command.id, messages });
|
|
34
|
-
} catch (err) {
|
|
35
|
-
await publish({ type: 'chat_history_response', correlationId: command.id, messages: [], error: err instanceof Error ? err.message : String(err) });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function handleChatSend(
|
|
40
|
-
command: AgentCommand,
|
|
41
|
-
publish: (data: unknown) => Promise<void>,
|
|
42
|
-
agentId: string,
|
|
43
|
-
): Promise<void> {
|
|
44
|
-
const { sessionKey, text, attachments } = command.payload as {
|
|
45
|
-
sessionKey: string;
|
|
46
|
-
text: string;
|
|
47
|
-
attachments?: Array<{ type: 'image'; mimeType: string; content: string }>;
|
|
48
|
-
};
|
|
49
|
-
const correlationId = command.id;
|
|
50
|
-
|
|
51
|
-
// Typing indicator
|
|
52
|
-
await publish({ type: 'chat_typing', agentId, state: true }).catch(() => {});
|
|
53
|
-
|
|
54
|
-
const provider = getChatProvider();
|
|
55
|
-
if (!provider) {
|
|
56
|
-
await publish({ type: 'chat_response', correlationId, sessionKey, text: '', error: 'Chat provider not initialized' });
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
await provider.sendMessage({
|
|
62
|
-
sessionKey,
|
|
63
|
-
text,
|
|
64
|
-
idempotencyKey: correlationId,
|
|
65
|
-
attachments, // base64 inline — Centrifugo limit raised to 10MB
|
|
66
|
-
onDelta: async (accumulated) => {
|
|
67
|
-
await publish({ type: 'chat_delta', correlationId, sessionKey, text: accumulated }).catch(() => {});
|
|
68
|
-
},
|
|
69
|
-
onDone: async (finalText) => {
|
|
70
|
-
await publish({ type: 'chat_response', correlationId, sessionKey, text: finalText }).catch(() => {});
|
|
71
|
-
},
|
|
72
|
-
onError: async (error) => {
|
|
73
|
-
await publish({ type: 'chat_response', correlationId, sessionKey, text: '', error }).catch(() => {});
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
} catch (err) {
|
|
77
|
-
await publish({ type: 'chat_response', correlationId, sessionKey, text: '', error: err instanceof Error ? err.message : String(err) }).catch(() => {});
|
|
78
|
-
}
|
|
79
|
-
}
|
package/src/handlers/config.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { exec } from 'node:child_process';
|
|
4
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR = '/etc/openclaw';
|
|
7
|
-
|
|
8
|
-
export function handleConfig(command: AgentCommand): Promise<AgentResponse> {
|
|
9
|
-
const filename = command.payload.filename as string;
|
|
10
|
-
const content = command.payload.content as string;
|
|
11
|
-
|
|
12
|
-
if (!filename || content === undefined) {
|
|
13
|
-
return Promise.resolve({
|
|
14
|
-
id: command.id,
|
|
15
|
-
type: 'config',
|
|
16
|
-
success: false,
|
|
17
|
-
error: 'Missing "filename" or "content" in payload',
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const configPath = path.join(CONFIG_DIR, path.basename(filename));
|
|
22
|
-
|
|
23
|
-
return fs.mkdir(CONFIG_DIR, { recursive: true })
|
|
24
|
-
.then(() => fs.writeFile(configPath, content, 'utf-8'))
|
|
25
|
-
.then(() => {
|
|
26
|
-
return new Promise<AgentResponse>((resolve) => {
|
|
27
|
-
exec('openclaw gateway restart', { timeout: 30_000 }, (error, stdout, stderr) => {
|
|
28
|
-
resolve({
|
|
29
|
-
id: command.id,
|
|
30
|
-
type: 'config',
|
|
31
|
-
success: !error,
|
|
32
|
-
data: {
|
|
33
|
-
path: configPath,
|
|
34
|
-
stdout: stdout.toString(),
|
|
35
|
-
stderr: stderr.toString(),
|
|
36
|
-
},
|
|
37
|
-
...(error ? { error: error.message } : {}),
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
})
|
|
42
|
-
.catch((err) => ({
|
|
43
|
-
id: command.id,
|
|
44
|
-
type: 'config',
|
|
45
|
-
success: false,
|
|
46
|
-
error: err instanceof Error ? err.message : String(err),
|
|
47
|
-
}));
|
|
48
|
-
}
|
package/src/handlers/deploy.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
3
|
-
|
|
4
|
-
export function handleDeploy(command: AgentCommand): Promise<AgentResponse> {
|
|
5
|
-
return new Promise((resolve) => {
|
|
6
|
-
exec('npm i -g openclaw', { timeout: 120_000 }, (installErr, installOut, installStderr) => {
|
|
7
|
-
if (installErr) {
|
|
8
|
-
resolve({
|
|
9
|
-
id: command.id,
|
|
10
|
-
type: 'deploy',
|
|
11
|
-
success: false,
|
|
12
|
-
data: { stdout: installOut.toString(), stderr: installStderr.toString() },
|
|
13
|
-
error: `Install failed: ${installErr.message}`,
|
|
14
|
-
});
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
exec('openclaw gateway restart', { timeout: 30_000 }, (restartErr, restartOut, restartStderr) => {
|
|
19
|
-
resolve({
|
|
20
|
-
id: command.id,
|
|
21
|
-
type: 'deploy',
|
|
22
|
-
success: !restartErr,
|
|
23
|
-
data: {
|
|
24
|
-
install: { stdout: installOut.toString(), stderr: installStderr.toString() },
|
|
25
|
-
restart: { stdout: restartOut.toString(), stderr: restartStderr.toString() },
|
|
26
|
-
},
|
|
27
|
-
...(restartErr ? { error: `Restart failed: ${restartErr.message}` } : {}),
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
package/src/handlers/exec.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
3
|
-
|
|
4
|
-
const EXEC_TIMEOUT = 60_000;
|
|
5
|
-
|
|
6
|
-
export function handleExec(command: AgentCommand): Promise<AgentResponse> {
|
|
7
|
-
const cmd = command.payload.command as string;
|
|
8
|
-
if (!cmd) {
|
|
9
|
-
return Promise.resolve({
|
|
10
|
-
id: command.id,
|
|
11
|
-
type: 'exec',
|
|
12
|
-
success: false,
|
|
13
|
-
error: 'Missing "command" in payload',
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
exec(cmd, { timeout: EXEC_TIMEOUT }, (error, stdout, stderr) => {
|
|
19
|
-
resolve({
|
|
20
|
-
id: command.id,
|
|
21
|
-
type: 'exec',
|
|
22
|
-
success: !error,
|
|
23
|
-
data: {
|
|
24
|
-
exitCode: error ? (error as NodeJS.ErrnoException & { code?: number }).code ?? 1 : 0,
|
|
25
|
-
stdout: stdout.toString(),
|
|
26
|
-
stderr: stderr.toString(),
|
|
27
|
-
},
|
|
28
|
-
...(error ? { error: error.message } : {}),
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
4
|
-
|
|
5
|
-
const WORKSPACE_DIR = process.env.WORKSPACE_DIR ?? '/etc/openclaw/workspace';
|
|
6
|
-
|
|
7
|
-
export async function handleFileDelete(command: AgentCommand): Promise<AgentResponse> {
|
|
8
|
-
const filePath = command.payload.path as string;
|
|
9
|
-
|
|
10
|
-
if (!filePath) {
|
|
11
|
-
return {
|
|
12
|
-
id: command.id,
|
|
13
|
-
type: 'file_delete',
|
|
14
|
-
success: false,
|
|
15
|
-
error: 'Missing "path" in payload',
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (filePath.includes('..') || filePath.startsWith('/')) {
|
|
20
|
-
return {
|
|
21
|
-
id: command.id,
|
|
22
|
-
type: 'file_delete',
|
|
23
|
-
success: false,
|
|
24
|
-
error: 'Invalid path: must not contain ".." or start with "/"',
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const resolvedPath = path.join(WORKSPACE_DIR, filePath);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
await fs.unlink(resolvedPath);
|
|
32
|
-
return {
|
|
33
|
-
id: command.id,
|
|
34
|
-
type: 'file_delete',
|
|
35
|
-
success: true,
|
|
36
|
-
data: { path: resolvedPath },
|
|
37
|
-
};
|
|
38
|
-
} catch (err) {
|
|
39
|
-
return {
|
|
40
|
-
id: command.id,
|
|
41
|
-
type: 'file_delete',
|
|
42
|
-
success: false,
|
|
43
|
-
error: err instanceof Error ? err.message : String(err),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
4
|
-
|
|
5
|
-
const WORKSPACE_DIR = process.env.WORKSPACE_DIR ?? '/etc/openclaw/workspace';
|
|
6
|
-
|
|
7
|
-
export async function handleFileWrite(command: AgentCommand): Promise<AgentResponse> {
|
|
8
|
-
const filePath = command.payload.path as string;
|
|
9
|
-
const content = command.payload.content;
|
|
10
|
-
const overwrite = (command.payload.overwrite as boolean | undefined) ?? true;
|
|
11
|
-
|
|
12
|
-
if (!filePath || content === undefined) {
|
|
13
|
-
return {
|
|
14
|
-
id: command.id,
|
|
15
|
-
type: 'file_write',
|
|
16
|
-
success: false,
|
|
17
|
-
error: 'Missing "path" or "content" in payload',
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (filePath.includes('..') || filePath.startsWith('/')) {
|
|
22
|
-
return {
|
|
23
|
-
id: command.id,
|
|
24
|
-
type: 'file_write',
|
|
25
|
-
success: false,
|
|
26
|
-
error: 'Invalid path: must not contain ".." or start with "/"',
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const resolvedPath = path.join(WORKSPACE_DIR, filePath);
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
if (!overwrite) {
|
|
34
|
-
try {
|
|
35
|
-
await fs.access(resolvedPath);
|
|
36
|
-
// File exists — skip
|
|
37
|
-
return {
|
|
38
|
-
id: command.id,
|
|
39
|
-
type: 'file_write',
|
|
40
|
-
success: true,
|
|
41
|
-
data: { path: resolvedPath, written: false, skipped: true },
|
|
42
|
-
};
|
|
43
|
-
} catch {
|
|
44
|
-
// File does not exist — proceed with write
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
49
|
-
await fs.writeFile(resolvedPath, content as string, 'utf-8');
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
id: command.id,
|
|
53
|
-
type: 'file_write',
|
|
54
|
-
success: true,
|
|
55
|
-
data: { path: resolvedPath, written: true },
|
|
56
|
-
};
|
|
57
|
-
} catch (err) {
|
|
58
|
-
return {
|
|
59
|
-
id: command.id,
|
|
60
|
-
type: 'file_write',
|
|
61
|
-
success: false,
|
|
62
|
-
error: err instanceof Error ? err.message : String(err),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
5
|
-
|
|
6
|
-
function getWorkspaceDir(): string {
|
|
7
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() ?? path.join(os.homedir(), '.openclaw');
|
|
8
|
-
return path.join(stateDir, 'workspace');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function handleKnowledgeSync(command: AgentCommand): Promise<AgentResponse> {
|
|
12
|
-
const requestedPaths = command.payload.paths as string[];
|
|
13
|
-
if (!Array.isArray(requestedPaths)) {
|
|
14
|
-
return { id: command.id, type: 'knowledge_sync', success: false, error: 'Missing "paths" in payload' };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const workspaceDir = getWorkspaceDir();
|
|
18
|
-
const resolvedPaths: string[] = [];
|
|
19
|
-
|
|
20
|
-
for (const p of requestedPaths) {
|
|
21
|
-
if (p.endsWith('/')) {
|
|
22
|
-
// Directory — scan for .md files
|
|
23
|
-
const dir = path.join(workspaceDir, p);
|
|
24
|
-
try {
|
|
25
|
-
const entries = await fs.readdir(dir);
|
|
26
|
-
for (const entry of entries) {
|
|
27
|
-
if (entry.endsWith('.md')) resolvedPaths.push(path.join(p, entry));
|
|
28
|
-
}
|
|
29
|
-
} catch {
|
|
30
|
-
// Directory doesn't exist — skip
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
resolvedPaths.push(p);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const files: { path: string; content: string }[] = [];
|
|
38
|
-
for (const filePath of resolvedPaths) {
|
|
39
|
-
try {
|
|
40
|
-
const content = await fs.readFile(path.join(workspaceDir, filePath), 'utf-8');
|
|
41
|
-
files.push({ path: filePath, content });
|
|
42
|
-
} catch {
|
|
43
|
-
// File doesn't exist or unreadable — skip
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
id: command.id,
|
|
49
|
-
type: 'knowledge_sync',
|
|
50
|
-
success: true,
|
|
51
|
-
data: { files },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
3
|
-
|
|
4
|
-
export function handleOnboardingComplete(command: AgentCommand): Promise<AgentResponse> {
|
|
5
|
-
return new Promise((resolve) => {
|
|
6
|
-
exec('openclaw gateway restart', { timeout: 30_000 }, (error, stdout, stderr) => {
|
|
7
|
-
resolve({
|
|
8
|
-
id: command.id,
|
|
9
|
-
type: 'onboarding_complete',
|
|
10
|
-
success: !error,
|
|
11
|
-
data: {
|
|
12
|
-
stdout: stdout.toString(),
|
|
13
|
-
stderr: stderr.toString(),
|
|
14
|
-
},
|
|
15
|
-
...(error ? { error: error.message } : {}),
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
3
|
-
|
|
4
|
-
const INSTALL_TIMEOUT_MS = 300_000; // 5 minutes total
|
|
5
|
-
|
|
6
|
-
function execPackage(cmd: string, timeoutMs: number): Promise<void> {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
exec(cmd, { timeout: timeoutMs }, (error) => {
|
|
9
|
-
if (error) reject(error);
|
|
10
|
-
else resolve();
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function handlePackageInstall(command: AgentCommand): Promise<AgentResponse> {
|
|
16
|
-
const packages = command.payload.packages as {
|
|
17
|
-
apt?: string[];
|
|
18
|
-
npm?: string[];
|
|
19
|
-
pip?: string[];
|
|
20
|
-
} | undefined;
|
|
21
|
-
|
|
22
|
-
const apt = packages?.apt ?? [];
|
|
23
|
-
const npm = packages?.npm ?? [];
|
|
24
|
-
const pip = packages?.pip ?? [];
|
|
25
|
-
|
|
26
|
-
if (apt.length === 0 && npm.length === 0 && pip.length === 0) {
|
|
27
|
-
return {
|
|
28
|
-
id: command.id,
|
|
29
|
-
type: 'package_install',
|
|
30
|
-
success: true,
|
|
31
|
-
data: { installed: { apt: [], npm: [], pip: [] }, errors: [] },
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const installed: { apt: string[]; npm: string[]; pip: string[] } = { apt: [], npm: [], pip: [] };
|
|
36
|
-
const errors: Array<{ name: string; error: string }> = [];
|
|
37
|
-
const deadline = Date.now() + INSTALL_TIMEOUT_MS;
|
|
38
|
-
|
|
39
|
-
async function tryInstall(pkg: string, cmd: string, type: 'apt' | 'npm' | 'pip'): Promise<void> {
|
|
40
|
-
const remaining = deadline - Date.now();
|
|
41
|
-
if (remaining <= 0) {
|
|
42
|
-
errors.push({ name: pkg, error: 'Install timeout exceeded' });
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
await execPackage(cmd, remaining);
|
|
47
|
-
installed[type].push(pkg);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
errors.push({ name: pkg, error: err instanceof Error ? err.message : String(err) });
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for (const pkg of apt) {
|
|
54
|
-
await tryInstall(pkg, `apt-get install -y ${pkg}`, 'apt');
|
|
55
|
-
}
|
|
56
|
-
for (const pkg of npm) {
|
|
57
|
-
await tryInstall(pkg, `npm install -g ${pkg}`, 'npm');
|
|
58
|
-
}
|
|
59
|
-
for (const pkg of pip) {
|
|
60
|
-
await tryInstall(pkg, `pip install ${pkg}`, 'pip');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
id: command.id,
|
|
65
|
-
type: 'package_install',
|
|
66
|
-
success: errors.length === 0,
|
|
67
|
-
data: { installed, errors },
|
|
68
|
-
};
|
|
69
|
-
}
|
package/src/handlers/pair.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
2
|
-
|
|
3
|
-
export function handlePair(command: AgentCommand): Promise<AgentResponse> {
|
|
4
|
-
const pairToken = command.payload.token as string;
|
|
5
|
-
const targetId = command.payload.targetId as string;
|
|
6
|
-
|
|
7
|
-
if (!pairToken || !targetId) {
|
|
8
|
-
return Promise.resolve({
|
|
9
|
-
id: command.id,
|
|
10
|
-
type: 'pair',
|
|
11
|
-
success: false,
|
|
12
|
-
error: 'Missing "token" or "targetId" in payload',
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return Promise.resolve({
|
|
17
|
-
id: command.id,
|
|
18
|
-
type: 'pair',
|
|
19
|
-
success: true,
|
|
20
|
-
data: {
|
|
21
|
-
paired: true,
|
|
22
|
-
targetId,
|
|
23
|
-
agentId: process.env.AGENT_ID,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|
package/src/handlers/restart.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
3
|
-
|
|
4
|
-
export function handleRestart(command: AgentCommand): Promise<AgentResponse> {
|
|
5
|
-
return new Promise((resolve) => {
|
|
6
|
-
exec('openclaw gateway restart', { timeout: 30_000 }, (error, stdout, stderr) => {
|
|
7
|
-
resolve({
|
|
8
|
-
id: command.id,
|
|
9
|
-
type: 'restart',
|
|
10
|
-
success: !error,
|
|
11
|
-
data: {
|
|
12
|
-
stdout: stdout.toString(),
|
|
13
|
-
stderr: stderr.toString(),
|
|
14
|
-
},
|
|
15
|
-
...(error ? { error: error.message } : {}),
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}
|
package/src/handlers/stop.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
2
|
-
|
|
3
|
-
export function handleStop(command: AgentCommand): Promise<AgentResponse> {
|
|
4
|
-
const response: AgentResponse = {
|
|
5
|
-
id: command.id,
|
|
6
|
-
type: 'stop',
|
|
7
|
-
success: true,
|
|
8
|
-
data: { message: 'Shutting down gracefully' },
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
setTimeout(() => {
|
|
12
|
-
console.log('Agent stopping gracefully...');
|
|
13
|
-
process.exit(0);
|
|
14
|
-
}, 500);
|
|
15
|
-
|
|
16
|
-
return Promise.resolve(response);
|
|
17
|
-
}
|
package/src/heartbeat.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import { exec } from 'node:child_process';
|
|
5
|
-
import { debugLog } from './debug.js';
|
|
6
|
-
import type { AgentApi } from './api.js';
|
|
7
|
-
import type { BoardState, HeartbeatPayload } from './types.js';
|
|
8
|
-
import { selfUpdate } from './commands/self-update.js';
|
|
9
|
-
|
|
10
|
-
const HEARTBEAT_INTERVAL = 30_000;
|
|
11
|
-
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24h
|
|
12
|
-
const PACKAGE_NAME = '@openclaw-cloud/agent-controller';
|
|
13
|
-
|
|
14
|
-
function execAsync(cmd: string, timeout: number): Promise<string> {
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
exec(cmd, { timeout }, (err, stdout) => {
|
|
17
|
-
if (err) reject(err);
|
|
18
|
-
else resolve(stdout.toString().trim());
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function checkForUpdate(currentVersion: string): Promise<void> {
|
|
24
|
-
try {
|
|
25
|
-
const latest = await execAsync(`npm view ${PACKAGE_NAME} version`, 10_000);
|
|
26
|
-
if (latest && latest !== currentVersion) {
|
|
27
|
-
console.log(`[update] New version available: ${latest} (current: ${currentVersion}). Updating...`);
|
|
28
|
-
await selfUpdate(currentVersion);
|
|
29
|
-
}
|
|
30
|
-
} catch {
|
|
31
|
-
// No internet, npm unavailable, or already up to date — silently ignore
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export async function getLastMessageAt(): Promise<string | null> {
|
|
36
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim()
|
|
37
|
-
?? path.join(os.homedir(), '.openclaw');
|
|
38
|
-
const sessionsDir = path.join(stateDir, 'agents', 'main', 'sessions');
|
|
39
|
-
try {
|
|
40
|
-
const files = await fs.readdir(sessionsDir);
|
|
41
|
-
const jsonlFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
42
|
-
if (jsonlFiles.length === 0) return null;
|
|
43
|
-
const mtimes = await Promise.all(
|
|
44
|
-
jsonlFiles.map((f) =>
|
|
45
|
-
fs.stat(path.join(sessionsDir, f)).then((s) => s.mtime.getTime())
|
|
46
|
-
)
|
|
47
|
-
);
|
|
48
|
-
return new Date(Math.max(...mtimes)).toISOString();
|
|
49
|
-
} catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface HeartbeatOptions {
|
|
55
|
-
api: AgentApi;
|
|
56
|
-
agentId: string;
|
|
57
|
-
version: string;
|
|
58
|
-
getBoardStatus?: () => BoardState;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function getMetrics(): HeartbeatPayload['metrics'] {
|
|
62
|
-
const cpus = os.cpus();
|
|
63
|
-
const cpuUsage = cpus.reduce((acc, cpu) => {
|
|
64
|
-
const total = Object.values(cpu.times).reduce((a, b) => a + b, 0);
|
|
65
|
-
return acc + (1 - cpu.times.idle / total);
|
|
66
|
-
}, 0) / cpus.length;
|
|
67
|
-
|
|
68
|
-
const totalMem = os.totalmem();
|
|
69
|
-
const freeMem = os.freemem();
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
cpu: Math.round(cpuUsage * 100) / 100,
|
|
73
|
-
mem: Math.round(((totalMem - freeMem) / totalMem) * 100) / 100,
|
|
74
|
-
uptime: os.uptime(),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function publishHeartbeat(opts: HeartbeatOptions): Promise<void> {
|
|
79
|
-
const payload: HeartbeatPayload = {
|
|
80
|
-
type: 'heartbeat',
|
|
81
|
-
agentId: opts.agentId,
|
|
82
|
-
ts: Date.now(),
|
|
83
|
-
version: opts.version,
|
|
84
|
-
metrics: getMetrics(),
|
|
85
|
-
...(opts.getBoardStatus ? { boardStatus: opts.getBoardStatus() } : {}),
|
|
86
|
-
lastMessageAt: await getLastMessageAt(),
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
debugLog('heartbeat', '→ sending', opts.agentId, JSON.stringify(payload).slice(0, 500));
|
|
91
|
-
await opts.api.publishHeartbeat(opts.agentId, payload);
|
|
92
|
-
debugLog('heartbeat', '← ok');
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error('Heartbeat publish failed:', err instanceof Error ? err.message : err);
|
|
95
|
-
debugLog('heartbeat', '← error', err);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function startHeartbeat(opts: HeartbeatOptions): NodeJS.Timeout {
|
|
100
|
-
const send = () => { publishHeartbeat(opts).catch(() => {}); };
|
|
101
|
-
send();
|
|
102
|
-
|
|
103
|
-
// Auto-update check: immediately on start, then every 24h
|
|
104
|
-
checkForUpdate(opts.version).catch(() => {});
|
|
105
|
-
setInterval(() => { checkForUpdate(opts.version).catch(() => {}); }, UPDATE_CHECK_INTERVAL);
|
|
106
|
-
|
|
107
|
-
return setInterval(send, HEARTBEAT_INTERVAL);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export { getMetrics };
|