@openclaw-cloud/agent-controller 0.1.5 → 0.1.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/BIZPLAN.md +530 -0
- package/CLAUDE.md +64 -0
- package/__tests__/connection.test.ts +171 -14
- package/__tests__/file-delete.test.ts +90 -0
- package/dist/api.js +7 -0
- package/dist/api.js.map +1 -1
- package/dist/connection.d.ts +1 -1
- package/dist/connection.js +32 -29
- package/dist/connection.js.map +1 -1
- package/dist/debug.d.ts +3 -0
- package/dist/debug.js +12 -0
- package/dist/debug.js.map +1 -0
- package/dist/handlers/file-delete.d.ts +2 -0
- package/dist/handlers/file-delete.js +41 -0
- package/dist/handlers/file-delete.js.map +1 -0
- package/dist/heartbeat.js +6 -0
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/api.ts +8 -0
- package/src/connection.ts +32 -28
- package/src/debug.ts +11 -0
- package/src/handlers/file-delete.ts +46 -0
- package/src/heartbeat.ts +5 -0
- package/src/index.ts +5 -5
- package/src/types.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { startHeartbeat } from './heartbeat.js';
|
|
|
3
3
|
import { createAgentApi } from './api.js';
|
|
4
4
|
import { BoardHandler } from './handlers/board-handler.js';
|
|
5
5
|
import { createChatProvider } from './openclaw/index.js';
|
|
6
|
+
import { DEBUG } from './debug.js';
|
|
6
7
|
function requireEnv(name) {
|
|
7
8
|
const value = process.env[name];
|
|
8
9
|
if (!value) {
|
|
@@ -17,14 +18,14 @@ export function main() {
|
|
|
17
18
|
const url = rawUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://') + '/connection/websocket';
|
|
18
19
|
const token = requireEnv('AGENT_TOKEN');
|
|
19
20
|
const agentId = requireEnv('AGENT_ID');
|
|
20
|
-
const backendUrl =
|
|
21
|
+
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
21
22
|
const centrifugoInternalUrl = process.env.CENTRIFUGO_INTERNAL_URL || '';
|
|
22
23
|
const centrifugoApiKey = process.env.CENTRIFUGO_API_KEY || '';
|
|
24
|
+
if (DEBUG)
|
|
25
|
+
console.log('[debug] Debug mode enabled');
|
|
23
26
|
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
const { client } = createConnection({ url, token, agentId, backendUrl: backendUrl || undefined });
|
|
27
|
+
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
28
|
+
const { client } = createConnection({ url, token, agentId, backendUrl });
|
|
28
29
|
// Chat provider via OpenClaw Gateway WS
|
|
29
30
|
const gatewayWsUrl = process.env.OPENCLAW_GATEWAY_WS || 'ws://localhost:18789';
|
|
30
31
|
const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN || '';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
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;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,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,UAAU,CAAC,sBAAsB,CAAC,CAAC;IACtD,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,EAAE,CAAC;IACxE,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAE9D,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,gBAAgB,UAAU,aAAa,CAAC,CAAC;IAErD,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IAEzE,wCAAwC;IACxC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,sBAAsB,CAAC;IAC/E,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,EAAE,CAAC;IAE9D,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,YAAY,GAAG,kBAAkB,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QACpE,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;YAC/B,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YACb,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;IACzE,CAAC;IAED,kEAAkE;IAClE,IAAI,YAAY,GAAwB,IAAI,CAAC;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,UAAU,CAAC;IACzD,IAAI,UAAU,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9C,YAAY,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;QAErC,2DAA2D;QAC3D,YAAY,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;YAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;gBACpE,OAAO;YACT,CAAC;YAED,MAAM,YAAY,GAAG,SAAS,WAAW,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;YAEtD,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE;gBACjC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAkB,CAAC;gBACrC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK;oBAAE,OAAO;gBACnC,YAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBAC9C,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBACxF,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,QAAQ,CAAC,SAAS,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,gCAAgC,YAAY,EAAE,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACvF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,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;YAC9B,qBAAqB;YACrB,gBAAgB;YAChB,OAAO;YACP,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,YAAa,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClF,CAAC,CAAC;IACL,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
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* agent:<agentId> — commands from backend, responses from agent
|
|
4
4
|
* heartbeat:<agentId> — heartbeat publications from agent
|
|
5
5
|
*/
|
|
6
|
-
export type CommandType = 'exec' | 'restart' | 'deploy' | 'config' | 'pair' | 'stop' | 'backup' | 'chat_list_sessions' | 'chat_history' | 'chat_send' | 'package_install' | 'file_write' | 'onboarding_complete';
|
|
6
|
+
export type CommandType = 'exec' | 'restart' | 'deploy' | 'config' | 'pair' | 'stop' | 'backup' | 'chat_list_sessions' | 'chat_history' | 'chat_send' | 'package_install' | 'file_write' | 'file_delete' | 'onboarding_complete';
|
|
7
7
|
export interface AgentCommand {
|
|
8
8
|
id: string;
|
|
9
9
|
type: CommandType;
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DEBUG, debugLog } from './debug.js';
|
|
2
|
+
|
|
1
3
|
const TIMEOUT = 10_000;
|
|
2
4
|
|
|
3
5
|
export interface AgentApi {
|
|
@@ -10,6 +12,7 @@ export function createAgentApi(backendUrl: string, agentToken: string): AgentApi
|
|
|
10
12
|
const controller = new AbortController();
|
|
11
13
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT);
|
|
12
14
|
try {
|
|
15
|
+
debugLog('api', '→', method, path, body ? JSON.stringify(body).slice(0, 500) : '');
|
|
13
16
|
const res = await fetch(`${backendUrl}${path}`, {
|
|
14
17
|
method,
|
|
15
18
|
headers: {
|
|
@@ -19,6 +22,11 @@ export function createAgentApi(backendUrl: string, agentToken: string): AgentApi
|
|
|
19
22
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
20
23
|
signal: controller.signal,
|
|
21
24
|
});
|
|
25
|
+
if (DEBUG) {
|
|
26
|
+
const clone = res.clone();
|
|
27
|
+
const bodyText = await clone.text().catch(() => '');
|
|
28
|
+
debugLog('api', '←', method, path, res.status, bodyText.slice(0, 1000));
|
|
29
|
+
}
|
|
22
30
|
return res;
|
|
23
31
|
} finally {
|
|
24
32
|
clearTimeout(timeout);
|
package/src/connection.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debugLog } from './debug.js';
|
|
1
2
|
import { Centrifuge, Subscription } from 'centrifuge';
|
|
2
3
|
import WebSocket from 'ws';
|
|
3
4
|
import type { AgentCommand, AgentResponse } from './types.js';
|
|
@@ -11,13 +12,14 @@ import { handleBackup } from './handlers/backup.js';
|
|
|
11
12
|
import { handleChatListSessions, handleChatHistory, handleChatSend } from './handlers/chat.js';
|
|
12
13
|
import { handlePackageInstall } from './handlers/package-install.js';
|
|
13
14
|
import { handleFileWrite } from './handlers/file-write.js';
|
|
15
|
+
import { handleFileDelete } from './handlers/file-delete.js';
|
|
14
16
|
import { handleOnboardingComplete } from './handlers/onboarding.js';
|
|
15
17
|
|
|
16
18
|
export interface ConnectionOptions {
|
|
17
19
|
url: string;
|
|
18
20
|
token: string;
|
|
19
21
|
agentId: string;
|
|
20
|
-
backendUrl
|
|
22
|
+
backendUrl: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const handlers: Record<string, (cmd: AgentCommand) => Promise<AgentResponse>> = {
|
|
@@ -30,6 +32,7 @@ const handlers: Record<string, (cmd: AgentCommand) => Promise<AgentResponse>> =
|
|
|
30
32
|
backup: handleBackup,
|
|
31
33
|
package_install: handlePackageInstall,
|
|
32
34
|
file_write: handleFileWrite,
|
|
35
|
+
file_delete: handleFileDelete,
|
|
33
36
|
onboarding_complete: handleOnboardingComplete,
|
|
34
37
|
};
|
|
35
38
|
|
|
@@ -60,40 +63,35 @@ export function createConnection(opts: ConnectionOptions): {
|
|
|
60
63
|
client: Centrifuge;
|
|
61
64
|
commandSub: Subscription;
|
|
62
65
|
} {
|
|
66
|
+
// Dynamic JWT: fetch fresh token on every connect/reconnect
|
|
67
|
+
const getToken = async (): Promise<string> => {
|
|
68
|
+
console.log('Fetching Centrifugo JWT from backend...');
|
|
69
|
+
try {
|
|
70
|
+
const token = await fetchCentrifugoToken(opts.backendUrl, opts.token, opts.agentId);
|
|
71
|
+
console.log('Got Centrifugo JWT');
|
|
72
|
+
return token;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('Failed to fetch Centrifugo JWT:', err instanceof Error ? err.message : err);
|
|
75
|
+
// Retry once before giving up — centrifuge will handle reconnect backoff
|
|
76
|
+
try {
|
|
77
|
+
const token = await fetchCentrifugoToken(opts.backendUrl, opts.token, opts.agentId);
|
|
78
|
+
console.log('Got Centrifugo JWT (retry)');
|
|
79
|
+
return token;
|
|
80
|
+
} catch (retryErr) {
|
|
81
|
+
console.error('JWT fetch retry failed:', retryErr instanceof Error ? retryErr.message : retryErr);
|
|
82
|
+
throw retryErr;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
63
87
|
const centrifugeOpts: Record<string, unknown> = {
|
|
64
88
|
websocket: WebSocket,
|
|
65
89
|
name: 'agent-controller',
|
|
90
|
+
getToken,
|
|
66
91
|
minReconnectDelay: 1000,
|
|
67
92
|
maxReconnectDelay: 30000,
|
|
68
93
|
};
|
|
69
94
|
|
|
70
|
-
if (opts.backendUrl) {
|
|
71
|
-
// Dynamic JWT: fetch fresh token on every connect/reconnect
|
|
72
|
-
centrifugeOpts.getToken = async () => {
|
|
73
|
-
console.log('Fetching Centrifugo JWT from backend...');
|
|
74
|
-
try {
|
|
75
|
-
const token = await fetchCentrifugoToken(opts.backendUrl!, opts.token, opts.agentId);
|
|
76
|
-
console.log('Got Centrifugo JWT');
|
|
77
|
-
return token;
|
|
78
|
-
} catch (err) {
|
|
79
|
-
console.error('Failed to fetch Centrifugo JWT:', err instanceof Error ? err.message : err);
|
|
80
|
-
// Retry once
|
|
81
|
-
try {
|
|
82
|
-
const token = await fetchCentrifugoToken(opts.backendUrl!, opts.token, opts.agentId);
|
|
83
|
-
console.log('Got Centrifugo JWT (retry)');
|
|
84
|
-
return token;
|
|
85
|
-
} catch (retryErr) {
|
|
86
|
-
console.error('JWT fetch retry failed:', retryErr instanceof Error ? retryErr.message : retryErr);
|
|
87
|
-
throw retryErr;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
} else {
|
|
92
|
-
// Fallback: use raw token (backward compat, won't work with Centrifugo JWT validation)
|
|
93
|
-
console.warn('BACKEND_INTERNAL_URL not set, using AGENT_TOKEN directly (may not work)');
|
|
94
|
-
centrifugeOpts.token = opts.token;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
95
|
const client = new Centrifuge(opts.url, centrifugeOpts);
|
|
98
96
|
|
|
99
97
|
const commandChannel = `agent:${opts.agentId}`;
|
|
@@ -101,6 +99,7 @@ export function createConnection(opts: ConnectionOptions): {
|
|
|
101
99
|
|
|
102
100
|
commandSub.on('publication', async (ctx) => {
|
|
103
101
|
console.log(`[WS] Received message on ${commandChannel}:`, JSON.stringify(ctx.data));
|
|
102
|
+
debugLog('connection', 'publication', commandChannel, JSON.stringify(ctx.data).slice(0, 500));
|
|
104
103
|
const command = ctx.data as AgentCommand;
|
|
105
104
|
if (!command || !command.type) {
|
|
106
105
|
console.warn('[WS] Ignoring message without type:', JSON.stringify(ctx.data));
|
|
@@ -148,22 +147,27 @@ export function createConnection(opts: ConnectionOptions): {
|
|
|
148
147
|
|
|
149
148
|
client.on('connected', (ctx) => {
|
|
150
149
|
console.log(`Connected to Centrifugo via ${ctx.transport}`);
|
|
150
|
+
debugLog('connection', 'connected', ctx);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
client.on('disconnected', (ctx) => {
|
|
154
154
|
console.log(`Disconnected: ${ctx.reason} (code ${ctx.code})`);
|
|
155
|
+
debugLog('connection', 'disconnected', ctx);
|
|
155
156
|
});
|
|
156
157
|
|
|
157
158
|
client.on('connecting', (ctx) => {
|
|
158
159
|
console.log(`Connecting: ${ctx.reason} (code ${ctx.code})`);
|
|
160
|
+
debugLog('connection', 'connecting', ctx);
|
|
159
161
|
});
|
|
160
162
|
|
|
161
163
|
commandSub.on('subscribed', (ctx) => {
|
|
162
164
|
console.log(`[WS] Subscribed to ${commandChannel} (recovered=${ctx.wasRecovering})`);
|
|
165
|
+
debugLog('connection', 'subscribed', commandChannel, ctx);
|
|
163
166
|
});
|
|
164
167
|
|
|
165
168
|
commandSub.on('subscribing', (ctx) => {
|
|
166
169
|
console.log(`[WS] Subscribing to ${commandChannel}: ${ctx.reason} (code ${ctx.code})`);
|
|
170
|
+
debugLog('connection', 'subscribing', commandChannel, ctx);
|
|
167
171
|
});
|
|
168
172
|
|
|
169
173
|
commandSub.on('error', (ctx) => {
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DEBUG = !!(process.env.AC_DEBUG || process.env.DEBUG);
|
|
2
|
+
|
|
3
|
+
export function debugLog(prefix: string, ...args: unknown[]): void {
|
|
4
|
+
if (!DEBUG) return;
|
|
5
|
+
console.log(`[debug][${prefix}]`, ...args);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function maskToken(token: string): string {
|
|
9
|
+
if (!token) return '';
|
|
10
|
+
return token.slice(0, 10) + '...';
|
|
11
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
}
|
package/src/heartbeat.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { debugLog } from './debug.js';
|
|
2
3
|
import type { BoardState, HeartbeatPayload } from './types.js';
|
|
3
4
|
|
|
4
5
|
const HEARTBEAT_INTERVAL = 30_000;
|
|
@@ -41,6 +42,7 @@ async function publishHeartbeat(opts: HeartbeatOptions): Promise<void> {
|
|
|
41
42
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
42
43
|
|
|
43
44
|
try {
|
|
45
|
+
debugLog('heartbeat', '→ sending', opts.agentId, JSON.stringify(payload).slice(0, 500));
|
|
44
46
|
const res = await fetch(`${opts.centrifugoInternalUrl}/api/publish`, {
|
|
45
47
|
method: 'POST',
|
|
46
48
|
headers: {
|
|
@@ -54,6 +56,9 @@ async function publishHeartbeat(opts: HeartbeatOptions): Promise<void> {
|
|
|
54
56
|
if (!res.ok) {
|
|
55
57
|
const body = await res.text().catch(() => '');
|
|
56
58
|
console.error(`Heartbeat publish failed (HTTP ${res.status}): ${body}`);
|
|
59
|
+
debugLog('heartbeat', '← error', res.status, body);
|
|
60
|
+
} else {
|
|
61
|
+
debugLog('heartbeat', '← ok', res.status);
|
|
57
62
|
}
|
|
58
63
|
} catch (err) {
|
|
59
64
|
if ((err as Error).name === 'AbortError') {
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { startHeartbeat } from './heartbeat.js';
|
|
|
3
3
|
import { createAgentApi } from './api.js';
|
|
4
4
|
import { BoardHandler } from './handlers/board-handler.js';
|
|
5
5
|
import { createChatProvider } from './openclaw/index.js';
|
|
6
|
+
import { DEBUG } from './debug.js';
|
|
6
7
|
import type { BoardEvent } from './types.js';
|
|
7
8
|
|
|
8
9
|
function requireEnv(name: string): string {
|
|
@@ -20,16 +21,15 @@ export function main(): void {
|
|
|
20
21
|
const url = rawUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://') + '/connection/websocket';
|
|
21
22
|
const token = requireEnv('AGENT_TOKEN');
|
|
22
23
|
const agentId = requireEnv('AGENT_ID');
|
|
23
|
-
const backendUrl =
|
|
24
|
+
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
24
25
|
const centrifugoInternalUrl = process.env.CENTRIFUGO_INTERNAL_URL || '';
|
|
25
26
|
const centrifugoApiKey = process.env.CENTRIFUGO_API_KEY || '';
|
|
26
27
|
|
|
28
|
+
if (DEBUG) console.log('[debug] Debug mode enabled');
|
|
27
29
|
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
28
|
-
|
|
29
|
-
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
30
|
-
}
|
|
30
|
+
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
31
31
|
|
|
32
|
-
const { client } = createConnection({ url, token, agentId, backendUrl
|
|
32
|
+
const { client } = createConnection({ url, token, agentId, backendUrl });
|
|
33
33
|
|
|
34
34
|
// Chat provider via OpenClaw Gateway WS
|
|
35
35
|
const gatewayWsUrl = process.env.OPENCLAW_GATEWAY_WS || 'ws://localhost:18789';
|
package/src/types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export type CommandType =
|
|
8
8
|
| 'exec' | 'restart' | 'deploy' | 'config' | 'pair' | 'stop' | 'backup'
|
|
9
9
|
| 'chat_list_sessions' | 'chat_history' | 'chat_send'
|
|
10
|
-
| 'package_install' | 'file_write' | 'onboarding_complete';
|
|
10
|
+
| 'package_install' | 'file_write' | 'file_delete' | 'onboarding_complete';
|
|
11
11
|
|
|
12
12
|
export interface AgentCommand {
|
|
13
13
|
id: string;
|