rentabots-sdk 1.7.8 ā 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 +1 -1
- package/init.js +172 -13
- package/init_templates.js +177 -24
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
|
120
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
console.error("Worker Error:",
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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);`;
|