@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/index.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
import { createConnection } from './connection.js';
|
|
5
|
-
import { startHeartbeat } from './heartbeat.js';
|
|
6
|
-
import { createAgentApi } from './api.js';
|
|
7
|
-
import { BoardHandler } from './handlers/board-handler.js';
|
|
8
|
-
import { createChatProvider } from './openclaw/index.js';
|
|
9
|
-
import { DEBUG } from './debug.js';
|
|
10
|
-
import type { BoardEvent } from './types.js';
|
|
11
|
-
|
|
12
|
-
const _require = createRequire(import.meta.url);
|
|
13
|
-
const { version: CONTROLLER_VERSION } = _require(join(dirname(fileURLToPath(import.meta.url)), '../package.json')) as { version: string };
|
|
14
|
-
|
|
15
|
-
function requireEnv(name: string): string {
|
|
16
|
-
const value = process.env[name];
|
|
17
|
-
if (!value) {
|
|
18
|
-
console.error(`Missing required environment variable: ${name}`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function main(): void {
|
|
25
|
-
const rawUrl = requireEnv('CENTRIFUGO_URL');
|
|
26
|
-
// Centrifuge client requires ws:// scheme
|
|
27
|
-
const url = rawUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://') + '/connection/websocket';
|
|
28
|
-
const token = requireEnv('AGENT_TOKEN');
|
|
29
|
-
const agentId = requireEnv('AGENT_ID');
|
|
30
|
-
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
31
|
-
if (DEBUG) console.log('[debug] Debug mode enabled');
|
|
32
|
-
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
33
|
-
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
34
|
-
|
|
35
|
-
const api = createAgentApi(backendUrl, token);
|
|
36
|
-
const { client } = createConnection({ url, token, agentId, backendUrl, api, version: CONTROLLER_VERSION });
|
|
37
|
-
|
|
38
|
-
// Chat provider via OpenClaw Gateway WS
|
|
39
|
-
const gatewayWsUrl = process.env.OPENCLAW_GATEWAY_WS || 'ws://localhost:18789';
|
|
40
|
-
const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN || '';
|
|
41
|
-
|
|
42
|
-
if (gatewayToken) {
|
|
43
|
-
const chatProvider = createChatProvider(gatewayWsUrl, gatewayToken);
|
|
44
|
-
chatProvider.connect().then(() => {
|
|
45
|
-
console.log('OpenClaw Gateway WS connected (chat provider ready)');
|
|
46
|
-
}).catch((e) => {
|
|
47
|
-
console.warn('Chat provider connect failed:', e instanceof Error ? e.message : e);
|
|
48
|
-
});
|
|
49
|
-
} else {
|
|
50
|
-
console.warn('OPENCLAW_GATEWAY_TOKEN not set, chat provider disabled');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Board handler
|
|
54
|
-
const boardHandler = new BoardHandler(api);
|
|
55
|
-
|
|
56
|
-
// Initialize board handler after connection is established
|
|
57
|
-
boardHandler.initialize().then((workspaceId) => {
|
|
58
|
-
if (!workspaceId) {
|
|
59
|
-
console.log('No board found for this agent, board handler dormant');
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const boardChannel = `board:${workspaceId}`;
|
|
64
|
-
const boardSub = client.newSubscription(boardChannel);
|
|
65
|
-
|
|
66
|
-
boardSub.on('publication', (ctx) => {
|
|
67
|
-
const event = ctx.data as BoardEvent;
|
|
68
|
-
if (!event || !event.event) return;
|
|
69
|
-
boardHandler.onBoardEvent(event).catch((err) => {
|
|
70
|
-
console.error('Board event handler error:', err instanceof Error ? err.message : err);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
boardSub.subscribe();
|
|
75
|
-
console.log(`Subscribed to board channel: ${boardChannel}`);
|
|
76
|
-
}).catch((err) => {
|
|
77
|
-
console.error('Board handler init error:', err instanceof Error ? err.message : err);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
console.log('Heartbeat via backend API');
|
|
81
|
-
const heartbeatTimer = startHeartbeat({
|
|
82
|
-
api,
|
|
83
|
-
agentId,
|
|
84
|
-
version: CONTROLLER_VERSION,
|
|
85
|
-
getBoardStatus: () => boardHandler.getBoardStatus(),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const shutdown = () => {
|
|
89
|
-
console.log('Shutting down...');
|
|
90
|
-
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
91
|
-
client.disconnect();
|
|
92
|
-
process.exit(0);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
process.on('SIGINT', shutdown);
|
|
96
|
-
process.on('SIGTERM', shutdown);
|
|
97
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { GatewayClient } from './gateway-client.js';
|
|
2
|
-
import type { IChatProvider, ChatSession, ChatMessage, ChatAttachment } from './types.js';
|
|
3
|
-
|
|
4
|
-
export class OpenclawGatewayAdapter implements IChatProvider {
|
|
5
|
-
private client: GatewayClient;
|
|
6
|
-
private streamCallbacks = new Map<string, {
|
|
7
|
-
onDelta?: (text: string) => void;
|
|
8
|
-
onDone?: (text: string) => void;
|
|
9
|
-
onError?: (error: string) => void;
|
|
10
|
-
lastText: string;
|
|
11
|
-
}>();
|
|
12
|
-
|
|
13
|
-
constructor(url: string, token: string) {
|
|
14
|
-
this.client = new GatewayClient(url, token);
|
|
15
|
-
this.client.setEventHandler(this.handleEvent.bind(this));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async connect(): Promise<void> {
|
|
19
|
-
await this.client.connect();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
disconnect(): void {
|
|
23
|
-
this.client.disconnect();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
isConnected(): boolean {
|
|
27
|
-
return this.client.isConnected();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async listSessions(): Promise<ChatSession[]> {
|
|
31
|
-
const result = await this.client.request('sessions.list', {});
|
|
32
|
-
return (result?.sessions ?? []).map((s: any) => ({
|
|
33
|
-
key: s.key,
|
|
34
|
-
sessionId: s.sessionId ?? '',
|
|
35
|
-
kind: s.kind ?? 'direct',
|
|
36
|
-
updatedAt: s.updatedAt ?? 0,
|
|
37
|
-
model: s.model,
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async getHistory(sessionKey: string, limit = 200): Promise<ChatMessage[]> {
|
|
42
|
-
const result = await this.client.request('chat.history', { sessionKey, limit });
|
|
43
|
-
return (result?.messages ?? [])
|
|
44
|
-
.filter((m: any) => m.role === 'user' || m.role === 'assistant')
|
|
45
|
-
.map((m: any) => {
|
|
46
|
-
const content = Array.isArray(m.content)
|
|
47
|
-
? m.content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('')
|
|
48
|
-
: String(m.content ?? '');
|
|
49
|
-
return {
|
|
50
|
-
role: m.role,
|
|
51
|
-
content,
|
|
52
|
-
timestamp: typeof m.timestamp === 'number'
|
|
53
|
-
? new Date(m.timestamp).toISOString()
|
|
54
|
-
: m.timestamp ?? new Date().toISOString(),
|
|
55
|
-
};
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async sendMessage(params: {
|
|
60
|
-
sessionKey: string;
|
|
61
|
-
text: string;
|
|
62
|
-
idempotencyKey: string;
|
|
63
|
-
attachments?: ChatAttachment[];
|
|
64
|
-
onDelta?: (text: string) => void;
|
|
65
|
-
onDone?: (text: string) => void;
|
|
66
|
-
onError?: (error: string) => void;
|
|
67
|
-
}): Promise<void> {
|
|
68
|
-
// Register stream callbacks BEFORE sending
|
|
69
|
-
this.streamCallbacks.set(params.idempotencyKey, {
|
|
70
|
-
onDelta: params.onDelta,
|
|
71
|
-
onDone: params.onDone,
|
|
72
|
-
onError: params.onError,
|
|
73
|
-
lastText: '',
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const payload: any = {
|
|
77
|
-
sessionKey: params.sessionKey,
|
|
78
|
-
message: params.text,
|
|
79
|
-
deliver: false,
|
|
80
|
-
idempotencyKey: params.idempotencyKey,
|
|
81
|
-
};
|
|
82
|
-
if (params.attachments?.length) {
|
|
83
|
-
payload.attachments = params.attachments;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
await this.client.request('chat.send', payload);
|
|
88
|
-
// Request accepted — response comes via events
|
|
89
|
-
} catch (err) {
|
|
90
|
-
this.streamCallbacks.delete(params.idempotencyKey);
|
|
91
|
-
params.onError?.(err instanceof Error ? err.message : String(err));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async abort(sessionKey: string, runId?: string): Promise<void> {
|
|
96
|
-
await this.client.request('chat.abort', { sessionKey, runId }).catch(() => {});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private handleEvent(event: { event: string; payload: any }): void {
|
|
100
|
-
const data = event.payload;
|
|
101
|
-
if (!data || !data.sessionKey) return;
|
|
102
|
-
|
|
103
|
-
// Stream events have runId matching idempotencyKey
|
|
104
|
-
const runId = data.runId;
|
|
105
|
-
const cb = runId ? this.streamCallbacks.get(runId) : null;
|
|
106
|
-
if (!cb) return;
|
|
107
|
-
|
|
108
|
-
if (data.state === 'delta' && data.message != null) {
|
|
109
|
-
// message is accumulated text
|
|
110
|
-
const text = typeof data.message === 'string'
|
|
111
|
-
? data.message
|
|
112
|
-
: Array.isArray(data.message)
|
|
113
|
-
? data.message.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('')
|
|
114
|
-
: '';
|
|
115
|
-
cb.lastText = text;
|
|
116
|
-
cb.onDelta?.(text);
|
|
117
|
-
} else if (data.state === 'final') {
|
|
118
|
-
this.streamCallbacks.delete(runId!);
|
|
119
|
-
cb.onDone?.(cb.lastText);
|
|
120
|
-
} else if (data.state === 'aborted') {
|
|
121
|
-
this.streamCallbacks.delete(runId!);
|
|
122
|
-
cb.onDone?.(cb.lastText); // still send whatever we have
|
|
123
|
-
} else if (data.state === 'error') {
|
|
124
|
-
this.streamCallbacks.delete(runId!);
|
|
125
|
-
const errMsg = typeof data.error === 'string' ? data.error : data.error?.message ?? 'Unknown error';
|
|
126
|
-
cb.onError?.(errMsg);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import WebSocket from 'ws';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
|
-
|
|
4
|
-
interface PendingRequest {
|
|
5
|
-
resolve: (payload: any) => void;
|
|
6
|
-
reject: (err: Error) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type EventHandler = (event: { event: string; payload: any; seq?: number }) => void;
|
|
10
|
-
|
|
11
|
-
export class GatewayClient {
|
|
12
|
-
private ws: WebSocket | null = null;
|
|
13
|
-
private pending = new Map<string, PendingRequest>();
|
|
14
|
-
private onEvent: EventHandler | null = null;
|
|
15
|
-
private connected = false;
|
|
16
|
-
private token: string;
|
|
17
|
-
private url: string;
|
|
18
|
-
|
|
19
|
-
constructor(url: string, token: string) {
|
|
20
|
-
this.url = url;
|
|
21
|
-
this.token = token;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
setEventHandler(handler: EventHandler): void {
|
|
25
|
-
this.onEvent = handler;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
connect(): Promise<void> {
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
this.ws = new WebSocket(this.url);
|
|
31
|
-
|
|
32
|
-
this.ws.on('open', () => {
|
|
33
|
-
// Wait for connect.challenge event, then authenticate
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
this.ws.on('message', (raw) => {
|
|
37
|
-
try {
|
|
38
|
-
const msg = JSON.parse(raw.toString());
|
|
39
|
-
this.handleMessage(msg);
|
|
40
|
-
} catch {}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
this.ws.on('error', (e) => {
|
|
44
|
-
if (!this.connected) reject(e);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
this.ws.on('close', () => {
|
|
48
|
-
this.connected = false;
|
|
49
|
-
this.flushPending(new Error('Connection closed'));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Wait for connect.challenge → authenticate → resolve
|
|
53
|
-
const origHandler = this.onEvent;
|
|
54
|
-
this.onEvent = async (ev) => {
|
|
55
|
-
if (ev.event === 'connect.challenge') {
|
|
56
|
-
try {
|
|
57
|
-
await this.request('connect', {
|
|
58
|
-
auth: { token: this.token },
|
|
59
|
-
scopes: ['operator.admin'],
|
|
60
|
-
caps: [],
|
|
61
|
-
});
|
|
62
|
-
this.connected = true;
|
|
63
|
-
this.onEvent = origHandler;
|
|
64
|
-
resolve();
|
|
65
|
-
} catch (e) {
|
|
66
|
-
reject(e);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
if (!this.connected) reject(new Error('Connect timeout'));
|
|
73
|
-
}, 10_000);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
disconnect(): void {
|
|
78
|
-
this.ws?.close();
|
|
79
|
-
this.ws = null;
|
|
80
|
-
this.connected = false;
|
|
81
|
-
this.flushPending(new Error('Disconnected'));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
isConnected(): boolean {
|
|
85
|
-
return this.connected && this.ws?.readyState === WebSocket.OPEN;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
request(method: string, params: any): Promise<any> {
|
|
89
|
-
return new Promise((resolve, reject) => {
|
|
90
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
91
|
-
return reject(new Error('Not connected'));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const id = randomUUID();
|
|
95
|
-
this.pending.set(id, { resolve, reject });
|
|
96
|
-
this.ws.send(JSON.stringify({ type: 'req', id, method, params }));
|
|
97
|
-
|
|
98
|
-
// Timeout per request
|
|
99
|
-
setTimeout(() => {
|
|
100
|
-
if (this.pending.has(id)) {
|
|
101
|
-
this.pending.delete(id);
|
|
102
|
-
reject(new Error(`Request timeout: ${method}`));
|
|
103
|
-
}
|
|
104
|
-
}, 120_000);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private handleMessage(msg: any): void {
|
|
109
|
-
if (msg.type === 'event') {
|
|
110
|
-
this.onEvent?.(msg);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (msg.type === 'res') {
|
|
114
|
-
const req = this.pending.get(msg.id);
|
|
115
|
-
if (!req) return;
|
|
116
|
-
this.pending.delete(msg.id);
|
|
117
|
-
if (msg.ok) {
|
|
118
|
-
req.resolve(msg.payload);
|
|
119
|
-
} else {
|
|
120
|
-
req.reject(new Error(msg.error?.message ?? 'Request failed'));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private flushPending(err: Error): void {
|
|
126
|
-
for (const [, req] of this.pending) {
|
|
127
|
-
req.reject(err);
|
|
128
|
-
}
|
|
129
|
-
this.pending.clear();
|
|
130
|
-
}
|
|
131
|
-
}
|
package/src/openclaw/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export { OpenclawGatewayAdapter } from './gateway-adapter.js';
|
|
2
|
-
export type { IChatProvider, ChatSession, ChatMessage, ChatAttachment } from './types.js';
|
|
3
|
-
|
|
4
|
-
import { OpenclawGatewayAdapter } from './gateway-adapter.js';
|
|
5
|
-
import type { IChatProvider } from './types.js';
|
|
6
|
-
|
|
7
|
-
let _provider: IChatProvider | null = null;
|
|
8
|
-
|
|
9
|
-
export function createChatProvider(url: string, token: string): IChatProvider & { connect(): Promise<void>; disconnect(): void } {
|
|
10
|
-
const adapter = new OpenclawGatewayAdapter(url, token);
|
|
11
|
-
_provider = adapter;
|
|
12
|
-
return adapter;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getChatProvider(): IChatProvider | null {
|
|
16
|
-
return _provider;
|
|
17
|
-
}
|
package/src/openclaw/types.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface ChatSession {
|
|
2
|
-
key: string;
|
|
3
|
-
sessionId: string;
|
|
4
|
-
kind: string;
|
|
5
|
-
updatedAt: number;
|
|
6
|
-
model?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ChatMessage {
|
|
10
|
-
role: 'user' | 'assistant';
|
|
11
|
-
content: string; // normalized to plain text
|
|
12
|
-
timestamp: string; // ISO 8601
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ChatAttachment {
|
|
16
|
-
type: 'image';
|
|
17
|
-
mimeType: string;
|
|
18
|
-
content: string; // base64
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface IChatProvider {
|
|
22
|
-
listSessions(): Promise<ChatSession[]>;
|
|
23
|
-
getHistory(sessionKey: string, limit?: number): Promise<ChatMessage[]>;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Send message. Returns after the agent finishes (full response).
|
|
27
|
-
* Calls onDelta with accumulated text during streaming.
|
|
28
|
-
* Calls onDone when agent response is complete.
|
|
29
|
-
*/
|
|
30
|
-
sendMessage(params: {
|
|
31
|
-
sessionKey: string;
|
|
32
|
-
text: string;
|
|
33
|
-
idempotencyKey: string;
|
|
34
|
-
attachments?: ChatAttachment[];
|
|
35
|
-
onDelta?: (accumulatedText: string) => void;
|
|
36
|
-
onDone?: (finalText: string) => void;
|
|
37
|
-
onError?: (error: string) => void;
|
|
38
|
-
}): Promise<void>;
|
|
39
|
-
|
|
40
|
-
abort(sessionKey: string, runId?: string): Promise<void>;
|
|
41
|
-
}
|
package/src/platform/linux.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
|
|
6
|
-
export interface PlatformConfig {
|
|
7
|
-
centrifugoUrl: string;
|
|
8
|
-
agentToken: string;
|
|
9
|
-
agentId: string;
|
|
10
|
-
backendInternalUrl: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function execAsync(cmd: string): Promise<string> {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
exec(cmd, (err, stdout) => {
|
|
16
|
-
if (err) reject(err);
|
|
17
|
-
else resolve(stdout.toString().trim());
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function which(bin: string): Promise<string> {
|
|
23
|
-
try {
|
|
24
|
-
return await execAsync(`which "${bin}"`);
|
|
25
|
-
} catch {
|
|
26
|
-
return '';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function installLinux(config: PlatformConfig): Promise<void> {
|
|
31
|
-
const home = homedir();
|
|
32
|
-
const systemdDir = join(home, '.config', 'systemd', 'user');
|
|
33
|
-
const servicePath = join(systemdDir, 'agent-controller.service');
|
|
34
|
-
|
|
35
|
-
const nodePath = process.execPath;
|
|
36
|
-
let agentControllerPath = await which('agent-controller');
|
|
37
|
-
if (!agentControllerPath) {
|
|
38
|
-
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
mkdirSync(systemdDir, { recursive: true });
|
|
42
|
-
|
|
43
|
-
const service = `[Unit]
|
|
44
|
-
Description=OpenClaw Agent Controller
|
|
45
|
-
After=network.target
|
|
46
|
-
|
|
47
|
-
[Service]
|
|
48
|
-
Type=simple
|
|
49
|
-
Restart=always
|
|
50
|
-
RestartSec=10
|
|
51
|
-
Environment=CENTRIFUGO_URL=${config.centrifugoUrl}
|
|
52
|
-
Environment=AGENT_TOKEN=${config.agentToken}
|
|
53
|
-
Environment=AGENT_ID=${config.agentId}
|
|
54
|
-
Environment=BACKEND_INTERNAL_URL=${config.backendInternalUrl}
|
|
55
|
-
ExecStart="${nodePath}" "${agentControllerPath}"
|
|
56
|
-
|
|
57
|
-
[Install]
|
|
58
|
-
WantedBy=default.target
|
|
59
|
-
`;
|
|
60
|
-
|
|
61
|
-
writeFileSync(servicePath, service, 'utf8');
|
|
62
|
-
console.log(`Written: ${servicePath}`);
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
await execAsync('systemctl --user daemon-reload');
|
|
66
|
-
console.log('systemd daemon reloaded.');
|
|
67
|
-
await execAsync('systemctl --user enable --now agent-controller');
|
|
68
|
-
console.log('Service enabled and started.');
|
|
69
|
-
} catch (err) {
|
|
70
|
-
console.warn('systemctl command failed:', err instanceof Error ? err.message : err);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log('');
|
|
74
|
-
console.log('Agent Controller installed as a systemd user service.');
|
|
75
|
-
console.log('');
|
|
76
|
-
console.log('Useful commands:');
|
|
77
|
-
console.log(' Start: systemctl --user start agent-controller');
|
|
78
|
-
console.log(' Stop: systemctl --user stop agent-controller');
|
|
79
|
-
console.log(' Status: systemctl --user status agent-controller');
|
|
80
|
-
console.log(' Logs: journalctl --user -u agent-controller -f');
|
|
81
|
-
console.log(' Uninstall: agent-controller uninstall');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function uninstallLinux(): Promise<void> {
|
|
85
|
-
try {
|
|
86
|
-
await execAsync('systemctl --user disable --now agent-controller');
|
|
87
|
-
console.log('Service disabled and stopped.');
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.warn('systemctl disable failed (may not be enabled):', err instanceof Error ? err.message : err);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const servicePath = join(homedir(), '.config', 'systemd', 'user', 'agent-controller.service');
|
|
93
|
-
const { unlinkSync, existsSync } = await import('node:fs');
|
|
94
|
-
if (existsSync(servicePath)) {
|
|
95
|
-
unlinkSync(servicePath);
|
|
96
|
-
console.log(`Removed: ${servicePath}`);
|
|
97
|
-
} else {
|
|
98
|
-
console.log(`Service file not found: ${servicePath}`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
await execAsync('systemctl --user daemon-reload');
|
|
103
|
-
} catch {
|
|
104
|
-
// best-effort
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
console.log('Agent Controller uninstalled.');
|
|
108
|
-
}
|
package/src/platform/macos.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
|
|
6
|
-
export interface PlatformConfig {
|
|
7
|
-
centrifugoUrl: string;
|
|
8
|
-
agentToken: string;
|
|
9
|
-
agentId: string;
|
|
10
|
-
backendInternalUrl: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function execAsync(cmd: string): Promise<string> {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
exec(cmd, (err, stdout) => {
|
|
16
|
-
if (err) reject(err);
|
|
17
|
-
else resolve(stdout.toString().trim());
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function which(bin: string): Promise<string> {
|
|
23
|
-
try {
|
|
24
|
-
return await execAsync(`which "${bin}"`);
|
|
25
|
-
} catch {
|
|
26
|
-
return '';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function installMacOS(config: PlatformConfig): Promise<void> {
|
|
31
|
-
const home = homedir();
|
|
32
|
-
const launchAgentsDir = join(home, 'Library', 'LaunchAgents');
|
|
33
|
-
const plistPath = join(launchAgentsDir, 'com.openclaw.agent-controller.plist');
|
|
34
|
-
const logDir = join(home, 'Library', 'Logs');
|
|
35
|
-
const logFile = join(logDir, 'agent-controller.log');
|
|
36
|
-
const errFile = join(logDir, 'agent-controller.err');
|
|
37
|
-
|
|
38
|
-
const nodePath = process.execPath;
|
|
39
|
-
let agentControllerPath = await which('agent-controller');
|
|
40
|
-
if (!agentControllerPath) {
|
|
41
|
-
// Fallback: derive from node path
|
|
42
|
-
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
mkdirSync(launchAgentsDir, { recursive: true });
|
|
46
|
-
|
|
47
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
49
|
-
<plist version="1.0">
|
|
50
|
-
<dict>
|
|
51
|
-
<key>Label</key>
|
|
52
|
-
<string>com.openclaw.agent-controller</string>
|
|
53
|
-
<key>ProgramArguments</key>
|
|
54
|
-
<array>
|
|
55
|
-
<string>${nodePath}</string>
|
|
56
|
-
<string>${agentControllerPath}</string>
|
|
57
|
-
</array>
|
|
58
|
-
<key>EnvironmentVariables</key>
|
|
59
|
-
<dict>
|
|
60
|
-
<key>CENTRIFUGO_URL</key>
|
|
61
|
-
<string>${config.centrifugoUrl}</string>
|
|
62
|
-
<key>AGENT_TOKEN</key>
|
|
63
|
-
<string>${config.agentToken}</string>
|
|
64
|
-
<key>AGENT_ID</key>
|
|
65
|
-
<string>${config.agentId}</string>
|
|
66
|
-
<key>BACKEND_INTERNAL_URL</key>
|
|
67
|
-
<string>${config.backendInternalUrl}</string>
|
|
68
|
-
</dict>
|
|
69
|
-
<key>RunAtLoad</key>
|
|
70
|
-
<true/>
|
|
71
|
-
<key>KeepAlive</key>
|
|
72
|
-
<true/>
|
|
73
|
-
<key>StandardOutPath</key>
|
|
74
|
-
<string>${logFile}</string>
|
|
75
|
-
<key>StandardErrorPath</key>
|
|
76
|
-
<string>${errFile}</string>
|
|
77
|
-
</dict>
|
|
78
|
-
</plist>
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
writeFileSync(plistPath, plist, 'utf8');
|
|
82
|
-
console.log(`Written: ${plistPath}`);
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
await execAsync(`launchctl load -w "${plistPath}"`);
|
|
86
|
-
console.log('Service loaded via launchctl.');
|
|
87
|
-
} catch (err) {
|
|
88
|
-
console.warn('launchctl load failed (may already be loaded):', err instanceof Error ? err.message : err);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
console.log('');
|
|
92
|
-
console.log('Agent Controller installed as a macOS LaunchAgent.');
|
|
93
|
-
console.log('');
|
|
94
|
-
console.log('Useful commands:');
|
|
95
|
-
console.log(` Start: launchctl load -w "${plistPath}"`);
|
|
96
|
-
console.log(` Stop: launchctl unload "${plistPath}"`);
|
|
97
|
-
console.log(` Logs: tail -f "${logFile}"`);
|
|
98
|
-
console.log(` Errors: tail -f "${errFile}"`);
|
|
99
|
-
console.log(` Uninstall: agent-controller uninstall`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export async function uninstallMacOS(): Promise<void> {
|
|
103
|
-
const home = homedir();
|
|
104
|
-
const plistPath = join(home, 'Library', 'LaunchAgents', 'com.openclaw.agent-controller.plist');
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
await execAsync(`launchctl unload "${plistPath}"`);
|
|
108
|
-
console.log('Service unloaded via launchctl.');
|
|
109
|
-
} catch (err) {
|
|
110
|
-
console.warn('launchctl unload failed (may not be loaded):', err instanceof Error ? err.message : err);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const { unlinkSync, existsSync } = await import('node:fs');
|
|
114
|
-
if (existsSync(plistPath)) {
|
|
115
|
-
unlinkSync(plistPath);
|
|
116
|
-
console.log(`Removed: ${plistPath}`);
|
|
117
|
-
} else {
|
|
118
|
-
console.log(`Plist not found: ${plistPath}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log('Agent Controller uninstalled.');
|
|
122
|
-
}
|