omnius 1.0.28 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -258096,50 +258096,91 @@ ${truncated}`, durationMs: performance.now() - start2 };
258096
258096
  import { readFile as readFile17, writeFile as writeFile19, mkdir as mkdir14 } from "node:fs/promises";
258097
258097
  import { resolve as resolve26, join as join46 } from "node:path";
258098
258098
  import { randomBytes as randomBytes12 } from "node:crypto";
258099
+ function formatLocalDateTime(date) {
258100
+ return new Intl.DateTimeFormat("en-US", {
258101
+ weekday: "short",
258102
+ year: "numeric",
258103
+ month: "short",
258104
+ day: "numeric",
258105
+ hour: "numeric",
258106
+ minute: "2-digit",
258107
+ timeZoneName: "short"
258108
+ }).format(date);
258109
+ }
258110
+ function withAbsoluteDue(relative15, target) {
258111
+ return `${relative15} (${formatLocalDateTime(target)})`;
258112
+ }
258113
+ function parseRelativeDueTime(input, now) {
258114
+ const lower = input.toLowerCase().trim().replace(/[.!?]+$/g, "").replace(/\s+/g, " ");
258115
+ const match = lower.match(/^(?:in\s+|after\s+)?(\d+)\s*(m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|wk|wks|weeks?)(?:\s+(?:from\s+now|later))?$/);
258116
+ if (!match)
258117
+ return null;
258118
+ const amount = parseInt(match[1], 10);
258119
+ if (!Number.isFinite(amount) || amount <= 0)
258120
+ return null;
258121
+ const unit = match[2].charAt(0);
258122
+ const target = new Date(now);
258123
+ if (unit === "m")
258124
+ target.setMinutes(target.getMinutes() + amount);
258125
+ else if (unit === "h")
258126
+ target.setHours(target.getHours() + amount);
258127
+ else if (unit === "d")
258128
+ target.setDate(target.getDate() + amount);
258129
+ else if (unit === "w")
258130
+ target.setDate(target.getDate() + amount * 7);
258131
+ return { target, description: `in ${amount}${unit}` };
258132
+ }
258099
258133
  function parseDueTime(due) {
258100
258134
  const lower = due.toLowerCase().trim();
258101
258135
  const now = /* @__PURE__ */ new Date();
258102
- const inMatch = lower.match(/^in\s+(\d+)\s*(m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|weeks?)$/);
258103
- if (inMatch) {
258104
- const amount = parseInt(inMatch[1], 10);
258105
- const unit = inMatch[2].charAt(0);
258106
- const target = new Date(now);
258107
- if (unit === "m")
258108
- target.setMinutes(target.getMinutes() + amount);
258109
- else if (unit === "h")
258110
- target.setHours(target.getHours() + amount);
258111
- else if (unit === "d")
258112
- target.setDate(target.getDate() + amount);
258113
- else if (unit === "w")
258114
- target.setDate(target.getDate() + amount * 7);
258115
- return { isoDate: target.toISOString(), description: `in ${amount}${unit}` };
258136
+ const relative15 = parseRelativeDueTime(lower, now);
258137
+ if (relative15) {
258138
+ return {
258139
+ isoDate: relative15.target.toISOString(),
258140
+ description: withAbsoluteDue(relative15.description, relative15.target)
258141
+ };
258116
258142
  }
258117
258143
  if (lower === "tomorrow") {
258118
258144
  const target = new Date(now);
258119
258145
  target.setDate(target.getDate() + 1);
258120
258146
  target.setHours(9, 0, 0, 0);
258121
- return { isoDate: target.toISOString(), description: "tomorrow morning" };
258147
+ return { isoDate: target.toISOString(), description: withAbsoluteDue("tomorrow morning", target) };
258148
+ }
258149
+ const tomorrowAtMatch = lower.match(/^tomorrow(?:\s+at)?\s+(\d{1,2}):(\d{2})$/);
258150
+ if (tomorrowAtMatch) {
258151
+ const hour2 = parseInt(tomorrowAtMatch[1], 10);
258152
+ const minute3 = parseInt(tomorrowAtMatch[2], 10);
258153
+ if (hour2 < 0 || hour2 > 23 || minute3 < 0 || minute3 > 59)
258154
+ return null;
258155
+ const target = new Date(now);
258156
+ target.setDate(target.getDate() + 1);
258157
+ target.setHours(hour2, minute3, 0, 0);
258158
+ return { isoDate: target.toISOString(), description: withAbsoluteDue(`tomorrow at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}`, target) };
258122
258159
  }
258123
258160
  if (lower === "next week") {
258124
258161
  const target = new Date(now);
258125
258162
  target.setDate(target.getDate() + 7);
258126
258163
  target.setHours(9, 0, 0, 0);
258127
- return { isoDate: target.toISOString(), description: "next week" };
258164
+ return { isoDate: target.toISOString(), description: withAbsoluteDue("next week", target) };
258128
258165
  }
258129
258166
  const todayMatch = lower.match(/^(?:today\s+)?at\s+(\d{1,2}):(\d{2})$/);
258130
258167
  if (todayMatch) {
258168
+ const hour2 = parseInt(todayMatch[1], 10);
258169
+ const minute3 = parseInt(todayMatch[2], 10);
258170
+ if (hour2 < 0 || hour2 > 23 || minute3 < 0 || minute3 > 59)
258171
+ return null;
258131
258172
  const target = new Date(now);
258132
- target.setHours(parseInt(todayMatch[1], 10), parseInt(todayMatch[2], 10), 0, 0);
258173
+ target.setHours(hour2, minute3, 0, 0);
258133
258174
  if (target <= now)
258134
258175
  target.setDate(target.getDate() + 1);
258135
- return { isoDate: target.toISOString(), description: `at ${todayMatch[1]}:${todayMatch[2]}` };
258176
+ return { isoDate: target.toISOString(), description: withAbsoluteDue(`at ${String(hour2).padStart(2, "0")}:${String(minute3).padStart(2, "0")}`, target) };
258136
258177
  }
258137
258178
  if (lower === "now" || lower === "next startup" || lower === "startup") {
258138
- return { isoDate: now.toISOString(), description: "next startup" };
258179
+ return { isoDate: now.toISOString(), description: withAbsoluteDue(lower === "now" ? "now" : "next startup", now) };
258139
258180
  }
258140
258181
  const parsed = new Date(due);
258141
258182
  if (!isNaN(parsed.getTime())) {
258142
- return { isoDate: parsed.toISOString(), description: parsed.toLocaleDateString() };
258183
+ return { isoDate: parsed.toISOString(), description: withAbsoluteDue("at requested time", parsed) };
258143
258184
  }
258144
258185
  return null;
258145
258186
  }
@@ -258375,12 +258416,43 @@ async function saveReminderStore(workingDir, store2) {
258375
258416
  store2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
258376
258417
  await writeFile19(storePath, JSON.stringify(store2, null, 2), "utf-8");
258377
258418
  }
258419
+ function sanitizeReminderDeliveryText(input) {
258420
+ let text = input.trim();
258421
+ if (!text)
258422
+ return text;
258423
+ const metaPhrase = /\s*\b(?:you asked me to remind you|you asked to be reminded|you asked me for a reminder|i(?:'ll| will) remind you|i(?:'ll| will) ping you|setting (?:your|a) reminder)\b[^.!?\n]*(?:ago|from now|later|when due|at that time)?[.!?]?/gi;
258424
+ text = text.replace(metaPhrase, "");
258425
+ text = text.replace(/^\s*(?:remind\s+(?:me|us)\s+(?:to\s+)?|reminder\s*:\s*)/i, "");
258426
+ text = text.replace(/\s{2,}/g, " ").replace(/\s+([.!?,;:])/g, "$1").trim();
258427
+ return text;
258428
+ }
258429
+ function firstStringValue(...values) {
258430
+ for (const value2 of values) {
258431
+ const raw = String(value2 ?? "").trim();
258432
+ if (raw)
258433
+ return raw;
258434
+ }
258435
+ return "";
258436
+ }
258437
+ function getDueInput(args) {
258438
+ return firstStringValue(args["due_at"], args["dueAt"], args["do_at"], args["doAt"], args["due"]);
258439
+ }
258440
+ function missingDueError() {
258441
+ const now = /* @__PURE__ */ new Date();
258442
+ return [
258443
+ "due_at is required for reminder(action='set') unless repeat/recurrence is provided.",
258444
+ `Current tool time: ${formatLocalDateTime(now)} (${now.toISOString()}).`,
258445
+ DUE_TIME_FORMAT_HELP
258446
+ ].join(" ");
258447
+ }
258378
258448
  async function getDueReminders(workingDir, query = {}) {
258379
258449
  const store2 = await loadReminderStore(workingDir);
258380
258450
  const now = query.now ?? /* @__PURE__ */ new Date();
258381
258451
  return store2.reminders.filter((r2) => {
258382
258452
  if (r2.status !== "pending")
258383
258453
  return false;
258454
+ if (!r2.dueAt)
258455
+ return false;
258384
258456
  if (r2.dueAt && new Date(r2.dueAt) > now)
258385
258457
  return false;
258386
258458
  if (query.deliveryMode && query.deliveryMode !== "any" && (r2.delivery?.mode ?? "minimal") !== query.deliveryMode)
@@ -258430,6 +258502,20 @@ function normalizeDeliveryKind(value2) {
258430
258502
  return "action";
258431
258503
  return "minimal";
258432
258504
  }
258505
+ function normalizeReminderAction(value2) {
258506
+ const raw = String(value2 ?? "list").trim().toLowerCase();
258507
+ if (raw === "create" || raw === "add" || raw === "new")
258508
+ return "set";
258509
+ if (raw === "show" || raw === "pending")
258510
+ return "list";
258511
+ if (raw === "done" || raw === "resolve" || raw === "resolved")
258512
+ return "complete";
258513
+ if (raw === "remove" || raw === "delete" || raw === "cancel")
258514
+ return "dismiss";
258515
+ if (raw === "set" || raw === "list" || raw === "complete" || raw === "dismiss" || raw === "snooze")
258516
+ return raw;
258517
+ return void 0;
258518
+ }
258433
258519
  function normalizeSurface(value2) {
258434
258520
  const raw = String(value2 ?? "").trim().toLowerCase();
258435
258521
  return raw === "tui" || raw === "gui" || raw === "telegram" || raw === "api" || raw === "global" ? raw : void 0;
@@ -258446,29 +258532,46 @@ function formatScope(scope) {
258446
258532
  const label = scope.label ? ` (${scope.label})` : "";
258447
258533
  return `${scope.surface}/${scope.visibility}/${scope.targetId}${label}`;
258448
258534
  }
258449
- var STORE_FILE, ReminderTool;
258535
+ var DUE_TIME_FORMAT_HELP, STORE_FILE, ReminderTool;
258450
258536
  var init_reminder = __esm({
258451
258537
  "packages/execution/dist/tools/reminder.js"() {
258452
258538
  "use strict";
258539
+ DUE_TIME_FORMAT_HELP = "due_at is required for action='set' unless repeat/recurrence is provided. Fill due_at from the user's requested trigger, not from your reply text. Examples: user says 'remind me in 5 minutes to get coffee' -> due_at='in 5 minutes', message/content='Get coffee'; user says 'tomorrow at 09:00' -> due_at='tomorrow at 09:00'; user says 'at 14:30' -> due_at='at 14:30'; user gives an exact timestamp -> due_at='2026-05-15T14:30:00-07:00'. Supported due_at formats: 'in 5 minutes', 'in 30m', '5m', 'tomorrow', 'tomorrow at 09:00', 'next week', 'today at 14:30', 'at 14:30', 'now', 'next startup', or ISO timestamp.";
258453
258540
  STORE_FILE = "reminders.json";
258454
258541
  ReminderTool = class {
258455
258542
  name = "reminder";
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.";
258543
+ description = "Set scoped reminders and time-triggered action requests. For action='set', you MUST provide due_at/do_at/due (camelCase aliases dueAt/doAt accepted) unless repeat/recurrence is provided; missing triggers fail instead of defaulting to next startup. " + DUE_TIME_FORMAT_HELP + " 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.";
258457
258544
  parameters = {
258458
258545
  type: "object",
258459
258546
  properties: {
258460
258547
  action: {
258461
258548
  type: "string",
258462
- enum: ["set", "list", "complete", "dismiss", "snooze"],
258463
- description: "Reminder action to perform"
258549
+ enum: ["set", "create", "add", "list", "complete", "dismiss", "snooze"],
258550
+ description: "Reminder action to perform. create/add are aliases for set."
258464
258551
  },
258465
258552
  message: {
258466
258553
  type: "string",
258467
- description: "The reminder message (for 'set')"
258554
+ description: "The reminder payload only. Do not include scheduling prose like 'you asked me to remind you in 5 minutes'."
258555
+ },
258556
+ due_at: {
258557
+ type: "string",
258558
+ description: DUE_TIME_FORMAT_HELP
258559
+ },
258560
+ dueAt: {
258561
+ type: "string",
258562
+ description: "CamelCase alias for due_at. Prefer due_at in new tool calls."
258563
+ },
258564
+ do_at: {
258565
+ type: "string",
258566
+ description: "Alias for due_at. Prefer due_at unless the caller already uses do_at."
258567
+ },
258568
+ doAt: {
258569
+ type: "string",
258570
+ description: "CamelCase alias for do_at/due_at. Prefer due_at in new tool calls."
258468
258571
  },
258469
258572
  due: {
258470
258573
  type: "string",
258471
- description: "When the reminder should surface: 'in 30m', 'tomorrow', 'at 14:30', 'next startup', 'in 2d' (for 'set' and 'snooze')"
258574
+ description: "Backward-compatible alias for due_at. Prefer due_at. " + DUE_TIME_FORMAT_HELP
258472
258575
  },
258473
258576
  priority: {
258474
258577
  type: "string",
@@ -258486,7 +258589,7 @@ var init_reminder = __esm({
258486
258589
  },
258487
258590
  content: {
258488
258591
  type: "string",
258489
- description: "For kind='minimal': raw text/content to deliver. Defaults to message."
258592
+ description: "For kind='minimal': raw text/content to deliver. Defaults to message. Must be payload only, with no due-time explanation."
258490
258593
  },
258491
258594
  repeat: {
258492
258595
  type: "string",
@@ -258553,7 +258656,7 @@ var init_reminder = __esm({
258553
258656
  }
258554
258657
  async execute(args) {
258555
258658
  const start2 = performance.now();
258556
- const action = String(args["action"] ?? "list");
258659
+ const action = normalizeReminderAction(args["action"]);
258557
258660
  try {
258558
258661
  switch (action) {
258559
258662
  case "set":
@@ -258570,7 +258673,7 @@ var init_reminder = __esm({
258570
258673
  return {
258571
258674
  success: false,
258572
258675
  output: "",
258573
- error: `Unknown action '${action}'. Use: set, list, complete, dismiss, snooze`,
258676
+ error: `Unknown action '${String(args["action"] ?? "list")}'. Use: set/create/add, list, complete, dismiss, snooze`,
258574
258677
  durationMs: performance.now() - start2
258575
258678
  };
258576
258679
  }
@@ -258584,14 +258687,15 @@ var init_reminder = __esm({
258584
258687
  }
258585
258688
  }
258586
258689
  async setReminder(args, start2) {
258587
- const message2 = String(args["message"] ?? "");
258588
- if (!message2)
258690
+ const rawMessage = String(args["message"] ?? "");
258691
+ if (!rawMessage.trim())
258589
258692
  return { success: false, output: "", error: "message is required for set action", durationMs: performance.now() - start2 };
258693
+ const message2 = sanitizeReminderDeliveryText(rawMessage) || rawMessage.trim();
258590
258694
  const priority = args["priority"] ?? "normal";
258591
258695
  const tagsStr = args["tags"] ?? "";
258592
258696
  const tags = tagsStr ? tagsStr.split(",").map((t2) => t2.trim()).filter(Boolean) : [];
258593
258697
  const context2 = args["context"] ?? void 0;
258594
- const dueStr = args["due"] ?? "";
258698
+ const dueStr = getDueInput(args);
258595
258699
  const recurrence = parseRecurrence(args);
258596
258700
  if (recurrence === null) {
258597
258701
  return {
@@ -258627,7 +258731,7 @@ var init_reminder = __esm({
258627
258731
  return {
258628
258732
  success: false,
258629
258733
  output: "",
258630
- error: `Cannot parse due time '${dueStr}'. Try: 'in 30m', 'tomorrow', 'at 14:30', 'next startup', 'in 2d'`,
258734
+ error: `Cannot parse due_at '${dueStr}'. ${DUE_TIME_FORMAT_HELP}`,
258631
258735
  durationMs: performance.now() - start2
258632
258736
  };
258633
258737
  }
@@ -258636,9 +258740,16 @@ var init_reminder = __esm({
258636
258740
  } else if (recurrence) {
258637
258741
  dueAt = nextDueFromRecurrence(recurrence).toISOString();
258638
258742
  dueDescription = describeRecurrence(recurrence);
258743
+ } else {
258744
+ return {
258745
+ success: false,
258746
+ output: "",
258747
+ error: missingDueError(),
258748
+ durationMs: performance.now() - start2
258749
+ };
258639
258750
  }
258640
258751
  const scope = this.resolveScope(args);
258641
- const content = String(args["content"] ?? message2).trim() || message2;
258752
+ const content = sanitizeReminderDeliveryText(String(args["content"] ?? message2)) || message2;
258642
258753
  const id = `rem-${randomBytes12(4).toString("hex")}`;
258643
258754
  const entry = {
258644
258755
  id,
@@ -258671,7 +258782,7 @@ var init_reminder = __esm({
258671
258782
  ` Priority: ${priority}`,
258672
258783
  ` Kind: ${kind}`,
258673
258784
  kind === "action" ? ` Task: ${taskDescription}` : "",
258674
- dueDescription ? ` Due: ${dueDescription}` : ` Due: next startup`,
258785
+ ` Due: ${dueDescription}`,
258675
258786
  describeRecurrence(recurrence) ? ` Repeats: ${describeRecurrence(recurrence)}` : "",
258676
258787
  scope ? ` Scope: ${formatScope(scope)}` : "",
258677
258788
  tags.length > 0 ? ` Tags: ${tags.join(", ")}` : "",
@@ -258693,7 +258804,7 @@ var init_reminder = __esm({
258693
258804
  if (filtered.length === 0) {
258694
258805
  return {
258695
258806
  success: true,
258696
- output: filter2 === "all" ? "No reminders set. Use reminder(action='set', message='...') to create one." : `No ${filter2} reminders. Use reminder(action='list', filter='all') to see all.`,
258807
+ output: filter2 === "all" ? "No reminders set. Use reminder(action='set', message='...', due_at='in 30m') to create one." : `No ${filter2} reminders. Use reminder(action='list', filter='all') to see all.`,
258697
258808
  durationMs: performance.now() - start2
258698
258809
  };
258699
258810
  }
@@ -258744,7 +258855,7 @@ var init_reminder = __esm({
258744
258855
  }
258745
258856
  async snoozeReminder(args, start2) {
258746
258857
  const id = String(args["id"] ?? "");
258747
- const due = String(args["due"] ?? "in 1h");
258858
+ const due = getDueInput(args) || "in 1h";
258748
258859
  if (!id)
258749
258860
  return { success: false, output: "", error: "id is required for snooze", durationMs: performance.now() - start2 };
258750
258861
  const parsed = parseDueTime(due);
@@ -517144,6 +517255,7 @@ __export(dist_exports, {
517144
517255
  runTests: () => runTests,
517145
517256
  runTypecheck: () => runTypecheck,
517146
517257
  runValidationPipeline: () => runValidationPipeline,
517258
+ sanitizeReminderDeliveryText: () => sanitizeReminderDeliveryText,
517147
517259
  saveCustomToolDefinition: () => saveCustomToolDefinition,
517148
517260
  saveMcpServerToConfig: () => saveMcpServerToConfig,
517149
517261
  savePacket: () => savePacket,
@@ -554193,6 +554305,47 @@ function getAgentToolCommandPolicy(command) {
554193
554305
  reason: `/${cmd.name} is allowed by registry policy.`
554194
554306
  };
554195
554307
  }
554308
+ function getSelfModifyCommandPolicy(command) {
554309
+ const name10 = normalizeCommandName(command.split(/\s+/)[0] ?? command);
554310
+ const cmd = findCommand(name10);
554311
+ if (!cmd) {
554312
+ return {
554313
+ allowed: false,
554314
+ name: name10,
554315
+ reason: "Self-modify mode can only invoke registered Omnius slash commands."
554316
+ };
554317
+ }
554318
+ if (cmd.implementationStatus !== "implemented") {
554319
+ return {
554320
+ allowed: false,
554321
+ name: cmd.name,
554322
+ command: cmd,
554323
+ reason: `/${cmd.name} is ${cmd.implementationStatus}, not runnable by self-modify mode.`
554324
+ };
554325
+ }
554326
+ if (cmd.safety.secretBearing) {
554327
+ return {
554328
+ allowed: false,
554329
+ name: cmd.name,
554330
+ command: cmd,
554331
+ reason: `/${cmd.name} handles secrets and remains user-only.`
554332
+ };
554333
+ }
554334
+ if (!SELF_MODIFY_ALLOWED.has(cmd.name)) {
554335
+ return {
554336
+ allowed: false,
554337
+ name: cmd.name,
554338
+ command: cmd,
554339
+ reason: `/${cmd.name} is outside the self-modify command surface.`
554340
+ };
554341
+ }
554342
+ return {
554343
+ allowed: true,
554344
+ name: cmd.name,
554345
+ command: cmd,
554346
+ reason: `/${cmd.name} is allowed by self-modify policy.`
554347
+ };
554348
+ }
554196
554349
  function describeAgentToolCommandPolicy() {
554197
554350
  const implemented = listCommandRegistry({ includePlanned: false });
554198
554351
  const safe = implemented.filter((cmd) => cmd.surfaces.agentTool).map((cmd) => cmd.name).sort();
@@ -554203,6 +554356,16 @@ function describeAgentToolCommandPolicy() {
554203
554356
  "Skill commands: /<skill-name> <args> are allowed when no registry entry exists."
554204
554357
  ].join(" ");
554205
554358
  }
554359
+ function describeSelfModifyCommandPolicy() {
554360
+ const allowed = Array.from(SELF_MODIFY_ALLOWED).filter((name10) => {
554361
+ const cmd = findCommand(name10);
554362
+ return cmd?.implementationStatus === "implemented" && !cmd.safety.secretBearing;
554363
+ }).sort();
554364
+ return [
554365
+ `Self-modify commands: ${allowed.join(", ")}.`,
554366
+ "Secret-bearing, gateway, process-destroying, and unregistered commands remain blocked."
554367
+ ].join(" ");
554368
+ }
554206
554369
  function buildCommandDef(name10, signatures) {
554207
554370
  const status = signatures.every((sig) => sig.implementationStatus === "planned") ? "planned" : "implemented";
554208
554371
  const safety = {
@@ -554256,7 +554419,7 @@ function inferArgsHint(signature) {
554256
554419
  function normalizeCommandName(name10) {
554257
554420
  return name10.replace(/^\//, "").trim().toLowerCase();
554258
554421
  }
554259
- var COMMAND_SIGNATURES, PLANNED_SIGNATURES, CATEGORY_OVERRIDES, ALIASES, USER_ONLY, DESTRUCTIVE, NETWORKED, SECRET_BEARING, PROFILE_GATED, REST_BLOCKED, CANONICAL_BY_ALIAS, DYNAMIC_COMMANDS, SLASH_COMMANDS;
554422
+ var COMMAND_SIGNATURES, PLANNED_SIGNATURES, CATEGORY_OVERRIDES, ALIASES, USER_ONLY, DESTRUCTIVE, NETWORKED, SECRET_BEARING, PROFILE_GATED, SELF_MODIFY_ALLOWED, REST_BLOCKED, CANONICAL_BY_ALIAS, DYNAMIC_COMMANDS, SLASH_COMMANDS;
554260
554423
  var init_command_registry = __esm({
554261
554424
  "packages/cli/src/tui/command-registry.ts"() {
554262
554425
  "use strict";
@@ -554280,6 +554443,9 @@ var init_command_registry = __esm({
554280
554443
  ["/upgrade", "Alias for /update"],
554281
554444
  ["/update auto", "Enable auto-update after task completion (default)"],
554282
554445
  ["/update manual", "Disable auto-update (only update on /update)"],
554446
+ ["/update check", "Check for Omnius and Ollama updates without opening the menu"],
554447
+ ["/update quick", "Install the latest Omnius package non-interactively"],
554448
+ ["/update full", "Run full non-interactive update: package, deps, rebuild, Python, cloudflared"],
554283
554449
  ["/voice", "Toggle TTS voice feedback"],
554284
554450
  ["/voice <model>", "Set voice: glados, overwatch, kokoro, luxtts, supertonic"],
554285
554451
  ["/voice clone <file>", "Set voice clone reference audio (wav/mp3/ogg/flac)"],
@@ -554431,7 +554597,7 @@ var init_command_registry = __esm({
554431
554597
  ["/scheduler list", "List all scheduled tasks and timers"],
554432
554598
  ["/scheduler kill", "Kill schedulers + active runs (with escalation if needed)"],
554433
554599
  ["/reminder [list|all]", "List scoped TUI reminders"],
554434
- ["/remind <message>", "Set a scoped minimal reminder for this TUI session"],
554600
+ ["/remind <when> <message>", "Set a scoped minimal reminder; a due time is required"],
554435
554601
  ["/remind in 30m <message>", "Set a scoped minimal reminder with a relative due time"],
554436
554602
  ["/reminders", "Alias for /reminder"],
554437
554603
  ["/daemon", "Show or manage the local API daemon"],
@@ -554467,6 +554633,10 @@ var init_command_registry = __esm({
554467
554633
  ["/commands auto", "Allow agent to invoke slash commands as tools"],
554468
554634
  ["/cmds auto", "Alias for /commands auto"],
554469
554635
  ["/commands manual", "Slash commands are user-only (default)"],
554636
+ ["/selfmodify", "Toggle agent self-modify mode for natural-language runtime commands"],
554637
+ ["/selfmodify on", "Allow the agent to decide when to invoke self-modifying slash commands"],
554638
+ ["/selfmodify off", "Disable agent self-modifying slash-command access (default)"],
554639
+ ["/selfmodify status", "Show current self-modify mode"],
554470
554640
  ["/voicechat", "Start voice chat session (async voice conversation)"],
554471
554641
  ["/voicechat stop", "Stop voice chat session"],
554472
554642
  ["/memory", "Toggle memory visualizer - graph/episodes/concepts/timeline"],
@@ -554588,7 +554758,10 @@ var init_command_registry = __esm({
554588
554758
  skin: "ui",
554589
554759
  prompt: "ui",
554590
554760
  personality: "ui",
554591
- reasoning: "ui"
554761
+ reasoning: "ui",
554762
+ selfmodify: "runtime",
554763
+ selfmod: "runtime",
554764
+ "self-modify": "runtime"
554592
554765
  };
554593
554766
  ALIASES = {
554594
554767
  help: ["h", "?"],
@@ -554616,6 +554789,7 @@ var init_command_registry = __esm({
554616
554789
  files: ["workspace"],
554617
554790
  skills: ["skill"],
554618
554791
  commands: ["cmds"],
554792
+ selfmodify: ["selfmod", "self-modify"],
554619
554793
  codegraph: ["cg"],
554620
554794
  endpoint: ["ep"],
554621
554795
  telegram: ["tg"],
@@ -554642,6 +554816,9 @@ var init_command_registry = __esm({
554642
554816
  "parallel",
554643
554817
  "commands",
554644
554818
  "cmds",
554819
+ "selfmodify",
554820
+ "selfmod",
554821
+ "self-modify",
554645
554822
  "mcp",
554646
554823
  "mcps",
554647
554824
  "update",
@@ -554748,6 +554925,31 @@ var init_command_registry = __esm({
554748
554925
  "deny",
554749
554926
  "sethome"
554750
554927
  ]);
554928
+ SELF_MODIFY_ALLOWED = /* @__PURE__ */ new Set([
554929
+ "bruteforce",
554930
+ "color",
554931
+ "colors",
554932
+ "compact",
554933
+ "config",
554934
+ "deep",
554935
+ "emojis",
554936
+ "flow",
554937
+ "help",
554938
+ "model",
554939
+ "models",
554940
+ "personality",
554941
+ "score",
554942
+ "selfmodify",
554943
+ "stats",
554944
+ "stream",
554945
+ "style",
554946
+ "task-type",
554947
+ "theme",
554948
+ "think",
554949
+ "update",
554950
+ "upgrade",
554951
+ "verbose"
554952
+ ]);
554751
554953
  REST_BLOCKED = /* @__PURE__ */ new Set([
554752
554954
  "pause",
554753
554955
  "resume",
@@ -580637,11 +580839,16 @@ function createSlashReminderTool(ctx3) {
580637
580839
  }
580638
580840
  function splitReminderDue(raw) {
580639
580841
  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
580842
+ const duePattern = "(?:(?:in\\s+|after\\s+)?\\d+\\s*(?:m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|wk|wks|weeks?)(?:\\s+(?:from\\s+now|later))?|tomorrow(?:\\s+at\\s+\\d{1,2}:\\d{2})?|next\\s+week|next\\s+startup|startup|now|(?:today\\s+)?at\\s+\\d{1,2}:\\d{2})";
580843
+ const prefixMatch = text.match(
580844
+ new RegExp(`^(${duePattern})\\s+(.+)$`, "i")
580845
+ );
580846
+ if (prefixMatch) return { due: prefixMatch[1].trim(), message: prefixMatch[2].trim() };
580847
+ const suffixMatch = text.match(
580848
+ new RegExp(`^(.+?)\\s+(${duePattern})$`, "i")
580642
580849
  );
580643
- if (!dueMatch) return { message: text };
580644
- return { due: dueMatch[1].trim(), message: dueMatch[2].trim() };
580850
+ if (suffixMatch) return { due: suffixMatch[2].trim(), message: suffixMatch[1].trim() };
580851
+ return { message: text };
580645
580852
  }
580646
580853
  async function handleReminderSlash(cmd, arg, ctx3) {
580647
580854
  const tool = createSlashReminderTool(ctx3);
@@ -580663,7 +580870,7 @@ async function handleReminderSlash(cmd, arg, ctx3) {
580663
580870
  } else {
580664
580871
  const { due, message: message2 } = splitReminderDue(trimmed);
580665
580872
  if (!message2) {
580666
- renderInfo("Usage: /remind [in 30m|tomorrow|at 14:30] <message>");
580873
+ renderInfo("Usage: /remind <when> <message> (examples: /remind in 30m check coffee, /remind tomorrow at 09:00 call mom)");
580667
580874
  return "handled";
580668
580875
  }
580669
580876
  args = {
@@ -580671,7 +580878,7 @@ async function handleReminderSlash(cmd, arg, ctx3) {
580671
580878
  kind: "minimal",
580672
580879
  message: message2,
580673
580880
  content: message2,
580674
- ...due ? { due } : {}
580881
+ ...due ? { due_at: due } : {}
580675
580882
  };
580676
580883
  }
580677
580884
  const result = await tool.execute(args);
@@ -581124,6 +581331,24 @@ async function handleSlashCommand(input, ctx3) {
581124
581331
  );
581125
581332
  return "handled";
581126
581333
  }
581334
+ if (["system", "branding", "custom"].includes(colorAction)) {
581335
+ setThemeMode(colorAction);
581336
+ refreshThemeVars();
581337
+ try {
581338
+ const tasksRenderer = await Promise.resolve().then(() => (init_tui_tasks_renderer(), tui_tasks_renderer_exports));
581339
+ tasksRenderer.refreshTuiTasksThemeVars?.();
581340
+ tasksRenderer.refreshTuiTasks?.();
581341
+ } catch {
581342
+ }
581343
+ ctx3.refreshBanner?.();
581344
+ const settings = { colorTheme: colorAction };
581345
+ const save2 = hasLocal ? ctx3.saveLocalSettings.bind(ctx3) : ctx3.saveSettings.bind(ctx3);
581346
+ save2(settings);
581347
+ renderInfo(
581348
+ `Theme set to: ${colorAction}${hasLocal ? " (project-local)" : ""}`
581349
+ );
581350
+ return "handled";
581351
+ }
581127
581352
  await showColorMenu(ctx3);
581128
581353
  return "handled";
581129
581354
  }
@@ -583691,6 +583916,39 @@ Clone a new voice: /voice clone <wav-file> [name]`);
583691
583916
  }
