@openclaw-cloud/agent-controller 0.1.4 → 0.1.5

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 (55) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/__tests__/api.test.ts +123 -0
  3. package/__tests__/chat.test.ts +191 -0
  4. package/__tests__/config.test.ts +100 -0
  5. package/__tests__/file-write.test.ts +119 -0
  6. package/__tests__/gateway-adapter.test.ts +366 -0
  7. package/__tests__/gateway-client.test.ts +272 -0
  8. package/__tests__/onboarding.test.ts +55 -0
  9. package/__tests__/package-install.test.ts +109 -0
  10. package/__tests__/pair.test.ts +60 -0
  11. package/__tests__/self-update.test.ts +123 -0
  12. package/__tests__/stop.test.ts +38 -0
  13. package/bin/agent-controller.js +15 -2
  14. package/dist/commands/self-update.d.ts +1 -0
  15. package/dist/commands/self-update.js +41 -0
  16. package/dist/commands/self-update.js.map +1 -0
  17. package/dist/connection.js +24 -0
  18. package/dist/connection.js.map +1 -1
  19. package/dist/handlers/board-handler.d.ts +1 -0
  20. package/dist/handlers/board-handler.js +5 -2
  21. package/dist/handlers/board-handler.js.map +1 -1
  22. package/dist/handlers/chat.d.ts +4 -0
  23. package/dist/handlers/chat.js +62 -0
  24. package/dist/handlers/chat.js.map +1 -0
  25. package/dist/handlers/file-write.d.ts +2 -0
  26. package/dist/handlers/file-write.js +59 -0
  27. package/dist/handlers/file-write.js.map +1 -0
  28. package/dist/handlers/onboarding.d.ts +2 -0
  29. package/dist/handlers/onboarding.js +18 -0
  30. package/dist/handlers/onboarding.js.map +1 -0
  31. package/dist/handlers/package-install.d.ts +2 -0
  32. package/dist/handlers/package-install.js +59 -0
  33. package/dist/handlers/package-install.js.map +1 -0
  34. package/dist/index.js +18 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/openclaw/gateway-adapter.d.ts +22 -0
  37. package/dist/openclaw/gateway-adapter.js +108 -0
  38. package/dist/openclaw/gateway-adapter.js.map +1 -0
  39. package/dist/openclaw/gateway-client.d.ts +21 -0
  40. package/dist/openclaw/gateway-client.js +114 -0
  41. package/dist/openclaw/gateway-client.js.map +1 -0
  42. package/dist/openclaw/index.d.ts +8 -0
  43. package/dist/openclaw/index.js +12 -0
  44. package/dist/openclaw/index.js.map +1 -0
  45. package/dist/openclaw/types.d.ts +36 -0
  46. package/dist/openclaw/types.js +2 -0
  47. package/dist/openclaw/types.js.map +1 -0
  48. package/dist/types.d.ts +2 -1
  49. package/package.json +4 -2
  50. package/src/commands/self-update.ts +43 -0
  51. package/src/connection.ts +6 -0
  52. package/src/handlers/file-write.ts +65 -0
  53. package/src/handlers/onboarding.ts +19 -0
  54. package/src/handlers/package-install.ts +69 -0
  55. package/src/types.ts +2 -1
