rentabots-sdk 1.7.7 → 1.7.13

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/dist/index.js CHANGED
@@ -71,7 +71,7 @@ exports.MessageSchema = zod_1.z.object({
71
71
  })
72
72
  });
73
73
  // --- CORE SDK ENGINE ---
74
- let SDK_VERSION = '1.7.7'; // fallback when package.json is unavailable
74
+ let SDK_VERSION = '1.7.13'; // fallback when package.json is unavailable
75
75
  try {
76
76
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
77
77
  SDK_VERSION = pkg.version;
package/init.js CHANGED
@@ -66,29 +66,101 @@ async function main() {
66
66
  const queen = new Agent({ debug: true, persistState: true });
67
67
  const conn = await queen.connect();
68
68
  if (!conn.success) { console.error("āŒ Auth failed:", conn.error); process.exit(1); }
69
- updateStatus(queen, "Supervisor connected.");
70
69
 
71
- queen.on('assignment', async (job) => {
72
- console.log("šŸŽÆ Mission Secured:", job.title);
73
- updateStatus(queen, "Mission Secured: " + job.title);
70
+ const agentId = conn.agent?.id;
71
+ const agentApiKey = process.env.RENTABOTS_API_KEY || process.env.RENTABOTS_SECRET_KEY;
72
+ const apiBase = (process.env.RENTABOTS_API_URL || 'https://rentabots.com/api').replace(/\/$/, '');
74
73
 
75
- // Write job to temp file (avoids CLI arg length limits)
74
+ const pushLog = async (level, message) => {
75
+ try {
76
+ if (!agentId || !agentApiKey) return;
77
+ await fetch(apiBase + '/agents/' + agentId + '/logs', {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ Authorization: 'Bearer ' + agentApiKey
82
+ },
83
+ body: JSON.stringify({ level, message })
84
+ });
85
+ } catch (_) {}
86
+ };
87
+
88
+ const spawnMissionWorker = async (job, source = 'assignment') => {
89
+ try {
76
90
  const jobDataPath = path.join(__dirname, 'workspace', '_job_' + job.id + '.json');
77
91
  fs.mkdirSync(path.dirname(jobDataPath), { recursive: true });
78
92
  fs.writeFileSync(jobDataPath, JSON.stringify(job));
79
93
 
94
+ await pushLog('INFO', 'Spawning worker for mission ' + job.id + ' from ' + source);
95
+
80
96
  const worker = fork(path.join(__dirname, 'worker.js'), [jobDataPath], {
81
- stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
82
- env: { ...process.env, RENTABOTS_API_KEY: process.env.RENTABOTS_API_KEY || process.env.RENTABOTS_SECRET_KEY }
97
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
98
+ env: { ...process.env, RENTABOTS_API_KEY: process.env.RENTABOTS_API_KEY || process.env.RENTABOTS_SECRET_KEY }
83
99
  });
100
+
84
101
  queen.workers.set(job.id, worker);
85
- worker.on('exit', () => { queen.workers.delete(job.id); try { fs.unlinkSync(jobDataPath); } catch {} });
102
+
103
+ worker.stdout?.on('data', async (d) => {
104
+ const line = d.toString().trim();
105
+ if (!line) return;
106
+ console.log('[WORKER:' + job.id + '] ' + line);
107
+ await pushLog('INFO', '[WORKER:' + job.id + '] ' + line);
108
+ });
109
+
110
+ worker.stderr?.on('data', async (d) => {
111
+ const line = d.toString().trim();
112
+ if (!line) return;
113
+ console.error('[WORKER:' + job.id + '] ' + line);
114
+ await pushLog('ERROR', '[WORKER:' + job.id + '] ' + line);
115
+ });
116
+
117
+ worker.on('exit', async (code, signal) => {
118
+ queen.workers.delete(job.id);
119
+ try { fs.unlinkSync(jobDataPath); } catch {}
120
+ await pushLog('WARN', 'Worker exited for mission ' + job.id + ' (code=' + code + ', signal=' + (signal || 'none') + ')');
121
+ });
122
+
123
+ worker.on('error', async (err) => {
124
+ await pushLog('ERROR', 'Worker process error for mission ' + job.id + ': ' + err.message);
125
+ });
126
+
127
+ await queen.sendMessage(job.id, 'šŸ‘· Worker dispatched by Queen. Streaming execution logs...');
128
+ } catch (err) {
129
+ const msg = err?.message || String(err);
130
+ await pushLog('ERROR', 'Failed to spawn worker for mission ' + (job?.id || 'unknown') + ': ' + msg);
131
+ if (job?.id) {
132
+ try { await queen.sendMessage(job.id, 'āš ļø Worker spawn failed: ' + msg); } catch {}
133
+ }
134
+ }
135
+ };
136
+
137
+ updateStatus(queen, "Supervisor connected.");
138
+ await pushLog('SUCCESS', 'Queen online and connected.');
139
+
140
+ // Recover workers for already-active missions after restart
141
+ for (const [jobId, job] of queen.activeMissions) {
142
+ if (queen.workers.has(jobId)) continue;
143
+ await spawnMissionWorker(job, 'recovery');
144
+ }
145
+
146
+ queen.on('assignment', async (job) => {
147
+ console.log("šŸŽÆ Mission Secured:", job.title);
148
+ updateStatus(queen, "Mission Secured: " + job.title);
149
+ await pushLog('SUCCESS', 'Mission secured: ' + job.title + ' (' + job.id + ')');
150
+ await spawnMissionWorker(job, 'assignment');
86
151
  });
87
152
 
88
153
  queen.on('message', async (msg) => {
89
154
  if (msg.sender.type === 'agent') return;
90
155
  if (!queen.workers.has(msg.jobId)) {
156
+ const job = queen.activeMissions.get(msg.jobId);
157
+ if (!job) {
158
+ await queen.sendMessage(msg.jobId, "šŸ¤– Supervisor online. Mission sync in progress.");
159
+ await pushLog('WARN', 'Message received for unknown mission ' + msg.jobId);
160
+ return;
161
+ }
91
162
  await queen.sendMessage(msg.jobId, "šŸ¤– Supervisor here. Dispatching a worker shortly.");
163
+ await spawnMissionWorker(job, 'message-trigger');
92
164
  }
93
165
  });
94
166
 
@@ -116,15 +188,102 @@ async function main() {
116
188
  const task = 'PROJECT: ' + job.title + '. BRIEF: ' + job.description + '. INSTRUCTION: Execute this task. Create all files in the workspace.';
117
189
 
118
190
  // Use shell command string (not array args)
119
- const cmd = 'openclaw sessions_spawn --task ' + JSON.stringify(task);
120
- const { exitCode, output } = await agent.execute(job.id, cmd, { timeout: 300000, shell: true });
191
+ const taskArg = JSON.stringify(task);
192
+ const cmdCandidates = [
193
+ 'openclaw sessions spawn --task ' + taskArg,
194
+ 'openclaw sessions_spawn --task ' + taskArg,
195
+ ];
196
+
197
+ let beat = 0;
198
+ const heartbeat = setInterval(async () => {
199
+ try {
200
+ beat += 1;
201
+ const pct = Math.min(90, beat * 5);
202
+ await agent.setProgress(job.id, pct);
203
+ await agent.sendMessage(job.id, 'ā³ Worker still running... ' + (beat * 30) + 's elapsed (' + pct + '%).');
204
+ } catch (_) {}
205
+ }, 30000);
206
+
207
+ let overallSuccess = false;
208
+ let lastOutput = '';
209
+
210
+ try {
211
+ for (const cmd of cmdCandidates) {
212
+ let exitCode = -1;
213
+ let output = '';
214
+
215
+ for (let attempt = 1; attempt <= 2; attempt++) {
216
+ const result = await agent.execute(job.id, cmd, { timeout: 900000, shell: true });
217
+ exitCode = result.exitCode;
218
+ output = result.output || '';
121
219
 
122
- if (exitCode === 0) {
220
+ if (exitCode === 0) {
221
+ overallSuccess = true;
222
+ lastOutput = output;
223
+ break;
224
+ }
225
+
226
+ lastOutput = output;
227
+ if (attempt < 2) {
228
+ await agent.sendMessage(job.id, 'āš ļø OpenClaw run delayed. Retrying once...');
229
+ await new Promise(r => setTimeout(r, 5000));
230
+ }
231
+ }
232
+
233
+ if (overallSuccess) break;
234
+ if (lastOutput.toLowerCase().includes('unknown command')) {
235
+ await agent.sendMessage(job.id, 'ā„¹ļø Switching OpenClaw command compatibility mode...');
236
+ continue;
237
+ }
238
+ break;
239
+ }
240
+ } finally {
241
+ clearInterval(heartbeat);
242
+ }
243
+
244
+ if (overallSuccess) {
245
+ await agent.setProgress(job.id, 100);
123
246
  await agent.sendMessage(job.id, "āœ… Execution complete. Deliverables ready.");
124
247
  if (process.send) process.send({ event: 'phase_complete', phase: 'BUILDER' });
125
248
  } else {
126
- await agent.sendMessage(job.id, "āš ļø Technical delay. Awaiting review.");
127
- console.error("Worker Error:", output);
249
+ const snippet = lastOutput.slice(-500) || 'No output captured';
250
+ console.error("Worker Error:", snippet);
251
+
252
+ // Fail-safe: keep user informed + attach diagnostic report to mission repo
253
+ await agent.setProgress(job.id, 25);
254
+ await agent.sendMessage(job.id, "āš ļø I hit an execution issue, but fail-safe mode is active. I’m preparing a diagnostic report and recovery path.");
255
+
256
+ try {
257
+ const repoRes = await agent.getRepo(job.id);
258
+ if (!repoRes.success || !repoRes.exists) {
259
+ await agent.createRepo(job.id, 'mission-recovery-' + job.id.slice(0, 8));
260
+ }
261
+
262
+ const report = [
263
+ '# FAILSAFE_REPORT',
264
+ '',
265
+ 'Mission: ' + job.id,
266
+ 'Title: ' + job.title,
267
+ 'Time: ' + new Date().toISOString(),
268
+ '',
269
+ '## Failure Summary',
270
+ snippet,
271
+ '',
272
+ '## Auto-Recovery Actions Taken',
273
+ '- Retried OpenClaw run once',
274
+ '- Switched command compatibility mode when applicable',
275
+ '- Preserved mission state and logs',
276
+ '',
277
+ '## Next Human Step',
278
+ '- Send a short clarification in mission chat (expected output format, constraints, deadline)',
279
+ '- Worker will continue from current workspace state'
280
+ ].join('\n');
281
+
282
+ await agent.uploadRepoFile(job.id, 'FAILSAFE_REPORT.md', report, false);
283
+ await agent.sendMessage(job.id, "šŸ›Ÿ Fail-safe report uploaded to mission repo: FAILSAFE_REPORT.md");
284
+ } catch (e) {
285
+ await agent.sendMessage(job.id, "šŸ›Ÿ Fail-safe mode active, but report upload failed. You can still continue by sending updated instructions in chat.");
286
+ }
128
287
  }
129
288
  }
130
289
  main().catch(console.error);`;
package/init_templates.js CHANGED
@@ -8,6 +8,7 @@ require('dotenv').config();
8
8
  const { Agent } = require('rentabots-sdk');
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
+ const { fork } = require('child_process');
11
12
 
12
13
  const STATUS_FILE = path.join(__dirname, 'RENTABOT_STATUS.md');
13
14
 
@@ -26,7 +27,6 @@ function updateStatus(queen, event = "Monitoring grid.") {
26
27
  async function main() {
27
28
  const queen = new Agent({
28
29
  debug: true,
29
- // Ensure state is saved in the isolated workspace to prevent conflict
30
30
  persistState: true
31
31
  });
32
32
 
@@ -35,41 +35,108 @@ async function main() {
35
35
  console.error("āŒ Failed to connect:", connection.error);
36
36
  process.exit(1);
37
37
  }
38
-
39
- updateStatus(queen, "Supervisor connected.");
40
38
 
41
- queen.on('assignment', async (job) => {
42
- console.log("šŸŽÆ Mission Secured:", job.title);
43
- updateStatus(queen, "Mission Secured: " + job.title);
39
+ const agentId = connection.agent?.id;
40
+ const agentApiKey = process.env.RENTABOTS_API_KEY || process.env.RENTABOTS_SECRET_KEY;
41
+ const apiBase = (process.env.RENTABOTS_API_URL || 'https://rentabots.com/api').replace(/\/$/, '');
42
+
43
+ const pushLog = async (level, message) => {
44
+ try {
45
+ if (!agentId || !agentApiKey) return;
46
+ await fetch(\`${apiBase}/agents/\${agentId}/logs\`, {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ Authorization: \`Bearer \${agentApiKey}\`
51
+ },
52
+ body: JSON.stringify({ level, message })
53
+ });
54
+ } catch (_) {}
55
+ };
44
56
 
45
- // Write job to temp file (avoids CLI arg length limits)
57
+ const spawnMissionWorker = async (job, source = 'assignment') => {
58
+ try {
46
59
  const jobDataPath = path.join(__dirname, 'workspace', '_job_' + job.id + '.json');
47
60
  fs.mkdirSync(path.dirname(jobDataPath), { recursive: true });
48
61
  fs.writeFileSync(jobDataPath, JSON.stringify(job));
49
62
 
63
+ await pushLog('INFO', 'Spawning worker for mission ' + job.id + ' from ' + source);
64
+
50
65
  const worker = fork(path.join(__dirname, 'worker.js'), [jobDataPath], {
51
- stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
52
- env: { ...process.env, RENTABOTS_API_KEY: process.env.RENTABOTS_API_KEY || process.env.RENTABOTS_SECRET_KEY }
66
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
67
+ env: { ...process.env, RENTABOTS_API_KEY: process.env.RENTABOTS_API_KEY || process.env.RENTABOTS_SECRET_KEY }
53
68
  });
69
+
54
70
  queen.workers.set(job.id, worker);
55
- worker.on('exit', () => { queen.workers.delete(job.id); try { fs.unlinkSync(jobDataPath); } catch {} });
71
+
72
+ worker.stdout?.on('data', async (d) => {
73
+ const line = d.toString().trim();
74
+ if (!line) return;
75
+ console.log('[WORKER:' + job.id + '] ' + line);
76
+ await pushLog('INFO', '[WORKER:' + job.id + '] ' + line);
77
+ });
78
+
79
+ worker.stderr?.on('data', async (d) => {
80
+ const line = d.toString().trim();
81
+ if (!line) return;
82
+ console.error('[WORKER:' + job.id + '] ' + line);
83
+ await pushLog('ERROR', '[WORKER:' + job.id + '] ' + line);
84
+ });
85
+
86
+ worker.on('exit', async (code, signal) => {
87
+ queen.workers.delete(job.id);
88
+ try { fs.unlinkSync(jobDataPath); } catch {}
89
+ await pushLog('WARN', 'Worker exited for mission ' + job.id + ' (code=' + code + ', signal=' + (signal || 'none') + ')');
90
+ });
91
+
92
+ worker.on('error', async (err) => {
93
+ await pushLog('ERROR', 'Worker process error for mission ' + job.id + ': ' + err.message);
94
+ });
95
+
96
+ await queen.sendMessage(job.id, 'šŸ‘· Worker dispatched by Queen. Streaming execution logs...');
97
+ } catch (err) {
98
+ const msg = err?.message || String(err);
99
+ await pushLog('ERROR', 'Failed to spawn worker for mission ' + (job?.id || 'unknown') + ': ' + msg);
100
+ if (job?.id) {
101
+ try { await queen.sendMessage(job.id, 'āš ļø Worker spawn failed: ' + msg); } catch {}
102
+ }
103
+ }
104
+ };
105
+
106
+ updateStatus(queen, "Supervisor connected.");
107
+ await pushLog('SUCCESS', 'Queen online and connected.');
108
+
109
+ // Recover workers for already-active missions after restart
110
+ for (const [jobId, job] of queen.activeMissions) {
111
+ if (queen.workers.has(jobId)) continue;
112
+ await spawnMissionWorker(job, 'recovery');
113
+ }
114
+
115
+ queen.on('assignment', async (job) => {
116
+ console.log("šŸŽÆ Mission Secured:", job.title);
117
+ updateStatus(queen, "Mission Secured: " + job.title);
118
+ await pushLog('SUCCESS', 'Mission secured: ' + job.title + ' (' + job.id + ')');
119
+ await spawnMissionWorker(job, 'assignment');
56
120
  });
57
121
 
58
122
  queen.on('message', async (msg) => {
59
123
  if (msg.sender.type === 'agent') return;
60
- // In Fleet Mode, the Queen delegates chat to the specific Worker handling the job.
61
- // We only respond if no worker is active for this job.
62
124
  if (!queen.workers.has(msg.jobId)) {
63
- await queen.sendMessage(msg.jobId, "šŸ¤– Supervisor here. Dispatching a worker to this channel shortly.");
64
125
  const job = queen.activeMissions.get(msg.jobId);
65
- if (job) await queen.spawnWorker(job);
126
+ if (!job) {
127
+ await queen.sendMessage(msg.jobId, "šŸ¤– Supervisor online. Mission sync in progress.");
128
+ await pushLog('WARN', 'Message received for unknown mission ' + msg.jobId);
129
+ return;
130
+ }
131
+
132
+ await queen.sendMessage(msg.jobId, "šŸ¤– Supervisor here. Dispatching a worker to this channel shortly.");
133
+ await spawnMissionWorker(job, 'message-trigger');
66
134
  }
67
135
  });
68
136
 
69
- // Start Autopilot (Scout for jobs every 5 mins to save tokens)
70
137
  queen.startAutopilot({
71
138
  scoutingInterval: 300000,
72
- minBudget: 5 // Only bid on jobs > $5
139
+ minBudget: 5
73
140
  });
74
141
 
75
142
  setInterval(() => updateStatus(queen), 60000);
@@ -105,16 +172,102 @@ async function main() {
105
172
  const task = \`PROJECT: \${job.title}. BRIEF: \${job.description}. INSTRUCTION: Execute this task in the current directory. Do not leave the workspace.\`;
106
173
 
107
174
  // --- šŸ¦ž EXECUTE VIA OPENCLAW BRAIN ---
108
- // Use shell command string (not array args) for reliable argument passing
109
- const cmd = 'openclaw sessions_spawn --task ' + JSON.stringify(task);
110
- const { exitCode, output } = await agent.execute(job.id, cmd, { timeout: 300000, shell: true });
175
+ const taskArg = JSON.stringify(task);
176
+ const cmdCandidates = [
177
+ 'openclaw sessions spawn --task ' + taskArg,
178
+ 'openclaw sessions_spawn --task ' + taskArg,
179
+ ];
180
+
181
+ let beat = 0;
182
+ const heartbeat = setInterval(async () => {
183
+ try {
184
+ beat += 1;
185
+ const pct = Math.min(90, beat * 5);
186
+ await agent.setProgress(job.id, pct);
187
+ await agent.sendMessage(job.id, 'ā³ Worker still running... ' + (beat * 30) + 's elapsed (' + pct + '%).');
188
+ } catch (_) {}
189
+ }, 30000);
190
+
191
+ let overallSuccess = false;
192
+ let lastOutput = '';
193
+
194
+ try {
195
+ for (const cmd of cmdCandidates) {
196
+ let exitCode = -1;
197
+ let output = '';
198
+
199
+ for (let attempt = 1; attempt <= 2; attempt++) {
200
+ const result = await agent.execute(job.id, cmd, { timeout: 900000, shell: true });
201
+ exitCode = result.exitCode;
202
+ output = result.output || '';
111
203
 
112
- if (exitCode === 0) {
113
- await agent.sendMessage(job.id, "āœ… Execution complete. Deliverables ready.");
114
- if (process.send) process.send({ event: 'phase_complete', phase: 'BUILDER' });
204
+ if (exitCode === 0) {
205
+ overallSuccess = true;
206
+ lastOutput = output;
207
+ break;
208
+ }
209
+
210
+ lastOutput = output;
211
+ if (attempt < 2) {
212
+ await agent.sendMessage(job.id, 'āš ļø OpenClaw run delayed. Retrying once...');
213
+ await new Promise(r => setTimeout(r, 5000));
214
+ }
215
+ }
216
+
217
+ if (overallSuccess) break;
218
+ if (lastOutput.toLowerCase().includes('unknown command')) {
219
+ await agent.sendMessage(job.id, 'ā„¹ļø Switching OpenClaw command compatibility mode...');
220
+ continue;
221
+ }
222
+ break;
223
+ }
224
+ } finally {
225
+ clearInterval(heartbeat);
226
+ }
227
+
228
+ if (overallSuccess) {
229
+ await agent.setProgress(job.id, 100);
230
+ await agent.sendMessage(job.id, "āœ… Execution complete. Deliverables ready.");
231
+ if (process.send) process.send({ event: 'phase_complete', phase: 'BUILDER' });
115
232
  } else {
116
- await agent.sendMessage(job.id, "āš ļø Technical delay. OpenClaw execution timed out or failed. Retrying...");
117
- console.error("Worker Error:", output);
233
+ const snippet = lastOutput.slice(-500) || 'No output captured';
234
+ console.error("Worker Error:", snippet);
235
+
236
+ // Fail-safe: keep user informed + attach diagnostic report to mission repo
237
+ await agent.setProgress(job.id, 25);
238
+ await agent.sendMessage(job.id, "āš ļø I hit an execution issue, but fail-safe mode is active. I’m preparing a diagnostic report and recovery path.");
239
+
240
+ try {
241
+ const repoRes = await agent.getRepo(job.id);
242
+ if (!repoRes.success || !repoRes.exists) {
243
+ await agent.createRepo(job.id, 'mission-recovery-' + job.id.slice(0, 8));
244
+ }
245
+
246
+ const report = [
247
+ '# FAILSAFE_REPORT',
248
+ '',
249
+ 'Mission: ' + job.id,
250
+ 'Title: ' + job.title,
251
+ 'Time: ' + new Date().toISOString(),
252
+ '',
253
+ '## Failure Summary',
254
+ snippet,
255
+ '',
256
+ '## Auto-Recovery Actions Taken',
257
+ '- Retried OpenClaw run once',
258
+ '- Switched command compatibility mode when applicable',
259
+ '- Preserved mission state and logs',
260
+ '',
261
+ '## Next Human Step',
262
+ '- Send a short clarification in mission chat (expected output format, constraints, deadline)',
263
+ '- Worker will continue from current workspace state'
264
+ ].join('\n');
265
+
266
+ await agent.uploadRepoFile(job.id, 'FAILSAFE_REPORT.md', report, false);
267
+ await agent.sendMessage(job.id, "šŸ›Ÿ Fail-safe report uploaded to mission repo: FAILSAFE_REPORT.md");
268
+ } catch (e) {
269
+ await agent.sendMessage(job.id, "šŸ›Ÿ Fail-safe mode active, but report upload failed. You can still continue by sending updated instructions in chat.");
270
+ }
118
271
  }
119
272
  }
120
273
  main().catch(console.error);`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rentabots-sdk",
3
- "version": "1.7.7",
3
+ "version": "1.7.13",
4
4
  "description": "Official SDK for RentaBots AI Agent Marketplace",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",