0agent 1.0.28 → 1.0.29

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 CHANGED
@@ -189,6 +189,13 @@ function handleWsEvent(event) {
189
189
  lineBuffer += event.token;
190
190
  break;
191
191
  }
192
+ case 'schedule.fired': {
193
+ // Show when a scheduled job fires — even if user is idle
194
+ const ts = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
195
+ process.stdout.write(`\n ${fmt(C.magenta, '⏰')} [${ts}] Scheduled: ${fmt(C.bold, event.job_name)} — ${event.task}\n`);
196
+ if (!streaming) rl.prompt(true);
197
+ break;
198
+ }
192
199
  case 'session.completed': {
193
200
  spinner.stop();
194
201
  if (streaming) { process.stdout.write('\n'); streaming = false; }
@@ -380,6 +387,78 @@ async function handleCommand(input) {
380
387
  }
381
388
 
382
389
  // /skills
390
+ // /schedule — cron-like job scheduler
391
+ case '/schedule': {
392
+ const schedArgs = parts.slice(1);
393
+ const subCmd = schedArgs[0]?.toLowerCase() ?? 'list';
394
+
395
+ if (subCmd === 'list' || schedArgs.length === 0) {
396
+ const res = await fetch(`${BASE_URL}/api/schedule`).catch(() => null);
397
+ const jobs = res?.ok ? await res.json().catch(() => []) : [];
398
+ if (!Array.isArray(jobs) || jobs.length === 0) {
399
+ console.log('\n No scheduled jobs. Add one:\n ' +
400
+ fmt(C.dim, '/schedule add "run /retro" every Friday at 5pm') + '\n');
401
+ } else {
402
+ console.log('\n Scheduled jobs:\n');
403
+ for (const j of jobs) {
404
+ const status = j.enabled ? fmt(C.green, '●') : fmt(C.dim, '○');
405
+ const next = j.next_run_human ?? 'unknown';
406
+ console.log(` ${status} ${fmt(C.bold, j.id)} ${j.name}`);
407
+ console.log(` ${fmt(C.dim, j.schedule_human + ' · next: ' + next)}`);
408
+ }
409
+ console.log();
410
+ }
411
+ } else if (subCmd === 'add') {
412
+ // /schedule add "<task>" <schedule...>
413
+ // Parse: extract quoted task, rest is schedule
414
+ const rest = parts.slice(2).join(' ');
415
+ const quoted = rest.match(/^"([^"]+)"\s+(.+)$/) || rest.match(/^'([^']+)'\s+(.+)$/);
416
+ if (!quoted) {
417
+ console.log(` ${fmt(C.dim, 'Usage: /schedule add "<task>" <schedule>')}`);
418
+ console.log(` ${fmt(C.dim, 'Examples:')}`);
419
+ console.log(` ${fmt(C.cyan, ' /schedule add "run /retro" every Friday at 5pm')}`);
420
+ console.log(` ${fmt(C.cyan, ' /schedule add "run /review" every day at 9am')}`);
421
+ console.log(` ${fmt(C.cyan, ' /schedule add "check the build" in 2 hours')}\n`);
422
+ } else {
423
+ const task = quoted[1];
424
+ const schedule = quoted[2];
425
+ const res = await fetch(`${BASE_URL}/api/schedule`, {
426
+ method: 'POST',
427
+ headers: { 'Content-Type': 'application/json' },
428
+ body: JSON.stringify({ task, schedule }),
429
+ }).catch(() => null);
430
+ const data = res?.ok ? await res.json().catch(() => null) : null;
431
+ if (data?.id) {
432
+ console.log(` ${fmt(C.green, '✓')} Scheduled: ${fmt(C.bold, data.name)}`);
433
+ console.log(` ${fmt(C.dim, data.schedule_human + ' · next: ' + data.next_run_human)}\n`);
434
+ } else {
435
+ console.log(` ${fmt(C.red, '✗')} ${data?.error ?? 'Failed to create schedule'}\n`);
436
+ }
437
+ }
438
+ } else if (subCmd === 'delete' || subCmd === 'remove') {
439
+ const id = schedArgs[1];
440
+ if (!id) { console.log(' Usage: /schedule delete <id>\n'); break; }
441
+ const res = await fetch(`${BASE_URL}/api/schedule/${id}`, { method: 'DELETE' }).catch(() => null);
442
+ const data = res?.ok ? await res.json().catch(() => null) : null;
443
+ console.log(data?.ok
444
+ ? ` ${fmt(C.green, '✓')} Deleted ${id}\n`
445
+ : ` ${fmt(C.red, '✗')} ${data?.error ?? 'Not found'}\n`);
446
+ } else if (subCmd === 'pause') {
447
+ const id = schedArgs[1];
448
+ if (!id) { console.log(' Usage: /schedule pause <id>\n'); break; }
449
+ await fetch(`${BASE_URL}/api/schedule/${id}/pause`, { method: 'POST' });
450
+ console.log(` ${fmt(C.green, '✓')} Paused ${id}\n`);
451
+ } else if (subCmd === 'resume') {
452
+ const id = schedArgs[1];
453
+ if (!id) { console.log(' Usage: /schedule resume <id>\n'); break; }
454
+ await fetch(`${BASE_URL}/api/schedule/${id}/resume`, { method: 'POST' });
455
+ console.log(` ${fmt(C.green, '✓')} Resumed ${id}\n`);
456
+ } else {
457
+ console.log(' Usage: /schedule list | add "<task>" <schedule> | delete <id> | pause <id> | resume <id>\n');
458
+ }
459
+ break;
460
+ }
461
+
383
462
  case '/skills': {
384
463
  try {
385
464
  const skills = await fetch(`${BASE_URL}/api/skills`).then(r => r.json());
@@ -442,6 +521,7 @@ const rl = createInterface({
442
521
  historySize: 100,
443
522
  completer: (line) => {
444
523
  const commands = ['/model', '/key', '/status', '/skills', '/graph', '/clear', '/help',
524
+ '/schedule', '/schedule list', '/schedule add',
445
525
  '/review', '/build', '/debug', '/qa', '/research', '/refactor', '/test-writer', '/retro'];
446
526
  const hits = commands.filter(c => c.startsWith(line));
447
527
  return [hits.length ? hits : commands, line];
@@ -569,7 +649,7 @@ rl.on('line', async (input) => {
569
649
  const line = input.trim();
570
650
  if (!line) { rl.prompt(); return; }
571
651
 
572
- if (line.startsWith('/') || ['/model','/key','/status','/skills','/graph','/clear','/help'].some(c => line.startsWith(c))) {
652
+ if (line.startsWith('/') || ['/model','/key','/status','/skills','/graph','/clear','/help','/schedule'].some(c => line.startsWith(c))) {
573
653
  await handleCommand(line);
574
654
  rl.prompt();
575
655
  } else {
package/dist/daemon.mjs CHANGED
@@ -4748,7 +4748,7 @@ var SkillRegistry = class {
4748
4748
  };
4749
4749
 
4750
4750
  // packages/daemon/src/HTTPServer.ts
4751
- import { Hono as Hono12 } from "hono";
4751
+ import { Hono as Hono13 } from "hono";
4752
4752
  import { serve } from "@hono/node-server";
4753
4753
  import { readFileSync as readFileSync8 } from "node:fs";
4754
4754
  import { resolve as resolve7, dirname as dirname3 } from "node:path";
@@ -5137,6 +5137,398 @@ function codespaceRoutes(deps) {
5137
5137
  return app;
5138
5138
  }
5139
5139
 
5140
+ // packages/daemon/src/routes/schedule.ts
5141
+ import { Hono as Hono12 } from "hono";
5142
+
5143
+ // packages/daemon/src/SchedulerManager.ts
5144
+ var DAYS = {
5145
+ sunday: 0,
5146
+ sun: 0,
5147
+ monday: 1,
5148
+ mon: 1,
5149
+ tuesday: 2,
5150
+ tue: 2,
5151
+ tues: 2,
5152
+ wednesday: 3,
5153
+ wed: 3,
5154
+ thursday: 4,
5155
+ thu: 4,
5156
+ thur: 4,
5157
+ thurs: 4,
5158
+ friday: 5,
5159
+ fri: 5,
5160
+ saturday: 6,
5161
+ sat: 6
5162
+ };
5163
+ function parseTime(s) {
5164
+ if (!s) return { hour: 9, minute: 0 };
5165
+ const m = s.trim().match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/i);
5166
+ if (!m) throw new Error(`Cannot parse time: "${s}". Use format like "9am", "5:30pm", "14:00"`);
5167
+ let hour = parseInt(m[1], 10);
5168
+ const minute = parseInt(m[2] ?? "0", 10);
5169
+ const ampm = m[3]?.toLowerCase();
5170
+ if (ampm === "pm" && hour !== 12) hour += 12;
5171
+ if (ampm === "am" && hour === 12) hour = 0;
5172
+ return { hour, minute };
5173
+ }
5174
+ function parseSchedule(text) {
5175
+ const t = text.trim().toLowerCase();
5176
+ const inMatch = t.match(/^in\s+(\d+)\s+(hour|hours|hr|hrs|minute|minutes|min|mins)$/);
5177
+ if (inMatch) {
5178
+ const n = parseInt(inMatch[1], 10);
5179
+ const isHours = inMatch[2].startsWith("h");
5180
+ const at = Date.now() + n * (isHours ? 36e5 : 6e4);
5181
+ return { spec: { type: "once", at }, human: text.trim() };
5182
+ }
5183
+ const tomorrowMatch = t.match(/^(?:tomorrow|tom)\s+at\s+(.+)$/);
5184
+ if (tomorrowMatch) {
5185
+ const { hour, minute } = parseTime(tomorrowMatch[1]);
5186
+ const d = /* @__PURE__ */ new Date();
5187
+ d.setDate(d.getDate() + 1);
5188
+ d.setHours(hour, minute, 0, 0);
5189
+ return { spec: { type: "once", at: d.getTime() }, human: text.trim() };
5190
+ }
5191
+ if (t === "every hour") return { spec: { type: "hourly", minute: 0 }, human: "every hour" };
5192
+ const everyMinsMatch = t.match(/^every\s+(\d+)\s+minutes?$/);
5193
+ if (everyMinsMatch) {
5194
+ const interval = parseInt(everyMinsMatch[1], 10);
5195
+ return { spec: { type: "interval_minutes", interval }, human: `every ${interval} minutes` };
5196
+ }
5197
+ const DEFAULT_TIMES = {
5198
+ morning: "9am",
5199
+ evening: "6pm",
5200
+ night: "10pm",
5201
+ noon: "12pm",
5202
+ midnight: "12am"
5203
+ };
5204
+ const dailyMatch = t.match(/^every\s+(day|daily|morning|evening|night|noon|midnight)\s*(?:at\s+(.+))?$/);
5205
+ if (dailyMatch) {
5206
+ const period = dailyMatch[1];
5207
+ const timeStr = dailyMatch[2] ?? DEFAULT_TIMES[period] ?? "9am";
5208
+ const { hour, minute } = parseTime(timeStr);
5209
+ const human = `every ${period}${dailyMatch[2] ? " at " + dailyMatch[2] : ""}`;
5210
+ return { spec: { type: "daily", hour, minute }, human };
5211
+ }
5212
+ const weeklyMatch = t.match(/^every\s+(\w+)\s*(?:at\s+(.+))?$/);
5213
+ if (weeklyMatch && DAYS[weeklyMatch[1]] !== void 0) {
5214
+ const day = DAYS[weeklyMatch[1]];
5215
+ const { hour, minute } = parseTime(weeklyMatch[2] ?? "9am");
5216
+ const human = `every ${weeklyMatch[1]}${weeklyMatch[2] ? " at " + weeklyMatch[2] : ""}`;
5217
+ return { spec: { type: "weekly", day, hour, minute }, human };
5218
+ }
5219
+ throw new Error(
5220
+ `Could not understand schedule: "${text}"
5221
+ Try: "every Friday at 5pm" \xB7 "every day at 9am" \xB7 "every morning" \xB7 "in 2 hours" \xB7 "every 30 minutes"`
5222
+ );
5223
+ }
5224
+ function scheduleToHuman(spec) {
5225
+ const pad = (n) => String(n).padStart(2, "0");
5226
+ const hhmm = (h, m) => {
5227
+ const ampm = h >= 12 ? "pm" : "am";
5228
+ const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
5229
+ return m === 0 ? `${h12}${ampm}` : `${h12}:${pad(m)}${ampm}`;
5230
+ };
5231
+ const DAYS_REV = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
5232
+ switch (spec.type) {
5233
+ case "once":
5234
+ return `once at ${new Date(spec.at).toLocaleString()}`;
5235
+ case "hourly":
5236
+ return `every hour`;
5237
+ case "interval_minutes":
5238
+ return `every ${spec.interval} minutes`;
5239
+ case "daily":
5240
+ return `every day at ${hhmm(spec.hour, spec.minute)}`;
5241
+ case "weekly":
5242
+ return `every ${DAYS_REV[spec.day]} at ${hhmm(spec.hour, spec.minute)}`;
5243
+ case "monthly":
5244
+ return `monthly on the ${spec.date}th at ${hhmm(spec.hour, spec.minute)}`;
5245
+ default:
5246
+ return "unknown schedule";
5247
+ }
5248
+ }
5249
+ function nextRunAt(spec, now = Date.now()) {
5250
+ const d = new Date(now);
5251
+ const next = new Date(now);
5252
+ switch (spec.type) {
5253
+ case "once":
5254
+ return spec.at;
5255
+ case "hourly": {
5256
+ next.setMinutes(spec.minute, 0, 0);
5257
+ if (next.getTime() <= now) next.setTime(next.getTime() + 36e5);
5258
+ return next.getTime();
5259
+ }
5260
+ case "interval_minutes": {
5261
+ const minsUntil = spec.interval - d.getMinutes() % spec.interval;
5262
+ return now + minsUntil * 6e4;
5263
+ }
5264
+ case "daily": {
5265
+ next.setHours(spec.hour, spec.minute, 0, 0);
5266
+ if (next.getTime() <= now) next.setDate(next.getDate() + 1);
5267
+ return next.getTime();
5268
+ }
5269
+ case "weekly": {
5270
+ const daysUntil = (spec.day - d.getDay() + 7) % 7 || 7;
5271
+ next.setDate(d.getDate() + daysUntil);
5272
+ next.setHours(spec.hour, spec.minute, 0, 0);
5273
+ if (next.getTime() <= now) next.setDate(next.getDate() + 7);
5274
+ return next.getTime();
5275
+ }
5276
+ case "monthly": {
5277
+ next.setDate(spec.date);
5278
+ next.setHours(spec.hour, spec.minute, 0, 0);
5279
+ if (next.getTime() <= now) next.setMonth(next.getMonth() + 1);
5280
+ return next.getTime();
5281
+ }
5282
+ }
5283
+ }
5284
+ var DDL = `
5285
+ CREATE TABLE IF NOT EXISTS scheduled_jobs (
5286
+ id TEXT PRIMARY KEY,
5287
+ name TEXT NOT NULL,
5288
+ task TEXT NOT NULL,
5289
+ skill TEXT,
5290
+ schedule_json TEXT NOT NULL,
5291
+ schedule_human TEXT NOT NULL,
5292
+ enabled INTEGER NOT NULL DEFAULT 1,
5293
+ last_run_at INTEGER,
5294
+ next_run_at INTEGER NOT NULL,
5295
+ run_count INTEGER NOT NULL DEFAULT 0,
5296
+ created_at INTEGER NOT NULL
5297
+ );
5298
+ CREATE INDEX IF NOT EXISTS idx_jobs_next ON scheduled_jobs(next_run_at, enabled);
5299
+ `;
5300
+ var SchedulerStore = class {
5301
+ constructor(adapter) {
5302
+ this.adapter = adapter;
5303
+ }
5304
+ initialised = false;
5305
+ init() {
5306
+ if (this.initialised) return;
5307
+ const db = this.adapter.db;
5308
+ db.exec(DDL);
5309
+ this.initialised = true;
5310
+ }
5311
+ save(job) {
5312
+ this.init();
5313
+ const db = this.adapter.db;
5314
+ db.prepare(`
5315
+ INSERT OR REPLACE INTO scheduled_jobs
5316
+ (id, name, task, skill, schedule_json, schedule_human, enabled, last_run_at, next_run_at, run_count, created_at)
5317
+ VALUES (?,?,?,?,?,?,?,?,?,?,?)
5318
+ `).run(
5319
+ job.id,
5320
+ job.name,
5321
+ job.task,
5322
+ job.skill ?? null,
5323
+ JSON.stringify(job.schedule),
5324
+ job.schedule_human,
5325
+ job.enabled ? 1 : 0,
5326
+ job.last_run_at ?? null,
5327
+ job.next_run_at,
5328
+ job.run_count,
5329
+ job.created_at
5330
+ );
5331
+ }
5332
+ delete(id) {
5333
+ this.init();
5334
+ const db = this.adapter.db;
5335
+ db.prepare("DELETE FROM scheduled_jobs WHERE id = ?").run(id);
5336
+ }
5337
+ list() {
5338
+ this.init();
5339
+ const db = this.adapter.db;
5340
+ const rows = db.prepare("SELECT * FROM scheduled_jobs ORDER BY next_run_at ASC").all();
5341
+ return rows.map(this.rowToJob);
5342
+ }
5343
+ getDue(now) {
5344
+ this.init();
5345
+ const db = this.adapter.db;
5346
+ const rows = db.prepare(
5347
+ "SELECT * FROM scheduled_jobs WHERE enabled = 1 AND next_run_at <= ?"
5348
+ ).all(now);
5349
+ return rows.map(this.rowToJob);
5350
+ }
5351
+ rowToJob(row) {
5352
+ return {
5353
+ id: row.id,
5354
+ name: row.name,
5355
+ task: row.task,
5356
+ skill: row.skill,
5357
+ schedule: JSON.parse(row.schedule_json),
5358
+ schedule_human: row.schedule_human,
5359
+ enabled: row.enabled === 1,
5360
+ last_run_at: row.last_run_at,
5361
+ next_run_at: row.next_run_at,
5362
+ run_count: row.run_count,
5363
+ created_at: row.created_at
5364
+ };
5365
+ }
5366
+ };
5367
+ var SchedulerManager = class {
5368
+ constructor(adapter, sessions, eventBus) {
5369
+ this.sessions = sessions;
5370
+ this.eventBus = eventBus;
5371
+ this.store = new SchedulerStore(adapter);
5372
+ this.store.init();
5373
+ }
5374
+ store;
5375
+ timer = null;
5376
+ start() {
5377
+ if (this.timer) return;
5378
+ this.timer = setInterval(() => this.tick(), 3e4);
5379
+ if (this.timer && typeof this.timer === "object" && "unref" in this.timer) {
5380
+ this.timer.unref();
5381
+ }
5382
+ const init = setTimeout(() => this.tick(), 5e3);
5383
+ if (typeof init === "object" && "unref" in init) init.unref();
5384
+ }
5385
+ stop() {
5386
+ if (this.timer) {
5387
+ clearInterval(this.timer);
5388
+ this.timer = null;
5389
+ }
5390
+ }
5391
+ /** Add a new scheduled job. */
5392
+ add(params) {
5393
+ const { spec, human } = parseSchedule(params.schedule);
5394
+ const now = Date.now();
5395
+ const job = {
5396
+ id: crypto.randomUUID().slice(0, 8),
5397
+ // short ID for easy reference
5398
+ name: params.name ?? params.task.slice(0, 40),
5399
+ task: params.task,
5400
+ skill: params.skill,
5401
+ schedule: spec,
5402
+ schedule_human: human,
5403
+ enabled: true,
5404
+ next_run_at: nextRunAt(spec, now),
5405
+ run_count: 0,
5406
+ created_at: now
5407
+ };
5408
+ this.store.save(job);
5409
+ return job;
5410
+ }
5411
+ /** Pause/resume a job. */
5412
+ setPaused(id, paused) {
5413
+ const jobs = this.store.list();
5414
+ const job = jobs.find((j) => j.id === id);
5415
+ if (!job) return false;
5416
+ job.enabled = !paused;
5417
+ this.store.save(job);
5418
+ return true;
5419
+ }
5420
+ /** Delete a job. */
5421
+ remove(id) {
5422
+ const jobs = this.store.list();
5423
+ const exists = jobs.some((j) => j.id === id);
5424
+ if (!exists) return false;
5425
+ this.store.delete(id);
5426
+ return true;
5427
+ }
5428
+ /** List all jobs. */
5429
+ list() {
5430
+ return this.store.list();
5431
+ }
5432
+ // ─── Tick ─────────────────────────────────────────────────────────────────
5433
+ async tick() {
5434
+ const now = Date.now();
5435
+ const due = this.store.getDue(now);
5436
+ for (const job of due) {
5437
+ if (job.last_run_at && now - job.last_run_at < 5e4) continue;
5438
+ await this.fire(job);
5439
+ }
5440
+ }
5441
+ async fire(job) {
5442
+ job.last_run_at = Date.now();
5443
+ job.run_count++;
5444
+ if (job.schedule.type === "once") {
5445
+ job.enabled = false;
5446
+ } else {
5447
+ job.next_run_at = nextRunAt(job.schedule, Date.now() + 6e4);
5448
+ }
5449
+ this.store.save(job);
5450
+ this.eventBus.emit({
5451
+ type: "schedule.fired",
5452
+ job_id: job.id,
5453
+ job_name: job.name,
5454
+ task: job.task,
5455
+ run_count: job.run_count
5456
+ });
5457
+ try {
5458
+ const session = this.sessions.createSession({ task: job.task, skill: job.skill });
5459
+ this.sessions.runExistingSession(session.id, { task: job.task, skill: job.skill }).then(() => {
5460
+ this.eventBus.emit({ type: "schedule.completed", job_id: job.id, session_id: session.id });
5461
+ }).catch((err) => {
5462
+ this.eventBus.emit({ type: "schedule.error", job_id: job.id, error: String(err) });
5463
+ });
5464
+ } catch (err) {
5465
+ this.eventBus.emit({ type: "schedule.error", job_id: job.id, error: String(err) });
5466
+ }
5467
+ }
5468
+ };
5469
+
5470
+ // packages/daemon/src/routes/schedule.ts
5471
+ function scheduleRoutes(deps) {
5472
+ const app = new Hono12();
5473
+ const getScheduler = (c) => {
5474
+ if (!deps.scheduler) {
5475
+ return { error: c.json({ error: "Scheduler not available" }, 503) };
5476
+ }
5477
+ return { scheduler: deps.scheduler };
5478
+ };
5479
+ app.get("/", (c) => {
5480
+ const { scheduler, error } = getScheduler(c);
5481
+ if (error) return error;
5482
+ const jobs = scheduler.list().map((j) => ({
5483
+ ...j,
5484
+ schedule_human: j.schedule_human || scheduleToHuman(j.schedule),
5485
+ next_run_human: new Date(j.next_run_at).toLocaleString()
5486
+ }));
5487
+ return c.json(jobs);
5488
+ });
5489
+ app.post("/", async (c) => {
5490
+ const { scheduler, error } = getScheduler(c);
5491
+ if (error) return error;
5492
+ const body = await c.req.json();
5493
+ if (!body.task || !body.schedule) {
5494
+ return c.json({ error: "task and schedule are required" }, 400);
5495
+ }
5496
+ try {
5497
+ const job = scheduler.add({
5498
+ task: body.task,
5499
+ schedule: body.schedule,
5500
+ name: body.name,
5501
+ skill: body.skill
5502
+ });
5503
+ return c.json({
5504
+ ...job,
5505
+ next_run_human: new Date(job.next_run_at).toLocaleString()
5506
+ }, 201);
5507
+ } catch (err) {
5508
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
5509
+ }
5510
+ });
5511
+ app.delete("/:id", (c) => {
5512
+ const { scheduler, error } = getScheduler(c);
5513
+ if (error) return error;
5514
+ const ok = scheduler.remove(c.req.param("id"));
5515
+ return ok ? c.json({ ok: true }) : c.json({ error: "Job not found" }, 404);
5516
+ });
5517
+ app.post("/:id/pause", (c) => {
5518
+ const { scheduler, error } = getScheduler(c);
5519
+ if (error) return error;
5520
+ const ok = scheduler.setPaused(c.req.param("id"), true);
5521
+ return ok ? c.json({ ok: true }) : c.json({ error: "Job not found" }, 404);
5522
+ });
5523
+ app.post("/:id/resume", (c) => {
5524
+ const { scheduler, error } = getScheduler(c);
5525
+ if (error) return error;
5526
+ const ok = scheduler.setPaused(c.req.param("id"), false);
5527
+ return ok ? c.json({ ok: true }) : c.json({ error: "Job not found" }, 404);
5528
+ });
5529
+ return app;
5530
+ }
5531
+
5140
5532
  // packages/daemon/src/HTTPServer.ts
5141
5533
  function findGraphHtml() {
5142
5534
  const candidates = [
@@ -5162,7 +5554,7 @@ var HTTPServer = class {
5162
5554
  deps;
5163
5555
  constructor(deps) {
5164
5556
  this.deps = deps;
5165
- this.app = new Hono12();
5557
+ this.app = new Hono13();
5166
5558
  this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
5167
5559
  this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
5168
5560
  this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
@@ -5173,6 +5565,7 @@ var HTTPServer = class {
5173
5565
  this.app.route("/api/insights", insightsRoutes({ proactiveSurface: deps.proactiveSurface ?? null }));
5174
5566
  this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
5175
5567
  this.app.route("/api/llm", llmRoutes());
5568
+ this.app.route("/api/schedule", scheduleRoutes({ scheduler: deps.scheduler ?? null }));
5176
5569
  this.app.route("/api/codespace", codespaceRoutes({
5177
5570
  getManager: deps.getCodespaceManager ?? (() => null),
5178
5571
  setup: deps.setupCodespace ?? (async () => ({ started: false, error: "Not configured" }))
@@ -6045,6 +6438,7 @@ var ZeroAgentDaemon = class {
6045
6438
  memorySyncTimer = null;
6046
6439
  proactiveSurfaceInstance = null;
6047
6440
  codespaceManager = null;
6441
+ schedulerManager = null;
6048
6442
  startedAt = 0;
6049
6443
  pidFilePath;
6050
6444
  constructor() {
@@ -6144,6 +6538,8 @@ var ZeroAgentDaemon = class {
6144
6538
  proactiveSurface = new ProactiveSurface2(this.graph, this.eventBus, cwd);
6145
6539
  } catch {
6146
6540
  }
6541
+ this.schedulerManager = new SchedulerManager(this.adapter, this.sessionManager, this.eventBus);
6542
+ this.schedulerManager.start();
6147
6543
  this.backgroundWorkers = new BackgroundWorkers({
6148
6544
  graph: this.graph,
6149
6545
  traceStore: this.traceStore,
@@ -6168,6 +6564,7 @@ var ZeroAgentDaemon = class {
6168
6564
  getMemorySync: () => memSyncRef,
6169
6565
  proactiveSurface,
6170
6566
  getCodespaceManager: () => this.codespaceManager,
6567
+ scheduler: this.schedulerManager,
6171
6568
  setupCodespace: async () => {
6172
6569
  if (!this.codespaceManager) return { started: false, error: "GitHub memory not configured. Run: 0agent memory connect github" };
6173
6570
  try {
@@ -6213,6 +6610,8 @@ var ZeroAgentDaemon = class {
6213
6610
  this.memorySyncTimer = null;
6214
6611
  }
6215
6612
  this.githubMemorySync = null;
6613
+ this.schedulerManager?.stop();
6614
+ this.schedulerManager = null;
6216
6615
  this.codespaceManager?.closeTunnel();
6217
6616
  this.codespaceManager = null;
6218
6617
  this.sessionManager = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "A persistent, learning AI agent that runs on your machine. An agent that learns.",
5
5
  "private": false,
6
6
  "license": "Apache-2.0",