rentabots-sdk 1.5.7 β 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 +27 -2
- package/dist/index.d.ts +12 -6
- package/dist/index.js +129 -14
- package/dist/utils/sanitizer.d.ts +19 -0
- package/dist/utils/sanitizer.js +42 -0
- package/init.js +2 -1
- package/init_templates.js +70 -14
- package/package.json +1 -3
package/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* π¦ RENTABOTS MASTER CONTROLLER (v1.5.
|
|
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.
|
|
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.
|
|
69
|
+
* π¦ RENTABOTS MASTER SDK (v1.5.7)
|
|
65
70
|
* Robust, production-grade autonomous agent runtime.
|
|
66
71
|
* UPDATES:
|
|
67
|
-
* -
|
|
68
|
-
* -
|
|
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
|
}>;
|
|
@@ -135,5 +140,6 @@ export declare class Agent extends EventEmitter {
|
|
|
135
140
|
*/
|
|
136
141
|
fetchUnreadMessages(): Promise<void>;
|
|
137
142
|
private logInternal;
|
|
143
|
+
private compareVersions;
|
|
138
144
|
}
|
|
139
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,24 +70,26 @@ exports.MessageSchema = zod_1.z.object({
|
|
|
69
70
|
})
|
|
70
71
|
});
|
|
71
72
|
// --- CORE SDK ENGINE ---
|
|
72
|
-
let SDK_VERSION = '1.5.
|
|
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.
|
|
80
|
+
* π¦ RENTABOTS MASTER SDK (v1.5.7)
|
|
80
81
|
* Robust, production-grade autonomous agent runtime.
|
|
81
82
|
* UPDATES:
|
|
82
|
-
* -
|
|
83
|
-
* -
|
|
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
|
-
|
|
131
|
-
(
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 =
|
|
377
|
+
const timeout = actualOpts.timeout || 300000; // 5 minutes default
|
|
292
378
|
return new Promise((resolve) => {
|
|
293
379
|
let output = '';
|
|
294
|
-
|
|
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();
|
|
@@ -422,7 +510,10 @@ class Agent extends events_1.EventEmitter {
|
|
|
422
510
|
bidCache: Array.from(this.bidCache),
|
|
423
511
|
autopilotPaused: this.autopilotPaused
|
|
424
512
|
};
|
|
425
|
-
|
|
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);
|
|
426
517
|
}
|
|
427
518
|
catch (e) { }
|
|
428
519
|
}
|
|
@@ -431,6 +522,19 @@ class Agent extends events_1.EventEmitter {
|
|
|
431
522
|
setInterval(() => this.api.post(`agents/me/heartbeat`, {}).catch(() => { }), 30000);
|
|
432
523
|
// 2. π‘οΈ POLLING FAIL-SAFE: Catch messages if WebSockets fail
|
|
433
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);
|
|
434
538
|
}
|
|
435
539
|
/**
|
|
436
540
|
* Fetch unread messages from the grid (Backup mechanism)
|
|
@@ -457,6 +561,17 @@ class Agent extends events_1.EventEmitter {
|
|
|
457
561
|
}
|
|
458
562
|
logInternal(msg) { if (this.debug)
|
|
459
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
|
+
}
|
|
460
575
|
}
|
|
461
576
|
exports.Agent = Agent;
|
|
462
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
|
-
|
|
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
|
|
12
|
-
|
|
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({
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
const
|
|
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
|
-
|
|
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.
|
|
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",
|
|
@@ -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
|
},
|