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.
Files changed (3) hide show
  1. package/bin/chat.js +154 -1
  2. package/dist/daemon.mjs +815 -90
  3. 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 {