@openclaw-cloud/agent-controller 0.1.8 → 0.2.1

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.
Files changed (42) hide show
  1. package/CLAUDE.md +9 -8
  2. package/__tests__/api.test.ts +60 -0
  3. package/__tests__/board-handler.test.ts +2 -0
  4. package/__tests__/connection.test.ts +25 -4
  5. package/__tests__/heartbeat.test.ts +22 -29
  6. package/bin/agent-controller.js +6 -0
  7. package/dist/api.d.ts +2 -0
  8. package/dist/api.js +12 -0
  9. package/dist/api.js.map +1 -1
  10. package/dist/commands/install.d.ts +1 -0
  11. package/dist/commands/install.js +45 -0
  12. package/dist/commands/install.js.map +1 -0
  13. package/dist/commands/uninstall.d.ts +1 -0
  14. package/dist/commands/uninstall.js +22 -0
  15. package/dist/commands/uninstall.js.map +1 -0
  16. package/dist/connection.d.ts +2 -0
  17. package/dist/connection.js +3 -3
  18. package/dist/connection.js.map +1 -1
  19. package/dist/heartbeat.d.ts +2 -2
  20. package/dist/heartbeat.js +4 -29
  21. package/dist/heartbeat.js.map +1 -1
  22. package/dist/index.js +28 -41
  23. package/dist/index.js.map +1 -1
  24. package/dist/platform/linux.d.ts +8 -0
  25. package/dist/platform/linux.js +96 -0
  26. package/dist/platform/linux.js.map +1 -0
  27. package/dist/platform/macos.d.ts +8 -0
  28. package/dist/platform/macos.js +109 -0
  29. package/dist/platform/macos.js.map +1 -0
  30. package/dist/platform/windows.d.ts +8 -0
  31. package/dist/platform/windows.js +77 -0
  32. package/dist/platform/windows.js.map +1 -0
  33. package/package.json +1 -1
  34. package/src/api.ts +14 -0
  35. package/src/commands/install.ts +50 -0
  36. package/src/commands/uninstall.ts +19 -0
  37. package/src/connection.ts +5 -3
  38. package/src/heartbeat.ts +6 -30
  39. package/src/index.ts +29 -42
  40. package/src/platform/linux.ts +108 -0
  41. package/src/platform/macos.ts +122 -0
  42. package/src/platform/windows.ts +92 -0
package/dist/index.js CHANGED
@@ -19,13 +19,12 @@ 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
- const centrifugoInternalUrl = process.env.CENTRIFUGO_INTERNAL_URL || '';
23
- const centrifugoApiKey = process.env.CENTRIFUGO_API_KEY || '';
24
22
  if (DEBUG)
25
23
  console.log('[debug] Debug mode enabled');
26
24
  console.log(`Starting agent-controller for agent: ${agentId}`);
27
25
  console.log(`Backend URL: ${backendUrl} (JWT mode)`);
28
- const { client } = createConnection({ url, token, agentId, backendUrl });
26
+ const api = createAgentApi(backendUrl, token);
27
+ const { client } = createConnection({ url, token, agentId, backendUrl, api });
29
28
  // Chat provider via OpenClaw Gateway WS
30
29
  const gatewayWsUrl = process.env.OPENCLAW_GATEWAY_WS || 'ws://localhost:18789';
31
30
  const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN || '';
