omnius 1.0.24 → 1.0.26

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 CHANGED
@@ -1483,7 +1483,7 @@ var init_security_classifier = __esm({
1483
1483
  // ── Local writes (fs mutation)
1484
1484
  { match: /^(file_write|file_edit|file_patch|batch_edit|notebook_edit|structured_file|create_structured_file|image_resize)$/, info: LOCAL_WRITE },
1485
1485
  { match: /^(working_notes|todo_write)$/, info: LOCAL_WRITE },
1486
- { match: /^(scheduler|reminder|agenda)$/, info: SCHEDULER_REMINDER },
1486
+ { match: /^(scheduler|cronjob|reminder|remind|reminders|agenda)$/, info: SCHEDULER_REMINDER },
1487
1487
  { match: /^(exploration_culture|explore)$/, info: LOCAL_WRITE },
1488
1488
  // ── Task control
1489
1489
  { match: /^(task_status|task_output|task_stop)$/, info: TASK_CONTROL },
@@ -5763,7 +5763,10 @@ var init_explore_tools = __esm({
5763
5763
  browser_action: "Interactive browser: login, fill forms, click buttons, screenshot — session persists between calls",
5764
5764
  carbonyl_browser: "Terminal-rendered real browser automation via Carbonyl: navigate, read rendered text, click/type, sessions, daemon mode",
5765
5765
  scheduler: "Schedule tasks for automatic future execution via OS cron",
5766
- reminder: "Set reminders that surface at future agent startups",
5766
+ cronjob: "Alias for scheduler: OS cron-backed time triggers",
5767
+ reminder: "Set scoped minimal reminders or scheduled action triggers",
5768
+ remind: "Alias for reminder: create or list scoped reminders",
5769
+ reminders: "Alias for reminder: list and manage scoped reminders",
5767
5770
  agenda: "View all pending reminders, scheduled tasks, and attention items",
5768
5771
  background_run: "Run a shell command in the background",
5769
5772
  task_status: "Check status of background tasks",
@@ -74631,12 +74634,12 @@ var require_x509_cjs = __commonJS({
74631
74634
  var require_crypto = __commonJS({
74632
74635
  "../node_modules/acme-client/src/crypto/index.js"(exports) {
74633
74636
  var net5 = __require("net");
74634
- var { promisify: promisify6 } = __require("util");
74637
+ var { promisify: promisify7 } = __require("util");
74635
74638
  var crypto14 = __require("crypto");
74636
74639
  var asn1js4 = require_build2();
74637
74640
  var x5093 = require_x509_cjs();
74638
- var randomInt2 = promisify6(crypto14.randomInt);
74639
- var generateKeyPair2 = promisify6(crypto14.generateKeyPair);
74641
+ var randomInt2 = promisify7(crypto14.randomInt);
74642
+ var generateKeyPair2 = promisify7(crypto14.generateKeyPair);
74640
74643
  x5093.cryptoProvider.set(crypto14.webcrypto);
74641
74644
  var subjectAltNameOID = "2.5.29.17";
74642
74645
  var alpnAcmeIdentifierOID = "1.3.6.1.5.5.7.1.31";
@@ -109481,9 +109484,9 @@ var require_lib = __commonJS({
109481
109484
  var require_forge2 = __commonJS({
109482
109485
  "../node_modules/acme-client/src/crypto/forge.js"(exports) {
109483
109486
  var net5 = __require("net");
109484
- var { promisify: promisify6 } = __require("util");
109487
+ var { promisify: promisify7 } = __require("util");
109485
109488
  var forge = require_lib();
109486
- var generateKeyPair2 = promisify6(forge.pki.rsa.generateKeyPair);
109489
+ var generateKeyPair2 = promisify7(forge.pki.rsa.generateKeyPair);
109487
109490
  function forgeObjectFromPem(input) {
109488
109491
  const msg = forge.pem.decode(input)[0];
109489
109492
  let result;
@@ -126709,7 +126712,7 @@ var require_mock_interceptor = __commonJS({
126709
126712
  var require_mock_client = __commonJS({
126710
126713
  "../node_modules/undici/lib/mock/mock-client.js"(exports, module) {
126711
126714
  "use strict";
126712
- var { promisify: promisify6 } = __require("node:util");
126715
+ var { promisify: promisify7 } = __require("node:util");
126713
126716
  var Client2 = require_client2();
126714
126717
  var { buildMockDispatch } = require_mock_utils();
126715
126718
  var {
@@ -126757,7 +126760,7 @@ var require_mock_client = __commonJS({
126757
126760
  this[kDispatches] = [];
126758
126761
  }
126759
126762
  async [kClose]() {
126760
- await promisify6(this[kOriginalClose])();
126763
+ await promisify7(this[kOriginalClose])();
126761
126764
  this[kConnected] = 0;
126762
126765
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
126763
126766
  }
@@ -126970,7 +126973,7 @@ var require_mock_call_history = __commonJS({
126970
126973
  var require_mock_pool = __commonJS({
126971
126974
  "../node_modules/undici/lib/mock/mock-pool.js"(exports, module) {
126972
126975
  "use strict";
126973
- var { promisify: promisify6 } = __require("node:util");
126976
+ var { promisify: promisify7 } = __require("node:util");
126974
126977
  var Pool = require_pool();
126975
126978
  var { buildMockDispatch } = require_mock_utils();
126976
126979
  var {
@@ -127018,7 +127021,7 @@ var require_mock_pool = __commonJS({
127018
127021
  this[kDispatches] = [];
127019
127022
  }
127020
127023
  async [kClose]() {
127021
- await promisify6(this[kOriginalClose])();
127024
+ await promisify7(this[kOriginalClose])();
127022
127025
  this[kConnected] = 0;
127023
127026
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
127024
127027
  }
@@ -257491,6 +257494,58 @@ function resolveSchedule(schedule) {
257491
257494
  const lower = schedule.toLowerCase().trim();
257492
257495
  if (SCHEDULE_PRESETS[lower])
257493
257496
  return SCHEDULE_PRESETS[lower];
257497
+ const dailyAt = lower.match(/^(?:daily|every\s+day)\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
257498
+ if (dailyAt) {
257499
+ const hour2 = parseInt(dailyAt[1], 10);
257500
+ const minute3 = parseInt(dailyAt[2], 10);
257501
+ if (isClockTime(hour2, minute3))
257502
+ return `${minute3} ${hour2} * * *`;
257503
+ }
257504
+ const weeklyAt = lower.match(/^weekly(?:\s+on)?\s+([a-z]+|\d)\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
257505
+ if (weeklyAt) {
257506
+ const dow = parseWeekday(weeklyAt[1]);
257507
+ const hour2 = parseInt(weeklyAt[2], 10);
257508
+ const minute3 = parseInt(weeklyAt[3], 10);
257509
+ if (dow !== null && isClockTime(hour2, minute3))
257510
+ return `${minute3} ${hour2} * * ${dow}`;
257511
+ }
257512
+ const monthlyAt = lower.match(/^monthly(?:\s+(?:on|day))?\s+(\d{1,2})(?:st|nd|rd|th)?\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
257513
+ if (monthlyAt) {
257514
+ const day = parseInt(monthlyAt[1], 10);
257515
+ const hour2 = parseInt(monthlyAt[2], 10);
257516
+ const minute3 = parseInt(monthlyAt[3], 10);
257517
+ if (day >= 1 && day <= 31 && isClockTime(hour2, minute3))
257518
+ return `${minute3} ${hour2} ${day} * *`;
257519
+ }
257520
+ const yearlyNumeric = lower.match(/^(?:yearly|annually|every\s+year)\s+(?:on\s+)?(\d{1,2})-(\d{1,2})\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
257521
+ if (yearlyNumeric) {
257522
+ const month = parseInt(yearlyNumeric[1], 10);
257523
+ const day = parseInt(yearlyNumeric[2], 10);
257524
+ const hour2 = parseInt(yearlyNumeric[3], 10);
257525
+ const minute3 = parseInt(yearlyNumeric[4], 10);
257526
+ if (month >= 1 && month <= 12 && day >= 1 && day <= 31 && isClockTime(hour2, minute3)) {
257527
+ return `${minute3} ${hour2} ${day} ${month} *`;
257528
+ }
257529
+ }
257530
+ const yearlyNamed = lower.match(/^(?:yearly|annually|every\s+year)\s+(?:on\s+)?([a-z]+)\s+(\d{1,2})(?:st|nd|rd|th)?\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
257531
+ if (yearlyNamed) {
257532
+ const month = parseMonth(yearlyNamed[1]);
257533
+ const day = parseInt(yearlyNamed[2], 10);
257534
+ const hour2 = parseInt(yearlyNamed[3], 10);
257535
+ const minute3 = parseInt(yearlyNamed[4], 10);
257536
+ if (month !== null && day >= 1 && day <= 31 && isClockTime(hour2, minute3)) {
257537
+ return `${minute3} ${hour2} ${day} ${month} *`;
257538
+ }
257539
+ }
257540
+ const everyLong = lower.match(/^every\s+(\d+)\s*(w|wk|wks|weeks?|mo|mos|months?)$/);
257541
+ if (everyLong) {
257542
+ const amount = parseInt(everyLong[1], 10);
257543
+ const unit = everyLong[2].charAt(0);
257544
+ if (amount > 0 && unit === "w")
257545
+ return `0 9 */${amount * 7} * *`;
257546
+ if (amount > 0 && unit === "m")
257547
+ return `0 9 1 */${amount} *`;
257548
+ }
257494
257549
  const inMatch = lower.match(/^in\s+(\d+)\s*(m|min|mins|minutes?|h|hr|hrs|hours?|d|days?)$/);
257495
257550
  if (inMatch) {
257496
257551
  const amount = parseInt(inMatch[1], 10);
@@ -257516,6 +257571,65 @@ function resolveSchedule(schedule) {
257516
257571
  return schedule.trim();
257517
257572
  return null;
257518
257573
  }
257574
+ function isClockTime(hour2, minute3) {
257575
+ return hour2 >= 0 && hour2 <= 23 && minute3 >= 0 && minute3 <= 59;
257576
+ }
257577
+ function parseWeekday(raw) {
257578
+ const value2 = raw.toLowerCase();
257579
+ if (/^[0-6]$/.test(value2))
257580
+ return Number(value2);
257581
+ const days = {
257582
+ sunday: 0,
257583
+ sun: 0,
257584
+ monday: 1,
257585
+ mon: 1,
257586
+ tuesday: 2,
257587
+ tue: 2,
257588
+ tues: 2,
257589
+ wednesday: 3,
257590
+ wed: 3,
257591
+ thursday: 4,
257592
+ thu: 4,
257593
+ thurs: 4,
257594
+ friday: 5,
257595
+ fri: 5,
257596
+ saturday: 6,
257597
+ sat: 6
257598
+ };
257599
+ return days[value2] ?? null;
257600
+ }
257601
+ function parseMonth(raw) {
257602
+ const value2 = raw.toLowerCase();
257603
+ if (/^(?:[1-9]|1[0-2])$/.test(value2))
257604
+ return Number(value2);
257605
+ const months = {
257606
+ january: 1,
257607
+ jan: 1,
257608
+ february: 2,
257609
+ feb: 2,
257610
+ march: 3,
257611
+ mar: 3,
257612
+ april: 4,
257613
+ apr: 4,
257614
+ may: 5,
257615
+ june: 6,
257616
+ jun: 6,
257617
+ july: 7,
257618
+ jul: 7,
257619
+ august: 8,
257620
+ aug: 8,
257621
+ september: 9,
257622
+ sep: 9,
257623
+ sept: 9,
257624
+ october: 10,
257625
+ oct: 10,
257626
+ november: 11,
257627
+ nov: 11,
257628
+ december: 12,
257629
+ dec: 12
257630
+ };
257631
+ return months[value2] ?? null;
257632
+ }
257519
257633
  function describeCron(expr) {
257520
257634
  const parts = expr.split(/\s+/);
257521
257635
  if (parts.length !== 5)
@@ -257705,12 +257819,14 @@ var init_scheduler = __esm({
257705
257819
  "daily evening": "0 18 * * *",
257706
257820
  "weekly": "0 9 * * 1",
257707
257821
  "monthly": "0 9 1 * *",
257822
+ "yearly": "0 9 1 1 *",
257823
+ "annually": "0 9 1 1 *",
257708
257824
  "hourly": "0 * * * *"
257709
257825
  };
257710
257826
  CRON_MARKER = "# OMNIUS-SCHEDULED:";
257711
257827
  SchedulerTool = class {
257712
257828
  name = "scheduler";
257713
- description = "Schedule tasks for automatic future execution using OS-level cron. Actions: 'create' a new scheduled task, 'list' all scheduled tasks, 'cancel' a task by ID, 'pause'/'resume' a task. Schedules: use presets ('daily', 'every 5 minutes', 'hourly', 'weekly'), natural language ('in 30m', 'at 14:30'), or raw cron expressions ('0 */2 * * *'). Scope: 'local' (default, stored in .omnius/) or 'global' (stored in ~/.omnius/, visible from all projects). Tasks launch the agent automatically at the scheduled time.";
257829
+ description = "Schedule tasks for automatic future execution using OS-level cron. Actions: 'create' a new scheduled task, 'list' all scheduled tasks, 'cancel' a task by ID, 'pause'/'resume' a task. Schedules: use presets ('daily', 'weekly', 'monthly', 'yearly'), intervals ('every 5 minutes'), 24-hour times ('at 14:30', 'weekly friday at 17:45', 'monthly on 15 at 08:30'), or raw cron expressions ('0 */2 * * *'). Scope: 'local' (default, stored in .omnius/) or 'global' (stored in ~/.omnius/, visible from all projects). Tasks launch the agent automatically at the scheduled time.";
257714
257830
  parameters = {
257715
257831
  type: "object",
257716
257832
  properties: {
@@ -257725,7 +257841,7 @@ var init_scheduler = __esm({
257725
257841
  },
257726
257842
  schedule: {
257727
257843
  type: "string",
257728
- description: "When to run: preset ('daily', 'every 5 minutes'), natural ('in 30m', 'at 14:30'), or cron ('0 */2 * * *')"
257844
+ description: "When to run: preset ('daily', 'weekly', 'monthly', 'yearly'), interval ('every 5 minutes'), 24-hour time ('at 14:30', 'weekly friday at 17:45'), or cron ('0 */2 * * *')"
257729
257845
  },
257730
257846
  id: {
257731
257847
  type: "string",
@@ -258027,6 +258143,219 @@ function parseDueTime(due) {
258027
258143
  }
258028
258144
  return null;
258029
258145
  }
258146
+ function parseDurationMs(input) {
258147
+ const lower = input.toLowerCase().trim();
258148
+ const match = lower.match(/^(?:every\s+)?(\d+)\s*(m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|wk|wks|weeks?)$/);
258149
+ if (!match)
258150
+ return null;
258151
+ const amount = parseInt(match[1], 10);
258152
+ if (!Number.isFinite(amount) || amount <= 0)
258153
+ return null;
258154
+ const unit = match[2].charAt(0);
258155
+ const ms = unit === "m" ? amount * 6e4 : unit === "h" ? amount * 36e5 : unit === "d" ? amount * 864e5 : amount * 7 * 864e5;
258156
+ return {
258157
+ ms,
258158
+ description: `every ${amount} ${unit === "m" ? "minute" : unit === "h" ? "hour" : unit === "d" ? "day" : "week"}${amount === 1 ? "" : "s"}`
258159
+ };
258160
+ }
258161
+ function parseTimeOfDay(value2) {
258162
+ const raw = String(value2 ?? "").trim();
258163
+ const match = raw.match(/^(\d{1,2}):(\d{2})$/);
258164
+ if (!match)
258165
+ return void 0;
258166
+ const hour2 = parseInt(match[1], 10);
258167
+ const minute3 = parseInt(match[2], 10);
258168
+ if (hour2 < 0 || hour2 > 23 || minute3 < 0 || minute3 > 59)
258169
+ return void 0;
258170
+ return `${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}`;
258171
+ }
258172
+ function parseWeekday2(value2) {
258173
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
258174
+ const day = Math.trunc(value2);
258175
+ return day >= 0 && day <= 6 ? day : void 0;
258176
+ }
258177
+ const raw = String(value2 ?? "").trim().toLowerCase();
258178
+ if (!raw)
258179
+ return void 0;
258180
+ const names = {
258181
+ sunday: 0,
258182
+ sun: 0,
258183
+ monday: 1,
258184
+ mon: 1,
258185
+ tuesday: 2,
258186
+ tue: 2,
258187
+ tues: 2,
258188
+ wednesday: 3,
258189
+ wed: 3,
258190
+ thursday: 4,
258191
+ thu: 4,
258192
+ thurs: 4,
258193
+ friday: 5,
258194
+ fri: 5,
258195
+ saturday: 6,
258196
+ sat: 6
258197
+ };
258198
+ if (/^[0-6]$/.test(raw))
258199
+ return Number(raw);
258200
+ return names[raw];
258201
+ }
258202
+ function parseMonth2(value2) {
258203
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
258204
+ const month = Math.trunc(value2);
258205
+ return month >= 1 && month <= 12 ? month : void 0;
258206
+ }
258207
+ const raw = String(value2 ?? "").trim().toLowerCase();
258208
+ if (!raw)
258209
+ return void 0;
258210
+ const names = {
258211
+ january: 1,
258212
+ jan: 1,
258213
+ february: 2,
258214
+ feb: 2,
258215
+ march: 3,
258216
+ mar: 3,
258217
+ april: 4,
258218
+ apr: 4,
258219
+ may: 5,
258220
+ june: 6,
258221
+ jun: 6,
258222
+ july: 7,
258223
+ jul: 7,
258224
+ august: 8,
258225
+ aug: 8,
258226
+ september: 9,
258227
+ sep: 9,
258228
+ sept: 9,
258229
+ october: 10,
258230
+ oct: 10,
258231
+ november: 11,
258232
+ nov: 11,
258233
+ december: 12,
258234
+ dec: 12
258235
+ };
258236
+ if (/^(?:[1-9]|1[0-2])$/.test(raw))
258237
+ return Number(raw);
258238
+ return names[raw];
258239
+ }
258240
+ function parseRecurrence(args) {
258241
+ const repeatRaw = String(args["repeat"] ?? args["recurrence"] ?? "").trim();
258242
+ if (!repeatRaw)
258243
+ return void 0;
258244
+ const lower = repeatRaw.toLowerCase();
258245
+ if (["none", "off", "false", "no"].includes(lower))
258246
+ return void 0;
258247
+ const maxOccurrences = typeof args["max_occurrences"] === "number" && Number.isFinite(args["max_occurrences"]) ? Math.max(1, Math.floor(args["max_occurrences"])) : typeof args["max_runs"] === "number" && Number.isFinite(args["max_runs"]) ? Math.max(1, Math.floor(args["max_runs"])) : void 0;
258248
+ const intervalInput = lower.startsWith("every ") ? lower : String(args["every"] ?? "").trim().toLowerCase();
258249
+ const interval = parseDurationMs(intervalInput);
258250
+ if (interval) {
258251
+ return {
258252
+ kind: "interval",
258253
+ everyMs: interval.ms,
258254
+ intervalDescription: interval.description,
258255
+ ...maxOccurrences ? { maxOccurrences } : {}
258256
+ };
258257
+ }
258258
+ const repeatTime = lower.match(/\bat\s+(\d{1,2}:\d{2})\b/)?.[1];
258259
+ const timeOfDay = parseTimeOfDay(args["time"]) ?? parseTimeOfDay(args["at"]) ?? parseTimeOfDay(repeatTime);
258260
+ const withMax = (recurrence) => maxOccurrences ? { ...recurrence, maxOccurrences } : recurrence;
258261
+ if (lower === "daily" || lower === "every day") {
258262
+ return withMax({ kind: "daily", timeOfDay: timeOfDay ?? "09:00" });
258263
+ }
258264
+ if (lower.startsWith("weekly") || lower === "every week") {
258265
+ const dayFromText = lower.match(/\b(sun(?:day)?|mon(?:day)?|tue(?:s|sday)?|wed(?:nesday)?|thu(?:rs|rsday)?|fri(?:day)?|sat(?:urday)?)\b/i)?.[1];
258266
+ return withMax({
258267
+ kind: "weekly",
258268
+ weekday: parseWeekday2(args["weekday"] ?? dayFromText) ?? 1,
258269
+ timeOfDay: timeOfDay ?? "09:00"
258270
+ });
258271
+ }
258272
+ if (lower.startsWith("monthly") || lower === "every month") {
258273
+ const dayRaw = args["day_of_month"] ?? args["day"] ?? lower.match(/\b(?:day|on)\s+(\d{1,2})\b/)?.[1];
258274
+ const day = typeof dayRaw === "number" ? Math.trunc(dayRaw) : parseInt(String(dayRaw ?? "1"), 10);
258275
+ return withMax({
258276
+ kind: "monthly",
258277
+ dayOfMonth: Math.max(1, Math.min(31, Number.isFinite(day) ? day : 1)),
258278
+ timeOfDay: timeOfDay ?? "09:00"
258279
+ });
258280
+ }
258281
+ if (lower.startsWith("yearly") || lower === "annually" || lower === "every year") {
258282
+ const yearlyText = lower.match(/\b(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?|\d{1,2})\s+(\d{1,2})(?:st|nd|rd|th)?\b/i);
258283
+ const month = parseMonth2(args["month"] ?? yearlyText?.[1]) ?? 1;
258284
+ const dayRaw = args["day_of_month"] ?? args["day"] ?? yearlyText?.[2] ?? 1;
258285
+ const day = typeof dayRaw === "number" ? Math.trunc(dayRaw) : parseInt(String(dayRaw), 10);
258286
+ return withMax({
258287
+ kind: "yearly",
258288
+ month,
258289
+ dayOfMonth: Math.max(1, Math.min(31, Number.isFinite(day) ? day : 1)),
258290
+ timeOfDay: timeOfDay ?? "09:00"
258291
+ });
258292
+ }
258293
+ return null;
258294
+ }
258295
+ function setLocalTime(target, timeOfDay, fallback = "09:00") {
258296
+ const [hourRaw, minuteRaw] = (timeOfDay ?? fallback).split(":");
258297
+ target.setHours(parseInt(hourRaw ?? "9", 10), parseInt(minuteRaw ?? "0", 10), 0, 0);
258298
+ }
258299
+ function clampDateToMonth(target, dayOfMonth) {
258300
+ target.setDate(1);
258301
+ const maxDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
258302
+ target.setDate(Math.min(Math.max(1, dayOfMonth), maxDay));
258303
+ }
258304
+ function nextDueFromRecurrence(recurrence, after = /* @__PURE__ */ new Date()) {
258305
+ const target = new Date(after);
258306
+ switch (recurrence.kind) {
258307
+ case "interval":
258308
+ return new Date(after.getTime() + Math.max(6e4, recurrence.everyMs ?? 864e5));
258309
+ case "daily":
258310
+ setLocalTime(target, recurrence.timeOfDay);
258311
+ if (target <= after)
258312
+ target.setDate(target.getDate() + 1);
258313
+ return target;
258314
+ case "weekly": {
258315
+ setLocalTime(target, recurrence.timeOfDay);
258316
+ const weekday = recurrence.weekday ?? 1;
258317
+ const daysAhead = (weekday - target.getDay() + 7) % 7;
258318
+ target.setDate(target.getDate() + daysAhead);
258319
+ if (target <= after)
258320
+ target.setDate(target.getDate() + 7);
258321
+ return target;
258322
+ }
258323
+ case "monthly":
258324
+ setLocalTime(target, recurrence.timeOfDay);
258325
+ clampDateToMonth(target, recurrence.dayOfMonth ?? 1);
258326
+ if (target <= after) {
258327
+ target.setMonth(target.getMonth() + 1);
258328
+ clampDateToMonth(target, recurrence.dayOfMonth ?? 1);
258329
+ }
258330
+ return target;
258331
+ case "yearly":
258332
+ target.setMonth((recurrence.month ?? 1) - 1, 1);
258333
+ clampDateToMonth(target, recurrence.dayOfMonth ?? 1);
258334
+ setLocalTime(target, recurrence.timeOfDay);
258335
+ if (target <= after) {
258336
+ target.setFullYear(target.getFullYear() + 1);
258337
+ target.setMonth((recurrence.month ?? 1) - 1, 1);
258338
+ clampDateToMonth(target, recurrence.dayOfMonth ?? 1);
258339
+ }
258340
+ return target;
258341
+ }
258342
+ }
258343
+ function describeRecurrence(recurrence) {
258344
+ if (!recurrence)
258345
+ return void 0;
258346
+ switch (recurrence.kind) {
258347
+ case "interval":
258348
+ return recurrence.intervalDescription ?? `every ${Math.round((recurrence.everyMs ?? 0) / 6e4)} minutes`;
258349
+ case "daily":
258350
+ return `daily at ${recurrence.timeOfDay ?? "09:00"}`;
258351
+ case "weekly":
258352
+ return `weekly on day ${recurrence.weekday ?? 1} at ${recurrence.timeOfDay ?? "09:00"}`;
258353
+ case "monthly":
258354
+ return `monthly on day ${recurrence.dayOfMonth ?? 1} at ${recurrence.timeOfDay ?? "09:00"}`;
258355
+ case "yearly":
258356
+ return `yearly on ${String(recurrence.month ?? 1).padStart(2, "0")}-${String(recurrence.dayOfMonth ?? 1).padStart(2, "0")} at ${recurrence.timeOfDay ?? "09:00"}`;
258357
+ }
258358
+ }
258030
258359
  async function getStorePath(workingDir) {
258031
258360
  const dir = resolve26(workingDir, ".omnius", "scheduled");
258032
258361
  await mkdir14(dir, { recursive: true });
@@ -258046,28 +258375,77 @@ async function saveReminderStore(workingDir, store2) {
258046
258375
  store2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
258047
258376
  await writeFile19(storePath, JSON.stringify(store2, null, 2), "utf-8");
258048
258377
  }
258049
- async function getDueReminders(workingDir) {
258378
+ async function getDueReminders(workingDir, query = {}) {
258050
258379
  const store2 = await loadReminderStore(workingDir);
258051
- const now = /* @__PURE__ */ new Date();
258380
+ const now = query.now ?? /* @__PURE__ */ new Date();
258052
258381
  return store2.reminders.filter((r2) => {
258053
258382
  if (r2.status !== "pending")
258054
258383
  return false;
258055
- if (!r2.dueAt)
258056
- return true;
258057
- return new Date(r2.dueAt) <= now;
258384
+ if (r2.dueAt && new Date(r2.dueAt) > now)
258385
+ return false;
258386
+ if (query.deliveryMode && query.deliveryMode !== "any" && (r2.delivery?.mode ?? "minimal") !== query.deliveryMode)
258387
+ return false;
258388
+ if (query.scope && !reminderScopeMatches(r2.scope, query.scope, query.includeUnscoped === true))
258389
+ return false;
258390
+ return true;
258058
258391
  });
258059
258392
  }
258060
258393
  async function markRemindersSeen(workingDir, ids) {
258394
+ await markRemindersTriggered(workingDir, ids);
258395
+ }
258396
+ async function markRemindersTriggered(workingDir, ids, now = /* @__PURE__ */ new Date()) {
258061
258397
  const store2 = await loadReminderStore(workingDir);
258062
258398
  const idSet = new Set(ids);
258063
258399
  for (const r2 of store2.reminders) {
258064
258400
  if (idSet.has(r2.id) && r2.status === "pending") {
258065
- r2.status = "seen";
258066
- r2.seenAt = (/* @__PURE__ */ new Date()).toISOString();
258401
+ r2.lastTriggeredAt = now.toISOString();
258402
+ r2.triggerCount = (r2.triggerCount ?? 0) + 1;
258403
+ const max = r2.recurrence?.maxOccurrences;
258404
+ if (r2.recurrence && (!max || r2.triggerCount < max)) {
258405
+ r2.dueAt = nextDueFromRecurrence(r2.recurrence, now).toISOString();
258406
+ r2.dueDescription = describeRecurrence(r2.recurrence);
258407
+ r2.seenAt = void 0;
258408
+ } else {
258409
+ r2.status = "seen";
258410
+ r2.seenAt = now.toISOString();
258411
+ }
258067
258412
  }
258068
258413
  }
258069
258414
  await saveReminderStore(workingDir, store2);
258070
258415
  }
258416
+ function reminderScopeMatches(scope, query, includeUnscoped) {
258417
+ if (!scope)
258418
+ return includeUnscoped;
258419
+ const keys = ["surface", "visibility", "targetId", "sessionId", "chatId", "threadId", "userId", "username", "label"];
258420
+ for (const key of keys) {
258421
+ const expected = query[key];
258422
+ if (expected !== void 0 && scope[key] !== expected)
258423
+ return false;
258424
+ }
258425
+ return true;
258426
+ }
258427
+ function normalizeDeliveryKind(value2) {
258428
+ const raw = String(value2 ?? "").trim().toLowerCase();
258429
+ if (raw === "action" || raw === "agent" || raw === "agent_action")
258430
+ return "action";
258431
+ return "minimal";
258432
+ }
258433
+ function normalizeSurface(value2) {
258434
+ const raw = String(value2 ?? "").trim().toLowerCase();
258435
+ return raw === "tui" || raw === "gui" || raw === "telegram" || raw === "api" || raw === "global" ? raw : void 0;
258436
+ }
258437
+ function normalizeVisibility(value2) {
258438
+ const raw = String(value2 ?? "").trim().toLowerCase();
258439
+ return raw === "private" || raw === "public" ? raw : void 0;
258440
+ }
258441
+ function stringValue(value2) {
258442
+ const raw = String(value2 ?? "").trim();
258443
+ return raw || void 0;
258444
+ }
258445
+ function formatScope(scope) {
258446
+ const label = scope.label ? ` (${scope.label})` : "";
258447
+ return `${scope.surface}/${scope.visibility}/${scope.targetId}${label}`;
258448
+ }
258071
258449
  var STORE_FILE, ReminderTool;
258072
258450
  var init_reminder = __esm({
258073
258451
  "packages/execution/dist/tools/reminder.js"() {
@@ -258075,7 +258453,7 @@ var init_reminder = __esm({
258075
258453
  STORE_FILE = "reminders.json";
258076
258454
  ReminderTool = class {
258077
258455
  name = "reminder";
258078
- description = "Set reminders that surface at future agent startups. Leave instructions for your future self across sessions. Actions: 'set' a new reminder, 'list' all reminders, 'complete' or 'dismiss' a reminder, 'snooze' to postpone. Reminders appear automatically when the agent starts and can have due dates, priority levels, and tags for filtering.";
258456
+ description = "Set scoped reminders and time-triggered action requests. Use kind='minimal' for raw text/content delivery and kind='action' when the trigger should start inference/tool-calling from a task_description. Actions: 'set' a new reminder, 'list' all reminders, 'complete' or 'dismiss' a reminder, 'snooze' to postpone. Reminders can have due dates, recurrence, priority, tags, and per-platform scopes.";
258079
258457
  parameters = {
258080
258458
  type: "object",
258081
258459
  properties: {
@@ -258097,6 +258475,43 @@ var init_reminder = __esm({
258097
258475
  enum: ["low", "normal", "high", "critical"],
258098
258476
  description: "Priority level (default: normal)"
258099
258477
  },
258478
+ kind: {
258479
+ type: "string",
258480
+ enum: ["minimal", "action"],
258481
+ description: "minimal sends raw reminder text/content only. action triggers an inference/tool-call task using task_description."
258482
+ },
258483
+ task_description: {
258484
+ type: "string",
258485
+ description: "For kind='action': the task the agent should perform when the trigger fires."
258486
+ },
258487
+ content: {
258488
+ type: "string",
258489
+ description: "For kind='minimal': raw text/content to deliver. Defaults to message."
258490
+ },
258491
+ repeat: {
258492
+ type: "string",
258493
+ description: "Optional recurrence: 'every 30m', 'daily', 'weekly monday', 'monthly', 'yearly'."
258494
+ },
258495
+ time: {
258496
+ type: "string",
258497
+ description: "24-hour local time HH:MM for daily/weekly/monthly/yearly recurrence."
258498
+ },
258499
+ weekday: {
258500
+ type: "string",
258501
+ description: "Weekday for weekly recurrence, e.g. 'monday' or 1."
258502
+ },
258503
+ day_of_month: {
258504
+ type: "number",
258505
+ description: "Day of month for monthly/yearly recurrence."
258506
+ },
258507
+ month: {
258508
+ type: "string",
258509
+ description: "Month for yearly recurrence, e.g. 'january' or 1."
258510
+ },
258511
+ max_occurrences: {
258512
+ type: "number",
258513
+ description: "Optional cap for recurring reminders."
258514
+ },
258100
258515
  tags: {
258101
258516
  type: "string",
258102
258517
  description: "Comma-separated tags for categorization (e.g. 'deploy,auth,urgent')"
@@ -258112,13 +258527,29 @@ var init_reminder = __esm({
258112
258527
  filter: {
258113
258528
  type: "string",
258114
258529
  description: "Filter by status: 'pending', 'seen', 'all' (for 'list', default: 'pending')"
258530
+ },
258531
+ surface: {
258532
+ type: "string",
258533
+ enum: ["tui", "gui", "telegram", "api", "global"],
258534
+ description: "Optional scope surface. Ignored when this tool is context-locked by a platform adapter."
258535
+ },
258536
+ visibility: {
258537
+ type: "string",
258538
+ enum: ["private", "public"],
258539
+ description: "Optional scope visibility. Ignored when context-locked."
258540
+ },
258541
+ target_id: {
258542
+ type: "string",
258543
+ description: "Optional scope target id. For Telegram this is the chat id. Ignored when context-locked."
258115
258544
  }
258116
258545
  },
258117
258546
  required: ["action"]
258118
258547
  };
258119
258548
  workingDir;
258120
- constructor(workingDir) {
258549
+ options;
258550
+ constructor(workingDir, options2 = {}) {
258121
258551
  this.workingDir = workingDir;
258552
+ this.options = options2;
258122
258553
  }
258123
258554
  async execute(args) {
258124
258555
  const start2 = performance.now();
@@ -258161,6 +258592,33 @@ var init_reminder = __esm({
258161
258592
  const tags = tagsStr ? tagsStr.split(",").map((t2) => t2.trim()).filter(Boolean) : [];
258162
258593
  const context2 = args["context"] ?? void 0;
258163
258594
  const dueStr = args["due"] ?? "";
258595
+ const recurrence = parseRecurrence(args);
258596
+ if (recurrence === null) {
258597
+ return {
258598
+ success: false,
258599
+ output: "",
258600
+ error: `Cannot parse repeat '${String(args["repeat"] ?? args["recurrence"])}'. Try: 'every 30m', 'daily', 'weekly monday', 'monthly', or 'yearly'.`,
258601
+ durationMs: performance.now() - start2
258602
+ };
258603
+ }
258604
+ const kind = normalizeDeliveryKind(args["kind"] ?? args["mode"] ?? args["delivery"]);
258605
+ if (kind === "action" && !this.options.allowActionDelivery) {
258606
+ return {
258607
+ success: false,
258608
+ output: "",
258609
+ error: "kind='action' is not allowed in this context. Use kind='minimal' for a plain reminder.",
258610
+ durationMs: performance.now() - start2
258611
+ };
258612
+ }
258613
+ const taskDescription = String(args["task_description"] ?? args["task"] ?? args["prompt"] ?? "").trim();
258614
+ if (kind === "action" && !taskDescription) {
258615
+ return {
258616
+ success: false,
258617
+ output: "",
258618
+ error: "task_description is required when kind='action'",
258619
+ durationMs: performance.now() - start2
258620
+ };
258621
+ }
258164
258622
  let dueAt;
258165
258623
  let dueDescription;
258166
258624
  if (dueStr) {
@@ -258175,7 +258633,12 @@ var init_reminder = __esm({
258175
258633
  }
258176
258634
  dueAt = parsed.isoDate;
258177
258635
  dueDescription = parsed.description;
258636
+ } else if (recurrence) {
258637
+ dueAt = nextDueFromRecurrence(recurrence).toISOString();
258638
+ dueDescription = describeRecurrence(recurrence);
258178
258639
  }
258640
+ const scope = this.resolveScope(args);
258641
+ const content = String(args["content"] ?? message2).trim() || message2;
258179
258642
  const id = `rem-${randomBytes12(4).toString("hex")}`;
258180
258643
  const entry = {
258181
258644
  id,
@@ -258187,7 +258650,14 @@ var init_reminder = __esm({
258187
258650
  tags,
258188
258651
  status: "pending",
258189
258652
  context: context2,
258190
- source: "agent"
258653
+ source: this.options.source ?? "agent",
258654
+ ...scope ? { scope } : {},
258655
+ delivery: {
258656
+ mode: kind,
258657
+ ...kind === "action" ? { taskDescription } : { content },
258658
+ ...this.options.createdFrom ? { createdFrom: this.options.createdFrom } : {}
258659
+ },
258660
+ ...recurrence ? { recurrence } : {}
258191
258661
  };
258192
258662
  const store2 = await loadReminderStore(this.workingDir);
258193
258663
  store2.reminders.push(entry);
@@ -258199,11 +258669,15 @@ var init_reminder = __esm({
258199
258669
  ` ID: ${id}`,
258200
258670
  ` Message: ${message2}`,
258201
258671
  ` Priority: ${priority}`,
258672
+ ` Kind: ${kind}`,
258673
+ kind === "action" ? ` Task: ${taskDescription}` : "",
258202
258674
  dueDescription ? ` Due: ${dueDescription}` : ` Due: next startup`,
258675
+ describeRecurrence(recurrence) ? ` Repeats: ${describeRecurrence(recurrence)}` : "",
258676
+ scope ? ` Scope: ${formatScope(scope)}` : "",
258203
258677
  tags.length > 0 ? ` Tags: ${tags.join(", ")}` : "",
258204
258678
  context2 ? ` Context: ${context2.slice(0, 100)}` : "",
258205
258679
  ``,
258206
- `This reminder will surface when the agent next starts${dueDescription ? ` (after ${dueDescription})` : ""}.`
258680
+ kind === "action" ? `This action will trigger when due${dueDescription ? ` (${dueDescription})` : ""}.` : `This reminder will surface when due${dueDescription ? ` (${dueDescription})` : ""}.`
258207
258681
  ].filter(Boolean).join("\n"),
258208
258682
  durationMs: performance.now() - start2
258209
258683
  };
@@ -258232,8 +258706,14 @@ var init_reminder = __esm({
258232
258706
  const statusLabel = r2.status === "seen" ? " (seen)" : r2.status === "completed" ? " (done)" : r2.status === "dismissed" ? " (dismissed)" : "";
258233
258707
  lines.push(` ${priorityIcon} [${r2.id}]${statusLabel}`);
258234
258708
  lines.push(` ${r2.message}`);
258709
+ lines.push(` Kind: ${r2.delivery?.mode ?? "minimal"}`);
258235
258710
  if (r2.dueDescription)
258236
258711
  lines.push(` Due: ${r2.dueDescription}`);
258712
+ const recurrence = describeRecurrence(r2.recurrence);
258713
+ if (recurrence)
258714
+ lines.push(` Repeats: ${recurrence}`);
258715
+ if (r2.scope)
258716
+ lines.push(` Scope: ${formatScope(r2.scope)}`);
258237
258717
  if (r2.tags.length > 0)
258238
258718
  lines.push(` Tags: ${r2.tags.join(", ")}`);
258239
258719
  if (r2.context)
@@ -258285,6 +258765,26 @@ var init_reminder = __esm({
258285
258765
  durationMs: performance.now() - start2
258286
258766
  };
258287
258767
  }
258768
+ resolveScope(args) {
258769
+ if (this.options.lockScope)
258770
+ return this.options.defaultScope;
258771
+ const surface = normalizeSurface(args["surface"]) ?? this.options.defaultScope?.surface;
258772
+ const visibility = normalizeVisibility(args["visibility"]) ?? this.options.defaultScope?.visibility ?? "private";
258773
+ const targetId = String(args["target_id"] ?? args["targetId"] ?? this.options.defaultScope?.targetId ?? "").trim();
258774
+ if (!surface || !targetId)
258775
+ return this.options.defaultScope;
258776
+ return {
258777
+ surface,
258778
+ visibility,
258779
+ targetId,
258780
+ sessionId: stringValue(args["session_id"] ?? args["sessionId"] ?? this.options.defaultScope?.sessionId),
258781
+ chatId: stringValue(args["chat_id"] ?? args["chatId"] ?? this.options.defaultScope?.chatId),
258782
+ threadId: stringValue(args["thread_id"] ?? args["threadId"] ?? this.options.defaultScope?.threadId),
258783
+ userId: stringValue(args["user_id"] ?? args["userId"] ?? this.options.defaultScope?.userId),
258784
+ username: stringValue(args["username"] ?? this.options.defaultScope?.username),
258785
+ label: stringValue(args["label"] ?? this.options.defaultScope?.label)
258786
+ };
258787
+ }
258288
258788
  };
258289
258789
  }
258290
258790
  });
@@ -258613,6 +259113,23 @@ ${sections.join("\n")}`,
258613
259113
  }
258614
259114
  });
258615
259115
 
259116
+ // packages/execution/dist/tools/tool-alias.js
259117
+ function aliasTool(tool, name10, description = `Alias for ${tool.name}: ${tool.description}`) {
259118
+ return {
259119
+ name: name10,
259120
+ description,
259121
+ parameters: tool.parameters,
259122
+ async execute(args) {
259123
+ return tool.execute(args);
259124
+ }
259125
+ };
259126
+ }
259127
+ var init_tool_alias = __esm({
259128
+ "packages/execution/dist/tools/tool-alias.js"() {
259129
+ "use strict";
259130
+ }
259131
+ });
259132
+
258616
259133
  // packages/execution/dist/tools/opencode.js
258617
259134
  import { execSync as execSync23, spawn as spawn14 } from "node:child_process";
258618
259135
  import { existsSync as existsSync33 } from "node:fs";
@@ -259213,6 +259730,47 @@ function resolveSchedule2(schedule) {
259213
259730
  if (LONG_HORIZON_PRESETS[lower]) {
259214
259731
  return { cron: LONG_HORIZON_PRESETS[lower], description: lower };
259215
259732
  }
259733
+ const dailyAt = lower.match(/^(?:daily|every\s+day)\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
259734
+ if (dailyAt) {
259735
+ const hour2 = parseInt(dailyAt[1], 10);
259736
+ const minute3 = parseInt(dailyAt[2], 10);
259737
+ if (isClockTime2(hour2, minute3))
259738
+ return { cron: `${minute3} ${hour2} * * *`, description: `daily at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}` };
259739
+ }
259740
+ const weeklyAt = lower.match(/^weekly(?:\s+on)?\s+([a-z]+|\d)\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
259741
+ if (weeklyAt) {
259742
+ const dow = parseWeekday3(weeklyAt[1]);
259743
+ const hour2 = parseInt(weeklyAt[2], 10);
259744
+ const minute3 = parseInt(weeklyAt[3], 10);
259745
+ if (dow !== null && isClockTime2(hour2, minute3))
259746
+ return { cron: `${minute3} ${hour2} * * ${dow}`, description: `weekly on day ${dow} at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}` };
259747
+ }
259748
+ const monthlyAt = lower.match(/^monthly(?:\s+(?:on|day))?\s+(\d{1,2})(?:st|nd|rd|th)?\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
259749
+ if (monthlyAt) {
259750
+ const day = parseInt(monthlyAt[1], 10);
259751
+ const hour2 = parseInt(monthlyAt[2], 10);
259752
+ const minute3 = parseInt(monthlyAt[3], 10);
259753
+ if (day >= 1 && day <= 31 && isClockTime2(hour2, minute3))
259754
+ return { cron: `${minute3} ${hour2} ${day} * *`, description: `monthly on day ${day} at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}` };
259755
+ }
259756
+ const yearlyNumeric = lower.match(/^(?:yearly|annually|every\s+year)\s+(?:on\s+)?(\d{1,2})-(\d{1,2})\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
259757
+ if (yearlyNumeric) {
259758
+ const month = parseInt(yearlyNumeric[1], 10);
259759
+ const day = parseInt(yearlyNumeric[2], 10);
259760
+ const hour2 = parseInt(yearlyNumeric[3], 10);
259761
+ const minute3 = parseInt(yearlyNumeric[4], 10);
259762
+ if (month >= 1 && month <= 12 && day >= 1 && day <= 31 && isClockTime2(hour2, minute3))
259763
+ return { cron: `${minute3} ${hour2} ${day} ${month} *`, description: `yearly on ${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")} at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}` };
259764
+ }
259765
+ const yearlyNamed = lower.match(/^(?:yearly|annually|every\s+year)\s+(?:on\s+)?([a-z]+)\s+(\d{1,2})(?:st|nd|rd|th)?\s+(?:at\s+)?(\d{1,2}):(\d{2})$/);
259766
+ if (yearlyNamed) {
259767
+ const month = parseMonth3(yearlyNamed[1]);
259768
+ const day = parseInt(yearlyNamed[2], 10);
259769
+ const hour2 = parseInt(yearlyNamed[3], 10);
259770
+ const minute3 = parseInt(yearlyNamed[4], 10);
259771
+ if (month !== null && day >= 1 && day <= 31 && isClockTime2(hour2, minute3))
259772
+ return { cron: `${minute3} ${hour2} ${day} ${month} *`, description: `yearly on ${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")} at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}` };
259773
+ }
259216
259774
  const everyMatch = lower.match(/^every\s+(\d+)\s*(h|hr|hrs|hours?|d|days?|w|wk|wks|weeks?|m|mo|mos|months?)$/);
259217
259775
  if (everyMatch) {
259218
259776
  const amount = parseInt(everyMatch[1], 10);
@@ -259260,6 +259818,65 @@ function resolveSchedule2(schedule) {
259260
259818
  }
259261
259819
  return null;
259262
259820
  }
259821
+ function isClockTime2(hour2, minute3) {
259822
+ return hour2 >= 0 && hour2 <= 23 && minute3 >= 0 && minute3 <= 59;
259823
+ }
259824
+ function parseWeekday3(raw) {
259825
+ const value2 = raw.toLowerCase();
259826
+ if (/^[0-6]$/.test(value2))
259827
+ return Number(value2);
259828
+ const days = {
259829
+ sunday: 0,
259830
+ sun: 0,
259831
+ monday: 1,
259832
+ mon: 1,
259833
+ tuesday: 2,
259834
+ tue: 2,
259835
+ tues: 2,
259836
+ wednesday: 3,
259837
+ wed: 3,
259838
+ thursday: 4,
259839
+ thu: 4,
259840
+ thurs: 4,
259841
+ friday: 5,
259842
+ fri: 5,
259843
+ saturday: 6,
259844
+ sat: 6
259845
+ };
259846
+ return days[value2] ?? null;
259847
+ }
259848
+ function parseMonth3(raw) {
259849
+ const value2 = raw.toLowerCase();
259850
+ if (/^(?:[1-9]|1[0-2])$/.test(value2))
259851
+ return Number(value2);
259852
+ const months = {
259853
+ january: 1,
259854
+ jan: 1,
259855
+ february: 2,
259856
+ feb: 2,
259857
+ march: 3,
259858
+ mar: 3,
259859
+ april: 4,
259860
+ apr: 4,
259861
+ may: 5,
259862
+ june: 6,
259863
+ jun: 6,
259864
+ july: 7,
259865
+ jul: 7,
259866
+ august: 8,
259867
+ aug: 8,
259868
+ september: 9,
259869
+ sep: 9,
259870
+ sept: 9,
259871
+ october: 10,
259872
+ oct: 10,
259873
+ november: 11,
259874
+ nov: 11,
259875
+ december: 12,
259876
+ dec: 12
259877
+ };
259878
+ return months[value2] ?? null;
259879
+ }
259263
259880
  function getCurrentCrontab2() {
259264
259881
  try {
259265
259882
  return execSync25("crontab -l 2>/dev/null", { stdio: "pipe" }).toString().split("\n");
@@ -259376,12 +259993,14 @@ var init_cron_agent = __esm({
259376
259993
  "weekly": "0 9 * * 1",
259377
259994
  "biweekly": "0 9 1,15 * *",
259378
259995
  "monthly": "0 9 1 * *",
259379
- "quarterly": "0 9 1 1,4,7,10 *"
259996
+ "quarterly": "0 9 1 1,4,7,10 *",
259997
+ "yearly": "0 9 1 1 *",
259998
+ "annually": "0 9 1 1 *"
259380
259999
  };
259381
260000
  CRON_AGENT_MARKER = "# Omnius-CRON-AGENT:";
259382
260001
  CronAgentTool = class {
259383
260002
  name = "cron_agent";
259384
- description = "Schedule long-horizon autonomous agent tasks that execute on a cron schedule. Unlike one-shot scheduler tasks, cron_agent jobs have goals, completion criteria, and execution history tracking. Actions: 'create' a monitored cron job, 'list' all jobs, 'check' a job's status and history, 'cancel' a job, 'logs' view execution output, 'evaluate' check if goal is met. Schedules: 'every 2 hours', 'daily', 'weekly', 'monthly', 'every 3 days', or raw cron. Use this for long-running autonomous workflows: periodic code reviews, test monitoring, dependency updates, performance tracking, log analysis, or any recurring agent task.";
260003
+ description = "Schedule long-horizon autonomous agent tasks that execute on a cron schedule. Unlike one-shot scheduler tasks, cron_agent jobs have goals, completion criteria, and execution history tracking. Actions: 'create' a monitored cron job, 'list' all jobs, 'check' a job's status and history, 'cancel' a job, 'logs' view execution output, 'evaluate' check if goal is met. Schedules: 'every 2 hours', 'daily at 14:30', 'weekly friday at 17:45', 'monthly', 'yearly', or raw cron. Use this for long-running autonomous workflows: periodic code reviews, test monitoring, dependency updates, performance tracking, log analysis, or any recurring agent task.";
259385
260004
  parameters = {
259386
260005
  type: "object",
259387
260006
  properties: {
@@ -259400,7 +260019,7 @@ var init_cron_agent = __esm({
259400
260019
  },
259401
260020
  schedule: {
259402
260021
  type: "string",
259403
- description: "When to run: 'every 2 hours', 'daily', 'weekly', 'monthly', 'every 3 days', or cron expression"
260022
+ description: "When to run: 'every 2 hours', 'daily at 14:30', 'weekly friday at 17:45', 'monthly', 'yearly', or cron expression"
259404
260023
  },
259405
260024
  completion_criteria: {
259406
260025
  type: "string",
@@ -287360,9 +287979,9 @@ ${lanes.join("\n")}
287360
287979
  function isJsonEqual(a2, b) {
287361
287980
  return a2 === b || typeof a2 === "object" && a2 !== null && typeof b === "object" && b !== null && equalOwnProperties(a2, b, isJsonEqual);
287362
287981
  }
287363
- function parsePseudoBigInt(stringValue) {
287982
+ function parsePseudoBigInt(stringValue2) {
287364
287983
  let log2Base;
287365
- switch (stringValue.charCodeAt(1)) {
287984
+ switch (stringValue2.charCodeAt(1)) {
287366
287985
  // "x" in "0x123"
287367
287986
  case 98:
287368
287987
  case 66:
@@ -287377,19 +287996,19 @@ ${lanes.join("\n")}
287377
287996
  log2Base = 4;
287378
287997
  break;
287379
287998
  default:
287380
- const nIndex = stringValue.length - 1;
287999
+ const nIndex = stringValue2.length - 1;
287381
288000
  let nonZeroStart = 0;
287382
- while (stringValue.charCodeAt(nonZeroStart) === 48) {
288001
+ while (stringValue2.charCodeAt(nonZeroStart) === 48) {
287383
288002
  nonZeroStart++;
287384
288003
  }
287385
- return stringValue.slice(nonZeroStart, nIndex) || "0";
288004
+ return stringValue2.slice(nonZeroStart, nIndex) || "0";
287386
288005
  }
287387
- const startIndex = 2, endIndex = stringValue.length - 1;
288006
+ const startIndex = 2, endIndex = stringValue2.length - 1;
287388
288007
  const bitsNeeded = (endIndex - startIndex) * log2Base;
287389
288008
  const segments = new Uint16Array((bitsNeeded >>> 4) + (bitsNeeded & 15 ? 1 : 0));
287390
288009
  for (let i2 = endIndex - 1, bitOffset = 0; i2 >= startIndex; i2--, bitOffset += log2Base) {
287391
288010
  const segment = bitOffset >>> 4;
287392
- const digitChar = stringValue.charCodeAt(i2);
288011
+ const digitChar = stringValue2.charCodeAt(i2);
287393
288012
  const digit = digitChar <= 57 ? digitChar - 48 : 10 + digitChar - (digitChar <= 70 ? 65 : 97);
287394
288013
  const shiftedDigit = digit << (bitOffset & 15);
287395
288014
  segments[segment] |= shiftedDigit;
@@ -515039,14 +515658,17 @@ function collectSnapshot(workingDir) {
515039
515658
  }
515040
515659
  } catch {
515041
515660
  }
515042
- let disk = { availableGB: 0, totalGB: 0, usedPercent: 0 };
515661
+ let disk = { path: workingDir || "/", availableGB: 0, usedGB: 0, totalGB: 0, usedPercent: 0 };
515043
515662
  try {
515044
515663
  const stats = statfsSync(workingDir || "/");
515045
515664
  const totalBytes = stats.blocks * stats.bsize;
515046
515665
  const availBytes = stats.bavail * stats.bsize;
515666
+ const usedBytes = totalBytes - availBytes;
515047
515667
  disk = {
515668
+ path: workingDir || "/",
515048
515669
  totalGB: Math.round(totalBytes / 1024 ** 3),
515049
515670
  availableGB: Math.round(availBytes / 1024 ** 3),
515671
+ usedGB: Math.round(usedBytes / 1024 ** 3),
515050
515672
  usedPercent: Math.round((1 - availBytes / totalBytes) * 100)
515051
515673
  };
515052
515674
  } catch {
@@ -515119,7 +515741,7 @@ function formatSnapshotForContext(snap) {
515119
515741
  const icon = snap.battery.charging ? "⚡" : snap.battery.percent < 20 ? "🪫" : "🔋";
515120
515742
  lines.push(`Battery: ${icon} ${snap.battery.percent}% ${snap.battery.charging ? "(charging)" : "(discharging)"}`);
515121
515743
  }
515122
- lines.push(`Disk: ${snap.disk.availableGB}GB free / ${snap.disk.totalGB}GB (${snap.disk.usedPercent}% used)`);
515744
+ lines.push(`Disk: disk_available_gb=${snap.disk.availableGB} disk_used_gb=${snap.disk.usedGB} disk_total_gb=${snap.disk.totalGB} disk_used_pct=${snap.disk.usedPercent} path=${snap.disk.path}`);
515123
515745
  lines.push(`Processes: ${snap.processes.total} total | ${snap.processes.nodeCount} node | ${snap.processes.omniusSpawned} Omnius-spawned`);
515124
515746
  lines.push(`Uptime: ${snap.uptime}`);
515125
515747
  if (snap.processes.topCpu.length > 0) {
@@ -516402,6 +517024,7 @@ __export(dist_exports, {
516402
517024
  YouTubeDownloadTool: () => YouTubeDownloadTool,
516403
517025
  addProjectConstraint: () => addProjectConstraint,
516404
517026
  addSessionConstraint: () => addSessionConstraint,
517027
+ aliasTool: () => aliasTool,
516405
517028
  applyPatch: () => applyPatch,
516406
517029
  audioGenerationDir: () => audioGenerationDir,
516407
517030
  audioGenerationSetupPlan: () => audioGenerationSetupPlan,
@@ -516493,6 +517116,7 @@ __export(dist_exports, {
516493
517116
  loadSkillContent: () => loadSkillContent,
516494
517117
  logsDir: () => logsDir,
516495
517118
  markRemindersSeen: () => markRemindersSeen,
517119
+ markRemindersTriggered: () => markRemindersTriggered,
516496
517120
  markReverted: () => markReverted,
516497
517121
  markSessionValidated: () => markSessionValidated,
516498
517122
  normalizeMcpName: () => normalizeMcpName,
@@ -516612,6 +517236,7 @@ var init_dist5 = __esm({
516612
517236
  init_scheduler();
516613
517237
  init_reminder();
516614
517238
  init_agenda();
517239
+ init_tool_alias();
516615
517240
  init_opencode();
516616
517241
  init_factory();
516617
517242
  init_cron_agent();
@@ -518049,9 +518674,9 @@ var init_verifierRunner = __esm({
518049
518674
  async executeTests(patch, repoRoot) {
518050
518675
  if (patch.testsToRun.length === 0)
518051
518676
  return "(no tests specified)";
518052
- const { execFile: execFile6 } = await import("node:child_process");
518053
- const { promisify: promisify6 } = await import("node:util");
518054
- const execFileAsync4 = promisify6(execFile6);
518677
+ const { execFile: execFile7 } = await import("node:child_process");
518678
+ const { promisify: promisify7 } = await import("node:util");
518679
+ const execFileAsync5 = promisify7(execFile7);
518055
518680
  const outputs = [];
518056
518681
  const workDir = this.options.workingDir || repoRoot;
518057
518682
  for (const cmd of patch.testsToRun.slice(0, 3)) {
@@ -518060,7 +518685,7 @@ var init_verifierRunner = __esm({
518060
518685
  const [bin, ...args] = parts;
518061
518686
  if (!bin)
518062
518687
  continue;
518063
- const { stdout, stderr } = await execFileAsync4(bin, args, {
518688
+ const { stdout, stderr } = await execFileAsync5(bin, args, {
518064
518689
  cwd: workDir,
518065
518690
  timeout: 6e4,
518066
518691
  maxBuffer: 1024 * 1024
@@ -553801,9 +554426,14 @@ var init_command_registry = __esm({
553801
554426
  ["/access <loopback|lan|any>", "Set access policy (OMNIUS_ACCESS) and restart daemon"],
553802
554427
  ["/k <any|lan|loopback|config>", "Quick network access helper"],
553803
554428
  ["/scheduler", "Scheduled tasks control panel (list/kill/toggle)"],
554429
+ ["/cron", "Alias for /scheduler"],
553804
554430
  ["/scheduler menu", "Interactive scheduler menu (toggle/kill)"],
553805
554431
  ["/scheduler list", "List all scheduled tasks and timers"],
553806
554432
  ["/scheduler kill", "Kill schedulers + active runs (with escalation if needed)"],
554433
+ ["/reminder [list|all]", "List scoped TUI reminders"],
554434
+ ["/remind <message>", "Set a scoped minimal reminder for this TUI session"],
554435
+ ["/remind in 30m <message>", "Set a scoped minimal reminder with a relative due time"],
554436
+ ["/reminders", "Alias for /reminder"],
553807
554437
  ["/daemon", "Show or manage the local API daemon"],
553808
554438
  ["/daemon <start|stop|restart|takeover>", "Control the local API daemon"],
553809
554439
  ["/apikey", "Show current API key (for pasting into Web UI / clients)"],
@@ -553858,7 +554488,6 @@ var init_command_registry = __esm({
553858
554488
  ["/skin", "Configure the visual skin/theme bundle"],
553859
554489
  ["/toolsets", "Show or select tool exposure sets"],
553860
554490
  ["/browser connect|disconnect|status", "Manage browser-agent connectivity"],
553861
- ["/cron", "Scheduled/cron agent controls"],
553862
554491
  ["/reload-mcp", "Reload MCP servers and tools"],
553863
554492
  ["/plugins", "List or manage plugins"],
553864
554493
  ["/usage", "Show usage and quota details"],
@@ -553929,6 +554558,7 @@ var init_command_registry = __esm({
553929
554558
  network: "network",
553930
554559
  k: "network",
553931
554560
  scheduler: "runtime",
554561
+ reminder: "runtime",
553932
554562
  daemon: "runtime",
553933
554563
  apikey: "secret",
553934
554564
  key: "secret",
@@ -553995,6 +554625,8 @@ var init_command_registry = __esm({
553995
554625
  emojis: ["emoji"],
553996
554626
  compact: ["gc"],
553997
554627
  network: ["k"],
554628
+ scheduler: ["cron"],
554629
+ reminder: ["remind", "reminders"],
553998
554630
  statusbar: ["sb"],
553999
554631
  platforms: ["gateway"]
554000
554632
  };
@@ -554053,6 +554685,7 @@ var init_command_registry = __esm({
554053
554685
  "network",
554054
554686
  "k",
554055
554687
  "scheduler",
554688
+ "cron",
554056
554689
  "daemon",
554057
554690
  "fortemi",
554058
554691
  "mouse",
@@ -556518,7 +557151,7 @@ import { EventEmitter as EventEmitter6 } from "node:events";
556518
557151
  import { randomBytes as randomBytes18 } from "node:crypto";
556519
557152
  import { URL as URL2 } from "node:url";
556520
557153
  import { loadavg, cpus as cpus2, totalmem as totalmem3, freemem as freemem3 } from "node:os";
556521
- import { existsSync as existsSync80, readFileSync as readFileSync63, writeFileSync as writeFileSync42, unlinkSync as unlinkSync13, mkdirSync as mkdirSync45, readdirSync as readdirSync25, statSync as statSync28 } from "node:fs";
557154
+ import { existsSync as existsSync80, readFileSync as readFileSync63, writeFileSync as writeFileSync42, unlinkSync as unlinkSync13, mkdirSync as mkdirSync45, readdirSync as readdirSync25, statSync as statSync28, statfsSync as statfsSync2 } from "node:fs";
556522
557155
  import { join as join96 } from "node:path";
556523
557156
  function cleanForwardHeaders(raw, targetHost) {
556524
557157
  const out = {};
@@ -556608,6 +557241,27 @@ async function collectSystemMetricsAsync() {
556608
557241
  const totalMem = totalmem3();
556609
557242
  const freeMem = freemem3();
556610
557243
  const usedMem = totalMem - freeMem;
557244
+ let disk = {
557245
+ path: process.cwd(),
557246
+ totalGB: 0,
557247
+ freeGB: 0,
557248
+ usedGB: 0,
557249
+ utilization: -1
557250
+ };
557251
+ try {
557252
+ const fs10 = statfsSync2(process.cwd());
557253
+ const totalBytes = fs10.blocks * fs10.bsize;
557254
+ const freeBytes = fs10.bavail * fs10.bsize;
557255
+ const usedBytes = totalBytes - freeBytes;
557256
+ disk = {
557257
+ path: process.cwd(),
557258
+ totalGB: Math.round(totalBytes / 1024 ** 3 * 10) / 10,
557259
+ freeGB: Math.round(freeBytes / 1024 ** 3 * 10) / 10,
557260
+ usedGB: Math.round(usedBytes / 1024 ** 3 * 10) / 10,
557261
+ utilization: totalBytes > 0 ? Math.round(usedBytes / totalBytes * 100) : -1
557262
+ };
557263
+ } catch {
557264
+ }
556611
557265
  const gpu = {
556612
557266
  available: false,
556613
557267
  name: "",
@@ -556650,6 +557304,7 @@ async function collectSystemMetricsAsync() {
556650
557304
  usedGB: Math.round(usedMem / 1024 ** 3 * 10) / 10,
556651
557305
  utilization: Math.round(usedMem / totalMem * 100)
556652
557306
  },
557307
+ disk,
556653
557308
  gpu,
556654
557309
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
556655
557310
  };
@@ -561458,6 +562113,60 @@ var init_braille_spinner = __esm({
561458
562113
  }
561459
562114
  });
561460
562115
 
562116
+ // packages/cli/src/tui/disk-monitor.ts
562117
+ import { execFile as execFile5 } from "node:child_process";
562118
+ import { promisify as promisify5 } from "node:util";
562119
+ function unavailableDiskMetrics(path11 = process.cwd()) {
562120
+ return {
562121
+ path: path11,
562122
+ mount: "",
562123
+ totalGB: 0,
562124
+ usedGB: 0,
562125
+ freeGB: 0,
562126
+ util: -1
562127
+ };
562128
+ }
562129
+ async function collectDiskMetrics(path11 = process.cwd()) {
562130
+ if (process.platform === "win32") return unavailableDiskMetrics(path11);
562131
+ try {
562132
+ const { stdout } = await execFileAsync4("df", ["-Pk", path11], {
562133
+ encoding: "utf8",
562134
+ timeout: 3e3,
562135
+ maxBuffer: 128 * 1024
562136
+ });
562137
+ const lines = stdout.trim().split("\n").filter(Boolean);
562138
+ const line = lines[lines.length - 1];
562139
+ if (!line) return unavailableDiskMetrics(path11);
562140
+ const parts = line.trim().split(/\s+/);
562141
+ if (parts.length < 6) return unavailableDiskMetrics(path11);
562142
+ const totalKB = Number(parts[1] ?? 0);
562143
+ const usedKB = Number(parts[2] ?? 0);
562144
+ const freeKB = Number(parts[3] ?? 0);
562145
+ if (!Number.isFinite(totalKB) || totalKB <= 0) return unavailableDiskMetrics(path11);
562146
+ const totalGB = totalKB / (1024 * 1024);
562147
+ const usedGB = usedKB / (1024 * 1024);
562148
+ const freeGB = freeKB / (1024 * 1024);
562149
+ const util2 = Math.max(0, Math.min(100, Math.round(usedKB / totalKB * 100)));
562150
+ return {
562151
+ path: path11,
562152
+ mount: parts.slice(5).join(" "),
562153
+ totalGB: Math.round(totalGB * 10) / 10,
562154
+ usedGB: Math.round(usedGB * 10) / 10,
562155
+ freeGB: Math.round(freeGB * 10) / 10,
562156
+ util: util2
562157
+ };
562158
+ } catch {
562159
+ return unavailableDiskMetrics(path11);
562160
+ }
562161
+ }
562162
+ var execFileAsync4;
562163
+ var init_disk_monitor = __esm({
562164
+ "packages/cli/src/tui/disk-monitor.ts"() {
562165
+ "use strict";
562166
+ execFileAsync4 = promisify5(execFile5);
562167
+ }
562168
+ });
562169
+
561461
562170
  // packages/cli/src/tui/system-metrics.ts
561462
562171
  var system_metrics_exports = {};
561463
562172
  __export(system_metrics_exports, {
@@ -561575,6 +562284,7 @@ async function collectGpuMetrics() {
561575
562284
  }
561576
562285
  function getInstantSnapshot() {
561577
562286
  const cr = collectCpuRam();
562287
+ const disk = unavailableDiskMetrics();
561578
562288
  return {
561579
562289
  source: "local",
561580
562290
  hardware: {
@@ -561588,7 +562298,12 @@ function getInstantSnapshot() {
561588
562298
  vramTotalMB: 0,
561589
562299
  memUtil: cr.memUtil,
561590
562300
  memUsedGB: cr.memUsedGB,
561591
- memTotalGB: cr.memTotalGB
562301
+ memTotalGB: cr.memTotalGB,
562302
+ diskUtil: disk.util,
562303
+ diskUsedGB: disk.usedGB,
562304
+ diskTotalGB: disk.totalGB,
562305
+ diskFreeGB: disk.freeGB,
562306
+ diskPath: disk.path
561592
562307
  },
561593
562308
  network: { rxBytesPerSec: 0, txBytesPerSec: 0 }
561594
562309
  };
@@ -561635,8 +562350,9 @@ function collectCpuRam() {
561635
562350
  }
561636
562351
  async function collectLocalMetrics() {
561637
562352
  const cpuRam = collectCpuRam();
561638
- const [gpu, network] = await Promise.all([
562353
+ const [gpu, disk, network] = await Promise.all([
561639
562354
  collectGpuMetrics(),
562355
+ collectDiskMetrics(),
561640
562356
  collectNetworkMetrics()
561641
562357
  ]);
561642
562358
  return {
@@ -561652,7 +562368,12 @@ async function collectLocalMetrics() {
561652
562368
  vramTotalMB: gpu.vramTotalMB,
561653
562369
  memUtil: cpuRam.memUtil,
561654
562370
  memUsedGB: cpuRam.memUsedGB,
561655
- memTotalGB: cpuRam.memTotalGB
562371
+ memTotalGB: cpuRam.memTotalGB,
562372
+ diskUtil: disk.util,
562373
+ diskUsedGB: disk.usedGB,
562374
+ diskTotalGB: disk.totalGB,
562375
+ diskFreeGB: disk.freeGB,
562376
+ diskPath: disk.path
561656
562377
  },
561657
562378
  network
561658
562379
  };
@@ -561661,6 +562382,7 @@ var _lastNetSnapshot, _nvidiaSmiAvailable, _cpuPrevSnapshot, SystemMetricsCollec
561661
562382
  var init_system_metrics = __esm({
561662
562383
  "packages/cli/src/tui/system-metrics.ts"() {
561663
562384
  "use strict";
562385
+ init_disk_monitor();
561664
562386
  _lastNetSnapshot = null;
561665
562387
  _nvidiaSmiAvailable = null;
561666
562388
  _cpuPrevSnapshot = null;
@@ -561721,7 +562443,12 @@ var init_system_metrics = __esm({
561721
562443
  vramTotalMB: hw.vramTotalMB ?? 0,
561722
562444
  memUtil: hw.memUtil ?? -1,
561723
562445
  memUsedGB: hw.memUsedGB ?? 0,
561724
- memTotalGB: hw.memTotalGB ?? 0
562446
+ memTotalGB: hw.memTotalGB ?? 0,
562447
+ diskUtil: hw.diskUtil ?? -1,
562448
+ diskUsedGB: hw.diskUsedGB ?? 0,
562449
+ diskTotalGB: hw.diskTotalGB ?? 0,
562450
+ diskFreeGB: hw.diskFreeGB ?? 0,
562451
+ diskPath: hw.diskPath ?? ""
561725
562452
  };
561726
562453
  this._latest = {
561727
562454
  source: "remote",
@@ -563781,7 +564508,12 @@ var init_status_bar = __esm({
563781
564508
  vramTotalMB: metrics2.vramTotalMB ?? 0,
563782
564509
  memUtil: metrics2.memUtil,
563783
564510
  memTotalGB: metrics2.memTotalGB ?? 0,
563784
- memUsedGB: metrics2.memUsedGB ?? 0
564511
+ memUsedGB: metrics2.memUsedGB ?? 0,
564512
+ diskUtil: metrics2.diskUtil ?? -1,
564513
+ diskUsedGB: metrics2.diskUsedGB ?? 0,
564514
+ diskTotalGB: metrics2.diskTotalGB ?? 0,
564515
+ diskFreeGB: metrics2.diskFreeGB ?? 0,
564516
+ diskPath: metrics2.diskPath ?? ""
563785
564517
  });
563786
564518
  }
563787
564519
  /** Clear remote metrics and switch back to local collection */
@@ -563839,7 +564571,12 @@ var init_status_bar = __esm({
563839
564571
  gpuUtil: data.gpu?.available ? data.gpu.utilization ?? 0 : -1,
563840
564572
  gpuName: data.gpu?.name ?? "",
563841
564573
  vramUtil: data.gpu?.available ? data.gpu.vramUtilization ?? 0 : -1,
563842
- memUtil: data.memory?.utilization ?? 0
564574
+ memUtil: data.memory?.utilization ?? 0,
564575
+ diskUtil: data.disk?.utilization ?? -1,
564576
+ diskUsedGB: data.disk?.usedGB ?? 0,
564577
+ diskTotalGB: data.disk?.totalGB ?? 0,
564578
+ diskFreeGB: data.disk?.freeGB ?? 0,
564579
+ diskPath: data.disk?.path ?? ""
563843
564580
  });
563844
564581
  }
563845
564582
  } catch {
@@ -563938,7 +564675,12 @@ var init_status_bar = __esm({
563938
564675
  vramTotalMB: m2.gpu?.vramTotalMB ?? 0,
563939
564676
  memUtil: m2.memory?.utilization ?? 0,
563940
564677
  memTotalGB: m2.memory?.totalGB ?? 0,
563941
- memUsedGB: m2.memory?.usedGB ?? 0
564678
+ memUsedGB: m2.memory?.usedGB ?? 0,
564679
+ diskUtil: m2.disk?.utilization ?? -1,
564680
+ diskTotalGB: m2.disk?.totalGB ?? 0,
564681
+ diskUsedGB: m2.disk?.usedGB ?? 0,
564682
+ diskFreeGB: m2.disk?.freeGB ?? 0,
564683
+ diskPath: m2.disk?.path ?? ""
563942
564684
  });
563943
564685
  return;
563944
564686
  }
@@ -565407,6 +566149,14 @@ ${CONTENT_BG_SEQ}`);
565407
566149
  hwExpW += 5 + `${rm3.memUtil}%`.length + memDetail.length;
565408
566150
  hwCompW += 5 + `${rm3.memUtil}%`.length;
565409
566151
  }
566152
+ if (rm3.diskUtil >= 0) {
566153
+ const diskColor = rm3.diskUtil > 90 ? c3.red : rm3.diskUtil > 75 ? c3.yellow : c3.green;
566154
+ const diskDetail = rm3.diskTotalGB > 0 ? ` (${rm3.diskFreeGB.toFixed(rm3.diskFreeGB < 10 ? 1 : 0)}GB free)` : "";
566155
+ hwExpStr += ` Disk ${diskColor(rm3.diskUtil + "%")}${c3.dim(diskDetail)}`;
566156
+ hwCompStr += ` Disk ${diskColor(rm3.diskUtil + "%")}`;
566157
+ hwExpW += 6 + `${rm3.diskUtil}%`.length + diskDetail.length;
566158
+ hwCompW += 6 + `${rm3.diskUtil}%`.length;
566159
+ }
565410
566160
  if (rm3.gpuUtil >= 0) {
565411
566161
  const gpuColor = rm3.gpuUtil > 80 ? c3.red : rm3.gpuUtil > 50 ? c3.yellow : c3.green;
565412
566162
  const gpuNameShort = rm3.gpuName ? ` (${rm3.gpuName.slice(0, 20)})` : "";
@@ -567849,7 +568599,7 @@ __export(setup_exports, {
567849
568599
  });
567850
568600
  import * as readline from "node:readline";
567851
568601
  import { execSync as execSync50, spawn as spawn25, exec as exec4 } from "node:child_process";
567852
- import { promisify as promisify5 } from "node:util";
568602
+ import { promisify as promisify6 } from "node:util";
567853
568603
  import { existsSync as existsSync86, writeFileSync as writeFileSync46, readFileSync as readFileSync70, appendFileSync as appendFileSync4, mkdirSync as mkdirSync49 } from "node:fs";
567854
568604
  import { join as join103 } from "node:path";
567855
568605
  import { homedir as homedir30, platform as platform4 } from "node:os";
@@ -570231,7 +570981,7 @@ var init_setup = __esm({
570231
570981
  init_config();
570232
570982
  init_dist();
570233
570983
  init_tui_select();
570234
- execAsync2 = promisify5(exec4);
570984
+ execAsync2 = promisify6(exec4);
570235
570985
  OMNIUS_FIRST_RUN_BANNER = [
570236
570986
  " ░▒▓██████▓▒░░▒▓██████████████▓▒░░▒▓███████▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓███████▓▒░ ",
570237
570987
  "░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ",
@@ -579756,6 +580506,7 @@ import {
579756
580506
  writeFileSync as writeFileSync50,
579757
580507
  mkdirSync as mkdirSync53,
579758
580508
  readdirSync as readdirSync30,
580509
+ lstatSync,
579759
580510
  statSync as statSync34,
579760
580511
  rmSync as rmSync3,
579761
580512
  appendFileSync as appendFileSync5
@@ -579868,6 +580619,69 @@ function safeLog(text) {
579868
580619
  process.stdout.write(text + "\n");
579869
580620
  }
579870
580621
  }
580622
+ function createSlashReminderTool(ctx3) {
580623
+ const sessionId = process.env["OMNIUS_SESSION_ID"] || "terminal";
580624
+ return new ReminderTool(ctx3.repoRoot, {
580625
+ defaultScope: {
580626
+ surface: "tui",
580627
+ visibility: "private",
580628
+ targetId: sessionId,
580629
+ sessionId,
580630
+ label: "terminal"
580631
+ },
580632
+ lockScope: true,
580633
+ allowActionDelivery: false,
580634
+ source: "user",
580635
+ createdFrom: "tui-slash"
580636
+ });
580637
+ }
580638
+ function splitReminderDue(raw) {
580639
+ const text = raw.trim().replace(/^set\s+/i, "").trim();
580640
+ const dueMatch = text.match(
580641
+ /^((?:in\s+\d+\s*(?:m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|weeks?))|tomorrow|next week|next startup|startup|now|(?:today\s+)?at\s+\d{1,2}:\d{2})\s+(.+)$/i
580642
+ );
580643
+ if (!dueMatch) return { message: text };
580644
+ return { due: dueMatch[1].trim(), message: dueMatch[2].trim() };
580645
+ }
580646
+ async function handleReminderSlash(cmd, arg, ctx3) {
580647
+ const tool = createSlashReminderTool(ctx3);
580648
+ const trimmed = arg.trim();
580649
+ const lower = trimmed.toLowerCase();
580650
+ let args;
580651
+ if (!trimmed || lower === "list" || lower === "pending" || lower === "all" || lower === "seen") {
580652
+ args = { action: "list", filter: lower === "all" || lower === "seen" ? lower : "pending" };
580653
+ } else if (lower === "completed" || lower === "dismissed") {
580654
+ args = { action: "list", filter: lower };
580655
+ } else if (lower.startsWith("complete ")) {
580656
+ args = { action: "complete", id: trimmed.slice("complete ".length).trim() };
580657
+ } else if (lower.startsWith("dismiss ")) {
580658
+ args = { action: "dismiss", id: trimmed.slice("dismiss ".length).trim() };
580659
+ } else if (lower.startsWith("snooze ")) {
580660
+ const rest = trimmed.slice("snooze ".length).trim();
580661
+ const [id, ...dueParts] = rest.split(/\s+/);
580662
+ args = { action: "snooze", id, due: dueParts.join(" ").trim() || "in 1h" };
580663
+ } else {
580664
+ const { due, message: message2 } = splitReminderDue(trimmed);
580665
+ if (!message2) {
580666
+ renderInfo("Usage: /remind [in 30m|tomorrow|at 14:30] <message>");
580667
+ return "handled";
580668
+ }
580669
+ args = {
580670
+ action: "set",
580671
+ kind: "minimal",
580672
+ message: message2,
580673
+ content: message2,
580674
+ ...due ? { due } : {}
580675
+ };
580676
+ }
580677
+ const result = await tool.execute(args);
580678
+ if (!result.success) {
580679
+ renderError(result.error || `/${cmd} failed`);
580680
+ } else if (result.output) {
580681
+ safeLog(result.output);
580682
+ }
580683
+ return "handled";
580684
+ }
579871
580685
  function parseTelegramChatTarget(value2) {
579872
580686
  if (!value2) return null;
579873
580687
  if (/^-?\d+$/.test(value2)) return Number(value2);
@@ -580244,6 +581058,10 @@ async function handleSlashCommand(input, ctx3) {
580244
581058
  case "?":
580245
581059
  await showHelpMenu(ctx3);
580246
581060
  return "handled";
581061
+ case "reminder":
581062
+ case "remind":
581063
+ case "reminders":
581064
+ return handleReminderSlash(cmd, arg, ctx3);
580247
581065
  case "queue": {
580248
581066
  if (!arg) {
580249
581067
  const count2 = ctx3.queuedPromptCount?.() ?? 0;
@@ -583052,7 +583870,8 @@ sleep 1
583052
583870
  }
583053
583871
  return "handled";
583054
583872
  }
583055
- case "scheduler": {
583873
+ case "scheduler":
583874
+ case "cron": {
583056
583875
  const tokens = (arg || "").trim().length ? (arg || "").trim().split(/\s+/) : [];
583057
583876
  const sub = (tokens[0] || "menu").toLowerCase();
583058
583877
  const base3 = `http://127.0.0.1:${process.env["OMNIUS_PORT"] || "11435"}`;
@@ -586370,8 +587189,9 @@ function renderImagePresetDetail(prefix, text) {
586370
587189
  renderInfo(`${prefix}${first2}`);
586371
587190
  for (const line of rest) renderInfo(`${" ".repeat(prefix.length)}${line}`);
586372
587191
  }
586373
- function renderImageModelList() {
587192
+ async function renderImageModelList(ctx3) {
586374
587193
  const specs = detectSystemSpecs();
587194
+ const ollamaSizes = ctx3 ? await fetchOllamaModelSizes(ctx3).catch(() => /* @__PURE__ */ new Map()) : /* @__PURE__ */ new Map();
586375
587195
  const hardware = `${specs.totalRamGB.toFixed(0)}GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)}GB VRAM (${specs.gpuName || "NVIDIA GPU"})` : " + no NVIDIA VRAM detected");
586376
587196
  renderInfo(`Image models for this hardware: ${hardware}`);
586377
587197
  renderInfo("Fit legend: 85+ excellent, 60+ comfortable, 40+ offload/quantized, below 40 heavy/cloud.");
@@ -586389,7 +587209,9 @@ function renderImageModelList() {
586389
587209
  for (const preset of presets) {
586390
587210
  const fit2 = rateImagePresetForHardware(preset, specs);
586391
587211
  const primary = category === "Primary hyper-realistic baseline" ? c3.cyan(" ★") : "";
586392
- renderInfo(`${imageFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${c3.bold(preset.label)}${primary}`);
587212
+ const disk = ctx3 ? imageModelDiskStats(ctx3, preset, ollamaSizes) : { downloaded: false, bytes: 0, paths: [] };
587213
+ const diskInfo = disk.downloaded ? ` ${c3.green("✓")} ${formatFileSize(disk.bytes)}` : "";
587214
+ renderInfo(`${imageFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${c3.bold(preset.label)}${primary}${diskInfo}`);
586393
587215
  renderInfo(` id: ${preset.id}`);
586394
587216
  renderInfo(` type: ${preset.backend} · ${preset.sizeClass ?? "unknown size"} · ${fit2.label}`);
586395
587217
  renderImagePresetDetail(" quality: ", preset.quality ?? preset.note);
@@ -586412,6 +587234,32 @@ function cacheCandidatePaths(root, model) {
586412
587234
  join110(root, "cache", "huggingface", "hub", slug)
586413
587235
  ];
586414
587236
  }
587237
+ function directorySizeBytes2(path11, seen = /* @__PURE__ */ new Set()) {
587238
+ try {
587239
+ const stat5 = lstatSync(path11);
587240
+ if (stat5.isSymbolicLink()) return 0;
587241
+ if (stat5.isFile()) return stat5.size;
587242
+ if (!stat5.isDirectory()) return 0;
587243
+ const realKey = `${stat5.dev}:${stat5.ino}`;
587244
+ if (seen.has(realKey)) return 0;
587245
+ seen.add(realKey);
587246
+ let total = 0;
587247
+ for (const entry of readdirSync30(path11)) {
587248
+ total += directorySizeBytes2(join110(path11, entry), seen);
587249
+ }
587250
+ return total;
587251
+ } catch {
587252
+ return 0;
587253
+ }
587254
+ }
587255
+ function cachedModelDiskStats(root, model) {
587256
+ const paths = cacheCandidatePaths(root, model).filter((path11) => existsSync96(path11));
587257
+ const bytes = paths.reduce((sum, path11) => sum + directorySizeBytes2(path11), 0);
587258
+ return { downloaded: paths.length > 0, bytes, paths };
587259
+ }
587260
+ function downloadedModelSuffix(stats) {
587261
+ return stats.downloaded ? ` · downloaded ${formatFileSize(stats.bytes)}` : "";
587262
+ }
586415
587263
  function removeCachedModelPaths(root, model) {
586416
587264
  const removed = [];
586417
587265
  for (const path11 of cacheCandidatePaths(root, model)) {
@@ -586424,6 +587272,37 @@ function removeCachedModelPaths(root, model) {
586424
587272
  function ollamaApiBase(ctx3) {
586425
587273
  return String(ctx3.config.backendUrl || "http://localhost:11434").replace(/\/v1\/?$/, "").replace(/\/$/, "");
586426
587274
  }
587275
+ async function fetchOllamaModelSizes(ctx3) {
587276
+ const sizes = /* @__PURE__ */ new Map();
587277
+ const resp = await fetch(`${ollamaApiBase(ctx3)}/api/tags`, {
587278
+ signal: AbortSignal.timeout(5e3)
587279
+ });
587280
+ if (!resp.ok) return sizes;
587281
+ const data = await resp.json().catch(() => null);
587282
+ for (const entry of data?.models ?? []) {
587283
+ const size = Number(entry.size ?? 0);
587284
+ if (!Number.isFinite(size) || size <= 0) continue;
587285
+ for (const raw of [entry.name, entry.model]) {
587286
+ if (!raw) continue;
587287
+ sizes.set(raw, size);
587288
+ sizes.set(raw.replace(/:latest$/, ""), size);
587289
+ }
587290
+ }
587291
+ return sizes;
587292
+ }
587293
+ function ollamaModelDiskStats(model, sizes) {
587294
+ const bytes = sizes.get(model) ?? sizes.get(`${model}:latest`) ?? sizes.get(model.replace(/:latest$/, "")) ?? 0;
587295
+ return { downloaded: bytes > 0, bytes, paths: [] };
587296
+ }
587297
+ function imageModelDiskStats(ctx3, preset, ollamaSizes) {
587298
+ if (preset.backend === "ollama") return ollamaModelDiskStats(preset.id, ollamaSizes);
587299
+ if (preset.backend === "diffusers") return cachedModelDiskStats(imageGenerationDir(ctx3.repoRoot), preset.id);
587300
+ return { downloaded: false, bytes: 0, paths: [] };
587301
+ }
587302
+ function audioModelDiskStats(ctx3, preset) {
587303
+ if (preset.backend === "project") return { downloaded: false, bytes: 0, paths: [] };
587304
+ return cachedModelDiskStats(audioGenerationDir(ctx3.repoRoot), preset.id);
587305
+ }
586427
587306
  async function deleteOllamaWeights(ctx3, model) {
586428
587307
  const resp = await fetch(`${ollamaApiBase(ctx3)}/api/delete`, {
586429
587308
  method: "DELETE",
@@ -586453,19 +587332,23 @@ async function deleteImageModelWeights(ctx3, preset) {
586453
587332
  async function showImageModelsMenu(ctx3, hasLocal) {
586454
587333
  const settings = resolveSettings(ctx3.repoRoot);
586455
587334
  const specs = detectSystemSpecs();
587335
+ const ollamaSizes = await fetchOllamaModelSizes(ctx3).catch(() => /* @__PURE__ */ new Map());
587336
+ const buildModelItem = (preset) => {
587337
+ const fit2 = rateImagePresetForHardware(preset, specs);
587338
+ const disk = imageModelDiskStats(ctx3, preset, ollamaSizes);
587339
+ const downloaded = disk.downloaded ? `${c3.green("✓")} ` : "";
587340
+ return {
587341
+ key: `model:${preset.id}`,
587342
+ label: `${downloaded}${imageFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${preset.label}`,
587343
+ detail: `${fit2.score}/100 ${fit2.label} · ${preset.category ?? preset.backend} · ${preset.sizeClass ?? preset.id}${downloadedModelSuffix(disk)}`
587344
+ };
587345
+ };
586456
587346
  const items = [
586457
587347
  { key: "setup:ollama", label: "Setup Ollama", detail: "Pull x/z-image-turbo or x/flux2-klein" },
586458
587348
  { key: "setup:diffusers", label: "Setup Diffusers", detail: "Auto-installs SDXL Turbo under .omnius/image-gen/.venv" },
586459
587349
  { key: "setup:sdcpp", label: "Setup stable-diffusion.cpp", detail: "CPU/GGUF/checkpoint route" },
586460
587350
  { key: "hdr:models", label: selectColors.dim("─── Models ───") },
586461
- ...IMAGE_GENERATION_MODEL_PRESETS.map((preset) => {
586462
- const fit2 = rateImagePresetForHardware(preset, specs);
586463
- return {
586464
- key: `model:${preset.id}`,
586465
- label: `${imageFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${preset.label}`,
586466
- detail: `${fit2.score}/100 ${fit2.label} · ${preset.category ?? preset.backend} · ${preset.sizeClass ?? preset.id}`
586467
- };
586468
- })
587351
+ ...IMAGE_GENERATION_MODEL_PRESETS.map(buildModelItem)
586469
587352
  ];
586470
587353
  const result = await tuiSelect({
586471
587354
  items,
@@ -586491,6 +587374,11 @@ async function showImageModelsMenu(ctx3, hasLocal) {
586491
587374
  }
586492
587375
  deleteImageModelWeights(ctx3, preset).then((messages2) => {
586493
587376
  for (const message2 of messages2) renderInfo(message2);
587377
+ if (preset.backend === "ollama") {
587378
+ ollamaSizes.delete(preset.id);
587379
+ ollamaSizes.delete(`${preset.id}:latest`);
587380
+ }
587381
+ Object.assign(item, buildModelItem(preset));
586494
587382
  done(false);
586495
587383
  }).catch((err) => {
586496
587384
  renderError(`Image weight delete failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -586548,7 +587436,7 @@ async function handleImageCommand(ctx3, arg, hasLocal) {
586548
587436
  return "handled";
586549
587437
  }
586550
587438
  if (parsed.subcommand === "models" || parsed.subcommand === "list") {
586551
- renderImageModelList();
587439
+ await renderImageModelList(ctx3);
586552
587440
  return "handled";
586553
587441
  }
586554
587442
  if (parsed.subcommand === "setup") {
@@ -586683,7 +587571,7 @@ function rateAudioPresetForHardware(preset, specs) {
586683
587571
  function audioFitIcon(score) {
586684
587572
  return imageFitIcon(score);
586685
587573
  }
586686
- function renderAudioModelList(kind) {
587574
+ async function renderAudioModelList(ctx3, kind) {
586687
587575
  const specs = detectSystemSpecs();
586688
587576
  const title = kind === "music" ? "Music" : "Sound";
586689
587577
  const hardware = `${specs.totalRamGB.toFixed(0)}GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)}GB VRAM (${specs.gpuName || "NVIDIA GPU"})` : " + no NVIDIA VRAM detected");
@@ -586706,7 +587594,9 @@ function renderAudioModelList(kind) {
586706
587594
  renderInfo(c3.bold(category));
586707
587595
  for (const preset of presets) {
586708
587596
  const fit2 = rateAudioPresetForHardware(preset, specs);
586709
- renderInfo(`${audioFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${c3.bold(preset.label)}`);
587597
+ const disk = audioModelDiskStats(ctx3, preset);
587598
+ const diskInfo = disk.downloaded ? ` ${c3.green("✓")} ${formatFileSize(disk.bytes)}` : "";
587599
+ renderInfo(`${audioFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${c3.bold(preset.label)}${diskInfo}`);
586710
587600
  renderInfo(` id: ${preset.id}`);
586711
587601
  renderInfo(` type: ${preset.backend} · ${preset.sizeClass} · ${fit2.label}`);
586712
587602
  renderImagePresetDetail(" quality: ", preset.quality);
@@ -586728,6 +587618,16 @@ async function showAudioGenerationMenu(ctx3, hasLocal, kind) {
586728
587618
  const specs = detectSystemSpecs();
586729
587619
  const activeModel = activeAudioModel(settings, kind);
586730
587620
  const title = kind === "music" ? "Music Generation" : "Sound Generation";
587621
+ const buildModelItem = (preset) => {
587622
+ const fit2 = rateAudioPresetForHardware(preset, specs);
587623
+ const disk = audioModelDiskStats(ctx3, preset);
587624
+ const downloaded = disk.downloaded ? `${c3.green("✓")} ` : "";
587625
+ return {
587626
+ key: `model:${preset.id}`,
587627
+ label: `${downloaded}${audioFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${preset.label}`,
587628
+ detail: `${fit2.label} · ${preset.category} · ${preset.sizeClass}${downloadedModelSuffix(disk)}`
587629
+ };
587630
+ };
586731
587631
  const setupItems = kind === "music" ? [
586732
587632
  { key: "setup:transformers", label: "Setup Transformers", detail: "Default MusicGen path; avoids AudioCraft/PyAV headers" },
586733
587633
  { key: "setup:audiocraft", label: "Setup AudioCraft", detail: "Optional MusicGen/AudioGen upstream runtime" },
@@ -586742,14 +587642,7 @@ async function showAudioGenerationMenu(ctx3, hasLocal, kind) {
586742
587642
  const items = [
586743
587643
  ...setupItems,
586744
587644
  { key: "hdr:models", label: selectColors.dim("─── Models ───") },
586745
- ...AUDIO_GENERATION_MODEL_PRESETS.filter((preset) => preset.kind === kind).map((preset) => {
586746
- const fit2 = rateAudioPresetForHardware(preset, specs);
586747
- return {
586748
- key: `model:${preset.id}`,
586749
- label: `${audioFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${preset.label}`,
586750
- detail: `${fit2.label} · ${preset.category} · ${preset.sizeClass}`
586751
- };
586752
- })
587645
+ ...AUDIO_GENERATION_MODEL_PRESETS.filter((preset) => preset.kind === kind).map(buildModelItem)
586753
587646
  ];
586754
587647
  const result = await tuiSelect({
586755
587648
  items,
@@ -586775,6 +587668,7 @@ async function showAudioGenerationMenu(ctx3, hasLocal, kind) {
586775
587668
  }
586776
587669
  deleteAudioModelWeights(ctx3, preset).then((messages2) => {
586777
587670
  for (const message2 of messages2) renderInfo(message2);
587671
+ Object.assign(item, buildModelItem(preset));
586778
587672
  done(false);
586779
587673
  }).catch((err) => {
586780
587674
  renderError(`${kind} weight delete failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -586835,7 +587729,7 @@ async function handleAudioGenerationCommand(ctx3, arg, hasLocal, kind) {
586835
587729
  return "handled";
586836
587730
  }
586837
587731
  if (parsed.subcommand === "models" || parsed.subcommand === "list") {
586838
- renderAudioModelList(kind);
587732
+ await renderAudioModelList(ctx3, kind);
586839
587733
  return "handled";
586840
587734
  }
586841
587735
  if (parsed.subcommand === "setup") {
@@ -587355,7 +588249,9 @@ async function showModelPicker(ctx3, local = false) {
587355
588249
  function formatFileSize(bytes) {
587356
588250
  if (bytes < 1024) return `${bytes}B`;
587357
588251
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
587358
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
588252
+ if (bytes < 1024 ** 3) return `${(bytes / 1024 ** 2).toFixed(1)}MB`;
588253
+ if (bytes < 1024 ** 4) return `${(bytes / 1024 ** 3).toFixed(1)}GB`;
588254
+ return `${(bytes / 1024 ** 4).toFixed(1)}TB`;
587359
588255
  }
587360
588256
  async function handleVoiceMenu(ctx3, save2, hasLocal) {
587361
588257
  const modeLabels = {
@@ -595432,10 +596328,14 @@ var init_bless_engine = __esm({
595432
596328
  * Priority queue is checked first, then reminders/attention items are ingested. */
595433
596329
  async getNextTask() {
595434
596330
  try {
595435
- const dueReminders = await getDueReminders(this.repoRoot);
596331
+ const dueReminders = await getDueReminders(this.repoRoot, {
596332
+ deliveryMode: "action",
596333
+ includeUnscoped: true
596334
+ });
595436
596335
  for (const r2 of dueReminders.filter((r3) => r3.status === "pending")) {
595437
596336
  const priority = r2.priority === "critical" ? "critical" : r2.priority === "high" ? "high" : "moderate";
595438
- const prompt = `${priority === "critical" ? "URGENT " : ""}REMINDER: ${r2.message}${r2.tags?.length ? ` [tags: ${r2.tags.join(", ")}]` : ""}. Check the agenda for full context and take appropriate action.`;
596337
+ const task2 = r2.delivery?.taskDescription || r2.message;
596338
+ const prompt = `${priority === "critical" ? "URGENT " : ""}REMINDER ACTION: ${task2}${r2.tags?.length ? ` [tags: ${r2.tags.join(", ")}]` : ""}. Check the agenda for full context and take appropriate action.`;
595439
596339
  this.priorityEngine.ingest(prompt, "internal", "reminder", { reminderId: r2.id }, priority);
595440
596340
  }
595441
596341
  } catch {
@@ -597144,7 +598044,7 @@ var init_tool_policy = __esm({
597144
598044
  "aiwg_workflow",
597145
598045
  "autoresearch",
597146
598046
  "scheduler",
597147
- "reminder",
598047
+ "cronjob",
597148
598048
  "agenda"
597149
598049
  ]);
597150
598050
  SAFE_PUBLIC_TOOLS = /* @__PURE__ */ new Set([
@@ -597164,6 +598064,9 @@ var init_tool_policy = __esm({
597164
598064
  "transcribe_file",
597165
598065
  "video_understand",
597166
598066
  "audio_analyze",
598067
+ "reminder",
598068
+ "remind",
598069
+ "reminders",
597167
598070
  "explore_tools",
597168
598071
  "telegram_media_recent",
597169
598072
  "generate_image",
@@ -597191,6 +598094,9 @@ var init_tool_policy = __esm({
597191
598094
  "transcribe_file",
597192
598095
  "video_understand",
597193
598096
  "audio_analyze",
598097
+ "reminder",
598098
+ "remind",
598099
+ "reminders",
597194
598100
  "explore_tools",
597195
598101
  "telegram_media_recent",
597196
598102
  "generate_image",
@@ -597926,7 +598832,7 @@ var init_vision_ingress = __esm({
597926
598832
  });
597927
598833
 
597928
598834
  // packages/cli/src/tui/telegram-bridge.ts
597929
- import { mkdirSync as mkdirSync60, existsSync as existsSync106, unlinkSync as unlinkSync21, readdirSync as readdirSync36, statSync as statSync36, readFileSync as readFileSync87, writeFileSync as writeFileSync57 } from "node:fs";
598835
+ import { mkdirSync as mkdirSync60, existsSync as existsSync106, unlinkSync as unlinkSync21, readdirSync as readdirSync36, statSync as statSync36, statfsSync as statfsSync3, readFileSync as readFileSync87, writeFileSync as writeFileSync57 } from "node:fs";
597930
598836
  import { join as join121, resolve as resolve39, basename as basename23, relative as relative13, isAbsolute as isAbsolute7, extname as extname15 } from "node:path";
597931
598837
  import { writeFile as writeFileAsync } from "node:fs/promises";
597932
598838
  import { createHash as createHash19, randomInt } from "node:crypto";
@@ -598194,12 +599100,28 @@ function buildTelegramRuntimeContext(now = /* @__PURE__ */ new Date(), repoRoot)
598194
599100
  timeZoneName: "short"
598195
599101
  }).format(now);
598196
599102
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || process.env["TZ"] || "system";
599103
+ let diskLine = "";
599104
+ if (repoRoot) {
599105
+ try {
599106
+ const stats = statfsSync3(repoRoot);
599107
+ const totalBytes = stats.blocks * stats.bsize;
599108
+ const freeBytes = stats.bavail * stats.bsize;
599109
+ const usedBytes = totalBytes - freeBytes;
599110
+ const totalGB = Math.round(totalBytes / 1024 ** 3);
599111
+ const freeGB = Math.round(freeBytes / 1024 ** 3);
599112
+ const usedGB = Math.round(usedBytes / 1024 ** 3);
599113
+ const usedPct = totalBytes > 0 ? Math.round(usedBytes / totalBytes * 100) : 0;
599114
+ diskLine = `Disk: disk_available_gb=${freeGB} disk_used_gb=${usedGB} disk_total_gb=${totalGB} disk_used_pct=${usedPct} path=${repoRoot}`;
599115
+ } catch {
599116
+ }
599117
+ }
598197
599118
  return [
598198
599119
  `Current date: ${date}`,
598199
599120
  `Current time: ${time}`,
598200
599121
  `Current ISO timestamp: ${now.toISOString()}`,
598201
599122
  `Timezone: ${timezone}`,
598202
- repoRoot ? `Working directory: ${repoRoot}` : ""
599123
+ repoRoot ? `Working directory: ${repoRoot}` : "",
599124
+ diskLine
598203
599125
  ].filter(Boolean).join("\n");
598204
599126
  }
598205
599127
  function telegramSessionIdFromKey(sessionKey) {
@@ -598251,6 +599173,14 @@ function renderTelegramLiveProgressHTML(progressLines, accumulated) {
598251
599173
  }
598252
599174
  return progressLines.slice(-6).map((line) => line.trim()).filter((line) => !isTelegramInternalStatusText(line)).filter(Boolean).join("\n");
598253
599175
  }
599176
+ function splitTelegramReminderDue(raw) {
599177
+ const text = raw.trim().replace(/^set\s+/i, "").trim();
599178
+ const dueMatch = text.match(
599179
+ /^((?:in\s+\d+\s*(?:m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|weeks?))|tomorrow|next week|next startup|startup|now|(?:today\s+)?at\s+\d{1,2}:\d{2})\s+(.+)$/i
599180
+ );
599181
+ if (!dueMatch) return { message: text };
599182
+ return { due: dueMatch[1].trim(), message: dueMatch[2].trim() };
599183
+ }
598254
599184
  function telegramSyntheticHelpSignatures() {
598255
599185
  return [
598256
599186
  { signature: "/help", description: "Show Telegram command help" },
@@ -598740,7 +599670,7 @@ function renderTelegramSubAgentError(username, error) {
598740
599670
  process.stdout.write(` ${c3.dim("⎿")} ${c3.red("✘")} @${username}: ${c3.dim(preview)}
598741
599671
  `);
598742
599672
  }
598743
- var TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, TELEGRAM_PUBLIC_SOUL_PROFILE, TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT, TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, ADMIN_CHAT_PROFILE_PROMPT, TELEGRAM_ACTION_RESPONSE_CONTRACT, TELEGRAM_CHAT_HISTORY_LIMIT, TELEGRAM_CONTEXT_RECENT_DEFAULT, TELEGRAM_CONTEXT_LINE_LIMIT, TELEGRAM_CONTEXT_SAMPLE_LIMIT, TELEGRAM_MEMORY_CARD_LIMIT, TELEGRAM_MEMORY_NOTE_LIMIT, TELEGRAM_MEMORY_STOPWORDS, TELEGRAM_PUBLIC_HELP_COMMANDS, TELEGRAM_IMAGE_EXTENSIONS, MEDIA_CACHE_TTL_MS, TelegramBridge;
599673
+ var TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, TELEGRAM_PUBLIC_SOUL_PROFILE, TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT, TELEGRAM_PUBLIC_MEMORY_SCOPE_CONTRACT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, ADMIN_CHAT_PROFILE_PROMPT, TELEGRAM_ACTION_RESPONSE_CONTRACT, TELEGRAM_CHAT_HISTORY_LIMIT, TELEGRAM_CONTEXT_RECENT_DEFAULT, TELEGRAM_CONTEXT_LINE_LIMIT, TELEGRAM_CONTEXT_SAMPLE_LIMIT, TELEGRAM_MEMORY_CARD_LIMIT, TELEGRAM_MEMORY_NOTE_LIMIT, TELEGRAM_MEMORY_STOPWORDS, TELEGRAM_PUBLIC_HELP_COMMANDS, TELEGRAM_REMINDER_SLASH_COMMANDS, TELEGRAM_IMAGE_EXTENSIONS, MEDIA_CACHE_TTL_MS, TelegramBridge;
598744
599674
  var init_telegram_bridge = __esm({
598745
599675
  "packages/cli/src/tui/telegram-bridge.ts"() {
598746
599676
  "use strict";
@@ -598936,6 +599866,7 @@ Telegram response contract:
598936
599866
  "your"
598937
599867
  ]);
598938
599868
  TELEGRAM_PUBLIC_HELP_COMMANDS = /* @__PURE__ */ new Set(["help", "start", "auth", "call"]);
599869
+ TELEGRAM_REMINDER_SLASH_COMMANDS = /* @__PURE__ */ new Set(["remind", "reminder", "reminders"]);
598939
599870
  TELEGRAM_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tiff", ".tif", ".svg"]);
598940
599871
  MEDIA_CACHE_TTL_MS = 30 * 60 * 1e3;
598941
599872
  TelegramBridge = class {
@@ -599136,6 +600067,49 @@ Telegram response contract:
599136
600067
  const botless = first2.startsWith("/") ? first2.slice(1).split("@")[0]?.toLowerCase() : "";
599137
600068
  return !!botless && this.telegramCommandMap.has(botless);
599138
600069
  }
600070
+ async handleTelegramReminderSlash(msg, commandText, context2) {
600071
+ if (!this.repoRoot) {
600072
+ await this.replyToTelegramMessage(msg, "Reminder storage is not available yet.");
600073
+ return;
600074
+ }
600075
+ const first2 = commandText.trim().split(/\s+/)[0] ?? "";
600076
+ const cmd = first2.slice(1).split("@")[0]?.toLowerCase() || "reminder";
600077
+ const arg = commandText.trim().slice(first2.length).trim();
600078
+ const lower = arg.toLowerCase();
600079
+ const tool = this.buildTelegramReminderTool(context2, this.repoRoot, msg.chatId, msg);
600080
+ let args;
600081
+ if (!arg || lower === "list" || lower === "pending" || lower === "all" || lower === "seen") {
600082
+ args = { action: "list", filter: lower === "all" || lower === "seen" ? lower : "pending" };
600083
+ } else if (lower === "completed" || lower === "dismissed") {
600084
+ args = { action: "list", filter: lower };
600085
+ } else if (lower.startsWith("complete ")) {
600086
+ args = { action: "complete", id: arg.slice("complete ".length).trim() };
600087
+ } else if (lower.startsWith("dismiss ")) {
600088
+ args = { action: "dismiss", id: arg.slice("dismiss ".length).trim() };
600089
+ } else if (lower.startsWith("snooze ")) {
600090
+ const rest = arg.slice("snooze ".length).trim();
600091
+ const [id, ...dueParts] = rest.split(/\s+/);
600092
+ args = { action: "snooze", id, due: dueParts.join(" ").trim() || "in 1h" };
600093
+ } else {
600094
+ const { due, message: message2 } = splitTelegramReminderDue(arg);
600095
+ if (!message2) {
600096
+ await this.replyToTelegramMessage(msg, "Usage: /remind [in 30m|tomorrow|at 14:30] <message>");
600097
+ return;
600098
+ }
600099
+ args = {
600100
+ action: "set",
600101
+ kind: "minimal",
600102
+ message: message2,
600103
+ content: message2,
600104
+ ...due ? { due } : {}
600105
+ };
600106
+ }
600107
+ const result = await tool.execute(args);
600108
+ await this.replyToTelegramMessage(
600109
+ msg,
600110
+ result.success ? result.output || "Reminder updated." : result.error || `/${cmd} failed`
600111
+ );
600112
+ }
599139
600113
  /** Write to the scrollable TUI waterfall area (respects status bar scroll region) */
599140
600114
  tuiWrite(fn) {
599141
600115
  if (this.writeContent) {
@@ -600210,6 +601184,11 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
600210
601184
  const toolContext = this.resolveToolContext(msg, isAdmin);
600211
601185
  const isAdminDM = toolContext === "telegram-admin-dm";
600212
601186
  const sessionKey = this.sessionKeyForMessage(msg);
601187
+ const telegramSlash = this.telegramSlashName(normalizedCommandText);
601188
+ if (msg.text.trim().startsWith("/") && TELEGRAM_REMINDER_SLASH_COMMANDS.has(telegramSlash)) {
601189
+ await this.handleTelegramReminderSlash(msg, normalizedCommandText, toolContext);
601190
+ return;
601191
+ }
600213
601192
  if (msg.text.trim().toLowerCase() === "/hangup" && isAdmin && this.callStopper) {
600214
601193
  const callUrl = this.callUrlGetter?.();
600215
601194
  if (callUrl) {
@@ -600264,7 +601243,6 @@ Join: ${newUrl}`);
600264
601243
  return;
600265
601244
  }
600266
601245
  }
600267
- const telegramSlash = this.telegramSlashName(normalizedCommandText);
600268
601246
  const publicCreativeSlash = telegramSlash === "image";
600269
601247
  if (!isAdmin && msg.text.trim().startsWith("/") && this.isKnownTelegramSlash(normalizedCommandText) && !publicCreativeSlash) {
600270
601248
  await this.replyToTelegramMessage(
@@ -601357,7 +602335,8 @@ Scoped workspace: ${scopedRoot}`,
601357
602335
  new VideoUnderstandTool(repoRoot),
601358
602336
  new AudioAnalyzeTool(),
601359
602337
  new ExploreToolsTool(),
601360
- this.buildTelegramMediaRecentTool(chatId, msg)
602338
+ this.buildTelegramMediaRecentTool(chatId, msg),
602339
+ ...this.buildTelegramReminderTools(context2, repoRoot, chatId, msg)
601361
602340
  ];
601362
602341
  const adminTools = [
601363
602342
  new ShellTool(repoRoot),
@@ -601415,6 +602394,7 @@ Scoped workspace: ${scopedRoot}`,
601415
602394
  new ImpactAnalysisTool(repoRoot),
601416
602395
  new CodeNeighborsTool(repoRoot),
601417
602396
  new ProcessHealthTool(),
602397
+ ...this.buildTelegramReminderTools(context2, repoRoot, chatId, msg),
601418
602398
  fullSubAgentTool,
601419
602399
  this.buildTelegramSendFileTool(context2, repoRoot, chatId, msg)
601420
602400
  ];
@@ -601460,6 +602440,34 @@ Scoped workspace: ${scopedRoot}`,
601460
602440
  ]);
601461
602441
  return tools.filter((tool) => !blocked.has(tool.name));
601462
602442
  }
602443
+ buildTelegramReminderTool(context2, repoRoot, chatId, currentMsg) {
602444
+ const rawChatId = String(chatId ?? currentMsg?.chatId ?? "unknown");
602445
+ const isPrivate2 = currentMsg?.chatType === "private";
602446
+ const username = currentMsg?.username ? currentMsg.username.replace(/^@/, "") : void 0;
602447
+ return new ReminderTool(repoRoot, {
602448
+ defaultScope: {
602449
+ surface: "telegram",
602450
+ visibility: isPrivate2 ? "private" : "public",
602451
+ targetId: rawChatId,
602452
+ chatId: rawChatId,
602453
+ ...currentMsg?.fromUserId !== void 0 ? { userId: String(currentMsg.fromUserId) } : {},
602454
+ ...username ? { username } : {},
602455
+ label: isPrivate2 ? `telegram dm${username ? ` @${username}` : ""}` : `telegram group ${currentMsg?.chatTitle ?? rawChatId}`
602456
+ },
602457
+ lockScope: true,
602458
+ allowActionDelivery: context2 === "telegram-admin-dm",
602459
+ source: "user",
602460
+ createdFrom: context2
602461
+ });
602462
+ }
602463
+ buildTelegramReminderTools(context2, repoRoot, chatId, currentMsg) {
602464
+ const reminder = this.buildTelegramReminderTool(context2, repoRoot, chatId, currentMsg);
602465
+ return [
602466
+ reminder,
602467
+ aliasTool(reminder, "remind", "Alias for reminder: set scoped Telegram reminders."),
602468
+ aliasTool(reminder, "reminders", "Alias for reminder: list and manage scoped Telegram reminders.")
602469
+ ];
602470
+ }
601463
602471
  buildTelegramMediaRecentTool(chatId, currentMsg) {
601464
602472
  const bridge = this;
601465
602473
  return {
@@ -626570,6 +627578,33 @@ function adaptTool6(tool) {
626570
627578
  }
626571
627579
  };
626572
627580
  }
627581
+ function createTuiReminderOptions(allowActionDelivery = true) {
627582
+ const sessionId = process.env["OMNIUS_SESSION_ID"] || "terminal";
627583
+ return {
627584
+ defaultScope: {
627585
+ surface: "tui",
627586
+ visibility: "private",
627587
+ targetId: sessionId,
627588
+ sessionId,
627589
+ label: "terminal"
627590
+ },
627591
+ allowActionDelivery,
627592
+ createdFrom: "tui"
627593
+ };
627594
+ }
627595
+ function withReminderAliases(reminder) {
627596
+ return [
627597
+ reminder,
627598
+ aliasTool(reminder, "remind", "Alias for reminder: set or list scoped minimal reminders and action triggers."),
627599
+ aliasTool(reminder, "reminders", "Alias for reminder: list and manage scoped reminders.")
627600
+ ];
627601
+ }
627602
+ function withSchedulerAliases(scheduler) {
627603
+ return [
627604
+ scheduler,
627605
+ aliasTool(scheduler, "cronjob", "Alias for scheduler: create, list, and manage OS cron-backed time triggers.")
627606
+ ];
627607
+ }
626573
627608
  function scanForSessionSignals(toolOutput) {
626574
627609
  if (/SESSION_ACTIVE\s*=\s*true/i.test(toolOutput)) {
626575
627610
  _interactiveSessionActive = true;
@@ -626910,8 +627945,8 @@ function buildSubAgentTools(repoRoot, config) {
626910
627945
  new AiwgHealthTool(repoRoot),
626911
627946
  new AiwgWorkflowTool(repoRoot),
626912
627947
  // Scheduler + agenda (time-based tooling)
626913
- new SchedulerTool(repoRoot),
626914
- new ReminderTool(repoRoot),
627948
+ ...withSchedulerAliases(new SchedulerTool(repoRoot)),
627949
+ ...withReminderAliases(new ReminderTool(repoRoot, createTuiReminderOptions())),
626915
627950
  new AgendaTool(repoRoot),
626916
627951
  // Vision + multimodal memory
626917
627952
  new VisionTool(repoRoot),
@@ -627024,8 +628059,8 @@ function buildTools(repoRoot, config, contextWindowSize, modelTier) {
627024
628059
  // Autoresearch (autonomous ML experiment loop)
627025
628060
  new AutoresearchTool(repoRoot),
627026
628061
  // Temporal agency (scheduling, reminders, attention steering)
627027
- new SchedulerTool(repoRoot),
627028
- new ReminderTool(repoRoot),
628062
+ ...withSchedulerAliases(new SchedulerTool(repoRoot)),
628063
+ ...withReminderAliases(new ReminderTool(repoRoot, createTuiReminderOptions())),
627029
628064
  new AgendaTool(repoRoot),
627030
628065
  // OpenCode + Factory AI delegation + long-horizon cron agent
627031
628066
  new OpenCodeTool(repoRoot),
@@ -630341,6 +631376,7 @@ ${result.summary}`
630341
631376
  let telegramBridge = null;
630342
631377
  let activeTelegramChatId = null;
630343
631378
  let dmnRetriggerTimer = null;
631379
+ let reminderDispatchTimer = null;
630344
631380
  function scheduleDMNRetrigger(delayMs) {
630345
631381
  if (dmnRetriggerTimer) clearTimeout(dmnRetriggerTimer);
630346
631382
  dmnRetriggerTimer = setTimeout(async () => {
@@ -630415,10 +631451,10 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
630415
631451
  const turnQueue = [];
630416
631452
  let queueDrainScheduled = false;
630417
631453
  let sessionTitle = null;
630418
- const queuePrompt = (prompt, source = "user") => {
631454
+ const queuePrompt = (prompt, source = "user", options2 = {}) => {
630419
631455
  const trimmed = prompt.trim();
630420
631456
  if (!trimmed) return turnQueue.length;
630421
- turnQueue.push({ prompt: trimmed, source, enqueuedAt: Date.now() });
631457
+ turnQueue.push({ prompt: trimmed, source, enqueuedAt: Date.now(), ...options2 });
630422
631458
  return turnQueue.length;
630423
631459
  };
630424
631460
  const clearQueuedPrompts = () => {
@@ -630443,9 +631479,103 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
630443
631479
  `Starting queued prompt from ${next.source} (${turnQueue.length} remaining).`
630444
631480
  )
630445
631481
  );
631482
+ if (next.telegramChatId !== void 0) activeTelegramChatId = next.telegramChatId;
630446
631483
  rl.emit("line", next.prompt);
630447
631484
  }, 0);
630448
631485
  };
631486
+ const reminderTelegramTarget = (scope) => {
631487
+ if (!scope || scope.surface !== "telegram") return null;
631488
+ const raw = scope.chatId ?? scope.targetId;
631489
+ if (!raw) return null;
631490
+ return /^-?\d+$/.test(raw) ? Number(raw) : raw;
631491
+ };
631492
+ const reminderText = (reminder) => {
631493
+ const content = reminder.delivery?.content || reminder.message;
631494
+ const prefix = reminder.priority === "critical" ? "URGENT reminder" : "Reminder";
631495
+ return `${prefix}: ${content}`;
631496
+ };
631497
+ const reminderActionPrompt = (reminder) => {
631498
+ const task = reminder.delivery?.taskDescription || reminder.delivery?.prompt || reminder.message;
631499
+ const scope = reminder.scope ? `${reminder.scope.surface}/${reminder.scope.visibility}/${reminder.scope.targetId}` : "unscoped";
631500
+ return [
631501
+ `[Scheduled reminder action | ${reminder.id}]`,
631502
+ ``,
631503
+ `Trigger: ${reminder.message}`,
631504
+ `Scope: ${scope}`,
631505
+ reminder.context ? `Context: ${reminder.context}` : "",
631506
+ reminder.tags?.length ? `Tags: ${reminder.tags.join(", ")}` : "",
631507
+ ``,
631508
+ `Task description:`,
631509
+ task
631510
+ ].filter(Boolean).join("\n");
631511
+ };
631512
+ const dispatchDueReminder = async (reminder) => {
631513
+ const mode = reminder.delivery?.mode ?? "minimal";
631514
+ const scope = reminder.scope;
631515
+ if (scope?.surface === "telegram") {
631516
+ const target = reminderTelegramTarget(scope);
631517
+ if (!target || !telegramBridge?.isActive) return false;
631518
+ if (mode === "minimal") {
631519
+ await telegramBridge.sendMessage(target, reminderText(reminder));
631520
+ return true;
631521
+ }
631522
+ if (activeTask) return false;
631523
+ activeTelegramChatId = target;
631524
+ rl.emit("line", `${TELEGRAM_SAFETY_PROMPT}
631525
+
631526
+ ---
631527
+
631528
+ ${reminderActionPrompt(reminder)}
631529
+
631530
+ Respond to the scoped Telegram target when complete.`);
631531
+ return true;
631532
+ }
631533
+ if (scope?.surface === "gui") {
631534
+ if (mode === "minimal") {
631535
+ writeContent(() => renderInfo(reminderText(reminder)));
631536
+ showPrompt();
631537
+ return true;
631538
+ }
631539
+ if (activeTask) return false;
631540
+ queuePrompt(reminderActionPrompt(reminder), "reminder");
631541
+ drainQueuedPrompts();
631542
+ return true;
631543
+ }
631544
+ if (mode === "minimal") {
631545
+ writeContent(() => renderInfo(reminderText(reminder)));
631546
+ showPrompt();
631547
+ return true;
631548
+ }
631549
+ if (activeTask) {
631550
+ queuePrompt(reminderActionPrompt(reminder), "reminder");
631551
+ return true;
631552
+ }
631553
+ rl.emit("line", reminderActionPrompt(reminder));
631554
+ return true;
631555
+ };
631556
+ const dispatchDueReminders = async () => {
631557
+ try {
631558
+ const due = await getDueReminders(repoRoot, { deliveryMode: "any", includeUnscoped: true });
631559
+ const dispatched = [];
631560
+ for (const reminder of due) {
631561
+ const ok2 = await dispatchDueReminder(reminder);
631562
+ if (ok2) dispatched.push(reminder.id);
631563
+ }
631564
+ if (dispatched.length > 0) {
631565
+ await markRemindersTriggered(repoRoot, dispatched);
631566
+ }
631567
+ } catch {
631568
+ }
631569
+ };
631570
+ const startReminderDispatcher = () => {
631571
+ if (reminderDispatchTimer) return;
631572
+ reminderDispatchTimer = setInterval(() => {
631573
+ dispatchDueReminders().catch(() => {
631574
+ });
631575
+ }, 15e3);
631576
+ dispatchDueReminders().catch(() => {
631577
+ });
631578
+ };
630449
631579
  const listRollbackCheckpoints = () => {
630450
631580
  const out = [];
630451
631581
  const pushJsonFiles = (dir, prefix) => {
@@ -631538,6 +632668,10 @@ Log: ${nexusLogPath}`)
631538
632668
  statusBar.refreshHeaderContent();
631539
632669
  },
631540
632670
  exit() {
632671
+ if (reminderDispatchTimer) {
632672
+ clearInterval(reminderDispatchTimer);
632673
+ reminderDispatchTimer = null;
632674
+ }
631541
632675
  statusBar.deactivate();
631542
632676
  if (carousel.isRunning) carousel.stop();
631543
632677
  banner.stop();
@@ -633483,17 +634617,12 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
633483
634617
  });
633484
634618
  showPrompt();
633485
634619
  }, 300);
633486
- if (dueReminders.length > 0) {
633487
- await markRemindersSeen(
633488
- repoRoot,
633489
- dueReminders.map((r2) => r2.id)
633490
- );
633491
- }
633492
634620
  }
633493
634621
  } catch {
633494
634622
  }
633495
634623
  })();
633496
634624
  }
634625
+ startReminderDispatcher();
633497
634626
  if (hasTaskToResume) {
633498
634627
  const pendingTask = loadPendingTask(repoRoot);
633499
634628
  if (pendingTask) {