583692
583917
  return "handled";
583693
583918
  }
583919
+ case "selfmodify":
583920
+ case "selfmod":
583921
+ case "self-modify": {
583922
+ if (!ctx3.setSelfModify) {
583923
+ renderWarning("Self-modify mode not available.");
583924
+ return "handled";
583925
+ }
583926
+ const normalized = arg.toLowerCase();
583927
+ const current = ctx3.getSelfModify?.() ?? false;
583928
+ let next = null;
583929
+ if (!normalized || normalized === "toggle") {
583930
+ next = !current;
583931
+ } else if (["on", "auto", "enable", "enabled", "true", "yes"].includes(normalized)) {
583932
+ next = true;
583933
+ } else if (["off", "manual", "disable", "disabled", "false", "no"].includes(normalized)) {
583934
+ next = false;
583935
+ } else if (normalized === "status") {
583936
+ renderInfo(
583937
+ `Self-modify mode: ${current ? "on" : "off"} — ` + (current ? "agent may decide to invoke permitted slash commands for natural-language runtime/UI requests." : "slash commands stay user-directed unless /commands auto is enabled.")
583938
+ );
583939
+ return "handled";
583940
+ } else {
583941
+ renderWarning("Usage: /selfmodify [on|off|status]");
583942
+ return "handled";
583943
+ }
583944
+ ctx3.setSelfModify(next);
583945
+ const save2 = hasLocal ? ctx3.saveLocalSettings.bind(ctx3) : ctx3.saveSettings.bind(ctx3);
583946
+ save2({ selfModify: next });
583947
+ renderInfo(
583948
+ next ? "Self-modify mode: on — the agent can decide when to invoke permitted slash commands for natural-language runtime/UI requests." : "Self-modify mode: off — natural-language requests will not grant slash-command self-modification."
583949
+ );
583950
+ return "handled";
583951
+ }
583694
583952
  case "neovim":
