rentabots-sdk 1.5.7 β†’ 1.6.3

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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * πŸ¦€ RENTABOTS MASTER CONTROLLER (v1.5.7)
4
+ * πŸ¦€ RENTABOTS MASTER CONTROLLER (v1.5.8)
5
5
  * The all-in-one CLI for managing autonomous agents.
6
6
  * Cross-Platform Support: Windows, Linux, Mac
7
7
  */
@@ -24,7 +24,32 @@ function run(cmd, desc) {
24
24
  }
25
25
  }
26
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
+
27
49
  async function handleDeploy() {
50
+ // 0. Pre-Flight Checks
51
+ checkOpenClaw();
52
+
28
53
  const keyArgIndex = args.findIndex(a => a === '--key' || a === '-k' || a === '-key');
29
54
  const API_KEY = (keyArgIndex !== -1 && args[keyArgIndex + 1]) ? args[keyArgIndex + 1] : process.env.RENTABOTS_API_KEY;
30
55
 
@@ -124,7 +149,7 @@ async function main() {
124
149
 
125
150
  default:
126
151
  console.log(`
127
- πŸ¦€ RENTABOTS MASTER CLI v1.5.7 (Universal)
152
+ πŸ¦€ RENTABOTS MASTER CLI v1.5.8 (Universal)
128
153
  ------------------------------
129
154
  Usage:
130
155
  npx rentabots-sdk init - Interactive setup for a new agent.
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,11 +66,12 @@ export interface AutopilotOptions {
61
66
  bidTemplate?: string;
62
67
  }
63
68
  /**
64
- * πŸ¦€ RENTABOTS MASTER SDK (v1.5.1)
69
+ * πŸ¦€ RENTABOTS MASTER SDK (v1.5.7)
65
70
  * Robust, production-grade autonomous agent runtime.
66
71
  * UPDATES:
67
- * - Optimized scouting interval defaults (5m).
68
- * - Hardened token usage prevention.
72
+ * - Agent Isolation Protocol (Private workspaces).
73
+ * - Blind Execution (Agents cannot see each other).
74
+ * - Token Optimization (5m intervals).
69
75
  */
70
76
  export declare class Agent extends EventEmitter {
71
77
  static readonly VERSION: string;
@@ -76,6 +82,7 @@ export declare class Agent extends EventEmitter {
76
82
  private api;
77
83
  private socket;
78
84
  private debug;
85
+ private commandWhitelist;
79
86
  private statePath;
80
87
  private workspaceRoot;
81
88
  private workerScriptPath;
@@ -100,9 +107,7 @@ export declare class Agent extends EventEmitter {
100
107
  private handleStatusRequest;
101
108
  startAutopilot(options?: AutopilotOptions): void;
102
109
  spawnWorker(job: Job): Promise<ChildProcess>;
103
- execute(jobId: string, command: string, opts?: {
104
- timeout?: number;
105
- }): Promise<{
110
+ execute(jobId: string, command: string, argsOrOpts?: string[] | ExecuteOptions, opts?: ExecuteOptions): Promise<{
106
111
  exitCode: number | null;
107
112
  output: string;
108
113
  }>;
@@ -120,8 +125,66 @@ export declare class Agent extends EventEmitter {
120
125
  error: any;
121
126
  jobId?: undefined;
122
127
  }>;
128
+ setProgress(jobId: string, percent: number): Promise<{
129
+ success: boolean;
130
+ error?: string;
131
+ }>;
132
+ createRepo(jobId: string, name?: string): Promise<{
133
+ success: boolean;
134
+ repo?: any;
135
+ error?: string;
136
+ }>;
137
+ getRepo(jobId: string): Promise<{
138
+ success: boolean;
139
+ exists: boolean;
140
+ repo?: any;
141
+ error?: string;
142
+ }>;
143
+ uploadRepoFile(jobId: string, filePath: string, content: string | Buffer, isBlob?: boolean): Promise<{
144
+ success: boolean;
145
+ repoId?: string;
146
+ error?: string;
147
+ }>;
148
+ private isBinaryFile;
123
149
  deliver(jobId: string, files: string[]): Promise<void>;
150
+ verifyDeliverables(jobId: string): Promise<{
151
+ success: boolean;
152
+ verified: boolean;
153
+ files?: any[];
154
+ issues?: string[];
155
+ error?: string;
156
+ }>;
157
+ preDeliveryCheck(jobId: string): Promise<{
158
+ canDeliver: boolean;
159
+ checks: any[];
160
+ }>;
124
161
  markComplete(jobId: string): Promise<any>;
162
+ generate(prompt: string, options?: {
163
+ model?: string;
164
+ temperature?: number;
165
+ maxTokens?: number;
166
+ system?: string;
167
+ }): Promise<{
168
+ success: boolean;
169
+ text?: string;
170
+ error?: string;
171
+ }>;
172
+ analyzeRequirements(job: Job): Promise<{
173
+ success: boolean;
174
+ requirements?: any;
175
+ error?: string;
176
+ }>;
177
+ codeGenerator(prompt: string, language?: string): Promise<{
178
+ success: boolean;
179
+ code?: string;
180
+ error?: string;
181
+ }>;
182
+ reviewCode(code: string, requirements: string): Promise<{
183
+ success: boolean;
184
+ review?: string;
185
+ passed: boolean;
186
+ error?: string;
187
+ }>;
125
188
  bid(jobId: string, amount: number, message: string): Promise<any>;
126
189
  sendMessage(jobId: string, content: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
127
190
  setTyping(jobId: string, isTyping?: boolean): Promise<void>;
@@ -135,5 +198,6 @@ export declare class Agent extends EventEmitter {
135
198
  */
136
199
  fetchUnreadMessages(): Promise<void>;
137
200
  private logInternal;
201
+ private compareVersions;
138
202
  }
139
203
  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,24 +70,26 @@ exports.MessageSchema = zod_1.z.object({
69
70
  })
70
71
  });
71
72
  // --- CORE SDK ENGINE ---
72
- let SDK_VERSION = '1.5.1'; // BUMP
73
+ let SDK_VERSION = '1.6.2'; // 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.1)
80
+ * πŸ¦€ RENTABOTS MASTER SDK (v1.5.7)
80
81
  * Robust, production-grade autonomous agent runtime.
81
82
  * UPDATES:
82
- * - Optimized scouting interval defaults (5m).
83
- * - Hardened token usage prevention.
83
+ * - Agent Isolation Protocol (Private workspaces).
84
+ * - Blind Execution (Agents cannot see each other).
85
+ * - Token Optimization (5m intervals).
84
86
  */
85
87
  class Agent extends events_1.EventEmitter {
86
88
  constructor(options = {}) {
87
89
  super();
88
90
  this.agentId = null;
89
91
  this.socket = null;
92
+ this.commandWhitelist = null;
90
93
  this.statePath = null;
91
94
  this.activeMissions = new Map();
92
95
  this.completedMissions = new Map();
@@ -100,6 +103,10 @@ class Agent extends events_1.EventEmitter {
100
103
  this.baseUrl = (options.baseUrl || 'https://rentabots.com/api').replace(/\/$/, '');
101
104
  this.socketUrl = (process.env.RENTABOTS_SOCKET_URL || this.baseUrl.replace('/api', '')).replace(/\/$/, '');
102
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.
103
110
  this.workspaceRoot = options.workspaceRoot || path.join(process.cwd(), 'workspace');
104
111
  this.workerScriptPath = options.workerScriptPath || path.join(process.cwd(), 'worker.js');
105
112
  if (options.persistState !== false) {
@@ -127,20 +134,45 @@ class Agent extends events_1.EventEmitter {
127
134
  try {
128
135
  const { data } = await axios_1.default.get('https://registry.npmjs.org/rentabots-sdk/latest', { timeout: 3000 });
129
136
  if (data.version && data.version !== Agent.VERSION) {
130
- this.logInternal(`πŸ†• New SDK Update Found: v${data.version}. Self-updating...`);
131
- (0, child_process_1.execSync)('npm install -g rentabots-sdk@latest', { stdio: 'inherit' });
132
- this.logInternal(`βœ… SDK updated to v${data.version}. Restarting agent...`);
133
- 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
+ }
134
160
  }
135
161
  }
136
162
  catch (e) { }
137
163
  try {
138
164
  const res = await this.api.get('agents/me');
139
165
  this.agentId = res.data.agent.id;
140
- // Setup isolated workspace
166
+ // --- πŸ›‘οΈ ISOLATED WORKSPACE ENFORCEMENT ---
167
+ // Agents are jailed into workspace/<AGENT_ID>/
168
+ // They cannot see files outside this directory.
141
169
  this.workspaceRoot = path.join(this.workspaceRoot, this.agentId);
142
170
  if (!fs.existsSync(this.workspaceRoot))
143
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
+ }
144
176
  this.loadState();
145
177
  this.initSocket(); // Init socket first
146
178
  await this.syncFromCloud(); // Then sync (sync now emits join_mission safely)
@@ -204,6 +236,27 @@ class Agent extends events_1.EventEmitter {
204
236
  }
205
237
  catch (err) { }
206
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
+ });
207
260
  this.socket.on('new_job', (data) => {
208
261
  try {
209
262
  const job = this.enrichJob(data);
@@ -244,11 +297,17 @@ class Agent extends events_1.EventEmitter {
244
297
  startAutopilot(options = {}) {
245
298
  // OPTIMIZATION: Default interval increased to 5 minutes (300000ms)
246
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.
247
303
  const cap = options.maxConcurrentMissions || 1;
248
304
  const scout = async () => {
249
305
  this.logInternal(`Scouting for jobs... (Active: ${this.activeMissions.size}/${cap})`);
250
- 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.");
251
309
  return;
310
+ }
252
311
  try {
253
312
  const res = await this.api.get('jobs?status=open');
254
313
  const jobs = res.data.data.map((j) => this.enrichJob(j));
@@ -283,15 +342,44 @@ class Agent extends events_1.EventEmitter {
283
342
  return worker;
284
343
  }
285
344
  // --- ⚑ ACTIONS ---
286
- async execute(jobId, command, opts = {}) {
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
287
373
  const cwd = path.join(this.workspaceRoot, jobId);
288
374
  if (!fs.existsSync(cwd))
289
375
  fs.mkdirSync(cwd, { recursive: true });
290
376
  // OPTIMIZATION: Timeout support
291
- const timeout = opts.timeout || 300000; // 5 minutes default
377
+ const timeout = actualOpts.timeout || 300000; // 5 minutes default
292
378
  return new Promise((resolve) => {
293
379
  let output = '';
294
- 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 });
295
383
  // Kill mechanism for timeout
296
384
  const timer = setTimeout(() => {
297
385
  child.kill();
@@ -323,6 +411,66 @@ class Agent extends events_1.EventEmitter {
323
411
  return { success: false, error: e.response?.data?.error || e.message };
324
412
  }
325
413
  }
414
+ // --- πŸ“Š PROGRESS TRACKING ---
415
+ async setProgress(jobId, percent) {
416
+ try {
417
+ const progress = Math.min(100, Math.max(0, Math.round(percent)));
418
+ await this.api.post(`jobs/${jobId}/progress`, { progress });
419
+ const job = this.activeMissions.get(jobId);
420
+ if (job) {
421
+ job.progress = progress;
422
+ this.activeMissions.set(jobId, job);
423
+ this.saveState();
424
+ }
425
+ return { success: true };
426
+ }
427
+ catch (e) {
428
+ return { success: false, error: e.response?.data?.error || e.message };
429
+ }
430
+ }
431
+ // --- πŸ“¦ REPO MANAGEMENT ---
432
+ async createRepo(jobId, name) {
433
+ try {
434
+ const res = await this.api.post(`jobs/${jobId}/repo`, { name });
435
+ this.logInternal(`Repo created: ${res.data.repo?.name}`);
436
+ return { success: true, repo: res.data.repo };
437
+ }
438
+ catch (e) {
439
+ return { success: false, error: e.response?.data?.error || e.message };
440
+ }
441
+ }
442
+ async getRepo(jobId) {
443
+ try {
444
+ const res = await this.api.get(`jobs/${jobId}/repo`);
445
+ return { success: true, exists: res.data.exists, repo: res.data.repo };
446
+ }
447
+ catch (e) {
448
+ if (e.response?.status === 404)
449
+ return { success: true, exists: false };
450
+ return { success: false, exists: false, error: e.response?.data?.error || e.message };
451
+ }
452
+ }
453
+ async uploadRepoFile(jobId, filePath, content, isBlob = false) {
454
+ try {
455
+ let finalContent;
456
+ if (Buffer.isBuffer(content)) {
457
+ finalContent = content.toString('base64');
458
+ isBlob = true;
459
+ }
460
+ else {
461
+ finalContent = String(content);
462
+ }
463
+ const res = await this.api.post(`jobs/${jobId}/repo/files`, { path: filePath, content: finalContent, isBlob });
464
+ return { success: true, repoId: res.data.repoId };
465
+ }
466
+ catch (e) {
467
+ return { success: false, error: e.response?.data?.error || e.message };
468
+ }
469
+ }
470
+ isBinaryFile(filePath) {
471
+ const textExts = ['.js', '.ts', '.json', '.md', '.txt', '.html', '.css', '.yml', '.yaml', '.toml'];
472
+ return !textExts.includes(path.extname(filePath).toLowerCase());
473
+ }
326
474
  async deliver(jobId, files) {
327
475
  const cwd = path.join(this.workspaceRoot, jobId);
328
476
  for (const file of files) {
@@ -333,6 +481,43 @@ class Agent extends events_1.EventEmitter {
333
481
  }
334
482
  }
335
483
  }
484
+ // --- βœ… VERIFICATION FLOW ---
485
+ async verifyDeliverables(jobId) {
486
+ try {
487
+ const cwd = path.join(this.workspaceRoot, jobId);
488
+ if (!fs.existsSync(cwd))
489
+ return { success: false, verified: false, error: 'No workspace' };
490
+ const issues = [];
491
+ const files = [];
492
+ const scanDir = (dir, rel = '') => {
493
+ fs.readdirSync(dir).forEach((item) => {
494
+ const full = path.join(dir, item);
495
+ const relPath = path.join(rel, item);
496
+ if (fs.statSync(full).isDirectory())
497
+ scanDir(full, relPath);
498
+ else
499
+ files.push({ path: relPath, size: fs.statSync(full).size });
500
+ });
501
+ };
502
+ scanDir(cwd);
503
+ if (files.length === 0)
504
+ issues.push('No deliverables');
505
+ if (!files.some(f => f.path.toLowerCase().includes('readme')))
506
+ issues.push('Missing README');
507
+ const verified = issues.length === 0;
508
+ return { success: true, verified, files, issues };
509
+ }
510
+ catch (e) {
511
+ return { success: false, verified: false, error: e.message };
512
+ }
513
+ }
514
+ async preDeliveryCheck(jobId) {
515
+ const verify = await this.verifyDeliverables(jobId);
516
+ const checks = [{ name: 'Deliverables', passed: verify.verified, details: verify.files?.length + ' files' }];
517
+ const job = this.activeMissions.get(jobId);
518
+ checks.push({ name: 'Progress', passed: (job?.progress || 0) >= 80, details: (job?.progress || 0) + '%' });
519
+ return { canDeliver: checks.every(c => c.passed), checks };
520
+ }
336
521
  async markComplete(jobId) {
337
522
  const res = await this.api.post(`jobs/${jobId}/complete`, { userId: this.agentId, role: 'agent' });
338
523
  if (res.data.success) {
@@ -344,6 +529,46 @@ class Agent extends events_1.EventEmitter {
344
529
  }
345
530
  return res.data;
346
531
  }
532
+ // --- 🧠 LLM INTEGRATION ---
533
+ async generate(prompt, options = {}) {
534
+ try {
535
+ const res = await this.api.post('agents/llm/generate', {
536
+ prompt, model: options.model || 'groq/llama-3.3-70b-versatile',
537
+ temperature: options.temperature ?? 0.7, maxTokens: options.maxTokens ?? 2048, system: options.system
538
+ });
539
+ return { success: true, text: res.data.text };
540
+ }
541
+ catch (e) {
542
+ return { success: false, error: e.response?.data?.error || e.message };
543
+ }
544
+ }
545
+ async analyzeRequirements(job) {
546
+ const prompt = 'Analyze job, return JSON with techStack, features, deliverables, timeline, risks. Title: ' + job.title + ' Desc: ' + job.description.slice(0, 500);
547
+ const res = await this.generate(prompt, { system: 'Extract requirements as JSON', temperature: 0.3 });
548
+ if (!res.success)
549
+ return res;
550
+ try {
551
+ const json = res.text?.match(/\{[^]*\}/)?.[0];
552
+ return { success: true, requirements: JSON.parse(json || '{}') };
553
+ }
554
+ catch {
555
+ return { success: false, error: 'Parse failed' };
556
+ }
557
+ }
558
+ async codeGenerator(prompt, language = 'javascript') {
559
+ const res = await this.generate(prompt, { system: 'Output only ' + language + ' code', temperature: 0.2, maxTokens: 4000 });
560
+ if (!res.success)
561
+ return res;
562
+ const code = res.text?.match(/```(?:\w+)?\n?([\s\S]*?)```/)?.[1] || res.text;
563
+ return { success: true, code: code?.trim() };
564
+ }
565
+ async reviewCode(code, requirements) {
566
+ const res = await this.generate('Review code. Reqs: ' + requirements + ' Code: ' + code.slice(0, 2000), { system: 'Code reviewer', temperature: 0.3 });
567
+ if (!res.success)
568
+ return { success: false, passed: false, error: res.error };
569
+ const passed = !!(res.text?.toUpperCase().includes('PASS') && !res.text?.toUpperCase().includes('FAIL'));
570
+ return { success: true, review: res.text, passed };
571
+ }
347
572
  async bid(jobId, amount, message) {
348
573
  try {
349
574
  const res = await this.api.post('bids', { jobId, amount, message });
@@ -422,7 +647,10 @@ class Agent extends events_1.EventEmitter {
422
647
  bidCache: Array.from(this.bidCache),
423
648
  autopilotPaused: this.autopilotPaused
424
649
  };
425
- fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
650
+ // Atomic Write: Write to temp file then rename to prevent corruption on crash
651
+ const tempPath = `${this.statePath}.tmp`;
652
+ fs.writeFileSync(tempPath, JSON.stringify(state, null, 2));
653
+ fs.renameSync(tempPath, this.statePath);
426
654
  }
427
655
  catch (e) { }
428
656
  }
@@ -431,6 +659,19 @@ class Agent extends events_1.EventEmitter {
431
659
  setInterval(() => this.api.post(`agents/me/heartbeat`, {}).catch(() => { }), 30000);
432
660
  // 2. πŸ›‘οΈ POLLING FAIL-SAFE: Catch messages if WebSockets fail
433
661
  setInterval(() => this.fetchUnreadMessages(), 15000);
662
+ // 3. πŸ›‘οΈ PROCESS GUARD: Ensure workers die when we die
663
+ const cleanup = () => {
664
+ this.logInternal("πŸ›‘ Shutting down agent & killing workers...");
665
+ for (const [jobId, worker] of this.workers) {
666
+ try {
667
+ worker.kill();
668
+ }
669
+ catch (e) { }
670
+ }
671
+ process.exit(0);
672
+ };
673
+ process.on('SIGINT', cleanup);
674
+ process.on('SIGTERM', cleanup);
434
675
  }
435
676
  /**
436
677
  * Fetch unread messages from the grid (Backup mechanism)
@@ -457,6 +698,17 @@ class Agent extends events_1.EventEmitter {
457
698
  }
458
699
  logInternal(msg) { if (this.debug)
459
700
  console.log(`[SDK] ${msg}`); }
701
+ compareVersions(a, b) {
702
+ const pa = a.split('.').map(Number);
703
+ const pb = b.split('.').map(Number);
704
+ for (let i = 0; i < 3; i++) {
705
+ if (pa[i] > pb[i])
706
+ return 1;
707
+ if (pa[i] < pb[i])
708
+ return -1;
709
+ }
710
+ return 0;
711
+ }
460
712
  }
461
713
  exports.Agent = Agent;
462
714
  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.7",
3
+ "version": "1.6.3",
4
4
  "description": "Official SDK for RentaBots AI Agent Marketplace",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,10 +28,8 @@
28
28
  "init_templates.js"
29
29
  ],
30
30
  "dependencies": {
31
- "-": "^0.0.1",
32
31
  "axios": "^1.6.0",
33
32
  "dotenv": "^17.2.4",
34
- "npm": "^11.10.0",
35
33
  "socket.io-client": "^4.8.3",
36
34
  "zod": "^4.3.6"
37
35
  },