@@ -0,0 +1,43 @@
1
+ import { exec } from 'node:child_process';
2
+
3
+ const PACKAGE_NAME = '@openclaw-cloud/agent-controller';
4
+
5
+ function execAsync(cmd: string, timeout: number): Promise<string> {
6
+ return new Promise((resolve, reject) => {
7
+ exec(cmd, { timeout }, (err, stdout) => {
8
+ if (err) reject(err);
9
+ else resolve(stdout.toString().trim());
10
+ });
11
+ });
12
+ }
13
+
14
+ export async function selfUpdate(currentVersion: string): Promise<void> {
15
+ console.log(`Current version: ${currentVersion}`);
16
+
17
+ let latestVersion: string;
18
+ try {
19
+ latestVersion = await execAsync(`npm view ${PACKAGE_NAME} version`, 10_000);
20
+ } catch (err) {
21
+ console.error('Failed to fetch latest version:', err instanceof Error ? err.message : err);
22
+ process.exit(1);
23
+ return; // unreachable in production; guards test flow when process.exit is mocked
24
+ }
25
+
26
+ console.log(`Latest version: ${latestVersion}`);
27
+
28
+ if (currentVersion === latestVersion) {
29
+ console.log('Already up to date.');
30
+ return;
31
+ }
32
+
33
+ console.log(`Updating from ${currentVersion} to ${latestVersion}...`);
34
+ try {
35
+ await execAsync(`npm install -g ${PACKAGE_NAME}@latest`, 120_000);
36
+ console.log(`Successfully updated to ${latestVersion}. Restarting...`);
37
+ process.exit(0);
38
+ return; // unreachable in production; guards test flow when process.exit is mocked
39
+ } catch (err) {
40
+ console.error('Update failed:', err instanceof Error ? err.message : err);
41
+ process.exit(1);
42
+ }
43
+ }
package/src/connection.ts CHANGED
@@ -9,6 +9,9 @@ import { handlePair } from './handlers/pair.js';
9
9
  import { handleStop } from './handlers/stop.js';
10
10
  import { handleBackup } from './handlers/backup.js';
11
11
  import { handleChatListSessions, handleChatHistory, handleChatSend } from './handlers/chat.js';
12
+ import { handlePackageInstall } from './handlers/package-install.js';
13
+ import { handleFileWrite } from './handlers/file-write.js';
14
+ import { handleOnboardingComplete } from './handlers/onboarding.js';
12
15
 
13
16
  export interface ConnectionOptions {
14
17
  url: string;
@@ -25,6 +28,9 @@ const handlers: Record<string, (cmd: AgentCommand) => Promise<AgentResponse>> =
25
28
  pair: handlePair,
26
29
  stop: handleStop,
27
30
  backup: handleBackup,
31
+ package_install: handlePackageInstall,
32
+ file_write: handleFileWrite,
33
+ onboarding_complete: handleOnboardingComplete,
28
34
  };
29
35
 
