rentabots-sdk 1.7.27 → 1.7.31
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 +79 -51
- package/init_templates.js +79 -51
- 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.31'; // 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
|
@@ -148,6 +148,13 @@ async function main() {
|
|
|
148
148
|
queen.workers.delete(job.id);
|
|
149
149
|
try { fs.unlinkSync(jobDataPath); } catch {}
|
|
150
150
|
await pushLog('WARN', 'Worker exited for mission ' + job.id + ' (code=' + code + ', signal=' + (signal || 'none') + ')');
|
|
151
|
+
|
|
152
|
+
if (code === 42 && queen.activeMissions.has(job.id) && !queen.workers.has(job.id)) {
|
|
153
|
+
await pushLog('INFO', 'Worker requested controlled restart for ' + job.id + '; respawning...');
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
spawnMissionWorker(job, 'controlled-restart').catch(() => {});
|
|
156
|
+
}, 1200);
|
|
157
|
+
}
|
|
151
158
|
});
|
|
152
159
|
|
|
153
160
|
worker.on('error', async (err) => {
|
|
@@ -245,7 +252,8 @@ const job = JSON.parse(fs.readFileSync(jobDataPath, 'utf8'));
|
|
|
245
252
|
|
|
246
253
|
function inferContract(job) {
|
|
247
254
|
const text = ((job.title || '') + ' ' + (job.description || '')).toLowerCase();
|
|
248
|
-
const
|
|
255
|
+
const hasApiWord = /(^|[^a-z0-9])api([^a-z0-9]|$)/i.test(text) || /endpoint|rest\b|graphql\b/.test(text);
|
|
256
|
+
const adapter = hasApiWord ? 'api' : text.includes('csv') || text.includes('dataset') || text.includes('etl') ? 'data' : text.includes('landing page') || text.includes('frontend') || text.includes('ui') || text.includes('web') ? 'web' : text.includes('docs') || text.includes('documentation') ? 'docs' : 'script';
|
|
249
257
|
const contract = {
|
|
250
258
|
adapter,
|
|
251
259
|
taskType: adapter,
|
|
@@ -302,6 +310,7 @@ function generateAdapterFallbackFiles(workDir, job, contract) {
|
|
|
302
310
|
}
|
|
303
311
|
|
|
304
312
|
async function main() {
|
|
313
|
+
const RELAXED_MODE = (process.env.RENTABOTS_RELAXED_MODE || '1') !== '0';
|
|
305
314
|
const agent = new Agent({ persistState: false, debug: true });
|
|
306
315
|
await agent.connect();
|
|
307
316
|
await agent.sendMessage(job.id, "👷 Worker Unit [PID " + process.pid + "] deployed. Analyzing requirements...");
|
|
@@ -456,7 +465,13 @@ async function main() {
|
|
|
456
465
|
issues.push('whitespace cleanup requirement not reflected in output');
|
|
457
466
|
}
|
|
458
467
|
for (const d of contract.deliverables || []) {
|
|
459
|
-
|
|
468
|
+
const dl = String(d).toLowerCase();
|
|
469
|
+
let ok = false;
|
|
470
|
+
if (dl === 'readme.md') ok = hasFile((f) => /^readme(\..+)?\.md$/i.test(f) || /^readme\.md$/i.test(f));
|
|
471
|
+
else if (dl === 'solution.py') ok = hasFile((f) => f.toLowerCase().endsWith('.py'));
|
|
472
|
+
else if (dl === 'openapi-or-endpoint-docs.md') ok = hasFile((f) => /openapi|endpoint|api.*doc/i.test(f));
|
|
473
|
+
else ok = hasFile((f) => f.toLowerCase() === dl);
|
|
474
|
+
if (!ok) {
|
|
460
475
|
issues.push('missing contract deliverable: ' + d);
|
|
461
476
|
}
|
|
462
477
|
}
|
|
@@ -491,49 +506,54 @@ async function main() {
|
|
|
491
506
|
if (!hasSections) issues.push('docs adapter: missing core documentation sections');
|
|
492
507
|
if (!hasExamples) issues.push('docs adapter: missing usage examples');
|
|
493
508
|
} else {
|
|
494
|
-
const
|
|
509
|
+
const pyExists = hasFile((f) => f.toLowerCase().endsWith('.py'));
|
|
510
|
+
const hasRunnable = pyExists || /(main\(|if __name__ ==|module\.exports|export default|function\s+\w+|class\s+\w+)/i.test(combinedText);
|
|
495
511
|
if (!hasRunnable) issues.push('script adapter: missing runnable implementation entry');
|
|
496
512
|
}
|
|
497
513
|
|
|
498
514
|
if (issues.length > 0) {
|
|
499
515
|
await agent.setProgress(job.id, 70);
|
|
500
|
-
|
|
516
|
+
if (RELAXED_MODE) {
|
|
517
|
+
await agent.sendMessage(job.id, '⚠️ Soft QA warnings: ' + issues.slice(0, 3).join('; ') + '. Continuing with OpenClaw output in relaxed mode.');
|
|
518
|
+
} else {
|
|
519
|
+
await agent.sendMessage(job.id, '⚠️ Contract QA failed: ' + issues.slice(0, 4).join('; ') + '. Running targeted OpenClaw repair pass now.');
|
|
501
520
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
521
|
+
const repairPath = path.join(workDir, '.repair_count');
|
|
522
|
+
let repairCount = 0;
|
|
523
|
+
try { repairCount = Number(fs.readFileSync(repairPath, 'utf8') || '0'); } catch (_) {}
|
|
524
|
+
repairCount += 1;
|
|
525
|
+
fs.writeFileSync(repairPath, String(repairCount));
|
|
507
526
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
527
|
+
if (repairCount > 6) {
|
|
528
|
+
await agent.sendMessage(job.id, '🚨 OpenClaw repair limit reached. Please send one concrete clarification so I can continue with a precise fix.');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
512
531
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
532
|
+
const repairPrompt = [
|
|
533
|
+
'Repair the existing workspace to satisfy missing deterministic gates.',
|
|
534
|
+
'Do not explain. Edit files now and finish.',
|
|
535
|
+
'MISSING GATES:',
|
|
536
|
+
...issues.map(i => '- ' + i),
|
|
537
|
+
'',
|
|
538
|
+
'MISSION TITLE: ' + job.title,
|
|
539
|
+
'MISSION DESCRIPTION: ' + job.description,
|
|
540
|
+
].join('\n');
|
|
541
|
+
|
|
542
|
+
const repairArg = JSON.stringify(repairPrompt);
|
|
543
|
+
const repairCmds = [
|
|
544
|
+
'openclaw sessions spawn --task ' + repairArg,
|
|
545
|
+
'openclaw sessions_spawn --task ' + repairArg,
|
|
546
|
+
];
|
|
547
|
+
for (const rcmd of repairCmds) {
|
|
548
|
+
const r = await agent.execute(job.id, rcmd, { timeout: 900000, shell: true });
|
|
549
|
+
const out = (r.output || '').toLowerCase();
|
|
550
|
+
const pseudo = out.includes('usage: openclaw') || out.includes('unknown command') || out.includes('pass --to');
|
|
551
|
+
if (r.exitCode === 0 && !pseudo) break;
|
|
552
|
+
}
|
|
522
553
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
'openclaw sessions spawn --task ' + repairArg,
|
|
526
|
-
'openclaw sessions_spawn --task ' + repairArg,
|
|
527
|
-
];
|
|
528
|
-
for (const rcmd of repairCmds) {
|
|
529
|
-
const r = await agent.execute(job.id, rcmd, { timeout: 900000, shell: true });
|
|
530
|
-
const out = (r.output || '').toLowerCase();
|
|
531
|
-
const pseudo = out.includes('usage: openclaw') || out.includes('unknown command') || out.includes('pass --to');
|
|
532
|
-
if (r.exitCode === 0 && !pseudo) break;
|
|
554
|
+
await agent.sendMessage(job.id, '🔁 OpenClaw repair pass complete. Re-queueing worker validation pass now.');
|
|
555
|
+
process.exit(42);
|
|
533
556
|
}
|
|
534
|
-
|
|
535
|
-
await agent.sendMessage(job.id, '🔁 OpenClaw repair pass complete. Re-validating now.');
|
|
536
|
-
return main();
|
|
537
557
|
}
|
|
538
558
|
|
|
539
559
|
if (fallbackGenerated) {
|
|
@@ -556,8 +576,12 @@ async function main() {
|
|
|
556
576
|
});
|
|
557
577
|
if (!changed) {
|
|
558
578
|
await agent.setProgress(job.id, 88);
|
|
559
|
-
|
|
560
|
-
|
|
579
|
+
if (RELAXED_MODE) {
|
|
580
|
+
await agent.sendMessage(job.id, '⚠️ Soft warning: no non-template delta after clarification, but continuing in relaxed mode.');
|
|
581
|
+
} else {
|
|
582
|
+
await agent.sendMessage(job.id, '⚠️ Final gate blocked: no non-template file changed after your latest clarification. Continuing implementation.');
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
561
585
|
}
|
|
562
586
|
}
|
|
563
587
|
} catch (_) {}
|
|
@@ -592,21 +616,25 @@ async function main() {
|
|
|
592
616
|
|
|
593
617
|
if (!qaPassed) {
|
|
594
618
|
const why = (qaOutput || 'No QA output').slice(-300);
|
|
595
|
-
const qaStatePath = path.join(workDir, '.qa_fail_count');
|
|
596
|
-
let qaFails = 0;
|
|
597
|
-
try { qaFails = Number(fs.readFileSync(qaStatePath, 'utf8') || '0'); } catch (_) {}
|
|
598
|
-
qaFails += 1;
|
|
599
|
-
fs.writeFileSync(qaStatePath, String(qaFails));
|
|
600
|
-
|
|
601
619
|
await agent.setProgress(job.id, 80);
|
|
602
|
-
if (
|
|
603
|
-
await agent.sendMessage(job.id, '
|
|
604
|
-
} else if (qaFails >= 3) {
|
|
605
|
-
await agent.sendMessage(job.id, '⚠️ Second QA gate failed (attempt ' + qaFails + '). I am revising strategy. Reason: ' + why);
|
|
620
|
+
if (RELAXED_MODE) {
|
|
621
|
+
await agent.sendMessage(job.id, '⚠️ QA warning (relaxed mode): ' + why + '. Proceeding with current output.');
|
|
606
622
|
} else {
|
|
607
|
-
|
|
623
|
+
const qaStatePath = path.join(workDir, '.qa_fail_count');
|
|
624
|
+
let qaFails = 0;
|
|
625
|
+
try { qaFails = Number(fs.readFileSync(qaStatePath, 'utf8') || '0'); } catch (_) {}
|
|
626
|
+
qaFails += 1;
|
|
627
|
+
fs.writeFileSync(qaStatePath, String(qaFails));
|
|
628
|
+
|
|
629
|
+
if (qaFails >= 5) {
|
|
630
|
+
await agent.sendMessage(job.id, '🚨 Escalation: repeated QA failures (' + qaFails + '). Pausing finalization. Please provide concrete acceptance criteria/examples so I can resolve this precisely.');
|
|
631
|
+
} else if (qaFails >= 3) {
|
|
632
|
+
await agent.sendMessage(job.id, '⚠️ Second QA gate failed (attempt ' + qaFails + '). I am revising strategy. Reason: ' + why);
|
|
633
|
+
} else {
|
|
634
|
+
await agent.sendMessage(job.id, '⚠️ Second QA gate failed. I will revise before marking done. Reason: ' + why);
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
608
637
|
}
|
|
609
|
-
return;
|
|
610
638
|
}
|
|
611
639
|
|
|
612
640
|
try { fs.unlinkSync(path.join(workDir, '.qa_fail_count')); } catch (_) {}
|
|
@@ -657,9 +685,9 @@ async function main() {
|
|
|
657
685
|
|
|
658
686
|
if (fails <= 5) {
|
|
659
687
|
await agent.setProgress(job.id, 25);
|
|
660
|
-
await agent.sendMessage(job.id, '⚠️ OpenClaw run failed (attempt ' + fails + ').
|
|
688
|
+
await agent.sendMessage(job.id, '⚠️ OpenClaw run failed (attempt ' + fails + '). Re-queueing OpenClaw-only retry...');
|
|
661
689
|
await new Promise(r => setTimeout(r, 5000));
|
|
662
|
-
|
|
690
|
+
process.exit(42);
|
|
663
691
|
}
|
|
664
692
|
|
|
665
693
|
// Fail-safe report only after OpenClaw retry budget exhausted
|
package/init_templates.js
CHANGED
|
@@ -117,6 +117,13 @@ async function main() {
|
|
|
117
117
|
queen.workers.delete(job.id);
|
|
118
118
|
try { fs.unlinkSync(jobDataPath); } catch {}
|
|
119
119
|
await pushLog('WARN', 'Worker exited for mission ' + job.id + ' (code=' + code + ', signal=' + (signal || 'none') + ')');
|
|
120
|
+
|
|
121
|
+
if (code === 42 && queen.activeMissions.has(job.id) && !queen.workers.has(job.id)) {
|
|
122
|
+
await pushLog('INFO', 'Worker requested controlled restart for ' + job.id + '; respawning...');
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
spawnMissionWorker(job, 'controlled-restart').catch(() => {});
|
|
125
|
+
}, 1200);
|
|
126
|
+
}
|
|
120
127
|
});
|
|
121
128
|
|
|
122
129
|
worker.on('error', async (err) => {
|
|
@@ -221,7 +228,8 @@ const job = JSON.parse(fs.readFileSync(jobDataPath, 'utf8'));
|
|
|
221
228
|
|
|
222
229
|
function inferContract(job) {
|
|
223
230
|
const text = ((job.title || '') + ' ' + (job.description || '')).toLowerCase();
|
|
224
|
-
const
|
|
231
|
+
const hasApiWord = /(^|[^a-z0-9])api([^a-z0-9]|$)/i.test(text) || /endpoint|rest\b|graphql\b/.test(text);
|
|
232
|
+
const adapter = hasApiWord ? 'api' : text.includes('csv') || text.includes('dataset') || text.includes('etl') ? 'data' : text.includes('landing page') || text.includes('frontend') || text.includes('ui') || text.includes('web') ? 'web' : text.includes('docs') || text.includes('documentation') ? 'docs' : 'script';
|
|
225
233
|
const contract = {
|
|
226
234
|
adapter,
|
|
227
235
|
taskType: adapter,
|
|
@@ -279,6 +287,7 @@ function generateAdapterFallbackFiles(workDir, job, contract) {
|
|
|
279
287
|
}
|
|
280
288
|
|
|
281
289
|
async function main() {
|
|
290
|
+
const RELAXED_MODE = (process.env.RENTABOTS_RELAXED_MODE || '1') !== '0';
|
|
282
291
|
const agent = new Agent({
|
|
283
292
|
persistState: false,
|
|
284
293
|
debug: true
|
|
@@ -441,7 +450,13 @@ async function main() {
|
|
|
441
450
|
issues.push('whitespace cleanup requirement not reflected in output');
|
|
442
451
|
}
|
|
443
452
|
for (const d of contract.deliverables || []) {
|
|
444
|
-
|
|
453
|
+
const dl = String(d).toLowerCase();
|
|
454
|
+
let ok = false;
|
|
455
|
+
if (dl === 'readme.md') ok = hasFile((f) => /^readme(\..+)?\.md$/i.test(f) || /^readme\.md$/i.test(f));
|
|
456
|
+
else if (dl === 'solution.py') ok = hasFile((f) => f.toLowerCase().endsWith('.py'));
|
|
457
|
+
else if (dl === 'openapi-or-endpoint-docs.md') ok = hasFile((f) => /openapi|endpoint|api.*doc/i.test(f));
|
|
458
|
+
else ok = hasFile((f) => f.toLowerCase() === dl);
|
|
459
|
+
if (!ok) {
|
|
445
460
|
issues.push('missing contract deliverable: ' + d);
|
|
446
461
|
}
|
|
447
462
|
}
|
|
@@ -476,49 +491,54 @@ async function main() {
|
|
|
476
491
|
if (!hasSections) issues.push('docs adapter: missing core documentation sections');
|
|
477
492
|
if (!hasExamples) issues.push('docs adapter: missing usage examples');
|
|
478
493
|
} else {
|
|
479
|
-
const
|
|
494
|
+
const pyExists = hasFile((f) => f.toLowerCase().endsWith('.py'));
|
|
495
|
+
const hasRunnable = pyExists || /(main\(|if __name__ ==|module\.exports|export default|function\s+\w+|class\s+\w+)/i.test(combinedText);
|
|
480
496
|
if (!hasRunnable) issues.push('script adapter: missing runnable implementation entry');
|
|
481
497
|
}
|
|
482
498
|
|
|
483
499
|
if (issues.length > 0) {
|
|
484
500
|
await agent.setProgress(job.id, 70);
|
|
485
|
-
|
|
501
|
+
if (RELAXED_MODE) {
|
|
502
|
+
await agent.sendMessage(job.id, '⚠️ Soft QA warnings: ' + issues.slice(0, 3).join('; ') + '. Continuing with OpenClaw output in relaxed mode.');
|
|
503
|
+
} else {
|
|
504
|
+
await agent.sendMessage(job.id, '⚠️ Contract QA failed: ' + issues.slice(0, 4).join('; ') + '. Running targeted OpenClaw repair pass now.');
|
|
486
505
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
506
|
+
const repairPath = path.join(workDir, '.repair_count');
|
|
507
|
+
let repairCount = 0;
|
|
508
|
+
try { repairCount = Number(fs.readFileSync(repairPath, 'utf8') || '0'); } catch (_) {}
|
|
509
|
+
repairCount += 1;
|
|
510
|
+
fs.writeFileSync(repairPath, String(repairCount));
|
|
492
511
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
512
|
+
if (repairCount > 6) {
|
|
513
|
+
await agent.sendMessage(job.id, '🚨 OpenClaw repair limit reached. Please send one concrete clarification so I can continue with a precise fix.');
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
497
516
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
517
|
+
const repairPrompt = [
|
|
518
|
+
'Repair the existing workspace to satisfy missing deterministic gates.',
|
|
519
|
+
'Do not explain. Edit files now and finish.',
|
|
520
|
+
'MISSING GATES:',
|
|
521
|
+
...issues.map(i => '- ' + i),
|
|
522
|
+
'',
|
|
523
|
+
'MISSION TITLE: ' + job.title,
|
|
524
|
+
'MISSION DESCRIPTION: ' + job.description,
|
|
525
|
+
].join('\n');
|
|
526
|
+
|
|
527
|
+
const repairArg = JSON.stringify(repairPrompt);
|
|
528
|
+
const repairCmds = [
|
|
529
|
+
'openclaw sessions spawn --task ' + repairArg,
|
|
530
|
+
'openclaw sessions_spawn --task ' + repairArg,
|
|
531
|
+
];
|
|
532
|
+
for (const rcmd of repairCmds) {
|
|
533
|
+
const r = await agent.execute(job.id, rcmd, { timeout: 900000, shell: true });
|
|
534
|
+
const out = (r.output || '').toLowerCase();
|
|
535
|
+
const pseudo = out.includes('usage: openclaw') || out.includes('unknown command') || out.includes('pass --to');
|
|
536
|
+
if (r.exitCode === 0 && !pseudo) break;
|
|
537
|
+
}
|
|
507
538
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
'openclaw sessions spawn --task ' + repairArg,
|
|
511
|
-
'openclaw sessions_spawn --task ' + repairArg,
|
|
512
|
-
];
|
|
513
|
-
for (const rcmd of repairCmds) {
|
|
514
|
-
const r = await agent.execute(job.id, rcmd, { timeout: 900000, shell: true });
|
|
515
|
-
const out = (r.output || '').toLowerCase();
|
|
516
|
-
const pseudo = out.includes('usage: openclaw') || out.includes('unknown command') || out.includes('pass --to');
|
|
517
|
-
if (r.exitCode === 0 && !pseudo) break;
|
|
539
|
+
await agent.sendMessage(job.id, '🔁 OpenClaw repair pass complete. Re-queueing worker validation pass now.');
|
|
540
|
+
process.exit(42);
|
|
518
541
|
}
|
|
519
|
-
|
|
520
|
-
await agent.sendMessage(job.id, '🔁 OpenClaw repair pass complete. Re-validating now.');
|
|
521
|
-
return main();
|
|
522
542
|
}
|
|
523
543
|
|
|
524
544
|
if (fallbackGenerated) {
|
|
@@ -541,8 +561,12 @@ async function main() {
|
|
|
541
561
|
});
|
|
542
562
|
if (!changed) {
|
|
543
563
|
await agent.setProgress(job.id, 88);
|
|
544
|
-
|
|
545
|
-
|
|
564
|
+
if (RELAXED_MODE) {
|
|
565
|
+
await agent.sendMessage(job.id, '⚠️ Soft warning: no non-template delta after clarification, but continuing in relaxed mode.');
|
|
566
|
+
} else {
|
|
567
|
+
await agent.sendMessage(job.id, '⚠️ Final gate blocked: no non-template file changed after your latest clarification. Continuing implementation.');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
546
570
|
}
|
|
547
571
|
}
|
|
548
572
|
} catch (_) {}
|
|
@@ -577,21 +601,25 @@ async function main() {
|
|
|
577
601
|
|
|
578
602
|
if (!qaPassed) {
|
|
579
603
|
const why = (qaOutput || 'No QA output').slice(-300);
|
|
580
|
-
const qaStatePath = path.join(workDir, '.qa_fail_count');
|
|
581
|
-
let qaFails = 0;
|
|
582
|
-
try { qaFails = Number(fs.readFileSync(qaStatePath, 'utf8') || '0'); } catch (_) {}
|
|
583
|
-
qaFails += 1;
|
|
584
|
-
fs.writeFileSync(qaStatePath, String(qaFails));
|
|
585
|
-
|
|
586
604
|
await agent.setProgress(job.id, 80);
|
|
587
|
-
if (
|
|
588
|
-
await agent.sendMessage(job.id, '
|
|
589
|
-
} else if (qaFails >= 3) {
|
|
590
|
-
await agent.sendMessage(job.id, '⚠️ Second QA gate failed (attempt ' + qaFails + '). I am revising strategy. Reason: ' + why);
|
|
605
|
+
if (RELAXED_MODE) {
|
|
606
|
+
await agent.sendMessage(job.id, '⚠️ QA warning (relaxed mode): ' + why + '. Proceeding with current output.');
|
|
591
607
|
} else {
|
|
592
|
-
|
|
608
|
+
const qaStatePath = path.join(workDir, '.qa_fail_count');
|
|
609
|
+
let qaFails = 0;
|
|
610
|
+
try { qaFails = Number(fs.readFileSync(qaStatePath, 'utf8') || '0'); } catch (_) {}
|
|
611
|
+
qaFails += 1;
|
|
612
|
+
fs.writeFileSync(qaStatePath, String(qaFails));
|
|
613
|
+
|
|
614
|
+
if (qaFails >= 5) {
|
|
615
|
+
await agent.sendMessage(job.id, '🚨 Escalation: repeated QA failures (' + qaFails + '). Pausing finalization. Please provide concrete acceptance criteria/examples so I can resolve this precisely.');
|
|
616
|
+
} else if (qaFails >= 3) {
|
|
617
|
+
await agent.sendMessage(job.id, '⚠️ Second QA gate failed (attempt ' + qaFails + '). I am revising strategy. Reason: ' + why);
|
|
618
|
+
} else {
|
|
619
|
+
await agent.sendMessage(job.id, '⚠️ Second QA gate failed. I will revise before marking done. Reason: ' + why);
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
593
622
|
}
|
|
594
|
-
return;
|
|
595
623
|
}
|
|
596
624
|
|
|
597
625
|
try { fs.unlinkSync(path.join(workDir, '.qa_fail_count')); } catch (_) {}
|
|
@@ -642,9 +670,9 @@ async function main() {
|
|
|
642
670
|
|
|
643
671
|
if (fails <= 5) {
|
|
644
672
|
await agent.setProgress(job.id, 25);
|
|
645
|
-
await agent.sendMessage(job.id, '⚠️ OpenClaw run failed (attempt ' + fails + ').
|
|
673
|
+
await agent.sendMessage(job.id, '⚠️ OpenClaw run failed (attempt ' + fails + '). Re-queueing OpenClaw-only retry...');
|
|
646
674
|
await new Promise(r => setTimeout(r, 5000));
|
|
647
|
-
|
|
675
|
+
process.exit(42);
|
|
648
676
|
}
|
|
649
677
|
|
|
650
678
|
// Fail-safe report only after OpenClaw retry budget exhausted
|