@openclaw-cloud/agent-controller 0.2.6 → 0.2.8
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 +6 -0
- package/dist/commands/backup-cli.d.ts +1 -0
- package/dist/commands/backup-cli.js +65 -0
- package/dist/commands/backup-cli.js.map +1 -0
- package/dist/commands/install.js +3 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/knowledge-sync-cli.d.ts +1 -0
- package/dist/commands/knowledge-sync-cli.js +61 -0
- package/dist/commands/knowledge-sync-cli.js.map +1 -0
- 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 -68
- package/src/commands/self-update.ts +0 -43
- package/src/commands/uninstall.ts +0 -19
- package/src/config-file.ts +0 -56
- 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
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import type { AgentApi } from '../api.js';
|
|
3
|
-
import type { BoardState, BoardEvent, BoardInfo, CardDetail } from '../types.js';
|
|
4
|
-
|
|
5
|
-
const OPENCLAW_TIMEOUT = 30_000;
|
|
6
|
-
|
|
7
|
-
export class BoardHandler {
|
|
8
|
-
private boardState: BoardState = { state: 'idle', cardId: null };
|
|
9
|
-
private myColumnIds: string[] = [];
|
|
10
|
-
private boardId: string | null = null;
|
|
11
|
-
private workspaceId: string | null = null;
|
|
12
|
-
private api: AgentApi;
|
|
13
|
-
|
|
14
|
-
constructor(api: AgentApi) {
|
|
15
|
-
this.api = api;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async initialize(): Promise<string | null> {
|
|
19
|
-
try {
|
|
20
|
-
const data = await this.api.get('/api/agent/board') as BoardInfo;
|
|
21
|
-
this.boardId = data.board.id;
|
|
22
|
-
this.workspaceId = data.board.workspaceId;
|
|
23
|
-
this.myColumnIds = data.board.myColumnIds;
|
|
24
|
-
console.log(`Board initialized: ${this.boardId} (workspace: ${this.workspaceId}), watching ${this.myColumnIds.length} column(s)`);
|
|
25
|
-
|
|
26
|
-
// Backfill: check for existing unassigned cards in my columns
|
|
27
|
-
await this.checkQueue(data);
|
|
28
|
-
// Return workspaceId for channel subscription (board:<workspaceId>)
|
|
29
|
-
return this.workspaceId;
|
|
30
|
-
} catch (err) {
|
|
31
|
-
console.error('Board initialization failed:', err instanceof Error ? err.message : err);
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async onBoardEvent(event: BoardEvent): Promise<void> {
|
|
37
|
-
switch (event.event) {
|
|
38
|
-
case 'card:entered':
|
|
39
|
-
if (!event.columnId || !this.myColumnIds.includes(event.columnId)) return;
|
|
40
|
-
if (this.boardState.state === 'working') return;
|
|
41
|
-
await this.tryClaimCard(event.cardId);
|
|
42
|
-
break;
|
|
43
|
-
case 'card:claimed':
|
|
44
|
-
case 'card:moved':
|
|
45
|
-
case 'card:commented':
|
|
46
|
-
// Info only, ignore
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async tryClaimCard(cardId: string): Promise<void> {
|
|
52
|
-
try {
|
|
53
|
-
const res = await this.api.post(`/api/agent/board/cards/${cardId}/claim`);
|
|
54
|
-
if (res.ok) {
|
|
55
|
-
this.boardState = { state: 'working', cardId };
|
|
56
|
-
console.log(`Claimed card: ${cardId}`);
|
|
57
|
-
await this.startTask(cardId);
|
|
58
|
-
} else if (res.status === 409) {
|
|
59
|
-
console.log(`Card ${cardId} already claimed or agent busy, skipping`);
|
|
60
|
-
} else {
|
|
61
|
-
const body = await res.text().catch(() => '');
|
|
62
|
-
console.error(`Claim card ${cardId} failed (HTTP ${res.status}): ${body}`);
|
|
63
|
-
}
|
|
64
|
-
} catch (err) {
|
|
65
|
-
console.error('tryClaimCard error:', err instanceof Error ? err.message : err);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async startTask(cardId: string): Promise<void> {
|
|
70
|
-
try {
|
|
71
|
-
const data = await this.api.get(`/api/agent/board/cards/${cardId}`) as CardDetail;
|
|
72
|
-
const card = data.card;
|
|
73
|
-
const message = [
|
|
74
|
-
`New task from board: ${card.title}`,
|
|
75
|
-
card.description ? `Description: ${card.description}` : null,
|
|
76
|
-
card.priority ? `Priority: ${card.priority}` : null,
|
|
77
|
-
'',
|
|
78
|
-
'Instructions:',
|
|
79
|
-
'- Complete the task described above.',
|
|
80
|
-
'- Post progress and results as comments using: POST /api/agent/board/cards/' + cardId + '/comments',
|
|
81
|
-
'- When done, move the card forward using: POST /api/agent/board/cards/' + cardId + '/move',
|
|
82
|
-
].filter(Boolean).join('\n');
|
|
83
|
-
|
|
84
|
-
await this.execOpenclaw(message);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.error(`startTask(${cardId}) error:`, err instanceof Error ? err.message : err);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
completeTask(cardId: string): void {
|
|
91
|
-
console.log(`Task completed: ${cardId}`);
|
|
92
|
-
this.boardState = { state: 'idle', cardId: null };
|
|
93
|
-
this.checkQueue().catch((err) => {
|
|
94
|
-
console.error('checkQueue after completeTask error:', err instanceof Error ? err.message : err);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async checkQueue(existingData?: BoardInfo): Promise<void> {
|
|
99
|
-
try {
|
|
100
|
-
const data = existingData ?? (await this.api.get('/api/agent/board') as BoardInfo);
|
|
101
|
-
const candidates: Array<{ id: string; priority?: string; createdAt?: string }> = [];
|
|
102
|
-
|
|
103
|
-
for (const col of data.board.columns) {
|
|
104
|
-
if (!this.myColumnIds.includes(col.id)) continue;
|
|
105
|
-
for (const card of col.cards) {
|
|
106
|
-
if (!card.assignedAgentId) {
|
|
107
|
-
candidates.push(card);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (candidates.length === 0) return;
|
|
113
|
-
|
|
114
|
-
// Sort by priority (high > medium > low) then by creation date (oldest first)
|
|
115
|
-
const priorityOrder: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
116
|
-
candidates.sort((a, b) => {
|
|
117
|
-
const pa = priorityOrder[a.priority ?? 'medium'] ?? 2;
|
|
118
|
-
const pb = priorityOrder[b.priority ?? 'medium'] ?? 2;
|
|
119
|
-
if (pa !== pb) return pa - pb;
|
|
120
|
-
return (a.createdAt ?? '').localeCompare(b.createdAt ?? '');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
console.log(`Queue: ${candidates.length} unassigned card(s) in my columns, trying first`);
|
|
124
|
-
await this.tryClaimCard(candidates[0].id);
|
|
125
|
-
} catch (err) {
|
|
126
|
-
console.error('checkQueue error:', err instanceof Error ? err.message : err);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
getBoardStatus(): BoardState {
|
|
131
|
-
return this.boardState;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
getBoardId(): string | null {
|
|
135
|
-
return this.boardId;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private execOpenclaw(message: string): Promise<void> {
|
|
139
|
-
return new Promise((resolve) => {
|
|
140
|
-
const escaped = message.replace(/'/g, "'\\''");
|
|
141
|
-
exec(
|
|
142
|
-
`openclaw system event --text '${escaped}' --mode now`,
|
|
143
|
-
{ timeout: OPENCLAW_TIMEOUT },
|
|
144
|
-
(error, stdout, stderr) => {
|
|
145
|
-
if (error) {
|
|
146
|
-
console.error('openclaw system event failed:', stderr.toString() || error.message);
|
|
147
|
-
} else {
|
|
148
|
-
console.log('openclaw system event sent');
|
|
149
|
-
}
|
|
150
|
-
resolve();
|
|
151
|
-
},
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
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
|
-
}
|