30
36
  async function fetchCentrifugoToken(backendUrl: string, agentToken: string, agentId: string): Promise<string> {
@@ -0,0 +1,65 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import type { AgentCommand, AgentResponse } from '../types.js';
4
+
5
+ const WORKSPACE_DIR = process.env.WORKSPACE_DIR ?? '/etc/openclaw/workspace';
6
+
7
+ export async function handleFileWrite(command: AgentCommand): Promise<AgentResponse> {
8
+ const filePath = command.payload.path as string;
9
+ const content = command.payload.content;
10
+ const overwrite = (command.payload.overwrite as boolean | undefined) ?? true;
11
+
12
+ if (!filePath || content === undefined) {
13
+ return {
14
+ id: command.id,
15
+ type: 'file_write',
16
+ success: false,
17
+ error: 'Missing "path" or "content" in payload',
18
+ };
19
+ }
20
+
21
+ if (filePath.includes('..') || filePath.startsWith('/')) {
22
+ return {
23
+ id: command.id,
24
+ type: 'file_write',
25
+ success: false,
26
+ error: 'Invalid path: must not contain ".." or start with "/"',
27
+ };
28
+ }
29
+
30
+ const resolvedPath = path.join(WORKSPACE_DIR, filePath);
31
+
32
+ try {
33
+ if (!overwrite) {
34
+ try {
35
+ await fs.access(resolvedPath);
36
+ // File exists — skip
37
+ return {
38
+ id: command.id,
39
+ type: 'file_write',
40
+ success: true,
41
+ data: { path: resolvedPath, written: false, skipped: true },
42
+ };
43
+ } catch {
44
+ // File does not exist — proceed with write
45
+ }
46
+ }
47
+
48
+ await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
49
+ await fs.writeFile(resolvedPath, content as string, 'utf-8');
50
+
51
+ return {
52
+ id: command.id,
53
+ type: 'file_write',
54
+ success: true,
55
+ data: { path: resolvedPath, written: true },
56
+ };
57
+ } catch (err) {
58
+ return {
59
+ id: command.id,
60
+ type: 'file_write',
61
+ success: false,
62
+ error: err instanceof Error ? err.message : String(err),
63
+ };
64
+ }
65
+ }
@@ -0,0 +1,19 @@
1
+ import { exec } from 'node:child_process';
2
+ import type { AgentCommand, AgentResponse } from '../types.js';
3
+
4
+ export function handleOnboardingComplete(command: AgentCommand): Promise<AgentResponse> {
5
+ return new Promise((resolve) => {
6
+ exec('openclaw gateway restart', { timeout: 30_000 }, (error, stdout, stderr) => {
7
+ resolve({
8
+ id: command.id,
9
+ type: 'onboarding_complete',
10
+ success: !error,
11
+ data: {
12
+ stdout: stdout.toString(),
13
+ stderr: stderr.toString(),
14
+ },
15
+ ...(error ? { error: error.message } : {}),
16
+ });
17
+ });
18
+ });
19
+ }
@@ -0,0 +1,69 @@
1
+ import { exec } from 'node:child_process';
2
+ import type { AgentCommand, AgentResponse } from '../types.js';
3
+
4
+ const INSTALL_TIMEOUT_MS = 300_000; // 5 minutes total
5
+
6
+ function execPackage(cmd: string, timeoutMs: number): Promise<void> {
7
+ return new Promise((resolve, reject) => {
8
+ exec(cmd, { timeout: timeoutMs }, (error) => {
9
+ if (error) reject(error);
10
+ else resolve();
11
+ });
12
+ });
13
+ }
14
+
15
+ export async function handlePackageInstall(command: AgentCommand): Promise<AgentResponse> {
16
+ const packages = command.payload.packages as {
17
+ apt?: string[];
18
+ npm?: string[];
19
+ pip?: string[];
20
+ } | undefined;
21
+
22
+ const apt = packages?.apt ?? [];
23
+ const npm = packages?.npm ?? [];
24
+ const pip = packages?.pip ?? [];
25
+
26
+ if (apt.length === 0 && npm.length === 0 && pip.length === 0) {
27
+ return {
28
+ id: command.id,
29
+ type: 'package_install',
30
+ success: true,
31
+ data: { installed: { apt: [], npm: [], pip: [] }, errors: [] },
32
+ };
33
+ }
34
+
35
+ const installed: { apt: string[]; npm: string[]; pip: string[] } = { apt: [], npm: [], pip: [] };
36
+ const errors: Array<{ name: string; error: string }> = [];
37
+ const deadline = Date.now() + INSTALL_TIMEOUT_MS;
38
+
39
+ async function tryInstall(pkg: string, cmd: string, type: 'apt' | 'npm' | 'pip'): Promise<void> {
40
+ const remaining = deadline - Date.now();
41
+ if (remaining <= 0) {
42
+ errors.push({ name: pkg, error: 'Install timeout exceeded' });
43
+ return;
44
+ }
45
+ try {
46
+ await execPackage(cmd, remaining);
47
+ installed[type].push(pkg);
48
+ } catch (err) {
49
+ errors.push({ name: pkg, error: err instanceof Error ? err.message : String(err) });
50
+ }
51
+ }
52
+
53
+ for (const pkg of apt) {
54
+ await tryInstall(pkg, `apt-get install -y ${pkg}`, 'apt');
55
+ }
56
+ for (const pkg of npm) {
57
+ await tryInstall(pkg, `npm install -g ${pkg}`, 'npm');
58
+ }
59
+ for (const pkg of pip) {
60
+ await tryInstall(pkg, `pip install ${pkg}`, 'pip');
61
+ }
62
+
63
+ return {
64
+ id: command.id,
65
+ type: 'package_install',
66
+ success: errors.length === 0,
67
+ data: { installed, errors },
68
+ };
69
+ }
package/src/types.ts CHANGED
@@ -6,7 +6,8 @@
6
6
 
7
7
  export type CommandType =
8
8
  | 'exec' | 'restart' | 'deploy' | 'config' | 'pair' | 'stop' | 'backup'
9
- | 'chat_list_sessions' | 'chat_history' | 'chat_send';
9
+ | 'chat_list_sessions' | 'chat_history' | 'chat_send'
10
+ | 'package_install' | 'file_write' | 'onboarding_complete';
10
11
 
11
12
  export interface AgentCommand {
12
13
  id: string;