583695
583953
  case "nvim":
583696
583954
  case "vim": {
@@ -586349,6 +586607,14 @@ async function showConfigEditor(ctx3) {
586349
586607
  options: ["manual", "auto"],
586350
586608
  settingsKey: "commandsMode"
586351
586609
  },
586610
+ {
586611
+ key: "selfModify",
586612
+ label: "selfModify",
586613
+ kind: "boolean",
586614
+ value: String(ctx3.getSelfModify?.() ?? merged.selfModify ?? false),
586615
+ detail: String(ctx3.getSelfModify?.() ?? merged.selfModify ?? false),
586616
+ settingsKey: "selfModify"
586617
+ },
586352
586618
  {
586353
586619
  key: "updateMode",
586354
586620
  label: "updateMode",
@@ -586527,6 +586793,8 @@ function applyConfigChanges(ctx3, changes) {
586527
586793
  ctx3.setStyle?.(changes.style);
586528
586794
  if (changes.commandsMode !== void 0)
586529
586795
  ctx3.setCommandsMode?.(changes.commandsMode);
586796
+ if (changes.selfModify !== void 0)
586797
+ ctx3.setSelfModify?.(changes.selfModify);
586530
586798
  }
586531
586799
  async function handlePlatformsCommand(ctx3, arg) {
586532
586800
  const parts = arg.trim().split(/\s+/).filter(Boolean);
@@ -590866,6 +591134,29 @@ async function handleUpdate(subcommand, ctx3) {
590866
591134
  }
590867
591135
  } catch {
590868
591136
  }
591137
+ const updateActionAliases = {
591138
+ quick: "quick",
591139
+ package: "quick",
591140
+ pkg: "quick",
591141
+ full: "full",
591142
+ deps: "deps_only",
591143
+ "deps-only": "deps_only",
591144
+ deps_only: "deps_only",
591145
+ dependencies: "deps_only",
591146
+ rebuild: "rebuild",
591147
+ native: "rebuild",
591148
+ python: "python"
591149
+ };
591150
+ const requestedAction = updateActionAliases[subcommand] ?? "";
591151
+ if (["check", "status", "ls", "list"].includes(subcommand)) {
591152
+ return;
591153
+ }
591154
+ if (subcommand && !requestedAction) {
591155
+ renderWarning(
591156
+ "Usage: /update [check|quick|full|deps|rebuild|python|auto|manual|ollama]"
591157
+ );
591158
+ return;
591159
+ }
590869
591160
  const items = [];
590870
591161
  const skipKeys = [];
590871
591162
  items.push({
@@ -590926,69 +591217,71 @@ async function handleUpdate(subcommand, ctx3) {
590926
591217
  detail: "Upgrade venv packages (moondream, tesseract, etc.)",
590927
591218
  kind: "action"
590928
591219
  });
590929
- items.push({
590930
- key: "hdr_deps",
590931
- label: selectColors.dim("─── Dependencies ───"),
590932
- kind: "header"
590933
- });
590934
- skipKeys.push("hdr_deps");
590935
- try {
590936
- const prefix = await execA("npm prefix -g", { timeout: 5e3 });
590937
- const { join: pj2 } = await import("node:path");
590938
- const { existsSync: fe2, readFileSync: rf2 } = await import("node:fs");
590939
- const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
590940
- const pkgPath2 = pj2(globalModules, "omnius", "package.json");
590941
- if (fe2(pkgPath2)) {
590942
- const pkg2 = JSON.parse(rf2(pkgPath2, "utf8"));
590943
- const allDeps2 = {
590944
- ...pkg2.dependencies || {},
590945
- ...pkg2.optionalDependencies || {}
590946
- };
590947
- const depChecks = await Promise.all(
590948
- Object.entries(allDeps2).map(async ([name10, range]) => {
590949
- let installed = "";
590950
- const localPath = pj2(
590951
- globalModules,
590952
- "omnius",
590953
- "node_modules",
590954
- name10,
590955
- "package.json"
590956
- );
590957
- const hoistedPath = pj2(globalModules, name10, "package.json");
590958
- try {
590959
- if (fe2(localPath))
590960
- installed = JSON.parse(rf2(localPath, "utf8")).version || "";
590961
- else if (fe2(hoistedPath))
590962
- installed = JSON.parse(rf2(hoistedPath, "utf8")).version || "";
590963
- } catch {
590964
- }
590965
- let latest = "";
590966
- try {
590967
- latest = await execA(`npm view ${name10} version`, { timeout: 5e3 });
590968
- } catch {
591220
+ if (!requestedAction) {
591221
+ items.push({
591222
+ key: "hdr_deps",
591223
+ label: selectColors.dim("─── Dependencies ───"),
591224
+ kind: "header"
591225
+ });
591226
+ skipKeys.push("hdr_deps");
591227
+ try {
591228
+ const prefix = await execA("npm prefix -g", { timeout: 5e3 });
591229
+ const { join: pj2 } = await import("node:path");
591230
+ const { existsSync: fe2, readFileSync: rf2 } = await import("node:fs");
591231
+ const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
591232
+ const pkgPath2 = pj2(globalModules, "omnius", "package.json");
591233
+ if (fe2(pkgPath2)) {
591234
+ const pkg2 = JSON.parse(rf2(pkgPath2, "utf8"));
591235
+ const allDeps2 = {
591236
+ ...pkg2.dependencies || {},
591237
+ ...pkg2.optionalDependencies || {}
591238
+ };
591239
+ const depChecks = await Promise.all(
591240
+ Object.entries(allDeps2).map(async ([name10, range]) => {
591241
+ let installed = "";
591242
+ const localPath = pj2(
591243
+ globalModules,
591244
+ "omnius",
591245
+ "node_modules",
591246
+ name10,
591247
+ "package.json"
591248
+ );
591249
+ const hoistedPath = pj2(globalModules, name10, "package.json");
591250
+ try {
591251
+ if (fe2(localPath))
591252
+ installed = JSON.parse(rf2(localPath, "utf8")).version || "";
591253
+ else if (fe2(hoistedPath))
591254
+ installed = JSON.parse(rf2(hoistedPath, "utf8")).version || "";
591255
+ } catch {
591256
+ }
591257
+ let latest = "";
591258
+ try {
591259
+ latest = await execA(`npm view ${name10} version`, { timeout: 5e3 });
591260
+ } catch {
591261
+ }
591262
+ const outdated = installed && latest && latest !== installed;
591263
+ return { name: name10, range: String(range), installed, latest, outdated };
591264
+ })
591265
+ );
591266
+ for (const dep of depChecks) {
591267
+ const verStr = dep.installed || "missing";
591268
+ const updateTag = dep.outdated ? ` ${c3.bold(c3.yellow("→ " + dep.latest))}` : "";
591269
+ const label = ` ${dep.name} ${c3.dim(verStr)}${updateTag}`;
591270
+ if (dep.outdated) {
591271
+ items.push({
591272
+ key: `dep_${dep.name}`,
591273
+ label,
591274
+ detail: `Update ${dep.name} ${dep.installed} → ${dep.latest}`,
591275
+ kind: "action"
591276
+ });
591277
+ } else {
591278
+ items.push({ key: `dep_${dep.name}`, label, kind: "info" });
591279
+ skipKeys.push(`dep_${dep.name}`);
590969
591280
  }
590970
- const outdated = installed && latest && latest !== installed;
590971
- return { name: name10, range: String(range), installed, latest, outdated };
590972
- })
590973
- );
590974
- for (const dep of depChecks) {
590975
- const verStr = dep.installed || "missing";
590976
- const updateTag = dep.outdated ? ` ${c3.bold(c3.yellow("→ " + dep.latest))}` : "";
590977
- const label = ` ${dep.name} ${c3.dim(verStr)}${updateTag}`;
590978
- if (dep.outdated) {
590979
- items.push({
590980
- key: `dep_${dep.name}`,
590981
- label,
590982
- detail: `Update ${dep.name} ${dep.installed} → ${dep.latest}`,
590983
- kind: "action"
590984
- });
590985
- } else {
590986
- items.push({ key: `dep_${dep.name}`, label, kind: "info" });
590987
- skipKeys.push(`dep_${dep.name}`);
590988
591281
  }
590989
591282
  }
591283
+ } catch {
590990
591284
  }
590991
- } catch {
590992
591285
  }
590993
591286
  if (ollamaUpdate?.needsUpdate) {
590994
591287
  items.push({
@@ -591022,7 +591315,7 @@ async function handleUpdate(subcommand, ctx3) {
591022
591315
  detail: "Only update when you run /update",
591023
591316
  kind: "action"
591024
591317
  });
591025
- const menuResult = await tuiSelect({
591318
+ const menuResult = requestedAction ? { confirmed: true, key: requestedAction } : await tuiSelect({
591026
591319
  items,
591027
591320
  title: "Update Manager",
591028
591321
  rl: ctx3.rl,
@@ -598678,6 +598971,182 @@ ${result.output}`,
598678
598971
  }
598679
598972
  });
598680
598973
 
598974
+ // packages/cli/src/tui/stimulation.ts
598975
+ function clamp016(value2) {
598976
+ return Math.max(0, Math.min(1, value2));
598977
+ }
598978
+ function cloneState(state) {
598979
+ return { ...state };
598980
+ }
598981
+ function normalizePhase(value2) {
598982
+ const raw = String(value2 ?? "").trim().toLowerCase();
598983
+ return raw === "idle" || raw === "observing" || raw === "engaged" || raw === "cooldown" ? raw : void 0;
598984
+ }
598985
+ function phaseFromAttention(attention) {
598986
+ if (attention >= 0.72) return "engaged";
598987
+ if (attention >= 0.38) return "observing";
598988
+ if (attention >= 0.14) return "cooldown";
598989
+ return "idle";
598990
+ }
598991
+ function parseStimulationPhase(value2) {
598992
+ return normalizePhase(value2);
598993
+ }
598994
+ var DEFAULT_STATE, PHASE_DEFAULTS, PHASE_FLOORS, StimulationController;
598995
+ var init_stimulation = __esm({
598996
+ "packages/cli/src/tui/stimulation.ts"() {
598997
+ "use strict";
598998
+ DEFAULT_STATE = {
598999
+ phase: "idle",
599000
+ attention: 0,
599001
+ lastStimulusAtMs: 0,
599002
+ messagesSinceAnalysis: 0,
599003
+ messagesSinceAgentOutput: 0,
599004
+ consecutiveNoReply: 0,
599005
+ updatedAtMs: 0
599006
+ };
599007
+ PHASE_DEFAULTS = {
599008
+ idle: { messages: 6, ms: 10 * 6e4 },
599009
+ cooldown: { messages: 4, ms: 5 * 6e4 },
599010
+ observing: { messages: 2, ms: 2 * 6e4 },
599011
+ engaged: { messages: 1, ms: 45e3 }
599012
+ };
599013
+ PHASE_FLOORS = {
599014
+ idle: 0,
599015
+ cooldown: 0.18,
599016
+ observing: 0.45,
599017
+ engaged: 0.78
599018
+ };
599019
+ StimulationController = class {
599020
+ states = /* @__PURE__ */ new Map();
599021
+ getState(channelId) {
599022
+ return cloneState(this.stateFor(channelId, Date.now()));
599023
+ }
599024
+ setState(channelId, state) {
599025
+ if (!state) return;
599026
+ const now = Date.now();
599027
+ this.states.set(channelId, {
599028
+ ...DEFAULT_STATE,
599029
+ ...state,
599030
+ phase: normalizePhase(state.phase) ?? DEFAULT_STATE.phase,
599031
+ attention: clamp016(Number.isFinite(state.attention) ? Number(state.attention) : DEFAULT_STATE.attention),
599032
+ updatedAtMs: Number.isFinite(state.updatedAtMs) ? Number(state.updatedAtMs) : now,
599033
+ lastStimulusAtMs: Number.isFinite(state.lastStimulusAtMs) ? Number(state.lastStimulusAtMs) : now,
599034
+ messagesSinceAnalysis: Math.max(0, Math.floor(Number(state.messagesSinceAnalysis ?? 0))),
599035
+ messagesSinceAgentOutput: Math.max(0, Math.floor(Number(state.messagesSinceAgentOutput ?? 0))),
599036
+ consecutiveNoReply: Math.max(0, Math.floor(Number(state.consecutiveNoReply ?? 0)))
599037
+ });
599038
+ }
599039
+ observe(input) {
599040
+ const now = input.nowMs ?? Date.now();
599041
+ const state = this.stateFor(input.channelId, now);
599042
+ this.applyTimeDecay(state, now);
599043
+ state.lastStimulusAtMs = now;
599044
+ state.updatedAtMs = now;
599045
+ state.messagesSinceAnalysis += 1;
599046
+ state.messagesSinceAgentOutput += 1;
599047
+ const metadataStimulus = input.privateChannel || input.directSignal || input.replyToAgent || input.activeAgent || input.forceAnalyze;
599048
+ if (input.privateChannel) state.attention = Math.max(state.attention, 0.82);
599049
+ if (input.directSignal) state.attention = Math.max(state.attention, 0.76);
599050
+ if (input.replyToAgent) state.attention = Math.max(state.attention, 0.84);
599051
+ if (input.activeAgent) state.attention = Math.max(state.attention, 0.68);
599052
+ state.phase = phaseFromAttention(state.attention);
599053
+ const cadence = PHASE_DEFAULTS[state.phase];
599054
+ const messageBudget = state.nextAnalysisAfterMessages ?? cadence.messages;
599055
+ const timeDue = state.nextAnalysisAtMs !== void 0 ? now >= state.nextAnalysisAtMs : state.lastAnalysisAtMs !== void 0 && now - state.lastAnalysisAtMs >= cadence.ms;
599056
+ const shouldAnalyze = !state.lastAnalysisAtMs || metadataStimulus || state.messagesSinceAnalysis >= messageBudget || timeDue;
599057
+ let reason = "cadence-hold";
599058
+ if (!state.lastAnalysisAtMs) reason = "initial-analysis";
599059
+ else if (input.privateChannel) reason = "private-channel";
599060
+ else if (input.replyToAgent) reason = "reply-to-agent";
599061
+ else if (input.directSignal) reason = "direct-platform-signal";
599062
+ else if (input.activeAgent) reason = "active-agent-thread";
599063
+ else if (state.messagesSinceAnalysis >= messageBudget) reason = "chronological-sample";
599064
+ else if (timeDue) reason = "time-sample";
599065
+ if (shouldAnalyze) {
599066
+ state.lastAnalysisAtMs = now;
599067
+ state.messagesSinceAnalysis = 0;
599068
+ }
599069
+ this.states.set(input.channelId, cloneState(state));
599070
+ return {
599071
+ shouldAnalyze,
599072
+ reason,
599073
+ state: cloneState(state),
599074
+ context: this.formatContext(input.channelId)
599075
+ };
599076
+ }
599077
+ applyAgentDecision(channelId, decision, nowMs = Date.now()) {
599078
+ const state = this.stateFor(channelId, nowMs);
599079
+ this.applyTimeDecay(state, nowMs);
599080
+ if (Number.isFinite(decision.attentionScore)) {
599081
+ state.attention = clamp016(Number(decision.attentionScore));
599082
+ } else if (Number.isFinite(decision.attentionDelta)) {
599083
+ state.attention = clamp016(state.attention + Number(decision.attentionDelta));
599084
+ } else {
599085
+ state.attention = clamp016(state.attention + (decision.shouldReply ? 0.22 : -0.1));
599086
+ }
599087
+ if (decision.phase) {
599088
+ state.attention = Math.max(state.attention, PHASE_FLOORS[decision.phase]);
599089
+ state.phase = decision.phase;
599090
+ } else {
599091
+ state.phase = phaseFromAttention(state.attention);
599092
+ }
599093
+ state.consecutiveNoReply = decision.shouldReply ? 0 : state.consecutiveNoReply + 1;
599094
+ state.nextAnalysisAfterMessages = Number.isFinite(decision.nextAnalysisAfterMessages) ? Math.max(1, Math.floor(Number(decision.nextAnalysisAfterMessages))) : void 0;
599095
+ state.nextAnalysisAtMs = Number.isFinite(decision.nextAnalysisAfterMs) ? nowMs + Math.max(0, Number(decision.nextAnalysisAfterMs)) : void 0;
599096
+ state.updatedAtMs = nowMs;
599097
+ this.states.set(channelId, cloneState(state));
599098
+ return cloneState(state);
599099
+ }
599100
+ recordAgentOutput(channelId, nowMs = Date.now()) {
599101
+ const state = this.stateFor(channelId, nowMs);
599102
+ this.applyTimeDecay(state, nowMs);
599103
+ state.phase = "engaged";
599104
+ state.attention = Math.max(state.attention, 0.78);
599105
+ state.lastAgentOutputAtMs = nowMs;
599106
+ state.messagesSinceAgentOutput = 0;
599107
+ state.consecutiveNoReply = 0;
599108
+ state.updatedAtMs = nowMs;
599109
+ this.states.set(channelId, cloneState(state));
599110
+ return cloneState(state);
599111
+ }
599112
+ formatContext(channelId) {
599113
+ const state = this.stateFor(channelId, Date.now());
599114
+ const sinceAnalysis = state.lastAnalysisAtMs ? `${Math.max(0, Math.round((Date.now() - state.lastAnalysisAtMs) / 1e3))}s` : "never";
599115
+ const sinceAgent = state.lastAgentOutputAtMs ? `${Math.max(0, Math.round((Date.now() - state.lastAgentOutputAtMs) / 1e3))}s` : "never";
599116
+ return [
599117
+ `Stimulation phase: ${state.phase}`,
599118
+ `Attention score: ${state.attention.toFixed(2)}`,
599119
+ `Messages since analysis: ${state.messagesSinceAnalysis}`,
599120
+ `Messages since agent output: ${state.messagesSinceAgentOutput}`,
599121
+ `Consecutive no-reply decisions: ${state.consecutiveNoReply}`,
599122
+ `Last analysis: ${sinceAnalysis}`,
599123
+ `Last agent output: ${sinceAgent}`,
599124
+ state.nextAnalysisAfterMessages ? `Agent-requested next check after messages: ${state.nextAnalysisAfterMessages}` : "",
599125
+ state.nextAnalysisAtMs ? `Agent-requested next check at: ${new Date(state.nextAnalysisAtMs).toISOString()}` : ""
599126
+ ].filter(Boolean).join("\n");
599127
+ }
599128
+ stateFor(channelId, nowMs) {
599129
+ const existing = this.states.get(channelId);
599130
+ if (existing) return cloneState(existing);
599131
+ return {
599132
+ ...DEFAULT_STATE,
599133
+ lastStimulusAtMs: nowMs,
599134
+ updatedAtMs: nowMs
599135
+ };
599136
+ }
599137
+ applyTimeDecay(state, nowMs) {
599138
+ const elapsedMs2 = Math.max(0, nowMs - (state.updatedAtMs || nowMs));
599139
+ if (elapsedMs2 <= 0) return;
599140
+ const halfLives = elapsedMs2 / (12 * 6e4);
599141
+ state.attention = clamp016(state.attention * Math.pow(0.5, halfLives));
599142
+ if (state.phase !== "engaged" || elapsedMs2 > 4 * 6e4) {
599143
+ state.phase = phaseFromAttention(state.attention);
599144
+ }
599145
+ }
599146
+ };
599147
+ }
599148
+ });
599149
+
598681
599150
  // packages/cli/src/tui/vision-ingress.ts
598682
599151
  var vision_ingress_exports = {};
598683
599152
  __export(vision_ingress_exports, {
@@ -598850,13 +599319,22 @@ function parseTelegramInteractionDecision(text, forcedRoute, options2 = {}) {
598850
599319
  const confidenceRaw = Number(parsed["confidence"]);
598851
599320
  const confidence = Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : 0;
598852
599321
  const reason = String(parsed["reason"] ?? "live inference decision").slice(0, 240);
599322
+ const attentionDeltaRaw = Number(parsed["attention_delta"] ?? parsed["attentionDelta"]);
599323
+ const attentionScoreRaw = Number(parsed["attention_score"] ?? parsed["attentionScore"]);
599324
+ const nextMessagesRaw = Number(parsed["next_check_after_messages"] ?? parsed["nextCheckAfterMessages"]);
599325
+ const nextMsRaw = Number(parsed["next_check_after_ms"] ?? parsed["nextCheckAfterMs"]);
598853
599326
  return {
598854
599327
  route,
598855
599328
  shouldReply,
598856
599329
  confidence,
598857
599330
  reason,
598858
599331
  source: forcedRoute ? "forced-mode" : "live-inference",
598859
- raw: text
599332
+ raw: text,
599333
+ attentionState: parseStimulationPhase(parsed["attention_state"] ?? parsed["attentionState"]),
599334
+ attentionDelta: Number.isFinite(attentionDeltaRaw) ? Math.max(-1, Math.min(1, attentionDeltaRaw)) : void 0,
599335
+ attentionScore: Number.isFinite(attentionScoreRaw) ? Math.max(0, Math.min(1, attentionScoreRaw)) : void 0,
599336
+ nextCheckAfterMessages: Number.isFinite(nextMessagesRaw) ? Math.max(1, Math.floor(nextMessagesRaw)) : void 0,
599337
+ nextCheckAfterMs: Number.isFinite(nextMsRaw) ? Math.max(0, Math.floor(nextMsRaw)) : void 0
598860
599338
  };
598861
599339
  } catch {
598862
599340
  return null;
@@ -599175,11 +599653,16 @@ function renderTelegramLiveProgressHTML(progressLines, accumulated) {
599175
599653
  }
599176
599654
  function splitTelegramReminderDue(raw) {
599177
599655
  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
599656
+ const duePattern = "(?:(?:in\\s+|after\\s+)?\\d+\\s*(?:m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|wk|wks|weeks?)(?:\\s+(?:from\\s+now|later))?|tomorrow(?:\\s+at\\s+\\d{1,2}:\\d{2})?|next\\s+week|next\\s+startup|startup|now|(?:today\\s+)?at\\s+\\d{1,2}:\\d{2})";
599657
+ const prefixMatch = text.match(
599658
+ new RegExp(`^(${duePattern})\\s+(.+)$`, "i")
599659
+ );
599660
+ if (prefixMatch) return { due: prefixMatch[1].trim(), message: prefixMatch[2].trim() };
599661
+ const suffixMatch = text.match(
599662
+ new RegExp(`^(.+?)\\s+(${duePattern})$`, "i")
599180
599663
  );
599181
- if (!dueMatch) return { message: text };
599182
- return { due: dueMatch[1].trim(), message: dueMatch[2].trim() };
599664
+ if (suffixMatch) return { due: suffixMatch[2].trim(), message: suffixMatch[1].trim() };
599665
+ return { message: text };
599183
599666
  }
599184
599667
  function telegramSyntheticHelpSignatures() {
599185
599668
  return [
@@ -599684,6 +600167,7 @@ var init_telegram_bridge = __esm({
599684
600167
  init_scoped_personality();
599685
600168
  init_telegram_creative_tools();
599686
600169
  init_omnius_directory();
600170
+ init_stimulation();
599687
600171
  TELEGRAM_SAFETY_PROMPT = `
599688
600172
  CRITICAL SAFETY NOTICE — PUBLIC TELEGRAM CHANNEL
599689
600173
 
@@ -599908,6 +600392,8 @@ Telegram response contract:
599908
600392
  chatParticipants = /* @__PURE__ */ new Map();
599909
600393
  /** Lightweight Zettelkasten-style memory cards by chat/guest session key */
599910
600394
  chatMemoryCards = /* @__PURE__ */ new Map();
600395
+ /** Generic chronological attention cadence shared by live surfaces. */
600396
+ stimulation = new StimulationController();
599911
600397
  /** Throttles noisy "skipped group chatter" waterfall logs */
599912
600398
  groupSkipLogAt = /* @__PURE__ */ new Map();
599913
600399
  /** Telegram interaction routing profile */
@@ -600093,7 +600579,7 @@ Telegram response contract:
600093
600579
  } else {
600094
600580
  const { due, message: message2 } = splitTelegramReminderDue(arg);
600095
600581
  if (!message2) {
600096
- await this.replyToTelegramMessage(msg, "Usage: /remind [in 30m|tomorrow|at 14:30] <message>");
600582
+ await this.replyToTelegramMessage(msg, "Usage: /remind <when> <message> (examples: /remind in 30m check coffee, /remind tomorrow at 09:00 call mom)");
600097
600583
  return;
600098
600584
  }
600099
600585
  args = {
@@ -600101,7 +600587,7 @@ Telegram response contract:
600101
600587
  kind: "minimal",
600102
600588
  message: message2,
600103
600589
  content: message2,
600104
- ...due ? { due } : {}
600590
+ ...due ? { due_at: due } : {}
600105
600591
  };
600106
600592
  }
600107
600593
  const result = await tool.execute(args);
@@ -600197,6 +600683,9 @@ Telegram response contract:
600197
600683
  if (Array.isArray(parsed.memoryCards)) {
600198
600684
  this.chatMemoryCards.set(sessionKey, parsed.memoryCards.slice(0, TELEGRAM_MEMORY_CARD_LIMIT));
600199
600685
  }
600686
+ if (parsed.stimulation) {
600687
+ this.stimulation.setState(sessionKey, parsed.stimulation);
600688
+ }
600200
600689
  } catch {
600201
600690
  }
600202
600691
  }
@@ -600230,7 +600719,8 @@ Telegram response contract:
600230
600719
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
600231
600720
  history: this.chatHistory.get(sessionKey) ?? [],
600232
600721
  participants,
600233
- memoryCards: this.chatMemoryCards.get(sessionKey) ?? []
600722
+ memoryCards: this.chatMemoryCards.get(sessionKey) ?? [],
600723
+ stimulation: this.stimulation.getState(sessionKey)
600234
600724
  };
600235
600725
  writeFileSync57(this.telegramConversationPath(sessionKey), JSON.stringify(payload, null, 2) + "\n", "utf8");
600236
600726
  } catch {
@@ -600295,6 +600785,7 @@ Telegram response contract:
600295
600785
  chatTitle: msg.chatTitle
600296
600786
  };
600297
600787
  this.recordChatHistory(sessionKey, entry);
600788
+ this.stimulation.recordAgentOutput(sessionKey);
600298
600789
  this.updateTelegramMemoryCards(sessionKey, entry);
600299
600790
  try {
600300
600791
  updateScopedPersonality(this.telegramPersonalityScope(sessionKey, msg), {
@@ -600654,21 +601145,53 @@ ${lines.join("\n")}`);
600654
601145
  if (msg.replyToUsername && msg.replyToUsername.trim().replace(/^@/, "").toLowerCase() === bot) return true;
600655
601146
  return false;
600656
601147
  }
601148
+ telegramMessageRepliesToBot(msg) {
601149
+ const bot = this.state.botUsername.trim().replace(/^@/, "").toLowerCase();
601150
+ return !!bot && !!msg.replyToUsername && msg.replyToUsername.trim().replace(/^@/, "").toLowerCase() === bot;
601151
+ }
601152
+ applyTelegramStimulationDecision(sessionKey, decision) {
601153
+ this.stimulation.applyAgentDecision(sessionKey, {
601154
+ shouldReply: decision.shouldReply,
601155
+ phase: decision.attentionState,
601156
+ attentionDelta: decision.attentionDelta,
601157
+ attentionScore: decision.attentionScore,
601158
+ nextAnalysisAfterMessages: decision.nextCheckAfterMessages,
601159
+ nextAnalysisAfterMs: decision.nextCheckAfterMs
601160
+ });
601161
+ }
600657
601162
  async inferTelegramInteractionDecision(msg, toolContext) {
600658
601163
  const config = this.agentConfig;
600659
601164
  const forcedRoute = this.interactionMode === "chat" || this.interactionMode === "action" ? this.interactionMode : null;
600660
601165
  const isGroup = msg.chatType !== "private";
600661
601166
  const addressesBot = this.telegramMessageAddressesBot(msg);
600662
- if (!config) {
601167
+ const sessionKey = this.sessionKeyForMessage(msg);
601168
+ const stimulationProbe = this.stimulation.observe({
601169
+ channelId: sessionKey,
601170
+ privateChannel: !isGroup,
601171
+ directSignal: addressesBot,
601172
+ replyToAgent: this.telegramMessageRepliesToBot(msg),
601173
+ activeAgent: this.subAgents.has(sessionKey)
601174
+ });
601175
+ if (isGroup && !stimulationProbe.shouldAnalyze) {
600663
601176
  return {
601177
+ route: forcedRoute ?? "chat",
601178
+ shouldReply: false,
601179
+ confidence: 0.35,
601180
+ reason: `stimulation cadence held analysis (${stimulationProbe.reason}); context retained`,
601181
+ source: "attention-gated"
601182
+ };
601183
+ }
601184
+ if (!config) {
601185
+ const fallback2 = {
600664
601186
  route: forcedRoute ?? (isGroup ? "action" : "chat"),
600665
601187
  shouldReply: !isGroup || addressesBot,
600666
601188
  confidence: 0,
600667
601189
  reason: isGroup ? addressesBot ? "router inference unavailable; Telegram message directly addresses the bot" : "router inference unavailable; public group fails closed without keyword heuristics" : "router inference unavailable; private chat defaults to quick reply",
600668
601190
  source: "inference-unavailable"
600669
601191
  };
601192
+ this.applyTelegramStimulationDecision(sessionKey, fallback2);
601193
+ return fallback2;
600670
601194
  }
600671
- const sessionKey = this.sessionKeyForMessage(msg);
600672
601195
  const backend = new OllamaAgenticBackend(
600673
601196
  config.backendUrl,
600674
601197
  config.model,
@@ -600680,16 +601203,17 @@ ${lines.join("\n")}`);
600680
601203
  `You are the Telegram live routing and reply-discretion model.`,
600681
601204
  `Return JSON only, with no markdown and no explanation outside JSON.`,
600682
601205
  ``,
600683
- `Schema: {"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason"}`,
601206
+ `Schema: {"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12}`,
600684
601207
  ``,
600685
601208
  `Route meanings:`,
600686
601209
  `- chat: a short conversational answer can be produced without tools.`,
600687
601210
  `- action: tools, workspace context, media processing, web lookup, delegation, or a multi-step agent loop may be needed.`,
600688
601211
  `Route discipline: greetings, acknowledgements, casual tone/style discussion, and simple conversational questions are chat. Use action only when the message asks you to inspect, create, change, send, remember, search, analyze media, or otherwise do tool-backed work.`,
600689
601212
  ``,
600690
- `Reply discretion: infer from the live thread, speaker relationships, direct mentions, replies, tone, and current message. Do not use static keyword rules.`,
601213
+ `Reply discretion: infer from the live thread, speaker relationships, direct platform signals, replies, tone, and current message. Do not use static keyword rules.`,
600691
601214
  `Private chats: should_reply is normally true.`,
600692
601215
  `Group/public chats: default should_reply to false unless the current message clearly addresses the bot, replies to the bot, continues an active bot-involved exchange, assigns the bot work, or asks for the bot's view. Ambient chatter, third-person discussion about the bot, commands meant for a human, or questions among other people are false. Do not set true just because the bot could help.`,
601216
+ `Stimulation discipline: also set attention_state, attention_delta, and optional next_check_after_messages/next_check_after_ms. These control future analysis cadence only; they do not force a reply. Use engaged for active back-and-forth, observing for likely relevant context, cooldown for recently irrelevant context, and idle for ambient chatter.`,
600693
601217
  forcedLine,
600694
601218
  ``,
600695
601219
  `Tool context: ${toolContext}`,
@@ -600701,6 +601225,9 @@ ${lines.join("\n")}`);
600701
601225
  msg.replyToUsername ? `Current message replies to @${msg.replyToUsername}` : "",
600702
601226
  (msg.mentionedUsernames ?? []).length > 0 ? `Current message mentions: ${(msg.mentionedUsernames ?? []).map((name10) => `@${name10}`).join(", ")}` : "",
600703
601227
  msg.media ? `Current message has media: ${summarizeTelegramMessageAttachments(msg)}` : "",
601228
+ ``,
601229
+ `Current stimulation state before semantic decision:
601230
+ ${stimulationProbe.context}`,
600704
601231
  ``,
600705
601232
  context2,
600706
601233
  ``,
@@ -600712,7 +601239,7 @@ ${msg.text}`
600712
601239
  messages: [
600713
601240
  {
600714
601241
  role: "system",
600715
- content: "You perform live Telegram route inference. Output strict JSON only."
601242
+ content: "You perform live Telegram route and stimulation inference. Output strict JSON only."
600716
601243
  },
600717
601244
  { role: "user", content: userPrompt }
600718
601245
  ],
@@ -600726,16 +601253,21 @@ ${msg.text}`
600726
601253
  const parsed = parseTelegramInteractionDecision(text, forcedRoute, {
600727
601254
  defaultShouldReply: !isGroup
600728
601255
  });
600729
- if (parsed) return parsed;
601256
+ if (parsed) {
601257
+ this.applyTelegramStimulationDecision(sessionKey, parsed);
601258
+ return parsed;
601259
+ }
600730
601260
  } catch {
600731
601261
  }
600732
- return {
601262
+ const fallback = {
600733
601263
  route: forcedRoute ?? (isGroup ? "action" : "chat"),
600734
601264
  shouldReply: !isGroup || addressesBot,
600735
601265
  confidence: 0,
600736
601266
  reason: isGroup ? addressesBot ? "router inference failed; Telegram message directly addresses the bot" : "router inference failed; public group fails closed without keyword heuristics" : "router inference failed; private chat defaults to quick reply",
600737
601267
  source: "inference-unavailable"
600738
601268
  };
601269
+ this.applyTelegramStimulationDecision(sessionKey, fallback);
601270
+ return fallback;
600739
601271
  }
600740
601272
  buildTelegramWorkspaceContext(modelTier, budget = 14e3) {
600741
601273
  if (!this.repoRoot) return "";
@@ -601743,6 +602275,14 @@ ${mediaContext}` : ""}`
601743
602275
  const sessionContext = this.buildTelegramSessionContext(msg, ctx3, profile, modelTier);
601744
602276
  const contextWindowSize = this.contextWindowSize;
601745
602277
  const runnerStateDir = isAdminDM ? void 0 : this.telegramRunnerStateDir(sessionContext.sessionKey);
602278
+ const runtimeContext = buildTelegramRuntimeContext(/* @__PURE__ */ new Date(), isAdminDM ? repoRoot : void 0);
602279
+ const reminderToolContract = [
602280
+ "Reminder tool contract:",
602281
+ "- For reminder/remind action='set', always pass due_at (or do_at/due alias) from the user's time phrase unless repeat/recurrence is present.",
602282
+ "- Do not call reminder set without due_at; the tool will fail instead of defaulting to next startup.",
602283
+ "- Keep message/content as the reminder payload only. Never include prose like 'you asked me to remind you in 5 minutes' in message/content.",
602284
+ "- Examples: 'remind me in 5 minutes to get coffee' -> due_at='in 5 minutes', message='Get coffee', content='Get coffee'. 'tomorrow at 09:00 call mom' -> due_at='tomorrow at 09:00', message='Call mom'."
602285
+ ].join("\n");
601746
602286
  const backend = new OllamaAgenticBackend(
601747
602287
  config.backendUrl,
601748
602288
  config.model,
@@ -601884,6 +602424,8 @@ ${GROUP_REPLY_DISCRETION_PROMPT}` : "";
601884
602424
 
601885
602425
  ${TELEGRAM_ACTION_RESPONSE_CONTRACT}
601886
602426
 
602427
+ ${reminderToolContract}
602428
+
601887
602429
  ${profileLine}
601888
602430
 
601889
602431
  Telegram message from admin @${msg.username}:
@@ -601893,6 +602435,7 @@ ${msg.text}`;
601893
602435
  "You have access to isolated per-chat memory (memory_write, memory_read, memory_search) scoped to this conversation.",
601894
602436
  "memory_search may use scope=group/current_chat for this group or scope=user with user_id/username for a participant in this same group. Other groups, admin chats, and private DMs are not accessible here.",
601895
602437
  "You can remember facts about users and retrieve them later. You also have web_search and web_fetch to look up information.",
602438
+ reminderToolContract,
601896
602439
  "If the user asks you to create or send a file, image, or audio artifact, create it with the scoped creative tools and call telegram_send_file to upload it. The bridge still auto-attaches generated files as a fallback when tool results record them.",
601897
602440
  "For image generation requests, decide from the conversation whether generate_image is appropriate; do not ask the user to use a hardcoded shortcut when the request is clear.",
601898
602441
  creativeWorkspace
@@ -601914,10 +602457,11 @@ Respond concisely and safely. Send the actual chat reply, not router/status/comp
601914
602457
  [Media attached — processed content below]
601915
602458
  ${mediaContext}`;
601916
602459
  }
601917
- const systemCtx = isAdminDM ? `Working directory: ${repoRoot}
602460
+ const systemCtx = isAdminDM ? `${runtimeContext}
601918
602461
  Telegram admin: @${msg.username}
601919
602462
  Telegram profile: ${profile}
601920
- Todo/session id: ${sessionContext.sessionId}` : `Telegram ${isGroup ? "group" : "public"} chat. Respond concisely. Safety filter: ACTIVE.${creativeWorkspace ? `
602463
+ Todo/session id: ${sessionContext.sessionId}` : `${runtimeContext}
602464
+ Telegram ${isGroup ? "group" : "public"} chat. Respond concisely. Safety filter: ACTIVE.${creativeWorkspace ? `
601921
602465
 
601922
602466
  ${creativeWorkspace}` : ""}`;
601923
602467
  const result = await runner.run(userPrompt, systemCtx);
@@ -603891,6 +604435,237 @@ var init_chat_session = __esm({
603891
604435
  }
603892
604436
  });
603893
604437
 
604438
+ // packages/cli/src/api/command-passthrough.ts
604439
+ var command_passthrough_exports = {};
604440
+ __export(command_passthrough_exports, {
604441
+ runCommand: () => runCommand
604442
+ });
604443
+ function stripAnsi5(s2) {
604444
+ return s2.replace(/\x1B(?:\[[\d;?]*[a-zA-Z]|\][^\x07\x1B]*[\x07\x1B]?|[@-Z\\-_])/g, "");
604445
+ }
604446
+ async function runCommand(input, opts) {
604447
+ const start2 = Date.now();
604448
+ const trimmed = input.trim();
604449
+ const slashCmd = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
604450
+ const [rawCmd, ...rest] = slashCmd.slice(1).split(/\s+/);
604451
+ const cmdName = rawCmd ?? "";
604452
+ const argsStr = rest.join(" ");
604453
+ const release = await acquireLock2();
604454
+ try {
604455
+ const quick2 = buildNonInteractiveSummary(cmdName, argsStr, opts?.config);
604456
+ if (quick2) {
604457
+ return {
604458
+ ok: true,
604459
+ command: cmdName,
604460
+ args: argsStr,
604461
+ kind: "handled",
604462
+ output: quick2,
604463
+ ansi: quick2,
604464
+ durationMs: Date.now() - start2
604465
+ };
604466
+ }
604467
+ const buf = [];
604468
+ setContentWriteHook({
604469
+ begin: () => {
604470
+ },
604471
+ end: () => {
604472
+ },
604473
+ redirect: () => (text) => {
604474
+ buf.push(text);
604475
+ }
604476
+ });
604477
+ const origWrite = process.stdout.write.bind(process.stdout);
604478
+ process.stdout.write = function(chunk, ...rest2) {
604479
+ if (typeof chunk === "string") buf.push(chunk);
604480
+ else if (chunk instanceof Buffer) buf.push(chunk.toString("utf-8"));
604481
+ const cb = rest2.find((r2) => typeof r2 === "function");
604482
+ if (cb) cb();
604483
+ return true;
604484
+ };
604485
+ let kind = "handled";
604486
+ let errMsg;
604487
+ try {
604488
+ const ctx3 = buildSyntheticContext(opts?.config, opts?.repoRoot);
604489
+ kind = await handleSlashCommand(slashCmd, ctx3);
604490
+ } catch (e2) {
604491
+ kind = "error";
604492
+ errMsg = e2 instanceof Error ? e2.message : String(e2);
604493
+ buf.push(`
604494
+ [error] ${errMsg}
604495
+ `);
604496
+ } finally {
604497
+ process.stdout.write = origWrite;
604498
+ setContentWriteHook({ begin: () => {
604499
+ }, end: () => {
604500
+ } });
604501
+ }
604502
+ const ansi5 = buf.join("");
604503
+ return {
604504
+ ok: kind !== "error" && kind !== "not_a_command",
604505
+ command: cmdName,
604506
+ args: argsStr,
604507
+ kind,
604508
+ output: stripAnsi5(ansi5).trim(),
604509
+ ansi: ansi5,
604510
+ durationMs: Date.now() - start2,
604511
+ error: errMsg
604512
+ };
604513
+ } finally {
604514
+ release();
604515
+ }
604516
+ }
604517
+ function buildNonInteractiveSummary(cmdName, _args, config) {
604518
+ const cfg = config ?? loadConfig();
604519
+ if (cmdName === "setup" || cmdName === "wizard") {
604520
+ return [
604521
+ "omnius setup",
604522
+ "",
604523
+ "The setup wizard is an interactive terminal flow. In the GUI command bridge it is summarized instead of opening prompts, installing software, starting Ollama, or pulling models.",
604524
+ "",
604525
+ `Current backend: ${cfg.backendType ?? "ollama"}`,
604526
+ `Current endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
604527
+ `Current model: ${cfg.model ?? "qwen3.5:latest"}`,
604528
+ "",
604529
+ "Available non-interactive setup actions:",
604530
+ " /endpoint <url> Set or inspect the inference endpoint.",
604531
+ " /model <name> Set the active model directly.",
604532
+ " /models Show model-selection guidance.",
604533
+ " /config Inspect persisted configuration.",
604534
+ " /doctor Run diagnostics from the terminal if deeper repair is needed.",
604535
+ "",
604536
+ "Open a terminal and run `omnius`, then use /setup for the full guided wizard."
604537
+ ].join("\n");
604538
+ }
604539
+ if (cmdName === "models") {
604540
+ return [
604541
+ "omnius models",
604542
+ "",
604543
+ "The model picker is interactive in the TUI. The GUI bridge does not probe remote model endpoints here, so it cannot hang on a stale backend.",
604544
+ "",
604545
+ `Active model: ${cfg.model ?? "qwen3.5:latest"}`,
604546
+ `Endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
604547
+ `Backend: ${cfg.backendType ?? "ollama"}`,
604548
+ "",
604549
+ "Use /model <name> to set a model directly, /endpoint to switch providers, or open the TUI for the searchable model picker."
604550
+ ].join("\n");
604551
+ }
604552
+ return null;
604553
+ }
604554
+ function acquireLock2() {
604555
+ let release;
604556
+ const next = new Promise((res) => {
604557
+ release = res;
604558
+ });
604559
+ const prev = _passthroughLock;
604560
+ _passthroughLock = next;
604561
+ return prev.then(() => release);
604562
+ }
604563
+ function buildSyntheticContext(config, repoRoot) {
604564
+ const cfg = config ?? loadConfig();
604565
+ const root = repoRoot ?? process.cwd();
604566
+ let colorsEnabled = loadProjectSettings(root).colors ?? loadGlobalSettings().colors ?? true;
604567
+ let selfModifyEnabled = loadProjectSettings(root).selfModify ?? loadGlobalSettings().selfModify ?? false;
604568
+ return {
604569
+ config: cfg,
604570
+ repoRoot: root,
604571
+ rl: makeRejectingReadline(),
604572
+ setModel: (_model) => {
604573
+ },
604574
+ setVerbose: (_verbose) => {
604575
+ },
604576
+ setEndpoint: (_url, _t, _k) => {
604577
+ },
604578
+ deactivateStatusBar: () => {
604579
+ },
604580
+ disableMouse: () => {
604581
+ },
604582
+ enableMouse: () => {
604583
+ },
604584
+ isMouseEnabled: () => false,
604585
+ lockFooter: () => {
604586
+ },
604587
+ unlockFooter: () => {
604588
+ },
604589
+ stopBanner: () => {
604590
+ },
604591
+ killEphemeral: () => {
604592
+ },
604593
+ setKeyPool: (_keys) => {
604594
+ },
604595
+ clearScreen: () => {
604596
+ },
604597
+ newSession: () => {
604598
+ },
604599
+ refreshBanner: () => {
604600
+ },
604601
+ exit: () => {
604602
+ renderError("/quit and /exit are TUI-only — close the browser tab to end the GUI session.");
604603
+ },
604604
+ voiceToggle: async () => {
604605
+ renderError(`voice ${TUI_ONLY_HINT} — use the GUI voice button instead.`);
604606
+ return "voice not available in GUI";
604607
+ },
604608
+ voiceSetModel: async (_id2) => {
604609
+ renderError(`voice model ${TUI_ONLY_HINT}`);
604610
+ return "";
604611
+ },
604612
+ getColors: () => colorsEnabled,
604613
+ setColors: (enabled2) => {
604614
+ colorsEnabled = enabled2;
604615
+ },
604616
+ getSelfModify: () => selfModifyEnabled,
604617
+ setSelfModify: (enabled2) => {
604618
+ selfModifyEnabled = enabled2;
604619
+ },
604620
+ saveSettings: (settings) => {
604621
+ saveProjectSettings(root, settings);
604622
+ saveGlobalSettings(settings);
604623
+ },
604624
+ saveLocalSettings: (settings) => {
604625
+ saveProjectSettings(root, settings);
604626
+ }
604627
+ };
604628
+ }
604629
+ function makeRejectingReadline() {
604630
+ const reject = () => {
604631
+ throw new Error(
604632
+ "interactive prompts are not supported via the GUI command bridge — run this command from the TUI (open a terminal and type `omnius`)."
604633
+ );
604634
+ };
604635
+ const noop2 = () => {
604636
+ };
604637
+ return {
604638
+ question: (_q, _cb) => reject(),
604639
+ close: noop2,
604640
+ write: noop2,
604641
+ on: () => noop2,
604642
+ once: () => noop2,
604643
+ off: () => noop2,
604644
+ removeListener: () => noop2,
604645
+ pause: noop2,
604646
+ resume: noop2,
604647
+ setPrompt: noop2,
604648
+ prompt: noop2,
604649
+ line: "",
604650
+ cursor: 0,
604651
+ terminal: false,
604652
+ input: { isTTY: false, on: noop2, once: noop2, removeListener: noop2, pause: noop2, resume: noop2 },
604653
+ output: { write: noop2, columns: 80, rows: 24, isTTY: false }
604654
+ };
604655
+ }
604656
+ var _passthroughLock, TUI_ONLY_HINT;
604657
+ var init_command_passthrough = __esm({
604658
+ "packages/cli/src/api/command-passthrough.ts"() {
604659
+ "use strict";
604660
+ init_render();
604661
+ init_commands();
604662
+ init_config();
604663
+ init_omnius_directory();
604664
+ _passthroughLock = Promise.resolve();
604665
+ TUI_ONLY_HINT = "(this command is TUI-only — no-op in GUI)";
604666
+ }
604667
+ });
604668
+
603894
604669
  // packages/cli/src/api/projects.ts
603895
604670
  var projects_exports = {};
603896
604671
  __export(projects_exports, {
@@ -604820,7 +605595,7 @@ __export(voicechat_exports, {
604820
605595
  VoiceChatSession: () => VoiceChatSession
604821
605596
  });
604822
605597
  import { EventEmitter as EventEmitter11 } from "node:events";
604823
- function clamp016(x) {
605598
+ function clamp017(x) {
604824
605599
  return x < 0 ? 0 : x > 1 ? 1 : x;
604825
605600
  }
604826
605601
  function alnumRatio(s2) {
@@ -604859,9 +605634,9 @@ function computeSignalFromText(text, confidence) {
604859
605634
  else score = 0.15;
604860
605635
  score -= repeatingCharPenalty(t2) * 0.4;
604861
605636
  if (typeof confidence === "number" && !Number.isNaN(confidence)) {
604862
- score = 0.7 * score + 0.3 * clamp016(confidence);
605637
+ score = 0.7 * score + 0.3 * clamp017(confidence);
604863
605638
  }
604864
- return clamp016(score);
605639
+ return clamp017(score);
604865
605640
  }
604866
605641
  function truncateForLog(s2, n2) {
604867
605642
  return s2.length <= n2 ? s2 : s2.slice(0, n2 - 1) + "…";
@@ -605131,7 +605906,7 @@ Rules:
605131
605906
  }, MAX_SEGMENT_MS);
605132
605907
  }
605133
605908
  this.captureBuffer = text;
605134
- this.lastSignalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp016(snr) : computeSignalFromText(text, confidence);
605909
+ this.lastSignalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp017(snr) : computeSignalFromText(text, confidence);
605135
605910
  this.emit("snr", { score: this.lastSignalScore });
605136
605911
  this.onPartialTranscript(text);
605137
605912
  if (this.silenceTimer) clearTimeout(this.silenceTimer);
@@ -607208,220 +607983,6 @@ var init_graphical_sudo = __esm({
607208
607983
  }
607209
607984
  });
607210
607985
 
607211
- // packages/cli/src/api/command-passthrough.ts
607212
- var command_passthrough_exports = {};
607213
- __export(command_passthrough_exports, {
607214
- runCommand: () => runCommand
607215
- });
607216
- function stripAnsi5(s2) {
607217
- return s2.replace(/\x1B(?:\[[\d;?]*[a-zA-Z]|\][^\x07\x1B]*[\x07\x1B]?|[@-Z\\-_])/g, "");
607218
- }
607219
- async function runCommand(input, opts) {
607220
- const start2 = Date.now();
607221
- const trimmed = input.trim();
607222
- const slashCmd = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
607223
- const [rawCmd, ...rest] = slashCmd.slice(1).split(/\s+/);
607224
- const cmdName = rawCmd ?? "";
607225
- const argsStr = rest.join(" ");
607226
- const release = await acquireLock2();
607227
- try {
607228
- const quick2 = buildNonInteractiveSummary(cmdName, argsStr, opts?.config);
607229
- if (quick2) {
607230
- return {
607231
- ok: true,
607232
- command: cmdName,
607233
- args: argsStr,
607234
- kind: "handled",
607235
- output: quick2,
607236
- ansi: quick2,
607237
- durationMs: Date.now() - start2
607238
- };
607239
- }
607240
- const buf = [];
607241
- setContentWriteHook({
607242
- begin: () => {
607243
- },
607244
- end: () => {
607245
- },
607246
- redirect: () => (text) => {
607247
- buf.push(text);
607248
- }
607249
- });
607250
- const origWrite = process.stdout.write.bind(process.stdout);
607251
- process.stdout.write = function(chunk, ...rest2) {
607252
- if (typeof chunk === "string") buf.push(chunk);
607253
- else if (chunk instanceof Buffer) buf.push(chunk.toString("utf-8"));
607254
- const cb = rest2.find((r2) => typeof r2 === "function");
607255
- if (cb) cb();
607256
- return true;
607257
- };
607258
- let kind = "handled";
607259
- let errMsg;
607260
- try {
607261
- const ctx3 = buildSyntheticContext(opts?.config, opts?.repoRoot);
607262
- kind = await handleSlashCommand(slashCmd, ctx3);
607263
- } catch (e2) {
607264
- kind = "error";
607265
- errMsg = e2 instanceof Error ? e2.message : String(e2);
607266
- buf.push(`
607267
- [error] ${errMsg}
607268
- `);
607269
- } finally {
607270
- process.stdout.write = origWrite;
607271
- setContentWriteHook({ begin: () => {
607272
- }, end: () => {
607273
- } });
607274
- }
607275
- const ansi5 = buf.join("");
607276
- return {
607277
- ok: kind !== "error" && kind !== "not_a_command",
607278
- command: cmdName,
607279
- args: argsStr,
607280
- kind,
607281
- output: stripAnsi5(ansi5).trim(),
607282
- ansi: ansi5,
607283
- durationMs: Date.now() - start2,
607284
- error: errMsg
607285
- };
607286
- } finally {
607287
- release();
607288
- }
607289
- }
607290
- function buildNonInteractiveSummary(cmdName, _args, config) {
607291
- const cfg = config ?? loadConfig();
607292
- if (cmdName === "setup" || cmdName === "wizard") {
607293
- return [
607294
- "omnius setup",
607295
- "",
607296
- "The setup wizard is an interactive terminal flow. In the GUI command bridge it is summarized instead of opening prompts, installing software, starting Ollama, or pulling models.",
607297
- "",
607298
- `Current backend: ${cfg.backendType ?? "ollama"}`,
607299
- `Current endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
607300
- `Current model: ${cfg.model ?? "qwen3.5:latest"}`,
607301
- "",
607302
- "Available non-interactive setup actions:",
607303
- " /endpoint <url> Set or inspect the inference endpoint.",
607304
- " /model <name> Set the active model directly.",
607305
- " /models Show model-selection guidance.",
607306
- " /config Inspect persisted configuration.",
607307
- " /doctor Run diagnostics from the terminal if deeper repair is needed.",
607308
- "",
607309
- "Open a terminal and run `omnius`, then use /setup for the full guided wizard."
607310
- ].join("\n");
607311
- }
607312
- if (cmdName === "models") {
607313
- return [
607314
- "omnius models",
607315
- "",
607316
- "The model picker is interactive in the TUI. The GUI bridge does not probe remote model endpoints here, so it cannot hang on a stale backend.",
607317
- "",
607318
- `Active model: ${cfg.model ?? "qwen3.5:latest"}`,
607319
- `Endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
607320
- `Backend: ${cfg.backendType ?? "ollama"}`,
607321
- "",
607322
- "Use /model <name> to set a model directly, /endpoint to switch providers, or open the TUI for the searchable model picker."
607323
- ].join("\n");
607324
- }
607325
- return null;
607326
- }
607327
- function acquireLock2() {
607328
- let release;
607329
- const next = new Promise((res) => {
607330
- release = res;
607331
- });
607332
- const prev = _passthroughLock;
607333
- _passthroughLock = next;
607334
- return prev.then(() => release);
607335
- }
607336
- function buildSyntheticContext(config, repoRoot) {
607337
- const cfg = config ?? loadConfig();
607338
- return {
607339
- config: cfg,
607340
- repoRoot: repoRoot ?? process.cwd(),
607341
- rl: makeRejectingReadline(),
607342
- setModel: (_model) => {
607343
- },
607344
- setVerbose: (_verbose) => {
607345
- },
607346
- setEndpoint: (_url, _t, _k) => {
607347
- },
607348
- deactivateStatusBar: () => {
607349
- },
607350
- disableMouse: () => {
607351
- },
607352
- enableMouse: () => {
607353
- },
607354
- isMouseEnabled: () => false,
607355
- lockFooter: () => {
607356
- },
607357
- unlockFooter: () => {
607358
- },
607359
- stopBanner: () => {
607360
- },
607361
- killEphemeral: () => {
607362
- },
607363
- setKeyPool: (_keys) => {
607364
- },
607365
- clearScreen: () => {
607366
- },
607367
- newSession: () => {
607368
- },
607369
- refreshBanner: () => {
607370
- },
607371
- exit: () => {
607372
- renderError("/quit and /exit are TUI-only — close the browser tab to end the GUI session.");
607373
- },
607374
- voiceToggle: async () => {
607375
- renderError(`voice ${TUI_ONLY_HINT} — use the GUI voice button instead.`);
607376
- return "voice not available in GUI";
607377
- },
607378
- voiceSetModel: async (_id2) => {
607379
- renderError(`voice model ${TUI_ONLY_HINT}`);
607380
- return "";
607381
- },
607382
- saveSettings: (_settings) => {
607383
- }
607384
- };
607385
- }
607386
- function makeRejectingReadline() {
607387
- const reject = () => {
607388
- throw new Error(
607389
- "interactive prompts are not supported via the GUI command bridge — run this command from the TUI (open a terminal and type `omnius`)."
607390
- );
607391
- };
607392
- const noop2 = () => {
607393
- };
607394
- return {
607395
- question: (_q, _cb) => reject(),
607396
- close: noop2,
607397
- write: noop2,
607398
- on: () => noop2,
607399
- once: () => noop2,
607400
- off: () => noop2,
607401
- removeListener: () => noop2,
607402
- pause: noop2,
607403
- resume: noop2,
607404
- setPrompt: noop2,
607405
- prompt: noop2,
607406
- line: "",
607407
- cursor: 0,
607408
- terminal: false,
607409
- input: { isTTY: false, on: noop2, once: noop2, removeListener: noop2, pause: noop2, resume: noop2 },
607410
- output: { write: noop2, columns: 80, rows: 24, isTTY: false }
607411
- };
607412
- }
607413
- var _passthroughLock, TUI_ONLY_HINT;
607414
- var init_command_passthrough = __esm({
607415
- "packages/cli/src/api/command-passthrough.ts"() {
607416
- "use strict";
607417
- init_render();
607418
- init_commands();
607419
- init_config();
607420
- _passthroughLock = Promise.resolve();
607421
- TUI_ONLY_HINT = "(this command is TUI-only — no-op in GUI)";
607422
- }
607423
- });
607424
-
607425
607986
  // packages/cli/src/api/routes-v1.ts
607426
607987
  import { existsSync as existsSync116, readFileSync as readFileSync95, readdirSync as readdirSync39, statSync as statSync40 } from "node:fs";
607427
607988
  import { join as join130, resolve as pathResolve2 } from "node:path";
@@ -612302,7 +612863,7 @@ const $config = writable(null); // {endpoint, model, ...}
612302
612863
  const $activeTab = writable('chat');
612303
612864
 
612304
612865
  function parseInitialGuiRoute() {
612305
- const path = (location.pathname || '/').replace(//+$/, '') || '/';
612866
+ const path = (location.pathname || '/').replace(/\\/+$/, '') || '/';
612306
612867
  const pathToTab = {
612307
612868
  '/chat': 'chat',
612308
612869
  '/agent': 'agent',
@@ -612317,7 +612878,7 @@ function parseInitialGuiRoute() {
612317
612878
  const tab = pathToTab[path] || null;
612318
612879
  let chatSession = null;
612319
612880
  if (tab === 'chat') {
612320
- const qs = location.search.replace(/^?/, '').trim();
612881
+ const qs = location.search.replace(/^\\?/, '').trim();
612321
612882
  if (qs) {
612322
612883
  try {
612323
612884
  const params = new URLSearchParams(location.search);
@@ -614459,7 +615020,7 @@ function routePathForTab(tab) {
614459
615020
  return map[tab] || '/chat';
614460
615021
  }
614461
615022
  function tabForRoutePath(pathname) {
614462
- const path = (pathname || '/').replace(//+$/, '') || '/';
615023
+ const path = (pathname || '/').replace(/\\/+$/, '') || '/';
614463
615024
  const map = {
614464
615025
  '/': 'chat',
614465
615026
  '/chat': 'chat',
@@ -614475,7 +615036,7 @@ function tabForRoutePath(pathname) {
614475
615036
  return map[path] || 'chat';
614476
615037
  }
614477
615038
  function chatSessionFromRouteSearch(search) {
614478
- const qs = String(search || '').replace(/^?/, '').trim();
615039
+ const qs = String(search || '').replace(/^\\?/, '').trim();
614479
615040
  if (!qs) return null;
614480
615041
  try {
614481
615042
  const params = new URLSearchParams(search);
@@ -614527,6 +615088,7 @@ function switchTab(tab, opts) {
614527
615088
  try { loadVoiceTab(); } catch {}
614528
615089
  }
614529
615090
  }
615091
+ window.switchTab = switchTab;
614530
615092
  window.addEventListener('popstate', () => {
614531
615093
  const tab = tabForRoutePath(location.pathname);
614532
615094
  if (tab === 'chat') {
@@ -618789,7 +619351,7 @@ function getOpenApiSpec() {
618789
619351
  get: { summary: "Get daemon configuration and settings", tags: ["Config"], responses: { 200: { description: "Config + settings (secrets redacted)" } } },
618790
619352
  patch: {
618791
619353
  summary: "Update configuration (PT-01 full settings surface)",
618792
- description: "Includes TUI personality, deep_context, bruteforce, emojis, colors, stream, voice*, telegram*, cohere, commandsMode, updateMode, style.",
619354
+ description: "Includes TUI personality, deep_context, bruteforce, emojis, colors, stream, voice*, telegram*, cohere, commandsMode, selfModify, updateMode, style.",
618793
619355
  tags: ["Config"],
618794
619356
  requestBody: { required: true, content: { "application/json": { schema: { type: "object" } } } },
618795
619357
  responses: { 200: { description: "Updated" }, 400: { description: "Invalid body", content: { "application/problem+json": { schema: { $ref: "#/components/schemas/ProblemDetails" } } } } }
@@ -623480,6 +624042,9 @@ async function handlePatchConfig(req2, res) {
623480
624042
  if (updates["commandsMode"] === "auto" || updates["commandsMode"] === "manual") {
623481
624043
  settingsUpdate.commandsMode = updates["commandsMode"];
623482
624044
  }
624045
+ if (typeof updates["selfModify"] === "boolean") {
624046
+ settingsUpdate.selfModify = updates["selfModify"];
624047
+ }
623483
624048
  if (updates["updateMode"] === "auto" || updates["updateMode"] === "manual") {
623484
624049
  settingsUpdate.updateMode = updates["updateMode"];
623485
624050
  }
@@ -628803,7 +629368,7 @@ async function runSelfImprovementCycle(repoRoot) {
628803
629368
  } catch {
628804
629369
  }
628805
629370
  }
628806
- function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine, flowEnabled, slashCommandHandler, thinkingEnabled, askUserCallback) {
629371
+ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine, flowEnabled, slashCommandHandler, thinkingEnabled, askUserCallback, selfModifyEnabled) {
628807
629372
  const voiceStyleMap = {
628808
629373
  concise: 1,
628809
629374
  balanced: 3,
@@ -628921,9 +629486,27 @@ ${lines.join("\n")}
628921
629486
  if (taskType && modelTier !== "small") {
628922
629487
  dynamicContext += "\n\n" + buildTaskContext(taskType);
628923
629488
  }
629489
+ let persistedSelfModify = false;
629490
+ let persistedCommandsMode = "manual";
629491
+ try {
629492
+ const mergedSettings = {
629493
+ ...loadGlobalSettings(),
629494
+ ...loadProjectSettings(repoRoot)
629495
+ };
629496
+ persistedSelfModify = mergedSettings.selfModify ?? false;
629497
+ persistedCommandsMode = mergedSettings.commandsMode ?? "manual";
629498
+ } catch {
629499
+ }
629500
+ const effectiveSelfModifyEnabled = selfModifyEnabled ?? persistedSelfModify;
629501
+ const effectiveCommandsMode = persistedCommandsMode;
628924
629502
  if (flowEnabled) {
628925
629503
  dynamicContext += "\n\n" + FLOWSTATE_PROMPT;
628926
629504
  }
629505
+ if (effectiveSelfModifyEnabled) {
629506
+ dynamicContext += "\n\n<self-modify-mode>\nSelf-modify mode is ON. The user has granted permission for you to cognitively decide when a natural-language request is really a request to change Omnius runtime/UI behavior.\nWhen the request is about Omnius itself (for example updating Omnius with 'update quick' or 'update check', changing color/theme, changing style, toggling thinking/streaming/deep/flow/verbose, viewing stats/config), use the slash_command tool with the appropriate slash command instead of only explaining the command.\nDo not use hardcoded keyword aliasing. Infer intent from the full request, prefer the narrowest slash command, and report what command you invoked and the result.\n" + describeSelfModifyCommandPolicy() + "\n</self-modify-mode>";
629507
+ } else {
629508
+ dynamicContext += "\n\n<self-modify-mode>\nSelf-modify mode is OFF by default. Do not convert natural-language requests into self-modifying slash-command calls unless /commands auto separately enables safe command tooling. For runtime/UI changes, tell the user the exact slash command to run or ask them to enable /selfmodify on.\n</self-modify-mode>";
629509
+ }
628927
629510
  if (modelCaps?.vision) {
628928
629511
  dynamicContext += `
628929
629512
 
@@ -629165,6 +629748,14 @@ RULES:
629165
629748
  } catch {
629166
629749
  }
629167
629750
  const tools = buildTools(repoRoot, config, contextWindowSize, modelTier);
629751
+ if (!slashCommandHandler && (effectiveSelfModifyEnabled || effectiveCommandsMode === "auto")) {
629752
+ slashCommandHandler = async (command) => {
629753
+ const { runCommand: runCommand3 } = await Promise.resolve().then(() => (init_command_passthrough(), command_passthrough_exports));
629754
+ const result = await runCommand3(command, { repoRoot, config });
629755
+ if (result.ok) return result.output || `Command executed: ${command}`;
629756
+ return result.error || result.output || `Command failed: ${command}`;
629757
+ };
629758
+ }
629168
629759
  if (contextWindowSize && contextWindowSize > 0) {
629169
629760
  for (const tool of tools) {
629170
629761
  if ("setContextWindowSize" in tool && typeof tool.setContextWindowSize === "function") {
@@ -629562,13 +630153,13 @@ ${entry.fullContent}`
629562
630153
  {
629563
630154
  runner.registerTool({
629564
630155
  name: "slash_command",
629565
- description: "Invoke a slash command programmatically. Available when /commands auto is set by the user. " + describeAgentToolCommandPolicy() + " Use this to check configuration, run evaluations, discover skills, or adjust modes during a task.",
630156
+ description: "Invoke an Omnius slash command programmatically. Available when /commands auto or /selfmodify on is set by the user. " + (effectiveSelfModifyEnabled ? describeSelfModifyCommandPolicy() : describeAgentToolCommandPolicy()) + " Use this only when the user intent is to operate Omnius itself: check configuration, run evaluations, discover skills, update Omnius, or adjust runtime/UI modes during a task.",
629566
630157
  parameters: {
629567
630158
  type: "object",
629568
630159
  properties: {
629569
630160
  command: {
629570
630161
  type: "string",
629571
- description: "The slash command to run (without leading /), e.g. 'stats', 'skills security', 'verbose', 'config'"
630162
+ description: "The slash command to run (without leading /), e.g. 'stats', 'skills security', 'verbose', 'config', 'color branding', 'update quick'"
629572
630163
  }
629573
630164
  },
629574
630165
  required: ["command"]
@@ -629583,7 +630174,7 @@ ${entry.fullContent}`
629583
630174
  };
629584
630175
  }
629585
630176
  const cmdName = cmd.split(/\s+/)[0].toLowerCase();
629586
- const commandPolicy = getAgentToolCommandPolicy(cmd);
630177
+ const commandPolicy = effectiveSelfModifyEnabled ? getSelfModifyCommandPolicy(cmd) : getAgentToolCommandPolicy(cmd);
629587
630178
  if (!commandPolicy.allowed) {
629588
630179
  return {
629589
630180
  success: false,
@@ -629595,7 +630186,7 @@ ${entry.fullContent}`
629595
630186
  return {
629596
630187
  success: false,
629597
630188
  output: "",
629598
- error: "Slash commands are in manual mode. Ask the user to run /commands auto to enable agent access, or ask the user to run the command directly."
630189
+ error: "Slash commands are in manual mode. Ask the user to run /selfmodify on for natural-language self-modification, /commands auto for safe command tooling, or ask the user to run the command directly."
629599
630190
  };
629600
630191
  }
629601
630192
  try {
@@ -630834,6 +631425,7 @@ async function startInteractive(config, repoPath) {
630834
631425
  let flowEnabled = savedSettings.flow === true;
630835
631426
  let cohereEnabled = savedSettings.cohere ?? false;
630836
631427
  let commandsMode = savedSettings.commandsMode ?? "manual";
631428
+ let selfModifyEnabled = savedSettings.selfModify ?? false;
630837
631429
  if (savedSettings.emojis !== void 0)
630838
631430
  setEmojisEnabled(savedSettings.emojis);
630839
631431
  if (savedSettings.colors !== void 0)
@@ -631599,7 +632191,8 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
631599
632191
  return /^-?\d+$/.test(raw) ? Number(raw) : raw;
631600
632192
  };
631601
632193
  const reminderText = (reminder) => {
631602
- const content = reminder.delivery?.content || reminder.message;
632194
+ const rawContent2 = reminder.delivery?.content || reminder.message;
632195
+ const content = sanitizeReminderDeliveryText(rawContent2) || rawContent2;
631603
632196
  const prefix = reminder.priority === "critical" ? "URGENT reminder" : "Reminder";
631604
632197
  return `${prefix}: ${content}`;
631605
632198
  };
@@ -633098,6 +633691,12 @@ The user pasted a clipboard image saved at ${relPath}. Use the OCR, vision analy
633098
633691
  setCommandsMode(mode) {
633099
633692
  commandsMode = mode;
633100
633693
  },
633694
+ getSelfModify() {
633695
+ return selfModifyEnabled;
633696
+ },
633697
+ setSelfModify(enabled2) {
633698
+ selfModifyEnabled = enabled2;
633699
+ },
633101
633700
  setStyle(preset) {
633102
633701
  currentStyle = preset;
633103
633702
  },
@@ -634825,7 +635424,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
634825
635424
  pasteIndicatorShown = true;
634826
635425
  }
634827
635426
  const buildSlashCommandHandler = () => {
634828
- if (commandsMode !== "auto") return void 0;
635427
+ if (commandsMode !== "auto" && !selfModifyEnabled) return void 0;
634829
635428
  return async (command) => {
634830
635429
  const captured = [];
634831
635430
  const origWrite = process.stdout.write;
@@ -635033,7 +635632,8 @@ Execute this skill now. Follow the behavioral guidance above.`;
635033
635632
  flowEnabled,
635034
635633
  buildSlashCommandHandler(),
635035
635634
  thinkingEnabled,
635036
- handleAskUser
635635
+ handleAskUser,
635636
+ selfModifyEnabled
635037
635637
  );
635038
635638
  activeTask = task;
635039
635639
  setTerminalTitle(input.slice(0, 60), version4);
@@ -635417,7 +636017,8 @@ NEW TASK: ${fullInput}`;
635417
636017
  flowEnabled,
635418
636018
  buildSlashCommandHandler(),
635419
636019
  thinkingEnabled,
635420
- handleAskUser
636020
+ handleAskUser,
636021
+ selfModifyEnabled
635421
636022
  );
635422
636023
  activeTask = task;
635423
636024
  _recallText = null;
@@ -636776,6 +637377,7 @@ function handleShow(opts, config) {
636776
637377
  printKeyValue("voiceModel", String(resolved.voiceModel ?? "glados"), 2);
636777
637378
  printSection("Agent Autonomy");
636778
637379
  printKeyValue("commandsMode", String(resolved.commandsMode ?? "manual"), 2);
637380
+ printKeyValue("selfModify", String(resolved.selfModify ?? false), 2);
636779
637381
  printKeyValue("updateMode", String(resolved.updateMode ?? "auto"), 2);
636780
637382
  printSection("Integrations");
636781
637383
  printKeyValue("telegramKey", resolved.telegramKey ? redactIfSensitive("telegramKey", resolved.telegramKey) : "[not set]", 2);
@@ -636916,6 +637518,7 @@ var init_config6 = __esm({
636916
637518
  voiceModel: "TTS voice model: glados, overwatch",
636917
637519
  // -- Agent autonomy --
636918
637520
  commandsMode: "Agent slash-command access: auto (agent can invoke) or manual (user-only)",
637521
+ selfModify: "Agent self-modify slash-command access for natural-language runtime/UI requests (true/false)",
636919
637522
  updateMode: "Update behaviour: auto (after task completion) or manual (/update only)",
636920
637523
  // -- Integrations --
636921
637524
  telegramKey: "Telegram bot API token for /telegram bridge",
@@ -636923,7 +637526,7 @@ var init_config6 = __esm({
636923
637526
  };
636924
637527
  SENSITIVE_KEYS = /* @__PURE__ */ new Set(["apiKey", "api_key", "secret", "password", "token", "telegramKey"]);
636925
637528
  INT_KEYS = /* @__PURE__ */ new Set(["maxRetries", "timeoutMs"]);
636926
- BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream", "bruteforce", "deepContext", "emojis", "colors"]);
637529
+ BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream", "bruteforce", "deepContext", "emojis", "colors", "selfModify"]);
636927
637530
  ENUM_KEYS = {
636928
637531
  commandsMode: ["auto", "manual"],
636929
637532
  updateMode: ["auto", "manual"],