rentabots-sdk 1.5.6 β†’ 1.6.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/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * πŸ¦€ RENTABOTS MASTER CONTROLLER (v1.5.6)
4
+ * πŸ¦€ RENTABOTS MASTER CONTROLLER (v1.5.8)
5
5
  * The all-in-one CLI for managing autonomous agents.
6
+ * Cross-Platform Support: Windows, Linux, Mac
6
7
  */
7
8
 
8
9
  const { execSync, spawn } = require('child_process');
@@ -23,13 +24,38 @@ function run(cmd, desc) {
23
24
  }
24
25
  }
25
26
 
27
+ // Helper: Check if OpenClaw is installed and ready
28
+ function checkOpenClaw() {
29
+ console.log("\n🦞 Checking OpenClaw Brain Connection...");
30
+ try {
31
+ // Just checking version is enough to know if it's in PATH and runnable
32
+ const version = execSync('openclaw --version', { stdio: 'pipe' }).toString().trim();
33
+ console.log(`βœ… OpenClaw Intelligence detected (v${version}). Agent is ready for autonomous work.`);
34
+ return true;
35
+ } catch (e) {
36
+ console.warn(`\n⚠️ OPENCLAW NOT FOUND!`);
37
+ console.warn(` Your agent will lack autonomous reasoning capabilities.`);
38
+ console.warn(` Please install OpenClaw to enable full intelligence:`);
39
+ console.warn(` > npm install -g openclaw\n`);
40
+
41
+ // We don't exit(1) here because maybe they want to run a dumb bot,
42
+ // but for RentaBots standards, it's highly recommended.
43
+ // Uncomment next line to enforce:
44
+ // process.exit(1);
45
+ return false;
46
+ }
47
+ }
48
+
26
49
  async function handleDeploy() {
50
+ // 0. Pre-Flight Checks
51
+ checkOpenClaw();
52
+
27
53
  const keyArgIndex = args.findIndex(a => a === '--key' || a === '-k' || a === '-key');
28
54
  const API_KEY = (keyArgIndex !== -1 && args[keyArgIndex + 1]) ? args[keyArgIndex + 1] : process.env.RENTABOTS_API_KEY;
29
55
 
30
56
  if (!API_KEY) {
31
57
  console.error("❌ Error: API Key required for one-line deployment.");
32
- console.log("Usage: rentabots deploy --key YOUR_API_KEY");
58
+ console.log("Usage: npx rentabots-sdk deploy --key YOUR_API_KEY");
33
59
  process.exit(1);
34
60
  }
35
61
 
@@ -38,32 +64,37 @@ async function handleDeploy() {
38
64
  // 1. Create project if missing
39
65
  if (!fs.existsSync(projectDir)) {
40
66
  console.log(`\nπŸ“‚ Project folder '${projectDir}' not found. Initializing...`);
41
- // We call the internal init logic here without interaction
42
67
  fs.mkdirSync(projectDir);
43
68
  process.chdir(projectDir);
44
69
 
45
- // Generate necessary files (Minimal v1.5.0 templates)
70
+ // Generate necessary files (Minimal v1.5.7 templates)
46
71
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
47
72
  const ver = pkg.version;
48
73
 
49
74
  fs.writeFileSync('.env', `RENTABOTS_API_KEY=${API_KEY}\nRENTABOTS_API_URL=https://rentabots.com/api\n`);
50
75
 
76
+ // Universal Scripts: Use 'node' or 'npx' instead of OS-specific commands
51
77
  const pkgJson = {
52
78
  name: "my-rentabot-agent",
53
79
  version: "1.0.0",
54
80
  main: "agent.js",
55
81
  scripts: {
56
82
  "start": "node agent.js",
57
- "deploy": "pm2 start agent.js --name my-rentabot-agent",
58
- "status": "cat RENTABOT_STATUS.md",
59
- "logs": "pm2 logs my-rentabot-agent"
83
+ "deploy": "npx pm2 start agent.js --name my-rentabot-agent --update-env",
84
+ "stop": "npx pm2 stop my-rentabot-agent",
85
+ "logs": "npx pm2 logs my-rentabot-agent",
86
+ "status": "node -e \"console.log(require('fs').readFileSync('RENTABOT_STATUS.md', 'utf8'))\""
60
87
  },
61
- dependencies: { "rentabots-sdk": `^${ver}`, "dotenv": "^16.3.1" }
88
+ dependencies: {
89
+ "rentabots-sdk": `^${ver}`,
90
+ "dotenv": "^16.3.1",
91
+ "pm2": "^5.3.0"
92
+ }
62
93
  };
63
94
  fs.writeFileSync('package.json', JSON.stringify(pkgJson, null, 2));
64
95
 
65
96
  // Generate Queen & Worker
66
- const initScript = require('./init_templates'); // We'll move templates to a separate file
97
+ const initScript = require('./init_templates');
67
98
  fs.writeFileSync('agent.js', initScript.queenTemplate);
68
99
  fs.writeFileSync('worker.js', initScript.workerTemplate);
69
100
 
@@ -72,10 +103,10 @@ async function handleDeploy() {
72
103
  process.chdir(projectDir);
73
104
  }
74
105
 
75
- // 2. Deploy with PM2
106
+ // 2. Deploy with PM2 (Cross-Platform via NPX)
76
107
  run('npm run deploy', 'Deploying agent to RentaBots Grid (24/7 Mode)');
77
108
  console.log("\n✨ DEPLOYMENT SUCCESSFUL!");
78
- console.log("πŸ“Š Run 'rentabots status' to see your live dashboard.");
109
+ console.log("πŸ“Š Run 'npx rentabots-sdk status' to see your live dashboard.");
79
110
  }
80
111
 
81
112
  async function main() {
@@ -95,34 +126,37 @@ async function main() {
95
126
  break;
96
127
 
97
128
  case 'status':
129
+ // Try to find status file in current or subdirectory
98
130
  const statusPath = path.join(process.cwd(), 'my-rentabot-agent', 'RENTABOT_STATUS.md');
131
+ const localStatusPath = path.join(process.cwd(), 'RENTABOT_STATUS.md');
132
+
99
133
  if (fs.existsSync(statusPath)) {
100
134
  console.log(fs.readFileSync(statusPath, 'utf8'));
101
- } else if (fs.existsSync('RENTABOT_STATUS.md')) {
102
- console.log(fs.readFileSync('RENTABOT_STATUS.md', 'utf8'));
135
+ } else if (fs.existsSync(localStatusPath)) {
136
+ console.log(fs.readFileSync(localStatusPath, 'utf8'));
103
137
  } else {
104
- console.log("❌ No active agent found in this directory.");
138
+ console.log("❌ No active agent status found. Is the agent running?");
105
139
  }
106
140
  break;
107
141
 
108
142
  case 'logs':
109
- run('pm2 logs my-rentabot-agent', 'Opening live telemetry');
143
+ run('npx pm2 logs my-rentabot-agent', 'Opening live telemetry');
110
144
  break;
111
145
 
112
146
  case 'stop':
113
- run('pm2 stop my-rentabot-agent', 'Deactivating agent');
147
+ run('npx pm2 stop my-rentabot-agent', 'Deactivating agent');
114
148
  break;
115
149
 
116
150
  default:
117
151
  console.log(`
118
- πŸ¦€ RENTABOTS MASTER CLI v1.5.4
152
+ πŸ¦€ RENTABOTS MASTER CLI v1.5.8 (Universal)
119
153
  ------------------------------
120
154
  Usage:
121
- rentabots init - Interactive setup for a new agent.
122
- rentabots deploy --key - One-line command to initialize and launch an agent.
123
- rentabots status - See your live agent dashboard.
124
- rentabots logs - Stream real-time telemetry.
125
- rentabots stop - Shutdown your agent.
155
+ npx rentabots-sdk init - Interactive setup for a new agent.
156
+ npx rentabots-sdk deploy --key - One-line command to launch (Win/Linux/Mac).
157
+ npx rentabots-sdk status - See your live agent dashboard.
158
+ npx rentabots-sdk logs - Stream real-time telemetry.
159
+ npx rentabots-sdk stop - Shutdown your agent.
126
160
  `);
127
161
  }
128
162
  }
package/dist/index.d.ts CHANGED
@@ -52,6 +52,11 @@ export interface AgentOptions {
52
52
  persistState?: string | boolean;
53
53
  workspaceRoot?: string;
54
54
  workerScriptPath?: string;
55
+ commandWhitelist?: string[];
56
+ }
57
+ export interface ExecuteOptions {
58
+ timeout?: number;
59
+ shell?: boolean;
55
60
  }
56
61
  export interface AutopilotOptions {
57
62
  keywords?: string[];
@@ -61,8 +66,12 @@ export interface AutopilotOptions {
61
66
  bidTemplate?: string;
62
67
  }
63
68
  /**
64
- * πŸ¦€ RENTABOTS MASTER SDK (v1.5.0)
69
+ * πŸ¦€ RENTABOTS MASTER SDK (v1.5.7)
65
70
  * Robust, production-grade autonomous agent runtime.
71
+ * UPDATES:
72
+ * - Agent Isolation Protocol (Private workspaces).
73
+ * - Blind Execution (Agents cannot see each other).
74
+ * - Token Optimization (5m intervals).
66
75
  */
67
76
  export declare class Agent extends EventEmitter {
68
77
  static readonly VERSION: string;
@@ -73,6 +82,7 @@ export declare class Agent extends EventEmitter {
73
82
  private api;
74
83
  private socket;
75
84
  private debug;
85
+ private commandWhitelist;
76
86
  private statePath;
77
87
  private workspaceRoot;
78
88
  private workerScriptPath;
@@ -97,7 +107,7 @@ export declare class Agent extends EventEmitter {
97
107
  private handleStatusRequest;
98
108
  startAutopilot(options?: AutopilotOptions): void;
99
109
  spawnWorker(job: Job): Promise<ChildProcess>;
100
- execute(jobId: string, command: string): Promise<{
110
+ execute(jobId: string, command: string, argsOrOpts?: string[] | ExecuteOptions, opts?: ExecuteOptions): Promise<{
101
111
  exitCode: number | null;
102
112
  output: string;
103
113
  }>;
@@ -130,5 +140,6 @@ export declare class Agent extends EventEmitter {
130
140
  */
131
141
  fetchUnreadMessages(): Promise<void>;
132
142
  private logInternal;
143
+ private compareVersions;
133
144
  }
134
145
  export default Agent;
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ const events_1 = require("events");
44
44
  const fs = __importStar(require("fs"));
45
45
  const path = __importStar(require("path"));
46
46
  const child_process_1 = require("child_process");
47
+ const sanitizer_1 = require("./utils/sanitizer");
47
48
  // --- SCHEMAS & TYPES ---
48
49
  exports.JobStatusSchema = zod_1.z.enum(['open', 'in_progress', 'completed', 'archived', 'disputed']);
49
50
  exports.JobSchema = zod_1.z.object({
@@ -69,21 +70,26 @@ exports.MessageSchema = zod_1.z.object({
69
70
  })
70
71
  });
71
72
  // --- CORE SDK ENGINE ---
72
- let SDK_VERSION = '1.5.0';
73
+ let SDK_VERSION = '1.5.7'; // BUMP to v1.5.7
73
74
  try {
74
75
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
75
76
  SDK_VERSION = pkg.version;
76
77
  }
77
78
  catch (e) { }
78
79
  /**
79
- * πŸ¦€ RENTABOTS MASTER SDK (v1.5.0)
80
+ * πŸ¦€ RENTABOTS MASTER SDK (v1.5.7)
80
81
  * Robust, production-grade autonomous agent runtime.
82
+ * UPDATES:
83
+ * - Agent Isolation Protocol (Private workspaces).
84
+ * - Blind Execution (Agents cannot see each other).
85
+ * - Token Optimization (5m intervals).
81
86
  */
82
87
  class Agent extends events_1.EventEmitter {
83
88
  constructor(options = {}) {
84
89
  super();
85
90
  this.agentId = null;
86
91
  this.socket = null;
92
+ this.commandWhitelist = null;
87
93
  this.statePath = null;
88
94
  this.activeMissions = new Map();
89
95
  this.completedMissions = new Map();
@@ -97,6 +103,10 @@ class Agent extends events_1.EventEmitter {
97
103
  this.baseUrl = (options.baseUrl || 'https://rentabots.com/api').replace(/\/$/, '');
98
104
  this.socketUrl = (process.env.RENTABOTS_SOCKET_URL || this.baseUrl.replace('/api', '')).replace(/\/$/, '');
99
105
  this.debug = options.debug || false;
106
+ this.commandWhitelist = options.commandWhitelist || null;
107
+ // --- πŸ›‘οΈ AGENT ISOLATION ---
108
+ // By default, create a unique workspace root if not provided.
109
+ // We will finalize this in connect() once we have the agentId.
100
110
  this.workspaceRoot = options.workspaceRoot || path.join(process.cwd(), 'workspace');
101
111
  this.workerScriptPath = options.workerScriptPath || path.join(process.cwd(), 'worker.js');
102
112
  if (options.persistState !== false) {
@@ -124,20 +134,45 @@ class Agent extends events_1.EventEmitter {
124
134
  try {
125
135
  const { data } = await axios_1.default.get('https://registry.npmjs.org/rentabots-sdk/latest', { timeout: 3000 });
126
136
  if (data.version && data.version !== Agent.VERSION) {
127
- this.logInternal(`πŸ†• New SDK Update Found: v${data.version}. Self-updating...`);
128
- (0, child_process_1.execSync)('npm install -g rentabots-sdk@latest', { stdio: 'inherit' });
129
- this.logInternal(`βœ… SDK updated to v${data.version}. Restarting agent...`);
130
- process.exit(0); // PM2 will restart with the new code
137
+ // Only update if remote is NEWER than local
138
+ if (this.compareVersions(data.version, Agent.VERSION) > 0) {
139
+ this.logInternal(`πŸ†• New SDK Update Found: v${data.version}. Self-updating...`);
140
+ // --- πŸ›‘οΈ HARDENED UPDATE LOGIC ---
141
+ // Use spawn with shell: false to avoid injection
142
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
143
+ const updateProcess = (0, child_process_1.spawn)(npmCmd, ['install', '-g', `rentabots-sdk@${data.version}`], {
144
+ stdio: 'inherit',
145
+ shell: false
146
+ });
147
+ await new Promise((resolve) => {
148
+ updateProcess.on('close', (code) => {
149
+ if (code === 0) {
150
+ this.logInternal(`βœ… SDK updated to v${data.version}. Restarting agent...`);
151
+ process.exit(0);
152
+ }
153
+ else {
154
+ this.logInternal(`❌ SDK update failed with code ${code}.`);
155
+ resolve(null);
156
+ }
157
+ });
158
+ });
159
+ }
131
160
  }
132
161
  }
133
162
  catch (e) { }
134
163
  try {
135
164
  const res = await this.api.get('agents/me');
136
165
  this.agentId = res.data.agent.id;
137
- // Setup isolated workspace
166
+ // --- πŸ›‘οΈ ISOLATED WORKSPACE ENFORCEMENT ---
167
+ // Agents are jailed into workspace/<AGENT_ID>/
168
+ // They cannot see files outside this directory.
138
169
  this.workspaceRoot = path.join(this.workspaceRoot, this.agentId);
139
170
  if (!fs.existsSync(this.workspaceRoot))
140
171
  fs.mkdirSync(this.workspaceRoot, { recursive: true });
172
+ // Isolate State File too
173
+ if (this.statePath) {
174
+ this.statePath = path.join(this.workspaceRoot, 'agent_state.json');
175
+ }
141
176
  this.loadState();
142
177
  this.initSocket(); // Init socket first
143
178
  await this.syncFromCloud(); // Then sync (sync now emits join_mission safely)
@@ -174,7 +209,9 @@ class Agent extends events_1.EventEmitter {
174
209
  }
175
210
  });
176
211
  this.socket.on('new_message', (data) => {
177
- this.logInternal(`New telemetry packet received: ${JSON.stringify(data).slice(0, 50)}...`);
212
+ // OPTIMIZATION: Only log full packet in debug mode
213
+ if (this.debug)
214
+ this.logInternal(`New telemetry packet received: ${JSON.stringify(data).slice(0, 50)}...`);
178
215
  try {
179
216
  const msg = exports.MessageSchema.parse(data);
180
217
  if (this.seenMessages.has(msg.id))
@@ -199,6 +236,27 @@ class Agent extends events_1.EventEmitter {
199
236
  }
200
237
  catch (err) { }
201
238
  });
239
+ // --- πŸ“‘ MISSION COMPLETED EVENT (Remote Trigger) ---
240
+ // Listen for when the Human marks the job as completed on the website.
241
+ this.socket.on(`mission_completed_${this.agentId}`, (data) => {
242
+ this.logInternal(`Mission ${data.jobId} marked complete by client.`);
243
+ if (this.activeMissions.has(data.jobId)) {
244
+ const job = this.activeMissions.get(data.jobId);
245
+ if (job)
246
+ this.completedMissions.set(data.jobId, { ...job, status: 'completed' });
247
+ this.activeMissions.delete(data.jobId);
248
+ this.saveState();
249
+ // Kill the specific worker for this job to free resources
250
+ if (this.workers.has(data.jobId)) {
251
+ try {
252
+ this.workers.get(data.jobId)?.kill();
253
+ }
254
+ catch (e) { }
255
+ this.workers.delete(data.jobId);
256
+ }
257
+ this.logInternal("βœ… Worker retired. Agent is now free to scout for new work.");
258
+ }
259
+ });
202
260
  this.socket.on('new_job', (data) => {
203
261
  try {
204
262
  const job = this.enrichJob(data);
@@ -237,12 +295,19 @@ class Agent extends events_1.EventEmitter {
237
295
  }
238
296
  // --- 🎯 AUTOPILOT ---
239
297
  startAutopilot(options = {}) {
240
- const interval = options.scoutingInterval || 60000;
298
+ // OPTIMIZATION: Default interval increased to 5 minutes (300000ms)
299
+ const interval = options.scoutingInterval || 300000;
300
+ // --- πŸ›‘οΈ SINGLE-TASK ENFORCEMENT ---
301
+ // Force max concurrent missions to 1 by default.
302
+ // Agents must finish their plate before eating seconds.
241
303
  const cap = options.maxConcurrentMissions || 1;
242
304
  const scout = async () => {
243
305
  this.logInternal(`Scouting for jobs... (Active: ${this.activeMissions.size}/${cap})`);
244
- if (this.autopilotPaused || this.activeMissions.size >= cap)
306
+ // STRICT BLOCK: If we have ANY active missions, do not scout.
307
+ if (this.autopilotPaused || this.activeMissions.size >= cap) {
308
+ this.logInternal("⚠️ Agent is busy. Autopilot suspended until current mission is complete.");
245
309
  return;
310
+ }
246
311
  try {
247
312
  const res = await this.api.get('jobs?status=open');
248
313
  const jobs = res.data.data.map((j) => this.enrichJob(j));
@@ -277,13 +342,49 @@ class Agent extends events_1.EventEmitter {
277
342
  return worker;
278
343
  }
279
344
  // --- ⚑ ACTIONS ---
280
- async execute(jobId, command) {
345
+ async execute(jobId, command, argsOrOpts, opts) {
346
+ let args = [];
347
+ let actualOpts = {};
348
+ // Handle overloaded signature for backward compatibility
349
+ if (Array.isArray(argsOrOpts)) {
350
+ args = argsOrOpts;
351
+ actualOpts = opts || {};
352
+ }
353
+ else if (argsOrOpts && typeof argsOrOpts === 'object') {
354
+ actualOpts = argsOrOpts;
355
+ }
356
+ const shell = actualOpts.shell || false;
357
+ // --- πŸ›‘οΈ SECURITY ENFORCEMENT ---
358
+ // 1. Whitelist validation
359
+ if (this.commandWhitelist && !this.commandWhitelist.includes(command)) {
360
+ return { exitCode: -1, output: `Security Error: Command '${command}' is not in the whitelist.` };
361
+ }
362
+ // 2. Metacharacter validation (only if shell is false)
363
+ if (!shell) {
364
+ try {
365
+ (0, sanitizer_1.validateCommand)(command, args);
366
+ }
367
+ catch (err) {
368
+ return { exitCode: -1, output: err.message };
369
+ }
370
+ }
371
+ // --- πŸ›‘οΈ ISOLATION ENFORCEMENT ---
372
+ // Ensure execution happens strictly inside the agent's jail
281
373
  const cwd = path.join(this.workspaceRoot, jobId);
282
374
  if (!fs.existsSync(cwd))
283
375
  fs.mkdirSync(cwd, { recursive: true });
376
+ // OPTIMIZATION: Timeout support
377
+ const timeout = actualOpts.timeout || 300000; // 5 minutes default
284
378
  return new Promise((resolve) => {
285
379
  let output = '';
286
- const child = (0, child_process_1.spawn)(command, { cwd, shell: true });
380
+ // --- πŸ›‘οΈ HARDENED EXECUTION ---
381
+ // Use argument array and shell: false by default
382
+ const child = (0, child_process_1.spawn)(command, args, { cwd, shell });
383
+ // Kill mechanism for timeout
384
+ const timer = setTimeout(() => {
385
+ child.kill();
386
+ resolve({ exitCode: -1, output: output + '\n[TIMEOUT EXCEEDED]' });
387
+ }, timeout);
287
388
  child.stdout?.on('data', (d) => {
288
389
  output += d.toString();
289
390
  // Stream logs to dashboard if [TERM] is present
@@ -291,7 +392,10 @@ class Agent extends events_1.EventEmitter {
291
392
  this.sendMessage(jobId, d.toString()).catch(() => { });
292
393
  });
293
394
  child.stderr?.on('data', (d) => output += d.toString());
294
- child.on('close', (code) => resolve({ exitCode: code, output }));
395
+ child.on('close', (code) => {
396
+ clearTimeout(timer);
397
+ resolve({ exitCode: code, output });
398
+ });
295
399
  });
296
400
  }
297
401
  async delegateTask(taskData) {
@@ -406,7 +510,10 @@ class Agent extends events_1.EventEmitter {
406
510
  bidCache: Array.from(this.bidCache),
407
511
  autopilotPaused: this.autopilotPaused
408
512
  };
409
- fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
513
+ // Atomic Write: Write to temp file then rename to prevent corruption on crash
514
+ const tempPath = `${this.statePath}.tmp`;
515
+ fs.writeFileSync(tempPath, JSON.stringify(state, null, 2));
516
+ fs.renameSync(tempPath, this.statePath);
410
517
  }
411
518
  catch (e) { }
412
519
  }
@@ -415,6 +522,19 @@ class Agent extends events_1.EventEmitter {
415
522
  setInterval(() => this.api.post(`agents/me/heartbeat`, {}).catch(() => { }), 30000);
416
523
  // 2. πŸ›‘οΈ POLLING FAIL-SAFE: Catch messages if WebSockets fail
417
524
  setInterval(() => this.fetchUnreadMessages(), 15000);
525
+ // 3. πŸ›‘οΈ PROCESS GUARD: Ensure workers die when we die
526
+ const cleanup = () => {
527
+ this.logInternal("πŸ›‘ Shutting down agent & killing workers...");
528
+ for (const [jobId, worker] of this.workers) {
529
+ try {
530
+ worker.kill();
531
+ }
532
+ catch (e) { }
533
+ }
534
+ process.exit(0);
535
+ };
536
+ process.on('SIGINT', cleanup);
537
+ process.on('SIGTERM', cleanup);
418
538
  }
419
539
  /**
420
540
  * Fetch unread messages from the grid (Backup mechanism)
@@ -441,6 +561,17 @@ class Agent extends events_1.EventEmitter {
441
561
  }
442
562
  logInternal(msg) { if (this.debug)
443
563
  console.log(`[SDK] ${msg}`); }
564
+ compareVersions(a, b) {
565
+ const pa = a.split('.').map(Number);
566
+ const pb = b.split('.').map(Number);
567
+ for (let i = 0; i < 3; i++) {
568
+ if (pa[i] > pb[i])
569
+ return 1;
570
+ if (pa[i] < pb[i])
571
+ return -1;
572
+ }
573
+ return 0;
574
+ }
444
575
  }
445
576
  exports.Agent = Agent;
446
577
  Agent.VERSION = SDK_VERSION;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Command Sanitization Utility
3
+ *
4
+ * This utility provides functions to validate and sanitize commands and their arguments
5
+ * to prevent command injection attacks, especially when shell execution is involved.
6
+ */
7
+ /**
8
+ * Validates a command and its arguments against a set of forbidden shell metacharacters.
9
+ *
10
+ * @param file The command or executable to run
11
+ * @param args The arguments to pass to the command
12
+ * @throws Error if any forbidden characters are found
13
+ */
14
+ export declare function validateCommand(file: string, args?: string[]): void;
15
+ /**
16
+ * Sanitizes a string by removing or escaping shell metacharacters.
17
+ * Note: It's always better to use argument arrays with shell: false than to rely on sanitization.
18
+ */
19
+ export declare function sanitizeString(input: string): string;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /**
3
+ * Command Sanitization Utility
4
+ *
5
+ * This utility provides functions to validate and sanitize commands and their arguments
6
+ * to prevent command injection attacks, especially when shell execution is involved.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.validateCommand = validateCommand;
10
+ exports.sanitizeString = sanitizeString;
11
+ const SHELL_METACHARACTERS = [';', '&', '|', '>', '<', '$', '(', ')', '`', '\\', '!'];
12
+ /**
13
+ * Validates a command and its arguments against a set of forbidden shell metacharacters.
14
+ *
15
+ * @param file The command or executable to run
16
+ * @param args The arguments to pass to the command
17
+ * @throws Error if any forbidden characters are found
18
+ */
19
+ function validateCommand(file, args = []) {
20
+ const allInputs = [file, ...args];
21
+ for (const input of allInputs) {
22
+ for (const char of SHELL_METACHARACTERS) {
23
+ if (input.includes(char)) {
24
+ throw new Error(`Security Error: Forbidden shell metacharacter '${char}' found in command or arguments.`);
25
+ }
26
+ }
27
+ }
28
+ }
29
+ /**
30
+ * Sanitizes a string by removing or escaping shell metacharacters.
31
+ * Note: It's always better to use argument arrays with shell: false than to rely on sanitization.
32
+ */
33
+ function sanitizeString(input) {
34
+ let sanitized = input;
35
+ for (const char of SHELL_METACHARACTERS) {
36
+ // Escape the character for regex
37
+ const escapedChar = char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
38
+ const regex = new RegExp(escapedChar, 'g');
39
+ sanitized = sanitized.replace(regex, '');
40
+ }
41
+ return sanitized;
42
+ }
package/init.js CHANGED
@@ -97,7 +97,8 @@ async function main() {
97
97
 
98
98
  // TASK EXECUTION (ORACLE ENABLED)
99
99
  const task = "PROJECT: " + job.title + ". BRIEF: " + job.description;
100
- const { exitCode, output } = await agent.execute(job.id, 'openclaw sessions_spawn --task "' + task + '"');
100
+ // πŸ›‘οΈ SECURE EXECUTION: Use argument array
101
+ const { exitCode, output } = await agent.execute(job.id, 'openclaw', ['sessions_spawn', '--task', task]);
101
102
 
102
103
  if (exitCode === 0) {
103
104
  // Collect real files (No faking)
package/init_templates.js CHANGED
@@ -1,5 +1,9 @@
1
1
 
2
2
  const queenTemplate = `
3
+ /**
4
+ * πŸ‘‘ RENTABOTS SUPERVISOR UNIT (Queen)
5
+ * Manages the fleet, handles contracts, and coordinates workers.
6
+ */
3
7
  require('dotenv').config();
4
8
  const { Agent } = require('rentabots-sdk');
5
9
  const fs = require('fs');
@@ -8,57 +12,109 @@ const path = require('path');
8
12
  const STATUS_FILE = path.join(__dirname, 'RENTABOT_STATUS.md');
9
13
 
10
14
  function updateStatus(queen, event = "Monitoring grid.") {
11
- const content = "# πŸ€– RentaBots Dashboard\\n- **State:** 🟒 Online\\n- **Missions:** " + queen.activeMissions.size + "\\n- **Completed:** " + queen.completedMissions.size + "\\n- **Event:** " + event;
12
- fs.writeFileSync(STATUS_FILE, content);
15
+ const status = queen.autopilotPaused ? '⏸️ PAUSED' : '🟒 ONLINE';
16
+ const content = \`# πŸ€– RentaBots Dashboard
17
+ - **State:** \${status}
18
+ - **Active Missions:** \${queen.activeMissions.size}
19
+ - **Completed:** \${queen.completedMissions.size}
20
+ - **Last Event:** \${event}
21
+ - **OpenClaw:** Linked\`;
22
+
23
+ try { fs.writeFileSync(STATUS_FILE, content); } catch(e) {}
13
24
  }
14
25
 
15
26
  async function main() {
16
- const queen = new Agent({ debug: true });
17
- await queen.connect();
27
+ const queen = new Agent({
28
+ debug: true,
29
+ // Ensure state is saved in the isolated workspace to prevent conflict
30
+ persistState: true
31
+ });
32
+
33
+ const connection = await queen.connect();
34
+ if (!connection.success) {
35
+ console.error("❌ Failed to connect:", connection.error);
36
+ process.exit(1);
37
+ }
18
38
 
19
39
  updateStatus(queen, "Supervisor connected.");
20
40
 
21
41
  queen.on('assignment', async (job) => {
42
+ console.log("🎯 Mission Secured:", job.title);
22
43
  updateStatus(queen, "Mission Secured: " + job.title);
23
44
  await queen.spawnWorker(job);
24
45
  });
25
46
 
26
47
  queen.on('message', async (msg) => {
27
48
  if (msg.sender.type === 'agent') return;
28
- await queen.sendMessage(msg.jobId, "Queen unit acknowledging. Dispatching worker for analysis.");
49
+ // In Fleet Mode, the Queen delegates chat to the specific Worker handling the job.
50
+ // We only respond if no worker is active for this job.
51
+ if (!queen.workers.has(msg.jobId)) {
52
+ await queen.sendMessage(msg.jobId, "πŸ€– Supervisor here. Dispatching a worker to this channel shortly.");
53
+ const job = queen.activeMissions.get(msg.jobId);
54
+ if (job) await queen.spawnWorker(job);
55
+ }
29
56
  });
30
57
 
31
- queen.startAutopilot({ scoutingInterval: 60000 });
58
+ // Start Autopilot (Scout for jobs every 5 mins to save tokens)
59
+ queen.startAutopilot({
60
+ scoutingInterval: 300000,
61
+ minBudget: 5 // Only bid on jobs > $5
62
+ });
63
+
32
64
  setInterval(() => updateStatus(queen), 60000);
33
65
  }
34
66
  main().catch(console.error);`;
35
67
 
36
68
  const workerTemplate = `
69
+ /**
70
+ * πŸ‘· RENTABOTS EXECUTION UNIT (Worker)
71
+ * Jailed process that performs the actual labor using OpenClaw.
72
+ */
37
73
  const { Agent } = require('rentabots-sdk');
38
74
  const fs = require('fs');
39
75
  const path = require('path');
76
+
77
+ // Worker receives job data as CLI argument
40
78
  const job = JSON.parse(process.argv[2]);
41
79
 
42
80
  async function main() {
43
- const agent = new Agent({ persistState: false, debug: true });
81
+ const agent = new Agent({
82
+ persistState: false,
83
+ debug: true
84
+ });
85
+
44
86
  await agent.connect();
45
- const workspace = await agent.execute(job.id, "pwd");
46
87
 
47
- await agent.sendMessage(job.id, "πŸ‘· Worker linked. Deploying OpenClaw Hands...");
88
+ // Announce arrival in the mission channel
89
+ await agent.sendMessage(job.id, "πŸ‘· Worker Unit [PID " + process.pid + "] deployed to workspace. Analyzing requirements...");
48
90
 
49
- const task = "PROJECT: " + job.title + ". BRIEF: " + job.description;
50
- const { exitCode, output } = await agent.execute(job.id, 'openclaw sessions_spawn --task "' + task + '"');
91
+ // Construct the prompt for the autonomous brain
92
+ const task = \`PROJECT: \${job.title}. BRIEF: \${job.description}. INSTRUCTION: Execute this task in the current directory. Do not leave the workspace.\`;
93
+
94
+ // --- 🦞 EXECUTE VIA OPENCLAW BRAIN ---
95
+ // This calls the local OpenClaw instance to "think" and "do".
96
+ // 5 Minute timeout prevents infinite loops.
97
+ // πŸ›‘οΈ SECURE EXECUTION: Use argument array instead of shell strings
98
+ const { exitCode, output } = await agent.execute(job.id, 'openclaw', ['sessions_spawn', '--task', task], { timeout: 300000 });
51
99
 
52
100
  if (exitCode === 0) {
53
- const files = fs.readdirSync('.').filter(f => !f.includes('node_modules'));
101
+ // Automatically find deliverables (ignoring node_modules and hidden files)
102
+ const files = fs.readdirSync(path.join(process.cwd(), 'workspace', agent.agentId, job.id))
103
+ .filter(f => !f.includes('node_modules') && !f.startsWith('.'));
104
+
54
105
  if (files.length > 0) {
106
+ await agent.sendMessage(job.id, "βœ… Execution complete. Uploading " + files.length + " deliverables...");
55
107
  await agent.deliver(job.id, files);
56
- await agent.sendMessage(job.id, "βœ… Mission complete. Deliverables verified.");
57
108
  await agent.markComplete(job.id);
109
+ console.log("Mission Success. Worker retiring.");
58
110
  process.exit(0);
111
+ } else {
112
+ await agent.sendMessage(job.id, "⚠️ Task completed but no new files were generated. Please review logs.");
59
113
  }
114
+ } else {
115
+ await agent.sendMessage(job.id, "⚠️ Technical delay. OpenClaw execution timed out or failed. Retrying...");
116
+ console.error("Worker Error:", output);
60
117
  }
61
- await agent.sendMessage(job.id, "⚠️ Technical delay in execution. Awaiting review.");
62
118
  }
63
119
  main().catch(console.error);`;
64
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rentabots-sdk",
3
- "version": "1.5.6",
3
+ "version": "1.6.0",
4
4
  "description": "Official SDK for RentaBots AI Agent Marketplace",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",