@@ -40,47 +39,35 @@ export function main() {
40
39
  else {
41
40
  console.warn('OPENCLAW_GATEWAY_TOKEN not set, chat provider disabled');
42
41
  }
43
- // Board handler (optional — requires BACKEND_URL and AGENT_TOKEN)
44
- let boardHandler = null;
45
- const apiBaseUrl = process.env.BACKEND_URL || backendUrl;
46
- if (apiBaseUrl && token) {
47
- const api = createAgentApi(apiBaseUrl, token);
48
- boardHandler = new BoardHandler(api);
49
- // Initialize board handler after connection is established
50
- boardHandler.initialize().then((workspaceId) => {
51
- if (!workspaceId) {
52
- console.log('No board found for this agent, board handler dormant');
42
+ // Board handler
43
+ const boardHandler = new BoardHandler(api);
44
+ // Initialize board handler after connection is established
45
+ boardHandler.initialize().then((workspaceId) => {
46
+ if (!workspaceId) {
47
+ console.log('No board found for this agent, board handler dormant');
48
+ return;
49
+ }
50
+ const boardChannel = `board:${workspaceId}`;
51
+ const boardSub = client.newSubscription(boardChannel);
52
+ boardSub.on('publication', (ctx) => {
53
+ const event = ctx.data;
54
+ if (!event || !event.event)
53
55
  return;
54
- }
55
- const boardChannel = `board:${workspaceId}`;
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
- });
56
+ boardHandler.onBoardEvent(event).catch((err) => {
57
+ console.error('Board event handler error:', err instanceof Error ? err.message : err);
64
58
  });
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
59
  });
70
- }
71
- let heartbeatTimer;
72
- if (centrifugoInternalUrl && centrifugoApiKey) {
73
- console.log(`Heartbeat via Centrifugo internal API: ${centrifugoInternalUrl}`);
74
- heartbeatTimer = startHeartbeat({
75
- centrifugoInternalUrl,
76
- centrifugoApiKey,
77
- agentId,
78
- ...(boardHandler ? { getBoardStatus: () => boardHandler.getBoardStatus() } : {}),
79
- });
80
- }
81
- else {
82
- console.warn('CENTRIFUGO_INTERNAL_URL or CENTRIFUGO_API_KEY not set, heartbeat disabled');
83
- }
60
+ boardSub.subscribe();
61
+ console.log(`Subscribed to board channel: ${boardChannel}`);
62
+ }).catch((err) => {
63
+ console.error('Board handler init error:', err instanceof Error ? err.message : err);
64
+ });
65
+ console.log('Heartbeat via backend API');
66
+ const heartbeatTimer = startHeartbeat({
67
+ api,
68
+ agentId,
69
+ getBoardStatus: () => boardHandler.getBoardStatus(),
70
+ });
84
71
  const shutdown = () => {
85
72
  console.log('Shutting down...');
86
73
  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,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface PlatformConfig {
2
+ centrifugoUrl: string;
3
+ agentToken: string;
4
+ agentId: string;
5
+ backendInternalUrl: string;
6
+ }
7
+ export declare function installLinux(config: PlatformConfig): Promise<void>;
8
+ export declare function uninstallLinux(): Promise<void>;
@@ -0,0 +1,96 @@
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
+ function execAsync(cmd) {
6
+ return new Promise((resolve, reject) => {
7
+ exec(cmd, (err, stdout) => {
8
+ if (err)
9
+ reject(err);
10
+ else
11
+ resolve(stdout.toString().trim());
12
+ });
13
+ });
14
+ }
15
+ async function which(bin) {
16
+ try {
17
+ return await execAsync(`which "${bin}"`);
18
+ }
19
+ catch {
20
+ return '';
21
+ }
22
+ }
23
+ export async function installLinux(config) {
24
+ const home = homedir();
25
+ const systemdDir = join(home, '.config', 'systemd', 'user');
26
+ const servicePath = join(systemdDir, 'agent-controller.service');
27
+ const nodePath = process.execPath;
28
+ let agentControllerPath = await which('agent-controller');
29
+ if (!agentControllerPath) {
30
+ agentControllerPath = join(nodePath, '..', 'agent-controller');
31
+ }
32
+ mkdirSync(systemdDir, { recursive: true });
33
+ const service = `[Unit]
34
+ Description=OpenClaw Agent Controller
35
+ After=network.target
36
+
37
+ [Service]
38
+ Type=simple
39
+ Restart=always
40
+ RestartSec=10
41
+ Environment=CENTRIFUGO_URL=${config.centrifugoUrl}
42
+ Environment=AGENT_TOKEN=${config.agentToken}
43
+ Environment=AGENT_ID=${config.agentId}
44
+ Environment=BACKEND_INTERNAL_URL=${config.backendInternalUrl}
45
+ ExecStart="${nodePath}" "${agentControllerPath}"
46
+
47
+ [Install]
48
+ WantedBy=default.target
49
+ `;
50
+ writeFileSync(servicePath, service, 'utf8');
51
+ console.log(`Written: ${servicePath}`);
52
+ try {
53
+ await execAsync('systemctl --user daemon-reload');
54
+ console.log('systemd daemon reloaded.');
55
+ await execAsync('systemctl --user enable --now agent-controller');
56
+ console.log('Service enabled and started.');
57
+ }
58
+ catch (err) {
59
+ console.warn('systemctl command failed:', err instanceof Error ? err.message : err);
60
+ }
61
+ console.log('');
62
+ console.log('Agent Controller installed as a systemd user service.');
63
+ console.log('');
64
+ console.log('Useful commands:');
65
+ console.log(' Start: systemctl --user start agent-controller');
66
+ console.log(' Stop: systemctl --user stop agent-controller');
67
+ console.log(' Status: systemctl --user status agent-controller');
68
+ console.log(' Logs: journalctl --user -u agent-controller -f');
69
+ console.log(' Uninstall: agent-controller uninstall');
70
+ }
71
+ export async function uninstallLinux() {
72
+ try {
73
+ await execAsync('systemctl --user disable --now agent-controller');
74
+ console.log('Service disabled and stopped.');
75
+ }
76
+ catch (err) {
77
+ console.warn('systemctl disable failed (may not be enabled):', err instanceof Error ? err.message : err);
78
+ }
79
+ const servicePath = join(homedir(), '.config', 'systemd', 'user', 'agent-controller.service');
80
+ const { unlinkSync, existsSync } = await import('node:fs');
81
+ if (existsSync(servicePath)) {
82
+ unlinkSync(servicePath);
83
+ console.log(`Removed: ${servicePath}`);
84
+ }
85
+ else {
86
+ console.log(`Service file not found: ${servicePath}`);
87
+ }
88
+ try {
89
+ await execAsync('systemctl --user daemon-reload');
90
+ }
91
+ catch {
92
+ // best-effort
93
+ }
94
+ console.log('Agent Controller uninstalled.');
95
+ }
96
+ //# sourceMappingURL=linux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linux.js","sourceRoot":"","sources":["../../src/platform/linux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAsB;IACvD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,0BAA0B,CAAC,CAAC;IAEjE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,mBAAmB,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC1D,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,mBAAmB,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;IACjE,CAAC;IAED,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,MAAM,OAAO,GAAG;;;;;;;;6BAQW,MAAM,CAAC,aAAa;0BACvB,MAAM,CAAC,UAAU;uBACpB,MAAM,CAAC,OAAO;mCACF,MAAM,CAAC,kBAAkB;aAC/C,QAAQ,MAAM,mBAAmB;;;;CAI7C,CAAC;IAEA,aAAa,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,YAAY,WAAW,EAAE,CAAC,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,gCAAgC,CAAC,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,MAAM,SAAS,CAAC,gDAAgD,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,2BAA2B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtF,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,iDAAiD,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,gDAAgD,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3G,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,0BAA0B,CAAC,CAAC;IAC9F,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IAC3D,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,UAAU,CAAC,WAAW,CAAC,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,YAAY,WAAW,EAAE,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,2BAA2B,WAAW,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface PlatformConfig {
2
+ centrifugoUrl: string;
3
+ agentToken: string;
4
+ agentId: string;
5
+ backendInternalUrl: string;
6
+ }
7
+ export declare function installMacOS(config: PlatformConfig): Promise<void>;
8
+ export declare function uninstallMacOS(): Promise<void>;
@@ -0,0 +1,109 @@
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
+ function execAsync(cmd) {
6
+ return new Promise((resolve, reject) => {
7
+ exec(cmd, (err, stdout) => {
8
+ if (err)
9
+ reject(err);
10
+ else
11
+ resolve(stdout.toString().trim());
12
+ });
13
+ });
14
+ }
15
+ async function which(bin) {
16
+ try {
17
+ return await execAsync(`which "${bin}"`);
18
+ }
19
+ catch {
20
+ return '';
21
+ }
22
+ }
23
+ export async function installMacOS(config) {
24
+ const home = homedir();
25
+ const launchAgentsDir = join(home, 'Library', 'LaunchAgents');
26
+ const plistPath = join(launchAgentsDir, 'com.openclaw.agent-controller.plist');
27
+ const logDir = join(home, 'Library', 'Logs');
28
+ const logFile = join(logDir, 'agent-controller.log');
29
+ const errFile = join(logDir, 'agent-controller.err');
30
+ const nodePath = process.execPath;
31
+ let agentControllerPath = await which('agent-controller');
32
+ if (!agentControllerPath) {
33
+ // Fallback: derive from node path
34
+ agentControllerPath = join(nodePath, '..', 'agent-controller');
35
+ }
36
+ mkdirSync(launchAgentsDir, { recursive: true });
37
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
38
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39
+ <plist version="1.0">
40
+ <dict>
41
+ <key>Label</key>
42
+ <string>com.openclaw.agent-controller</string>
43
+ <key>ProgramArguments</key>
44
+ <array>
45
+ <string>${nodePath}</string>
46
+ <string>${agentControllerPath}</string>
47
+ </array>
48
+ <key>EnvironmentVariables</key>
49
+ <dict>
50
+ <key>CENTRIFUGO_URL</key>
51
+ <string>${config.centrifugoUrl}</string>
52
+ <key>AGENT_TOKEN</key>
53
+ <string>${config.agentToken}</string>
54
+ <key>AGENT_ID</key>
55
+ <string>${config.agentId}</string>
56
+ <key>BACKEND_INTERNAL_URL</key>
57
+ <string>${config.backendInternalUrl}</string>
58
+ </dict>
59
+ <key>RunAtLoad</key>
60
+ <true/>
61
+ <key>KeepAlive</key>
62
+ <true/>
63
+ <key>StandardOutPath</key>
64
+ <string>${logFile}</string>
65
+ <key>StandardErrorPath</key>
66
+ <string>${errFile}</string>
67
+ </dict>
68
+ </plist>
69
+ `;
70
+ writeFileSync(plistPath, plist, 'utf8');
71
+ console.log(`Written: ${plistPath}`);
72
+ try {
73
+ await execAsync(`launchctl load -w "${plistPath}"`);
74
+ console.log('Service loaded via launchctl.');
75
+ }
76
+ catch (err) {
77
+ console.warn('launchctl load failed (may already be loaded):', err instanceof Error ? err.message : err);
78
+ }
79
+ console.log('');
80
+ console.log('Agent Controller installed as a macOS LaunchAgent.');
81
+ console.log('');
82
+ console.log('Useful commands:');
83
+ console.log(` Start: launchctl load -w "${plistPath}"`);
84
+ console.log(` Stop: launchctl unload "${plistPath}"`);
85
+ console.log(` Logs: tail -f "${logFile}"`);
86
+ console.log(` Errors: tail -f "${errFile}"`);
87
+ console.log(` Uninstall: agent-controller uninstall`);
88
+ }
89
+ export async function uninstallMacOS() {
90
+ const home = homedir();
91
+ const plistPath = join(home, 'Library', 'LaunchAgents', 'com.openclaw.agent-controller.plist');
92
+ try {
93
+ await execAsync(`launchctl unload "${plistPath}"`);
94
+ console.log('Service unloaded via launchctl.');
95
+ }
96
+ catch (err) {
97
+ console.warn('launchctl unload failed (may not be loaded):', err instanceof Error ? err.message : err);
98
+ }
99
+ const { unlinkSync, existsSync } = await import('node:fs');
100
+ if (existsSync(plistPath)) {
101
+ unlinkSync(plistPath);
102
+ console.log(`Removed: ${plistPath}`);
103
+ }
104
+ else {
105
+ console.log(`Plist not found: ${plistPath}`);
106
+ }
107
+ console.log('Agent Controller uninstalled.');
108
+ }
109
+ //# sourceMappingURL=macos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macos.js","sourceRoot":"","sources":["../../src/platform/macos.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface PlatformConfig {
2
+ centrifugoUrl: string;
3
+ agentToken: string;
4
+ agentId: string;
5
+ backendInternalUrl: string;
6
+ }
7
+ export declare function installWindows(config: PlatformConfig): Promise<void>;
8
+ export declare function uninstallWindows(): Promise<void>;
@@ -0,0 +1,77 @@
1
+ import { exec } from 'node:child_process';
2
+ import { writeFileSync, mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ function execAsync(cmd) {
5
+ return new Promise((resolve, reject) => {
6
+ exec(cmd, (err, stdout) => {
7
+ if (err)
8
+ reject(err);
9
+ else
10
+ resolve(stdout.toString().trim());
11
+ });
12
+ });
13
+ }
14
+ async function which(bin) {
15
+ try {
16
+ return await execAsync(`where "${bin}"`);
17
+ }
18
+ catch {
19
+ return '';
20
+ }
21
+ }
22
+ export async function installWindows(config) {
23
+ const appData = process.env['APPDATA'] ?? join(process.env['USERPROFILE'] ?? 'C:\\Users\\Default', 'AppData', 'Roaming');
24
+ const openclawDir = join(appData, 'openclaw');
25
+ const wrapperPath = join(openclawDir, 'agent-controller.cmd');
26
+ const nodePath = process.execPath;
27
+ let agentControllerPath = await which('agent-controller');
28
+ if (!agentControllerPath) {
29
+ agentControllerPath = join(nodePath, '..', 'agent-controller');
30
+ }
31
+ mkdirSync(openclawDir, { recursive: true });
32
+ const wrapper = `@echo off
33
+ set CENTRIFUGO_URL=${config.centrifugoUrl}
34
+ set AGENT_TOKEN=${config.agentToken}
35
+ set AGENT_ID=${config.agentId}
36
+ set BACKEND_INTERNAL_URL=${config.backendInternalUrl}
37
+ "${nodePath}" "${agentControllerPath}"
38
+ `;
39
+ writeFileSync(wrapperPath, wrapper, 'utf8');
40
+ console.log(`Written: ${wrapperPath}`);
41
+ try {
42
+ await execAsync(`schtasks /create /tn "OpenClaw Agent Controller" /tr "${wrapperPath}" /sc ONLOGON /rl HIGHEST /f`);
43
+ console.log('Scheduled task created.');
44
+ }
45
+ catch (err) {
46
+ console.warn('schtasks create failed:', err instanceof Error ? err.message : err);
47
+ }
48
+ console.log('');
49
+ console.log('Agent Controller installed as a Windows scheduled task (ONLOGON).');
50
+ console.log('');
51
+ console.log('Useful commands:');
52
+ console.log(' Start: schtasks /run /tn "OpenClaw Agent Controller"');
53
+ console.log(' Stop: schtasks /end /tn "OpenClaw Agent Controller"');
54
+ console.log(' Uninstall: agent-controller uninstall');
55
+ console.log(` Wrapper: ${wrapperPath}`);
56
+ }
57
+ export async function uninstallWindows() {
58
+ try {
59
+ await execAsync('schtasks /delete /tn "OpenClaw Agent Controller" /f');
60
+ console.log('Scheduled task deleted.');
61
+ }
62
+ catch (err) {
63
+ console.warn('schtasks delete failed (may not exist):', err instanceof Error ? err.message : err);
64
+ }
65
+ const appData = process.env['APPDATA'] ?? join(process.env['USERPROFILE'] ?? 'C:\\Users\\Default', 'AppData', 'Roaming');
66
+ const wrapperPath = join(appData, 'openclaw', 'agent-controller.cmd');
67
+ const { unlinkSync, existsSync } = await import('node:fs');
68
+ if (existsSync(wrapperPath)) {
69
+ unlinkSync(wrapperPath);
70
+ console.log(`Removed: ${wrapperPath}`);
71
+ }
72
+ else {
73
+ console.log(`Wrapper not found: ${wrapperPath}`);
74
+ }
75
+ console.log('Agent Controller uninstalled.');
76
+ }
77
+ //# sourceMappingURL=windows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows.js","sourceRoot":"","sources":["../../src/platform/windows.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw-cloud/agent-controller",
3
- "version": "0.1.8",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
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
  }
@@ -0,0 +1,50 @@
1
+ import { createInterface } from 'node:readline';
2
+
3
+ async function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
4
+ return new Promise((resolve) => {
5
+ rl.question(question, (answer) => {
6
+ resolve(answer.trim());
7
+ });
8
+ });
9
+ }
10
+
11
+ export async function install(): Promise<void> {
12
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
13
+
14
+ console.log('OpenClaw Agent Controller — Installation Wizard');
15
+ console.log('================================================');
16
+ console.log('');
17
+
18
+ const centrifugoUrl = await prompt(rl, 'CENTRIFUGO_URL (e.g. ws://your-server:8000): ');
19
+ const agentToken = await prompt(rl, 'AGENT_TOKEN: ');
20
+ const agentId = await prompt(rl, 'AGENT_ID: ');
21
+ const backendInternalUrl = await prompt(rl, 'BACKEND_INTERNAL_URL (e.g. http://your-server:3000): ');
22
+
23
+ rl.close();
24
+
25
+ if (!centrifugoUrl || !agentToken || !agentId || !backendInternalUrl) {
26
+ console.error('All fields are required. Aborting.');
27
+ process.exit(1);
28
+ }
29
+
30
+ const config = { centrifugoUrl, agentToken, agentId, backendInternalUrl };
31
+
32
+ const platform = process.platform;
33
+ console.log('');
34
+ console.log(`Detected platform: ${platform}`);
35
+ console.log('');
36
+
37
+ if (platform === 'darwin') {
38
+ const { installMacOS } = await import('../platform/macos.js');
39
+ await installMacOS(config);
40
+ } else if (platform === 'linux') {
41
+ const { installLinux } = await import('../platform/linux.js');
42
+ await installLinux(config);
43
+ } else if (platform === 'win32') {
44
+ const { installWindows } = await import('../platform/windows.js');
45
+ await installWindows(config);
46
+ } else {
47
+ console.error(`Unsupported platform: ${platform}`);
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,19 @@
1
+ export async function uninstall(): Promise<void> {
2
+ const platform = process.platform;
3
+ console.log(`Uninstalling Agent Controller on platform: ${platform}`);
4
+ console.log('');
5
+
6
+ if (platform === 'darwin') {
7
+ const { uninstallMacOS } = await import('../platform/macos.js');
8
+ await uninstallMacOS();
9
+ } else if (platform === 'linux') {
10
+ const { uninstallLinux } = await import('../platform/linux.js');
11
+ await uninstallLinux();
12
+ } else if (platform === 'win32') {
13
+ const { uninstallWindows } = await import('../platform/windows.js');
14
+ await uninstallWindows();
15
+ } else {
16
+ console.error(`Unsupported platform: ${platform}`);
17
+ process.exit(1);
18
+ }
19
+ }
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 client.publish(commandChannel, data); };
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 client.publish(commandChannel, response);
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 client.publish(commandChannel, errorResponse);
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
- centrifugoInternalUrl: string;
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
- const res = await fetch(`${opts.centrifugoInternalUrl}/api/publish`, {
47
- method: 'POST',
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
- if ((err as Error).name === 'AbortError') {
65
- console.error('Heartbeat publish timed out');
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