@openclaw-cloud/agent-controller 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/api.test.ts +60 -0
- package/__tests__/board-handler.test.ts +2 -0
- package/__tests__/connection.test.ts +25 -4
- package/__tests__/heartbeat.test.ts +22 -29
- package/dist/api.d.ts +2 -0
- package/dist/api.js +12 -0
- package/dist/api.js.map +1 -1
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +3 -3
- package/dist/connection.js.map +1 -1
- package/dist/heartbeat.d.ts +2 -2
- package/dist/heartbeat.js +4 -29
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.js +31 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +14 -0
- package/src/connection.ts +5 -3
- package/src/heartbeat.ts +6 -30
- package/src/index.ts +32 -41
package/__tests__/api.test.ts
CHANGED
|
@@ -121,3 +121,63 @@ describe('createAgentApi – post()', () => {
|
|
|
121
121
|
await expect(api.post('/endpoint')).rejects.toThrow('timeout');
|
|
122
122
|
});
|
|
123
123
|
});
|
|
124
|
+
|
|
125
|
+
describe('createAgentApi – publishResponse()', () => {
|
|
126
|
+
it('POSTs to /api/internal/agent-response with agentId and data', async () => {
|
|
127
|
+
const api = createAgentApi('http://backend', 'tok');
|
|
128
|
+
mockFetch.mockResolvedValue(makeOkResponse(null));
|
|
129
|
+
|
|
130
|
+
await api.publishResponse('agent-1', { id: 'cmd-1', type: 'exec', success: true });
|
|
131
|
+
|
|
132
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
133
|
+
expect(url).toBe('http://backend/api/internal/agent-response');
|
|
134
|
+
expect(init.method).toBe('POST');
|
|
135
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
136
|
+
agentId: 'agent-1',
|
|
137
|
+
data: { id: 'cmd-1', type: 'exec', success: true },
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('throws when response is not ok', async () => {
|
|
142
|
+
const api = createAgentApi('http://backend', 'tok');
|
|
143
|
+
mockFetch.mockResolvedValue(makeErrorResponse(503));
|
|
144
|
+
|
|
145
|
+
await expect(api.publishResponse('agent-1', {})).rejects.toThrow('HTTP 503');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('propagates network errors', async () => {
|
|
149
|
+
const api = createAgentApi('http://backend', 'tok');
|
|
150
|
+
mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
151
|
+
|
|
152
|
+
await expect(api.publishResponse('agent-1', {})).rejects.toThrow('ECONNREFUSED');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('createAgentApi – publishHeartbeat()', () => {
|
|
157
|
+
it('POSTs to /api/internal/agent-heartbeat with agentId and payload', async () => {
|
|
158
|
+
const api = createAgentApi('http://backend', 'tok');
|
|
159
|
+
mockFetch.mockResolvedValue(makeOkResponse(null));
|
|
160
|
+
|
|
161
|
+
const payload = { type: 'heartbeat', agentId: 'agent-1', ts: 123, metrics: {} };
|
|
162
|
+
await api.publishHeartbeat('agent-1', payload);
|
|
163
|
+
|
|
164
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
165
|
+
expect(url).toBe('http://backend/api/internal/agent-heartbeat');
|
|
166
|
+
expect(init.method).toBe('POST');
|
|
167
|
+
expect(JSON.parse(init.body)).toEqual({ agentId: 'agent-1', payload });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('throws when response is not ok', async () => {
|
|
171
|
+
const api = createAgentApi('http://backend', 'tok');
|
|
172
|
+
mockFetch.mockResolvedValue(makeErrorResponse(500));
|
|
173
|
+
|
|
174
|
+
await expect(api.publishHeartbeat('agent-1', {})).rejects.toThrow('HTTP 500');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('propagates network errors', async () => {
|
|
178
|
+
const api = createAgentApi('http://backend', 'tok');
|
|
179
|
+
mockFetch.mockRejectedValue(new Error('timeout'));
|
|
180
|
+
|
|
181
|
+
await expect(api.publishHeartbeat('agent-1', {})).rejects.toThrow('timeout');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -10,6 +10,8 @@ function mockApi(overrides: Partial<AgentApi> = {}): AgentApi {
|
|
|
10
10
|
return {
|
|
11
11
|
get: jest.fn(),
|
|
12
12
|
post: jest.fn(),
|
|
13
|
+
publishResponse: jest.fn().mockResolvedValue(undefined),
|
|
14
|
+
publishHeartbeat: jest.fn().mockResolvedValue(undefined),
|
|
13
15
|
...overrides,
|
|
14
16
|
};
|
|
15
17
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Centrifuge } from 'centrifuge';
|
|
2
|
+
import type { AgentApi } from '../src/api';
|
|
2
3
|
|
|
3
4
|
jest.mock('centrifuge');
|
|
4
5
|
jest.mock('ws', () => jest.fn());
|
|
@@ -34,6 +35,15 @@ function createMockClient(mockSubs: ReturnType<typeof createMockSubscription>[])
|
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
function makeApi(): AgentApi & { publishResponse: jest.Mock } {
|
|
39
|
+
return {
|
|
40
|
+
get: jest.fn(),
|
|
41
|
+
post: jest.fn(),
|
|
42
|
+
publishResponse: jest.fn().mockResolvedValue(undefined),
|
|
43
|
+
publishHeartbeat: jest.fn().mockResolvedValue(undefined),
|
|
44
|
+
} as unknown as AgentApi & { publishResponse: jest.Mock };
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
function mockFetchOk(token = JWT) {
|
|
38
48
|
global.fetch = jest.fn().mockResolvedValue({
|
|
39
49
|
ok: true,
|
|
@@ -59,6 +69,7 @@ describe('createConnection', () => {
|
|
|
59
69
|
token: AGENT_TOKEN,
|
|
60
70
|
agentId: AGENT_ID,
|
|
61
71
|
backendUrl: BACKEND_URL,
|
|
72
|
+
api: makeApi(),
|
|
62
73
|
});
|
|
63
74
|
|
|
64
75
|
expect(MockCentrifuge).toHaveBeenCalledWith(
|
|
@@ -91,6 +102,7 @@ describe('createConnection', () => {
|
|
|
91
102
|
token: AGENT_TOKEN,
|
|
92
103
|
agentId: AGENT_ID,
|
|
93
104
|
backendUrl: BACKEND_URL,
|
|
105
|
+
api: makeApi(),
|
|
94
106
|
});
|
|
95
107
|
|
|
96
108
|
expect(capturedGetToken).toBeDefined();
|
|
@@ -135,6 +147,7 @@ describe('createConnection', () => {
|
|
|
135
147
|
token: AGENT_TOKEN,
|
|
136
148
|
agentId: AGENT_ID,
|
|
137
149
|
backendUrl: BACKEND_URL,
|
|
150
|
+
api: makeApi(),
|
|
138
151
|
});
|
|
139
152
|
|
|
140
153
|
const result = await capturedGetToken!();
|
|
@@ -167,6 +180,7 @@ describe('createConnection', () => {
|
|
|
167
180
|
token: AGENT_TOKEN,
|
|
168
181
|
agentId: AGENT_ID,
|
|
169
182
|
backendUrl: BACKEND_URL,
|
|
183
|
+
api: makeApi(),
|
|
170
184
|
});
|
|
171
185
|
|
|
172
186
|
await expect(capturedGetToken!()).rejects.toThrow('network down');
|
|
@@ -197,6 +211,7 @@ describe('createConnection', () => {
|
|
|
197
211
|
token: AGENT_TOKEN,
|
|
198
212
|
agentId: AGENT_ID,
|
|
199
213
|
backendUrl: BACKEND_URL,
|
|
214
|
+
api: makeApi(),
|
|
200
215
|
});
|
|
201
216
|
|
|
202
217
|
await expect(capturedGetToken!()).rejects.toThrow('HTTP 401');
|
|
@@ -205,7 +220,7 @@ describe('createConnection', () => {
|
|
|
205
220
|
consoleSpy.mockRestore();
|
|
206
221
|
});
|
|
207
222
|
|
|
208
|
-
it('routes exec command to handler', async () => {
|
|
223
|
+
it('routes exec command to handler and publishes via api.publishResponse', async () => {
|
|
209
224
|
const commandSub = createMockSubscription(`agent:${AGENT_ID}`);
|
|
210
225
|
const mockClient = createMockClient([commandSub]);
|
|
211
226
|
MockCentrifuge.mockImplementation(() => mockClient as any);
|
|
@@ -216,12 +231,14 @@ describe('createConnection', () => {
|
|
|
216
231
|
}),
|
|
217
232
|
}));
|
|
218
233
|
|
|
234
|
+
const api = makeApi();
|
|
219
235
|
const { createConnection } = await import('../src/connection');
|
|
220
236
|
createConnection({
|
|
221
237
|
url: 'ws://localhost:8000/connection/websocket',
|
|
222
238
|
token: AGENT_TOKEN,
|
|
223
239
|
agentId: AGENT_ID,
|
|
224
240
|
backendUrl: BACKEND_URL,
|
|
241
|
+
api,
|
|
225
242
|
});
|
|
226
243
|
|
|
227
244
|
// Simulate a publication on the command channel
|
|
@@ -233,9 +250,10 @@ describe('createConnection', () => {
|
|
|
233
250
|
channel: `agent:${AGENT_ID}`,
|
|
234
251
|
});
|
|
235
252
|
|
|
236
|
-
// Should have published a response
|
|
237
|
-
expect(mockClient.publish).
|
|
238
|
-
|
|
253
|
+
// Should have published a response via api, not client.publish
|
|
254
|
+
expect(mockClient.publish).not.toHaveBeenCalled();
|
|
255
|
+
expect(api.publishResponse).toHaveBeenCalledWith(
|
|
256
|
+
AGENT_ID,
|
|
239
257
|
expect.objectContaining({ id: 'cmd-1', type: 'exec' })
|
|
240
258
|
);
|
|
241
259
|
});
|
|
@@ -247,12 +265,14 @@ describe('createConnection', () => {
|
|
|
247
265
|
|
|
248
266
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
249
267
|
|
|
268
|
+
const api = makeApi();
|
|
250
269
|
const { createConnection } = await import('../src/connection');
|
|
251
270
|
createConnection({
|
|
252
271
|
url: 'ws://localhost:8000/connection/websocket',
|
|
253
272
|
token: AGENT_TOKEN,
|
|
254
273
|
agentId: AGENT_ID,
|
|
255
274
|
backendUrl: BACKEND_URL,
|
|
275
|
+
api,
|
|
256
276
|
});
|
|
257
277
|
|
|
258
278
|
const publicationHandler = commandSub._handlers['publication'];
|
|
@@ -263,6 +283,7 @@ describe('createConnection', () => {
|
|
|
263
283
|
|
|
264
284
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Unknown command type'));
|
|
265
285
|
expect(mockClient.publish).not.toHaveBeenCalled();
|
|
286
|
+
expect(api.publishResponse).not.toHaveBeenCalled();
|
|
266
287
|
consoleSpy.mockRestore();
|
|
267
288
|
});
|
|
268
289
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
|
|
3
3
|
import { getMetrics, startHeartbeat } from '../src/heartbeat';
|
|
4
|
+
import type { AgentApi } from '../src/api';
|
|
4
5
|
|
|
5
6
|
describe('getMetrics', () => {
|
|
6
7
|
it('returns cpu, mem, uptime', () => {
|
|
@@ -18,60 +19,52 @@ describe('getMetrics', () => {
|
|
|
18
19
|
});
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
function makeApi(publishHeartbeat = jest.fn().mockResolvedValue(undefined)): AgentApi {
|
|
23
|
+
return {
|
|
24
|
+
get: jest.fn(),
|
|
25
|
+
post: jest.fn(),
|
|
26
|
+
publishResponse: jest.fn(),
|
|
27
|
+
publishHeartbeat,
|
|
28
|
+
} as unknown as AgentApi;
|
|
29
|
+
}
|
|
23
30
|
|
|
31
|
+
describe('startHeartbeat', () => {
|
|
24
32
|
beforeEach(() => {
|
|
25
33
|
jest.useFakeTimers();
|
|
26
|
-
fetchSpy = jest.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
27
|
-
new Response('{}', { status: 200 }),
|
|
28
|
-
);
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
afterEach(() => {
|
|
32
37
|
jest.useRealTimers();
|
|
33
|
-
fetchSpy.mockRestore();
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
const opts = {
|
|
37
|
-
centrifugoInternalUrl: 'http://centrifugo-internal:9000',
|
|
38
|
-
centrifugoApiKey: 'test-key',
|
|
39
|
-
agentId: 'agent-42',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
40
|
it('publishes heartbeat immediately and at intervals', () => {
|
|
43
|
-
const
|
|
41
|
+
const publishHeartbeat = jest.fn().mockResolvedValue(undefined);
|
|
42
|
+
const api = makeApi(publishHeartbeat);
|
|
43
|
+
|
|
44
|
+
const timer = startHeartbeat({ api, agentId: 'agent-42' });
|
|
44
45
|
|
|
45
46
|
// Immediate call
|
|
46
|
-
expect(
|
|
47
|
-
expect(
|
|
48
|
-
'
|
|
47
|
+
expect(publishHeartbeat).toHaveBeenCalledTimes(1);
|
|
48
|
+
expect(publishHeartbeat).toHaveBeenCalledWith(
|
|
49
|
+
'agent-42',
|
|
49
50
|
expect.objectContaining({
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'Authorization': 'apikey test-key',
|
|
53
|
-
}),
|
|
51
|
+
agentId: 'agent-42',
|
|
52
|
+
type: 'heartbeat',
|
|
54
53
|
}),
|
|
55
54
|
);
|
|
56
55
|
|
|
57
|
-
// Check body contains correct channel and agent data
|
|
58
|
-
const body = JSON.parse(fetchSpy.mock.calls[0][1].body);
|
|
59
|
-
expect(body.channel).toBe('heartbeat:data');
|
|
60
|
-
expect(body.data.agentId).toBe('agent-42');
|
|
61
|
-
expect(body.data.type).toBe('heartbeat');
|
|
62
|
-
|
|
63
56
|
// After 30s
|
|
64
57
|
jest.advanceTimersByTime(30_000);
|
|
65
|
-
expect(
|
|
58
|
+
expect(publishHeartbeat).toHaveBeenCalledTimes(2);
|
|
66
59
|
|
|
67
60
|
clearInterval(timer);
|
|
68
61
|
});
|
|
69
62
|
|
|
70
63
|
it('handles publish errors gracefully', () => {
|
|
71
64
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
72
|
-
|
|
65
|
+
const api = makeApi(jest.fn().mockRejectedValue(new Error('network fail')));
|
|
73
66
|
|
|
74
|
-
const timer = startHeartbeat(
|
|
67
|
+
const timer = startHeartbeat({ api, agentId: 'agent-42' });
|
|
75
68
|
|
|
76
69
|
// Should not throw
|
|
77
70
|
clearInterval(timer);
|
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface AgentApi {
|
|
2
2
|
get(path: string): Promise<unknown>;
|
|
3
3
|
post(path: string, body?: unknown): Promise<Response>;
|
|
4
|
+
publishResponse(agentId: string, data: unknown): Promise<void>;
|
|
5
|
+
publishHeartbeat(agentId: string, payload: unknown): Promise<void>;
|
|
4
6
|
}
|
|
5
7
|
export declare function createAgentApi(backendUrl: string, agentToken: string): AgentApi;
|
package/dist/api.js
CHANGED
|
@@ -37,6 +37,18 @@ export function createAgentApi(backendUrl, agentToken) {
|
|
|
37
37
|
async post(path, body) {
|
|
38
38
|
return request('POST', path, body);
|
|
39
39
|
},
|
|
40
|
+
async publishResponse(agentId, data) {
|
|
41
|
+
const res = await request('POST', '/api/internal/agent-response', { agentId, data });
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`publishResponse failed: HTTP ${res.status}`);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
async publishHeartbeat(agentId, payload) {
|
|
47
|
+
const res = await request('POST', '/api/internal/agent-heartbeat', { agentId, payload });
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
throw new Error(`publishHeartbeat failed: HTTP ${res.status}`);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
40
52
|
};
|
|
41
53
|
}
|
|
42
54
|
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,OAAO,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { AgentApi } from './api.js';
|
|
1
2
|
import { Centrifuge, Subscription } from 'centrifuge';
|
|
2
3
|
export interface ConnectionOptions {
|
|
3
4
|
url: string;
|
|
4
5
|
token: string;
|
|
5
6
|
agentId: string;
|
|
6
7
|
backendUrl: string;
|
|
8
|
+
api: AgentApi;
|
|
7
9
|
}
|
|
8
10
|
export declare function createConnection(opts: ConnectionOptions): {
|
|
9
11
|
client: Centrifuge;
|
package/dist/connection.js
CHANGED
|
@@ -92,7 +92,7 @@ export function createConnection(opts) {
|
|
|
92
92
|
}
|
|
93
93
|
// Chat commands — handle before generic dispatch
|
|
94
94
|
if (command.type === 'chat_list_sessions' || command.type === 'chat_history' || command.type === 'chat_send') {
|
|
95
|
-
const publishFn = async (data) => { await
|
|
95
|
+
const publishFn = async (data) => { await opts.api.publishResponse(opts.agentId, data); };
|
|
96
96
|
console.log(`[WS] Handling chat command: type=${command.type} id=${command.id}`);
|
|
97
97
|
switch (command.type) {
|
|
98
98
|
case 'chat_list_sessions':
|
|
@@ -115,7 +115,7 @@ export function createConnection(opts) {
|
|
|
115
115
|
console.log(`[WS] Handling command: type=${command.type} id=${command.id}`);
|
|
116
116
|
try {
|
|
117
117
|
const response = await handler(command);
|
|
118
|
-
await
|
|
118
|
+
await opts.api.publishResponse(opts.agentId, response);
|
|
119
119
|
}
|
|
120
120
|
catch (err) {
|
|
121
121
|
const errorResponse = {
|
|
@@ -124,7 +124,7 @@ export function createConnection(opts) {
|
|
|
124
124
|
success: false,
|
|
125
125
|
error: err instanceof Error ? err.message : String(err),
|
|
126
126
|
};
|
|
127
|
-
await
|
|
127
|
+
await opts.api.publishResponse(opts.agentId, errorResponse);
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
130
|
client.on('connected', (ctx) => {
|
package/dist/connection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
|
package/dist/heartbeat.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { AgentApi } from './api.js';
|
|
1
2
|
import type { BoardState, HeartbeatPayload } from './types.js';
|
|
2
3
|
export interface HeartbeatOptions {
|
|
3
|
-
|
|
4
|
-
centrifugoApiKey: string;
|
|
4
|
+
api: AgentApi;
|
|
5
5
|
agentId: string;
|
|
6
6
|
getBoardStatus?: () => BoardState;
|
|
7
7
|
}
|
package/dist/heartbeat.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { debugLog } from './debug.js';
|
|
3
3
|
const HEARTBEAT_INTERVAL = 30_000;
|
|
4
|
-
const HEARTBEAT_CHANNEL = 'heartbeat:data';
|
|
5
4
|
function getMetrics() {
|
|
6
5
|
const cpus = os.cpus();
|
|
7
6
|
const cpuUsage = cpus.reduce((acc, cpu) => {
|
|
@@ -24,38 +23,14 @@ async function publishHeartbeat(opts) {
|
|
|
24
23
|
metrics: getMetrics(),
|
|
25
24
|
...(opts.getBoardStatus ? { boardStatus: opts.getBoardStatus() } : {}),
|
|
26
25
|
};
|
|
27
|
-
const controller = new AbortController();
|
|
28
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
29
26
|
try {
|
|
30
27
|
debugLog('heartbeat', '→ sending', opts.agentId, JSON.stringify(payload).slice(0, 500));
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
headers: {
|
|
34
|
-
'Content-Type': 'application/json',
|
|
35
|
-
'Authorization': `apikey ${opts.centrifugoApiKey}`,
|
|
36
|
-
},
|
|
37
|
-
body: JSON.stringify({ channel: HEARTBEAT_CHANNEL, data: payload }),
|
|
38
|
-
signal: controller.signal,
|
|
39
|
-
});
|
|
40
|
-
if (!res.ok) {
|
|
41
|
-
const body = await res.text().catch(() => '');
|
|
42
|
-
console.error(`Heartbeat publish failed (HTTP ${res.status}): ${body}`);
|
|
43
|
-
debugLog('heartbeat', '← error', res.status, body);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
debugLog('heartbeat', '← ok', res.status);
|
|
47
|
-
}
|
|
28
|
+
await opts.api.publishHeartbeat(opts.agentId, payload);
|
|
29
|
+
debugLog('heartbeat', '← ok');
|
|
48
30
|
}
|
|
49
31
|
catch (err) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
console.error('Heartbeat publish failed:', err instanceof Error ? err.message : err);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
clearTimeout(timeout);
|
|
32
|
+
console.error('Heartbeat publish failed:', err instanceof Error ? err.message : err);
|
|
33
|
+
debugLog('heartbeat', '← error', err);
|
|
59
34
|
}
|
|
60
35
|
}
|
|
61
36
|
export function startHeartbeat(opts) {
|
package/dist/heartbeat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAQlC,SAAS,UAAU;IACjB,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAClE,OAAO,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IAC5C,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IAEpB,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAE7B,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,GAAG;QACrC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG;QAC9D,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE;KACpB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,IAAsB;IACpD,MAAM,OAAO,GAAqB;QAChC,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;QACd,OAAO,EAAE,UAAU,EAAE;QACrB,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC;IAEF,IAAI,CAAC;QACH,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACxF,MAAM,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACrF,QAAQ,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAsB;IACnD,MAAM,IAAI,GAAG,GAAG,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,EAAE,CAAC;IACP,OAAO,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;AAC/C,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -19,13 +19,15 @@ export function main() {
|
|
|
19
19
|
const token = requireEnv('AGENT_TOKEN');
|
|
20
20
|
const agentId = requireEnv('AGENT_ID');
|
|
21
21
|
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Read for backward compatibility but no longer used directly
|
|
23
|
+
void process.env.CENTRIFUGO_INTERNAL_URL;
|
|
24
|
+
void process.env.CENTRIFUGO_API_KEY;
|
|
24
25
|
if (DEBUG)
|
|
25
26
|
console.log('[debug] Debug mode enabled');
|
|
26
27
|
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
27
28
|
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
28
|
-
const
|
|
29
|
+
const api = createAgentApi(backendUrl, token);
|
|
30
|
+
const { client } = createConnection({ url, token, agentId, backendUrl, api });
|
|
29
31
|
// Chat provider via OpenClaw Gateway WS
|
|
30
32
|
const gatewayWsUrl = process.env.OPENCLAW_GATEWAY_WS || 'ws://localhost:18789';
|
|
31
33
|
const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN || '';
|
|
@@ -40,47 +42,35 @@ export function main() {
|
|
|
40
42
|
else {
|
|
41
43
|
console.warn('OPENCLAW_GATEWAY_TOKEN not set, chat provider disabled');
|
|
42
44
|
}
|
|
43
|
-
// Board handler
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
// Board handler
|
|
46
|
+
const boardHandler = new BoardHandler(api);
|
|
47
|
+
// Initialize board handler after connection is established
|
|
48
|
+
boardHandler.initialize().then((workspaceId) => {
|
|
49
|
+
if (!workspaceId) {
|
|
50
|
+
console.log('No board found for this agent, board handler dormant');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const boardChannel = `board:${workspaceId}`;
|
|
54
|
+
const boardSub = client.newSubscription(boardChannel);
|
|
55
|
+
boardSub.on('publication', (ctx) => {
|
|
56
|
+
const event = ctx.data;
|
|
57
|
+
if (!event || !event.event)
|
|
53
58
|
return;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const boardSub = client.newSubscription(boardChannel);
|
|
57
|
-
boardSub.on('publication', (ctx) => {
|
|
58
|
-
const event = ctx.data;
|
|
59
|
-
if (!event || !event.event)
|
|
60
|
-
return;
|
|
61
|
-
boardHandler.onBoardEvent(event).catch((err) => {
|
|
62
|
-
console.error('Board event handler error:', err instanceof Error ? err.message : err);
|
|
63
|
-
});
|
|
59
|
+
boardHandler.onBoardEvent(event).catch((err) => {
|
|
60
|
+
console.error('Board event handler error:', err instanceof Error ? err.message : err);
|
|
64
61
|
});
|
|
65
|
-
boardSub.subscribe();
|
|
66
|
-
console.log(`Subscribed to board channel: ${boardChannel}`);
|
|
67
|
-
}).catch((err) => {
|
|
68
|
-
console.error('Board handler init error:', err instanceof Error ? err.message : err);
|
|
69
62
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
console.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
console.warn('CENTRIFUGO_INTERNAL_URL or CENTRIFUGO_API_KEY not set, heartbeat disabled');
|
|
83
|
-
}
|
|
63
|
+
boardSub.subscribe();
|
|
64
|
+
console.log(`Subscribed to board channel: ${boardChannel}`);
|
|
65
|
+
}).catch((err) => {
|
|
66
|
+
console.error('Board handler init error:', err instanceof Error ? err.message : err);
|
|
67
|
+
});
|
|
68
|
+
console.log('Heartbeat via backend API');
|
|
69
|
+
const heartbeatTimer = startHeartbeat({
|
|
70
|
+
api,
|
|
71
|
+
agentId,
|
|
72
|
+
getBoardStatus: () => boardHandler.getBoardStatus(),
|
|
73
|
+
});
|
|
84
74
|
const shutdown = () => {
|
|
85
75
|
console.log('Shutting down...');
|
|
86
76
|
if (heartbeatTimer)
|
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;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,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -5,6 +5,8 @@ const TIMEOUT = 10_000;
|
|
|
5
5
|
export interface AgentApi {
|
|
6
6
|
get(path: string): Promise<unknown>;
|
|
7
7
|
post(path: string, body?: unknown): Promise<Response>;
|
|
8
|
+
publishResponse(agentId: string, data: unknown): Promise<void>;
|
|
9
|
+
publishHeartbeat(agentId: string, payload: unknown): Promise<void>;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function createAgentApi(backendUrl: string, agentToken: string): AgentApi {
|
|
@@ -44,5 +46,17 @@ export function createAgentApi(backendUrl: string, agentToken: string): AgentApi
|
|
|
44
46
|
async post(path: string, body?: unknown): Promise<Response> {
|
|
45
47
|
return request('POST', path, body);
|
|
46
48
|
},
|
|
49
|
+
async publishResponse(agentId: string, data: unknown): Promise<void> {
|
|
50
|
+
const res = await request('POST', '/api/internal/agent-response', { agentId, data });
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
throw new Error(`publishResponse failed: HTTP ${res.status}`);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async publishHeartbeat(agentId: string, payload: unknown): Promise<void> {
|
|
56
|
+
const res = await request('POST', '/api/internal/agent-heartbeat', { agentId, payload });
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`publishHeartbeat failed: HTTP ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
47
61
|
};
|
|
48
62
|
}
|
package/src/connection.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { debugLog } from './debug.js';
|
|
2
|
+
import type { AgentApi } from './api.js';
|
|
2
3
|
import { Centrifuge, Subscription } from 'centrifuge';
|
|
3
4
|
import WebSocket from 'ws';
|
|
4
5
|
import type { AgentCommand, AgentResponse } from './types.js';
|
|
@@ -20,6 +21,7 @@ export interface ConnectionOptions {
|
|
|
20
21
|
token: string;
|
|
21
22
|
agentId: string;
|
|
22
23
|
backendUrl: string;
|
|
24
|
+
api: AgentApi;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
const handlers: Record<string, (cmd: AgentCommand) => Promise<AgentResponse>> = {
|
|
@@ -108,7 +110,7 @@ export function createConnection(opts: ConnectionOptions): {
|
|
|
108
110
|
|
|
109
111
|
// Chat commands — handle before generic dispatch
|
|
110
112
|
if (command.type === 'chat_list_sessions' || command.type === 'chat_history' || command.type === 'chat_send') {
|
|
111
|
-
const publishFn = async (data: unknown): Promise<void> => { await
|
|
113
|
+
const publishFn = async (data: unknown): Promise<void> => { await opts.api.publishResponse(opts.agentId, data); };
|
|
112
114
|
console.log(`[WS] Handling chat command: type=${command.type} id=${command.id}`);
|
|
113
115
|
switch (command.type) {
|
|
114
116
|
case 'chat_list_sessions':
|
|
@@ -133,7 +135,7 @@ export function createConnection(opts: ConnectionOptions): {
|
|
|
133
135
|
|
|
134
136
|
try {
|
|
135
137
|
const response = await handler(command);
|
|
136
|
-
await
|
|
138
|
+
await opts.api.publishResponse(opts.agentId, response);
|
|
137
139
|
} catch (err) {
|
|
138
140
|
const errorResponse: AgentResponse = {
|
|
139
141
|
id: command.id,
|
|
@@ -141,7 +143,7 @@ export function createConnection(opts: ConnectionOptions): {
|
|
|
141
143
|
success: false,
|
|
142
144
|
error: err instanceof Error ? err.message : String(err),
|
|
143
145
|
};
|
|
144
|
-
await
|
|
146
|
+
await opts.api.publishResponse(opts.agentId, errorResponse);
|
|
145
147
|
}
|
|
146
148
|
});
|
|
147
149
|
|
package/src/heartbeat.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { debugLog } from './debug.js';
|
|
3
|
+
import type { AgentApi } from './api.js';
|
|
3
4
|
import type { BoardState, HeartbeatPayload } from './types.js';
|
|
4
5
|
|
|
5
6
|
const HEARTBEAT_INTERVAL = 30_000;
|
|
6
|
-
const HEARTBEAT_CHANNEL = 'heartbeat:data';
|
|
7
7
|
|
|
8
8
|
export interface HeartbeatOptions {
|
|
9
|
-
|
|
10
|
-
centrifugoApiKey: string;
|
|
9
|
+
api: AgentApi;
|
|
11
10
|
agentId: string;
|
|
12
11
|
getBoardStatus?: () => BoardState;
|
|
13
12
|
}
|
|
@@ -38,36 +37,13 @@ async function publishHeartbeat(opts: HeartbeatOptions): Promise<void> {
|
|
|
38
37
|
...(opts.getBoardStatus ? { boardStatus: opts.getBoardStatus() } : {}),
|
|
39
38
|
};
|
|
40
39
|
|
|
41
|
-
const controller = new AbortController();
|
|
42
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
43
|
-
|
|
44
40
|
try {
|
|
45
41
|
debugLog('heartbeat', '→ sending', opts.agentId, JSON.stringify(payload).slice(0, 500));
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
headers: {
|
|
49
|
-
'Content-Type': 'application/json',
|
|
50
|
-
'Authorization': `apikey ${opts.centrifugoApiKey}`,
|
|
51
|
-
},
|
|
52
|
-
body: JSON.stringify({ channel: HEARTBEAT_CHANNEL, data: payload }),
|
|
53
|
-
signal: controller.signal,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
if (!res.ok) {
|
|
57
|
-
const body = await res.text().catch(() => '');
|
|
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);
|
|
62
|
-
}
|
|
42
|
+
await opts.api.publishHeartbeat(opts.agentId, payload);
|
|
43
|
+
debugLog('heartbeat', '← ok');
|
|
63
44
|
} catch (err) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
console.error('Heartbeat publish failed:', err instanceof Error ? err.message : err);
|
|
68
|
-
}
|
|
69
|
-
} finally {
|
|
70
|
-
clearTimeout(timeout);
|
|
45
|
+
console.error('Heartbeat publish failed:', err instanceof Error ? err.message : err);
|
|
46
|
+
debugLog('heartbeat', '← error', err);
|
|
71
47
|
}
|
|
72
48
|
}
|
|
73
49
|
|
package/src/index.ts
CHANGED
|
@@ -22,14 +22,16 @@ export function main(): void {
|
|
|
22
22
|
const token = requireEnv('AGENT_TOKEN');
|
|
23
23
|
const agentId = requireEnv('AGENT_ID');
|
|
24
24
|
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// Read for backward compatibility but no longer used directly
|
|
26
|
+
void process.env.CENTRIFUGO_INTERNAL_URL;
|
|
27
|
+
void process.env.CENTRIFUGO_API_KEY;
|
|
27
28
|
|
|
28
29
|
if (DEBUG) console.log('[debug] Debug mode enabled');
|
|
29
30
|
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
30
31
|
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
+
const api = createAgentApi(backendUrl, token);
|
|
34
|
+
const { client } = createConnection({ url, token, agentId, backendUrl, api });
|
|
33
35
|
|
|
34
36
|
// Chat provider via OpenClaw Gateway WS
|
|
35
37
|
const gatewayWsUrl = process.env.OPENCLAW_GATEWAY_WS || 'ws://localhost:18789';
|
|
@@ -46,50 +48,39 @@ export function main(): void {
|
|
|
46
48
|
console.warn('OPENCLAW_GATEWAY_TOKEN not set, chat provider disabled');
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
// Board handler
|
|
50
|
-
|
|
51
|
-
const apiBaseUrl = process.env.BACKEND_URL || backendUrl;
|
|
52
|
-
if (apiBaseUrl && token) {
|
|
53
|
-
const api = createAgentApi(apiBaseUrl, token);
|
|
54
|
-
boardHandler = new BoardHandler(api);
|
|
51
|
+
// Board handler
|
|
52
|
+
const boardHandler = new BoardHandler(api);
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
// Initialize board handler after connection is established
|
|
55
|
+
boardHandler.initialize().then((workspaceId) => {
|
|
56
|
+
if (!workspaceId) {
|
|
57
|
+
console.log('No board found for this agent, board handler dormant');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const boardChannel = `board:${workspaceId}`;
|
|
62
|
+
const boardSub = client.newSubscription(boardChannel);
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
64
|
+
boardSub.on('publication', (ctx) => {
|
|
65
|
+
const event = ctx.data as BoardEvent;
|
|
66
|
+
if (!event || !event.event) return;
|
|
67
|
+
boardHandler.onBoardEvent(event).catch((err) => {
|
|
68
|
+
console.error('Board event handler error:', err instanceof Error ? err.message : err);
|
|
72
69
|
});
|
|
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
70
|
});
|
|
79
|
-
}
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
72
|
+
boardSub.subscribe();
|
|
73
|
+
console.log(`Subscribed to board channel: ${boardChannel}`);
|
|
74
|
+
}).catch((err) => {
|
|
75
|
+
console.error('Board handler init error:', err instanceof Error ? err.message : err);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
console.log('Heartbeat via backend API');
|
|
79
|
+
const heartbeatTimer = startHeartbeat({
|
|
80
|
+
api,
|
|
81
|
+
agentId,
|
|
82
|
+
getBoardStatus: () => boardHandler.getBoardStatus(),
|
|
83
|
+
});
|
|
93
84
|
|
|
94
85
|
const shutdown = () => {
|
|
95
86
|
console.log('Shutting down...');
|