0agent 1.0.28 โ 1.0.30
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/bin/chat.js +154 -1
- package/dist/daemon.mjs +815 -90
- package/package.json +1 -1
package/bin/chat.js
CHANGED
|
@@ -189,6 +189,86 @@ function handleWsEvent(event) {
|
|
|
189
189
|
lineBuffer += event.token;
|
|
190
190
|
break;
|
|
191
191
|
}
|
|
192
|
+
case 'runtime.heal_proposal': {
|
|
193
|
+
// Daemon found a code bug and is proposing a fix โ requires human y/n
|
|
194
|
+
const p = event.proposal ?? {};
|
|
195
|
+
process.stdout.write('\n');
|
|
196
|
+
process.stdout.write(` ${fmt(C.yellow, '๐ง Runtime code bug detected')}\n`);
|
|
197
|
+
process.stdout.write(` ${fmt(C.dim, p.error_summary ?? '')}\n`);
|
|
198
|
+
process.stdout.write(` ${fmt(C.dim, 'File: ' + (p.location?.relPath ?? 'unknown'))}\n\n`);
|
|
199
|
+
|
|
200
|
+
if (p.explanation) {
|
|
201
|
+
process.stdout.write(` ${fmt(C.bold, 'Diagnosis:')} ${p.explanation}\n\n`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (p.diff) {
|
|
205
|
+
process.stdout.write(` ${fmt(C.bold, 'Proposed fix:')}\n`);
|
|
206
|
+
const diffLines = String(p.diff).split('\n');
|
|
207
|
+
for (const line of diffLines) {
|
|
208
|
+
if (line.startsWith('-')) process.stdout.write(` ${fmt(C.red, line)}\n`);
|
|
209
|
+
else if (line.startsWith('+')) process.stdout.write(` ${fmt(C.green, line)}\n`);
|
|
210
|
+
else process.stdout.write(` ${fmt(C.dim, line)}\n`);
|
|
211
|
+
}
|
|
212
|
+
process.stdout.write('\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const confidence = p.confidence ?? 'medium';
|
|
216
|
+
const confColor = confidence === 'high' ? C.green : confidence === 'medium' ? C.yellow : C.red;
|
|
217
|
+
process.stdout.write(` Confidence: ${fmt(confColor, confidence.toUpperCase())}\n\n`);
|
|
218
|
+
|
|
219
|
+
// Ask for approval โ use readline in raw mode for single keypress
|
|
220
|
+
process.stdout.write(` Apply this fix and restart daemon? ${fmt(C.bold, '[y/N]')} `);
|
|
221
|
+
|
|
222
|
+
const handleHealApproval = async (key) => {
|
|
223
|
+
process.stdin.removeListener('keypress', handleHealApproval);
|
|
224
|
+
const answer = key?.toLowerCase() ?? 'n';
|
|
225
|
+
process.stdout.write(answer + '\n\n');
|
|
226
|
+
|
|
227
|
+
if (answer === 'y') {
|
|
228
|
+
// Store proposal then approve
|
|
229
|
+
await fetch(`${BASE_URL}/api/runtime/proposals`, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: { 'Content-Type': 'application/json' },
|
|
232
|
+
body: JSON.stringify(p),
|
|
233
|
+
}).catch(() => {});
|
|
234
|
+
|
|
235
|
+
const approveRes = await fetch(`${BASE_URL}/api/runtime/proposals/${p.proposal_id}/approve`, {
|
|
236
|
+
method: 'POST',
|
|
237
|
+
}).catch(() => null);
|
|
238
|
+
const data = approveRes?.ok ? await approveRes.json().catch(() => null) : null;
|
|
239
|
+
|
|
240
|
+
if (data?.applied) {
|
|
241
|
+
process.stdout.write(` ${fmt(C.green, 'โ')} Patch applied. ${data.message}\n`);
|
|
242
|
+
process.stdout.write(` ${fmt(C.dim, 'Daemon restarting โ reconnecting in 5s...')}\n\n`);
|
|
243
|
+
} else {
|
|
244
|
+
process.stdout.write(` ${fmt(C.red, 'โ')} Could not apply: ${data?.message ?? 'unknown error'}\n\n`);
|
|
245
|
+
rl.prompt();
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
await fetch(`${BASE_URL}/api/runtime/proposals/${p.proposal_id}`, { method: 'DELETE' }).catch(() => {});
|
|
249
|
+
process.stdout.write(` ${fmt(C.dim, 'Fix rejected. The bug remains.')}\n\n`);
|
|
250
|
+
rl.prompt();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Listen for a single keypress
|
|
255
|
+
if (process.stdin.isTTY) {
|
|
256
|
+
process.stdin.once('data', (buf) => {
|
|
257
|
+
handleHealApproval(buf.toString().trim().toLowerCase());
|
|
258
|
+
});
|
|
259
|
+
} else {
|
|
260
|
+
rl.once('line', (line) => handleHealApproval(line.trim().toLowerCase()));
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case 'schedule.fired': {
|
|
266
|
+
// Show when a scheduled job fires โ even if user is idle
|
|
267
|
+
const ts = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
268
|
+
process.stdout.write(`\n ${fmt(C.magenta, 'โฐ')} [${ts}] Scheduled: ${fmt(C.bold, event.job_name)} โ ${event.task}\n`);
|
|
269
|
+
if (!streaming) rl.prompt(true);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
192
272
|
case 'session.completed': {
|
|
193
273
|
spinner.stop();
|
|
194
274
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
@@ -380,6 +460,78 @@ async function handleCommand(input) {
|
|
|
380
460
|
}
|
|
381
461
|
|
|
382
462
|
// /skills
|
|
463
|
+
// /schedule โ cron-like job scheduler
|
|
464
|
+
case '/schedule': {
|
|
465
|
+
const schedArgs = parts.slice(1);
|
|
466
|
+
const subCmd = schedArgs[0]?.toLowerCase() ?? 'list';
|
|
467
|
+
|
|
468
|
+
if (subCmd === 'list' || schedArgs.length === 0) {
|
|
469
|
+
const res = await fetch(`${BASE_URL}/api/schedule`).catch(() => null);
|
|
470
|
+
const jobs = res?.ok ? await res.json().catch(() => []) : [];
|
|
471
|
+
if (!Array.isArray(jobs) || jobs.length === 0) {
|
|
472
|
+
console.log('\n No scheduled jobs. Add one:\n ' +
|
|
473
|
+
fmt(C.dim, '/schedule add "run /retro" every Friday at 5pm') + '\n');
|
|
474
|
+
} else {
|
|
475
|
+
console.log('\n Scheduled jobs:\n');
|
|
476
|
+
for (const j of jobs) {
|
|
477
|
+
const status = j.enabled ? fmt(C.green, 'โ') : fmt(C.dim, 'โ');
|
|
478
|
+
const next = j.next_run_human ?? 'unknown';
|
|
479
|
+
console.log(` ${status} ${fmt(C.bold, j.id)} ${j.name}`);
|
|
480
|
+
console.log(` ${fmt(C.dim, j.schedule_human + ' ยท next: ' + next)}`);
|
|
481
|
+
}
|
|
482
|
+
console.log();
|
|
483
|
+
}
|
|
484
|
+
} else if (subCmd === 'add') {
|
|
485
|
+
// /schedule add "<task>" <schedule...>
|
|
486
|
+
// Parse: extract quoted task, rest is schedule
|
|
487
|
+
const rest = parts.slice(2).join(' ');
|
|
488
|
+
const quoted = rest.match(/^"([^"]+)"\s+(.+)$/) || rest.match(/^'([^']+)'\s+(.+)$/);
|
|
489
|
+
if (!quoted) {
|
|
490
|
+
console.log(` ${fmt(C.dim, 'Usage: /schedule add "<task>" <schedule>')}`);
|
|
491
|
+
console.log(` ${fmt(C.dim, 'Examples:')}`);
|
|
492
|
+
console.log(` ${fmt(C.cyan, ' /schedule add "run /retro" every Friday at 5pm')}`);
|
|
493
|
+
console.log(` ${fmt(C.cyan, ' /schedule add "run /review" every day at 9am')}`);
|
|
494
|
+
console.log(` ${fmt(C.cyan, ' /schedule add "check the build" in 2 hours')}\n`);
|
|
495
|
+
} else {
|
|
496
|
+
const task = quoted[1];
|
|
497
|
+
const schedule = quoted[2];
|
|
498
|
+
const res = await fetch(`${BASE_URL}/api/schedule`, {
|
|
499
|
+
method: 'POST',
|
|
500
|
+
headers: { 'Content-Type': 'application/json' },
|
|
501
|
+
body: JSON.stringify({ task, schedule }),
|
|
502
|
+
}).catch(() => null);
|
|
503
|
+
const data = res?.ok ? await res.json().catch(() => null) : null;
|
|
504
|
+
if (data?.id) {
|
|
505
|
+
console.log(` ${fmt(C.green, 'โ')} Scheduled: ${fmt(C.bold, data.name)}`);
|
|
506
|
+
console.log(` ${fmt(C.dim, data.schedule_human + ' ยท next: ' + data.next_run_human)}\n`);
|
|
507
|
+
} else {
|
|
508
|
+
console.log(` ${fmt(C.red, 'โ')} ${data?.error ?? 'Failed to create schedule'}\n`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
} else if (subCmd === 'delete' || subCmd === 'remove') {
|
|
512
|
+
const id = schedArgs[1];
|
|
513
|
+
if (!id) { console.log(' Usage: /schedule delete <id>\n'); break; }
|
|
514
|
+
const res = await fetch(`${BASE_URL}/api/schedule/${id}`, { method: 'DELETE' }).catch(() => null);
|
|
515
|
+
const data = res?.ok ? await res.json().catch(() => null) : null;
|
|
516
|
+
console.log(data?.ok
|
|
517
|
+
? ` ${fmt(C.green, 'โ')} Deleted ${id}\n`
|
|
518
|
+
: ` ${fmt(C.red, 'โ')} ${data?.error ?? 'Not found'}\n`);
|
|
519
|
+
} else if (subCmd === 'pause') {
|
|
520
|
+
const id = schedArgs[1];
|
|
521
|
+
if (!id) { console.log(' Usage: /schedule pause <id>\n'); break; }
|
|
522
|
+
await fetch(`${BASE_URL}/api/schedule/${id}/pause`, { method: 'POST' });
|
|
523
|
+
console.log(` ${fmt(C.green, 'โ')} Paused ${id}\n`);
|
|
524
|
+
} else if (subCmd === 'resume') {
|
|
525
|
+
const id = schedArgs[1];
|
|
526
|
+
if (!id) { console.log(' Usage: /schedule resume <id>\n'); break; }
|
|
527
|
+
await fetch(`${BASE_URL}/api/schedule/${id}/resume`, { method: 'POST' });
|
|
528
|
+
console.log(` ${fmt(C.green, 'โ')} Resumed ${id}\n`);
|
|
529
|
+
} else {
|
|
530
|
+
console.log(' Usage: /schedule list | add "<task>" <schedule> | delete <id> | pause <id> | resume <id>\n');
|
|
531
|
+
}
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
|
|
383
535
|
case '/skills': {
|
|
384
536
|
try {
|
|
385
537
|
const skills = await fetch(`${BASE_URL}/api/skills`).then(r => r.json());
|
|
@@ -442,6 +594,7 @@ const rl = createInterface({
|
|
|
442
594
|
historySize: 100,
|
|
443
595
|
completer: (line) => {
|
|
444
596
|
const commands = ['/model', '/key', '/status', '/skills', '/graph', '/clear', '/help',
|
|
597
|
+
'/schedule', '/schedule list', '/schedule add',
|
|
445
598
|
'/review', '/build', '/debug', '/qa', '/research', '/refactor', '/test-writer', '/retro'];
|
|
446
599
|
const hits = commands.filter(c => c.startsWith(line));
|
|
447
600
|
return [hits.length ? hits : commands, line];
|
|
@@ -569,7 +722,7 @@ rl.on('line', async (input) => {
|
|
|
569
722
|
const line = input.trim();
|
|
570
723
|
if (!line) { rl.prompt(); return; }
|
|
571
724
|
|
|
572
|
-
if (line.startsWith('/') || ['/model','/key','/status','/skills','/graph','/clear','/help'].some(c => line.startsWith(c))) {
|
|
725
|
+
if (line.startsWith('/') || ['/model','/key','/status','/skills','/graph','/clear','/help','/schedule'].some(c => line.startsWith(c))) {
|
|
573
726
|
await handleCommand(line);
|
|
574
727
|
rl.prompt();
|
|
575
728
|
} else {
|