@sideboard-ai/core 0.1.326 → 0.1.328

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/{abletime-RYYFGKMU.js → abletime-NW4PJGHN.js} +4 -5
  2. package/dist/{abletime-RDHYSZL2.js → abletime-OW7LTFQD.js} +4 -5
  3. package/dist/{abletime-oauth-OYGS2DAU.js → abletime-oauth-KWQQRHOQ.js} +3 -4
  4. package/dist/{abletime-oauth-QR2QDVMK.js → abletime-oauth-TPR2QDRI.js} +3 -4
  5. package/dist/{abletime-rest-QDPTWNS4.js → abletime-rest-5DCWN3P4.js} +3 -4
  6. package/dist/{abletime-rest-BKYKNPTC.js → abletime-rest-UJ6SXDBN.js} +3 -4
  7. package/dist/agents/claude-runner.cjs +11 -0
  8. package/dist/agents/claude-runner.js +7 -1
  9. package/dist/{agents-QO7GKBYT.js → agents-IYBY3JT5.js} +8 -9
  10. package/dist/{agents-HANWZNI6.js → agents-XWIPONJR.js} +8 -9
  11. package/dist/{app-settings-RXBH7KY4.js → app-settings-A5D364JD.js} +1 -2
  12. package/dist/{app-settings-TUVLZNDE.js → app-settings-SA4HX3ZQ.js} +1 -2
  13. package/dist/{chunk-3Q4XNYHY.js → chunk-2GEUFPQQ.js} +1 -1
  14. package/dist/{chunk-MAW7DIG7.js → chunk-4F7THWIO.js} +2 -2
  15. package/dist/{chunk-K7M7MIWL.js → chunk-4G3W23X3.js} +3 -3
  16. package/dist/{chunk-Q3FHTGIW.js → chunk-5Y2C7Q55.js} +441 -20
  17. package/dist/{chunk-TMCK5TLA.js → chunk-ATQLP22S.js} +1 -1
  18. package/dist/{chunk-PB6HW7SA.js → chunk-CHYUUETG.js} +3 -3
  19. package/dist/{chunk-QPSKQP6E.js → chunk-D446IWPP.js} +48 -3
  20. package/dist/{chunk-C7HUCBX7.js → chunk-DACWFN4O.js} +5 -5
  21. package/dist/{chunk-B244TJWC.js → chunk-ECY7WL6T.js} +2 -2
  22. package/dist/{chunk-D744HOI5.js → chunk-EJRCHCXM.js} +18 -3
  23. package/dist/{chunk-KXRF6RNL.js → chunk-EUIVQAPK.js} +2 -2
  24. package/dist/{chunk-XLVLMQKN.js → chunk-EYDLXVED.js} +40 -25
  25. package/dist/{chunk-75RPUXJS.js → chunk-JKHCADKB.js} +5 -5
  26. package/dist/{chunk-4H5MWZ6J.js → chunk-KLVW3J2B.js} +2 -2
  27. package/dist/{chunk-QG4CYRA3.js → chunk-MO46K6WK.js} +14 -14
  28. package/dist/{chunk-F2PVRF3B.js → chunk-N6ZF3XRP.js} +2 -1
  29. package/dist/chunk-NJV5YEKI.js +194 -0
  30. package/dist/{chunk-BBNWKL4I.js → chunk-NQGLDQYB.js} +8 -8
  31. package/dist/{chunk-YGVGXQA7.js → chunk-Q4UPRQOS.js} +12 -12
  32. package/dist/{chunk-ZUAYA57W.js → chunk-QMLDR63X.js} +2 -2
  33. package/dist/{chunk-KECHK3ZT.js → chunk-R2C45NHT.js} +2 -2
  34. package/dist/{chunk-FWVUGLBG.js → chunk-RDXODLEN.js} +4 -4
  35. package/dist/{chunk-JM2TGOHP.js → chunk-SB5ADEZZ.js} +5 -5
  36. package/dist/{chunk-SO7G6O24.js → chunk-SFXGD5KJ.js} +1 -1
  37. package/dist/{chunk-LRHUBGEM.js → chunk-TUH4ICHQ.js} +1 -1
  38. package/dist/{chunk-WUCAVIIP.js → chunk-UMXBH344.js} +1 -1
  39. package/dist/{chunk-GP3RLZ6A.js → chunk-WD222BVP.js} +109 -491
  40. package/dist/{chunk-46FECFZU.js → chunk-X3XTO45Y.js} +39 -196
  41. package/dist/{chunk-RZVOX2MT.js → chunk-XBBZZHJY.js} +3 -3
  42. package/dist/{chunk-2EOLWHXH.js → chunk-XOMVOBPU.js} +127 -477
  43. package/dist/{chunk-ZSHS46V5.js → chunk-Y6OHM672.js} +476 -20
  44. package/dist/{chunk-YWQF7WAG.js → chunk-ZPBOWO64.js} +2 -2
  45. package/dist/{conductor-4GVTHUUB.js → conductor-G6GH5ZMU.js} +7 -7
  46. package/dist/{conductor-TGWI6ETZ.js → conductor-U5LGJZUJ.js} +6 -7
  47. package/dist/{coordinator-prompt-3E6SIDTM.js → coordinator-prompt-3PJLU3UH.js} +5 -6
  48. package/dist/{coordinator-prompt-WEYJREV6.js → coordinator-prompt-J7O7JB26.js} +5 -6
  49. package/dist/{global-workspace-UIEXAG2B.js → global-workspace-4CTLU2BV.js} +5 -6
  50. package/dist/{global-workspace-FPDZKUPF.js → global-workspace-Y3UDZYJX.js} +5 -6
  51. package/dist/index.cjs +605 -508
  52. package/dist/index.d.cts +23 -11
  53. package/dist/index.d.ts +23 -11
  54. package/dist/index.js +67 -65
  55. package/dist/{linear-6S7MK5YK.js → linear-CXWGH2TW.js} +2 -3
  56. package/dist/{linear-Z2T4KUAQ.js → linear-WLDCJ3AF.js} +2 -3
  57. package/dist/{linear-list-Q4M7AZO2.js → linear-list-52LEDEWO.js} +1 -1
  58. package/dist/{linear-list-A37WLUBO.js → linear-list-FGVN65JU.js} +1 -1
  59. package/dist/mcp/run-stdio.cjs +19762 -19682
  60. package/dist/mcp/run-stdio.js +33 -34
  61. package/dist/{optional-cli-CA4LL322.js → optional-cli-FN4MGB6W.js} +4 -5
  62. package/dist/{optional-cli-AANBL4U5.js → optional-cli-INRVBBBG.js} +3 -4
  63. package/dist/{orchestrator-RQXRZW7Z.js → orchestrator-3F53AZEN.js} +11 -12
  64. package/dist/{orchestrator-7FVDBE7Y.js → orchestrator-YY2JUYKD.js} +12 -12
  65. package/dist/{thread-store-4ZN3DS2W.js → thread-store-4WE24XA2.js} +6 -2
  66. package/dist/{thread-store-IC4BQXDW.js → thread-store-K2ZNGUSM.js} +7 -3
  67. package/dist/{workspaces-LEW4UP56.js → workspaces-MM7CQYR6.js} +5 -6
  68. package/dist/{workspaces-QONNLHNR.js → workspaces-WYCVZ6XH.js} +5 -6
  69. package/dist/{worktree-KFEJAQ2H.js → worktree-JBNEMJ5Z.js} +7 -8
  70. package/dist/{worktree-6GIONDQO.js → worktree-NNVRGPP4.js} +4 -5
  71. package/package.json +1 -1
  72. package/dist/chunk-77WWLBCI.js +0 -50
  73. package/dist/chunk-KGZBYWZ3.js +0 -23
package/dist/index.cjs CHANGED
@@ -133,7 +133,8 @@ var init_claude_mcp = __esm({
133
133
  "TaskStop",
134
134
  "EnterWorktree",
135
135
  "ExitWorktree",
136
- "Skill"
136
+ "Skill",
137
+ "AskUserQuestion"
137
138
  ];
138
139
  CLAUDE_PRINT_BG_WAIT_CEILING_MS = 72e5;
139
140
  CLAUDE_CHROME_ALLOWED_TOOLS = [
@@ -5185,6 +5186,449 @@ var init_cursor_store = __esm({
5185
5186
  }
5186
5187
  });
5187
5188
 
5189
+ // src/store/schedule-when.ts
5190
+ function parseDurationMs(every) {
5191
+ const m = every.trim().match(DURATION_RE);
5192
+ if (!m) return null;
5193
+ const n = Number.parseInt(m[1], 10);
5194
+ if (!Number.isFinite(n) || n <= 0) return null;
5195
+ const unit = m[2].toLowerCase();
5196
+ return n * UNIT_MS[unit];
5197
+ }
5198
+ function dayIndex(token) {
5199
+ const text7 = token.trim().toLowerCase();
5200
+ if (/^[0-6]$/.test(text7)) return Number(text7);
5201
+ return DAY_ALIASES[text7];
5202
+ }
5203
+ function normalizeWeekdays(days) {
5204
+ const set = /* @__PURE__ */ new Set();
5205
+ for (const day of days) {
5206
+ if (!Number.isInteger(day) || day < 0 || day > 6) {
5207
+ throw new Error("Days must be 0 (Sunday) through 6 (Saturday)");
5208
+ }
5209
+ set.add(day);
5210
+ }
5211
+ if (set.size === 0) throw new Error("Pick at least one day");
5212
+ return [...set].sort((a, b) => a - b);
5213
+ }
5214
+ function parseWeeklyDays(raw) {
5215
+ const text7 = raw.trim().toLowerCase();
5216
+ if (!text7) throw new Error("Pick at least one day");
5217
+ if (text7 === "daily" || text7 === "all" || text7 === "every day" || text7 === "everyday") {
5218
+ return [0, 1, 2, 3, 4, 5, 6];
5219
+ }
5220
+ if (text7 === "weekdays" || text7 === "weekday") return [1, 2, 3, 4, 5];
5221
+ if (text7 === "weekends" || text7 === "weekend") return [0, 6];
5222
+ const days = [];
5223
+ for (const part of text7.split(/[\s,]+/).filter(Boolean)) {
5224
+ const range = part.split("-");
5225
+ if (range.length === 2 && range[0] && range[1]) {
5226
+ const start = dayIndex(range[0]);
5227
+ const end = dayIndex(range[1]);
5228
+ if (start === void 0 || end === void 0) {
5229
+ throw new Error(`Unknown day: ${part} (use mon,tue, weekdays, weekends, or daily)`);
5230
+ }
5231
+ if (start <= end) {
5232
+ for (let d = start; d <= end; d++) days.push(d);
5233
+ } else {
5234
+ for (let d = start; d <= 6; d++) days.push(d);
5235
+ for (let d = 0; d <= end; d++) days.push(d);
5236
+ }
5237
+ continue;
5238
+ }
5239
+ const n = dayIndex(part);
5240
+ if (n === void 0) {
5241
+ throw new Error(`Unknown day: ${part} (use mon,tue, weekdays, weekends, or daily)`);
5242
+ }
5243
+ days.push(n);
5244
+ }
5245
+ return normalizeWeekdays(days);
5246
+ }
5247
+ function normalizeWeeklyTime(raw) {
5248
+ const m = raw.trim().match(TIME_RE);
5249
+ if (!m) throw new Error(`Invalid time: ${raw} (use HH:mm, 24-hour)`);
5250
+ return `${m[1]}:${m[2]}`;
5251
+ }
5252
+ function formatWeeklyDays(days) {
5253
+ const key = normalizeWeekdays(days).join(",");
5254
+ if (key === "0,1,2,3,4,5,6") return "daily";
5255
+ if (key === "1,2,3,4,5") return "weekdays";
5256
+ if (key === "0,6") return "weekends";
5257
+ return normalizeWeekdays(days).map((day) => WEEKDAY_LABELS[day]).join(", ");
5258
+ }
5259
+ function weeklyCronExpr(when) {
5260
+ const time = normalizeWeeklyTime(when.time);
5261
+ const [hh, mm] = time.split(":");
5262
+ const days = normalizeWeekdays(when.days);
5263
+ return `${Number(mm)} ${Number(hh)} * * ${days.join(",")}`;
5264
+ }
5265
+ function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
5266
+ if (when.kind === "once") {
5267
+ const at = new Date(when.at);
5268
+ if (Number.isNaN(at.getTime())) {
5269
+ throw new Error(`Invalid once-at datetime: ${when.at}`);
5270
+ }
5271
+ return at;
5272
+ }
5273
+ if (when.kind === "every") {
5274
+ const ms = parseDurationMs(when.every);
5275
+ if (!ms) {
5276
+ throw new Error(`Invalid interval: ${when.every} (use 15m, 1h, 6h, 1d)`);
5277
+ }
5278
+ return new Date(from.getTime() + ms);
5279
+ }
5280
+ if (when.kind === "weekly") {
5281
+ return computeNextRunAt(
5282
+ { kind: "cron", expr: weeklyCronExpr(when), tz: when.tz },
5283
+ from
5284
+ );
5285
+ }
5286
+ try {
5287
+ const job = new import_croner.Cron(when.expr, {
5288
+ timezone: when.tz?.trim() || void 0,
5289
+ paused: true
5290
+ });
5291
+ const next = job.nextRun(from);
5292
+ if (!next) {
5293
+ throw new Error(`Cron expression has no next run: ${when.expr}`);
5294
+ }
5295
+ return next;
5296
+ } catch (err) {
5297
+ const message = err instanceof Error ? err.message : String(err);
5298
+ throw new Error(`Invalid cron: ${message}`);
5299
+ }
5300
+ }
5301
+ function formatScheduleWhen(when) {
5302
+ if (when.kind === "once") return `once at ${when.at}`;
5303
+ if (when.kind === "every") return `every ${when.every}`;
5304
+ if (when.kind === "weekly") {
5305
+ const label = `${formatWeeklyDays(when.days)} at ${normalizeWeeklyTime(when.time)}`;
5306
+ const tz2 = when.tz?.trim();
5307
+ return tz2 ? `${label} (${tz2})` : label;
5308
+ }
5309
+ const tz = when.tz?.trim();
5310
+ return tz ? `cron ${when.expr} (${tz})` : `cron ${when.expr}`;
5311
+ }
5312
+ function normalizeWhen(raw) {
5313
+ if (!raw || typeof raw !== "object") {
5314
+ throw new Error("Schedule is missing `when`");
5315
+ }
5316
+ const rec = raw;
5317
+ const kind = rec.kind;
5318
+ if (kind === "once" && typeof rec.at === "string") {
5319
+ return { kind: "once", at: rec.at };
5320
+ }
5321
+ if (kind === "every" && typeof rec.every === "string") {
5322
+ return { kind: "every", every: rec.every };
5323
+ }
5324
+ if (kind === "cron" && typeof rec.expr === "string") {
5325
+ return {
5326
+ kind: "cron",
5327
+ expr: rec.expr,
5328
+ tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
5329
+ };
5330
+ }
5331
+ if (kind === "weekly" && Array.isArray(rec.days) && typeof rec.time === "string") {
5332
+ return {
5333
+ kind: "weekly",
5334
+ days: normalizeWeekdays(rec.days.map((day) => Number(day))),
5335
+ time: normalizeWeeklyTime(rec.time),
5336
+ tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
5337
+ };
5338
+ }
5339
+ throw new Error("Schedule `when` must be once, every, weekly, or cron");
5340
+ }
5341
+ var import_croner, WEEKDAY_LABELS, DURATION_RE, UNIT_MS, DAY_ALIASES, TIME_RE;
5342
+ var init_schedule_when = __esm({
5343
+ "src/store/schedule-when.ts"() {
5344
+ "use strict";
5345
+ import_croner = require("croner");
5346
+ WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
5347
+ DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5348
+ UNIT_MS = {
5349
+ s: 1e3,
5350
+ m: 6e4,
5351
+ h: 36e5,
5352
+ d: 864e5
5353
+ };
5354
+ DAY_ALIASES = {
5355
+ sun: 0,
5356
+ sunday: 0,
5357
+ mon: 1,
5358
+ monday: 1,
5359
+ tue: 2,
5360
+ tues: 2,
5361
+ tuesday: 2,
5362
+ wed: 3,
5363
+ wednesday: 3,
5364
+ thu: 4,
5365
+ thur: 4,
5366
+ thurs: 4,
5367
+ thursday: 4,
5368
+ fri: 5,
5369
+ friday: 5,
5370
+ sat: 6,
5371
+ saturday: 6
5372
+ };
5373
+ TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)(?::[0-5]\d)?$/;
5374
+ }
5375
+ });
5376
+
5377
+ // src/store/schedules.ts
5378
+ function schedulesPath() {
5379
+ return (0, import_node_path15.join)(appDataDir(), "schedules.json");
5380
+ }
5381
+ function defaultScheduleName(prompt) {
5382
+ const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
5383
+ if (!line) return "Untitled automation";
5384
+ return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
5385
+ }
5386
+ function resolveScheduleThreadId(raw, env = process.env) {
5387
+ const value = raw?.trim() || "";
5388
+ if (!value) return null;
5389
+ if (value.toLowerCase() === "self") {
5390
+ return env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || env.SIDEBOARD_THREAD_ID?.trim() || null;
5391
+ }
5392
+ return value;
5393
+ }
5394
+ function nowIso(d = /* @__PURE__ */ new Date()) {
5395
+ return d.toISOString();
5396
+ }
5397
+ function normalizeOptionalBool(raw) {
5398
+ if (raw === true || raw === false) return raw;
5399
+ return null;
5400
+ }
5401
+ function normalizeAutonomy2(raw) {
5402
+ return raw === "default" || raw === "full" ? raw : null;
5403
+ }
5404
+ function normalizeAttachments(raw) {
5405
+ if (!Array.isArray(raw)) return [];
5406
+ const out = [];
5407
+ for (const item of raw) {
5408
+ if (!item || typeof item !== "object") continue;
5409
+ const row2 = item;
5410
+ if (typeof row2.id !== "string" || !row2.id.trim()) continue;
5411
+ if (typeof row2.name !== "string" || !row2.name) continue;
5412
+ if (typeof row2.content !== "string") continue;
5413
+ if (!ATTACHMENT_KINDS.has(row2.kind)) continue;
5414
+ const next = {
5415
+ id: row2.id,
5416
+ name: row2.name,
5417
+ kind: row2.kind,
5418
+ content: row2.content
5419
+ };
5420
+ if (typeof row2.path === "string" && row2.path) next.path = row2.path;
5421
+ if (typeof row2.previewDataUrl === "string" && row2.previewDataUrl) {
5422
+ next.previewDataUrl = row2.previewDataUrl;
5423
+ }
5424
+ out.push(next);
5425
+ }
5426
+ return out;
5427
+ }
5428
+ function normalizeTask(raw) {
5429
+ const when = normalizeWhen(raw.when);
5430
+ const agent = raw.agent && isOrchestratorCapableAgent(raw.agent) ? raw.agent : null;
5431
+ return {
5432
+ id: raw.id,
5433
+ name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : defaultScheduleName(raw.prompt),
5434
+ prompt: String(raw.prompt ?? ""),
5435
+ enabled: Boolean(raw.enabled),
5436
+ when,
5437
+ threadId: raw.threadId?.trim() || null,
5438
+ agent,
5439
+ model: raw.model?.trim() || null,
5440
+ effort: normalizeThinkingEffort(raw.effort),
5441
+ fast: normalizeOptionalBool(raw.fast),
5442
+ planMode: normalizeOptionalBool(raw.planMode),
5443
+ autonomy: normalizeAutonomy2(raw.autonomy),
5444
+ attachments: normalizeAttachments(raw.attachments),
5445
+ archiveChat: raw.archiveChat !== false,
5446
+ nextRunAt: raw.nextRunAt,
5447
+ lastRunAt: raw.lastRunAt ?? null,
5448
+ lastThreadId: raw.lastThreadId ?? null,
5449
+ lastError: raw.lastError ?? null,
5450
+ createdBy: raw.createdBy === "mcp" || raw.createdBy === "cli" || raw.createdBy === "ui" ? raw.createdBy : "ui",
5451
+ createdAt: raw.createdAt,
5452
+ updatedAt: raw.updatedAt
5453
+ };
5454
+ }
5455
+ function readAll() {
5456
+ const path2 = schedulesPath();
5457
+ if (!(0, import_node_fs12.existsSync)(path2)) return [];
5458
+ try {
5459
+ const parsed = JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
5460
+ if (!Array.isArray(parsed)) return [];
5461
+ return parsed.map((row2) => {
5462
+ try {
5463
+ return normalizeTask(row2);
5464
+ } catch {
5465
+ return null;
5466
+ }
5467
+ }).filter((row2) => Boolean(row2?.id && row2.prompt));
5468
+ } catch {
5469
+ return [];
5470
+ }
5471
+ }
5472
+ function writeAll(rows) {
5473
+ writePrivateFile(schedulesPath(), `${JSON.stringify(rows, null, 2)}
5474
+ `);
5475
+ }
5476
+ function buildTask(input, now) {
5477
+ const prompt = input.prompt.trim();
5478
+ if (!prompt) throw new Error("Schedule prompt is required");
5479
+ const when = normalizeWhen(input.when);
5480
+ const nextRunAt = computeNextRunAt(when, now).toISOString();
5481
+ const ts = nowIso(now);
5482
+ const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
5483
+ return {
5484
+ id: (0, import_node_crypto5.randomUUID)(),
5485
+ name: input.name?.trim() || defaultScheduleName(prompt),
5486
+ prompt,
5487
+ enabled: input.enabled !== false,
5488
+ when,
5489
+ threadId: input.threadId?.trim() || null,
5490
+ agent,
5491
+ model: input.model?.trim() || null,
5492
+ effort: input.effort != null ? normalizeThinkingEffort(input.effort) : null,
5493
+ fast: normalizeOptionalBool(input.fast),
5494
+ planMode: normalizeOptionalBool(input.planMode),
5495
+ autonomy: normalizeAutonomy2(input.autonomy),
5496
+ attachments: normalizeAttachments(input.attachments),
5497
+ archiveChat: input.archiveChat !== false,
5498
+ nextRunAt,
5499
+ lastRunAt: null,
5500
+ lastThreadId: null,
5501
+ lastError: null,
5502
+ createdBy: input.createdBy,
5503
+ createdAt: ts,
5504
+ updatedAt: ts
5505
+ };
5506
+ }
5507
+ function listSchedules() {
5508
+ return readAll().sort((a, b) => a.nextRunAt.localeCompare(b.nextRunAt));
5509
+ }
5510
+ function hasEnabledSchedules() {
5511
+ return listSchedules().some((row2) => row2.enabled);
5512
+ }
5513
+ function scheduleNeedsCaffeinate(task, now = /* @__PURE__ */ new Date()) {
5514
+ if (!task.enabled) return false;
5515
+ const at = new Date(task.nextRunAt);
5516
+ if (Number.isNaN(at.getTime())) return false;
5517
+ return at.getTime() > now.getTime();
5518
+ }
5519
+ function ensureCaffeinateForFutureSchedule(task) {
5520
+ if (!scheduleNeedsCaffeinate(task)) return;
5521
+ if (caffeinateWhileSchedulesEnabled()) return;
5522
+ updateAdvancedSettings({ caffeinateWhileSchedules: true });
5523
+ }
5524
+ function getSchedule(id) {
5525
+ const key = id.trim();
5526
+ if (!key) return null;
5527
+ return readAll().find(
5528
+ (row2) => row2.id === key || row2.id.startsWith(key) && key.length >= 8
5529
+ ) ?? null;
5530
+ }
5531
+ function createSchedule(input) {
5532
+ const task = buildTask(input, /* @__PURE__ */ new Date());
5533
+ const rows = readAll();
5534
+ rows.push(task);
5535
+ writeAll(rows);
5536
+ ensureCaffeinateForFutureSchedule(task);
5537
+ return task;
5538
+ }
5539
+ function updateSchedule(id, patch) {
5540
+ const rows = readAll();
5541
+ const idx = rows.findIndex((row2) => row2.id === id || id.length >= 8 && row2.id.startsWith(id));
5542
+ if (idx < 0) throw new Error(`Automation not found: ${id}`);
5543
+ const prev = rows[idx];
5544
+ const when = patch.when ? normalizeWhen(patch.when) : prev.when;
5545
+ const prompt = patch.prompt !== void 0 ? patch.prompt.trim() : prev.prompt;
5546
+ if (!prompt) throw new Error("Schedule prompt is required");
5547
+ const whenChanged = patch.when !== void 0;
5548
+ const next = {
5549
+ ...prev,
5550
+ name: patch.name !== void 0 ? patch.name.trim() || defaultScheduleName(prompt) : prev.name,
5551
+ prompt,
5552
+ enabled: patch.enabled ?? prev.enabled,
5553
+ when,
5554
+ threadId: patch.threadId !== void 0 ? patch.threadId?.trim() || null : prev.threadId,
5555
+ agent: patch.agent !== void 0 ? patch.agent ? coerceOrchestratorAgent(patch.agent) : null : prev.agent,
5556
+ model: patch.model !== void 0 ? patch.model?.trim() || null : prev.model,
5557
+ effort: patch.effort !== void 0 ? normalizeThinkingEffort(patch.effort) : prev.effort,
5558
+ fast: patch.fast !== void 0 ? normalizeOptionalBool(patch.fast) : prev.fast,
5559
+ planMode: patch.planMode !== void 0 ? normalizeOptionalBool(patch.planMode) : prev.planMode,
5560
+ autonomy: patch.autonomy !== void 0 ? normalizeAutonomy2(patch.autonomy) : prev.autonomy,
5561
+ attachments: patch.attachments !== void 0 ? normalizeAttachments(patch.attachments) : prev.attachments,
5562
+ archiveChat: patch.archiveChat !== void 0 ? patch.archiveChat !== false : prev.archiveChat,
5563
+ nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
5564
+ lastError: whenChanged ? null : prev.lastError,
5565
+ updatedAt: nowIso()
5566
+ };
5567
+ rows[idx] = next;
5568
+ writeAll(rows);
5569
+ ensureCaffeinateForFutureSchedule(next);
5570
+ return next;
5571
+ }
5572
+ function deleteSchedulesForThread(threadId) {
5573
+ const id = threadId.trim();
5574
+ if (!id) return;
5575
+ const rows = readAll();
5576
+ const next = rows.filter((row2) => row2.threadId !== id);
5577
+ if (next.length === rows.length) return;
5578
+ writeAll(next);
5579
+ }
5580
+ function deleteSchedule(id) {
5581
+ const rows = readAll();
5582
+ const next = rows.filter(
5583
+ (row2) => row2.id !== id && !(id.length >= 8 && row2.id.startsWith(id))
5584
+ );
5585
+ if (next.length === rows.length) throw new Error(`Automation not found: ${id}`);
5586
+ writeAll(next);
5587
+ }
5588
+ function recordScheduleRun(id, result) {
5589
+ const rows = readAll();
5590
+ const idx = rows.findIndex((row2) => row2.id === id);
5591
+ if (idx < 0) throw new Error(`Automation not found: ${id}`);
5592
+ const prev = rows[idx];
5593
+ const firedAt = result.firedAt ?? /* @__PURE__ */ new Date();
5594
+ const next = {
5595
+ ...prev,
5596
+ enabled: prev.when.kind === "once" ? false : prev.enabled,
5597
+ nextRunAt: prev.when.kind === "once" ? prev.nextRunAt : computeNextRunAt(prev.when, firedAt).toISOString(),
5598
+ lastRunAt: nowIso(firedAt),
5599
+ lastThreadId: result.lastThreadId !== void 0 ? result.lastThreadId : prev.lastThreadId,
5600
+ lastError: result.lastError ?? null,
5601
+ updatedAt: nowIso(firedAt)
5602
+ };
5603
+ rows[idx] = next;
5604
+ writeAll(rows);
5605
+ return next;
5606
+ }
5607
+ var import_node_crypto5, import_node_fs12, import_node_path15, ATTACHMENT_KINDS;
5608
+ var init_schedules = __esm({
5609
+ "src/store/schedules.ts"() {
5610
+ "use strict";
5611
+ import_node_crypto5 = require("crypto");
5612
+ import_node_fs12 = require("fs");
5613
+ import_node_path15 = require("path");
5614
+ init_schedule_when();
5615
+ init_orchestrator_capable();
5616
+ init_thinking_effort();
5617
+ init_paths();
5618
+ init_private_file();
5619
+ init_app_settings();
5620
+ init_schedule_when();
5621
+ ATTACHMENT_KINDS = /* @__PURE__ */ new Set([
5622
+ "transcript",
5623
+ "file",
5624
+ "issue",
5625
+ "workspace",
5626
+ "diff-comment",
5627
+ "code-ref"
5628
+ ]);
5629
+ }
5630
+ });
5631
+
5188
5632
  // src/store/thread-store.ts
5189
5633
  var thread_store_exports = {};
5190
5634
  __export(thread_store_exports, {
@@ -5205,7 +5649,7 @@ __export(thread_store_exports, {
5205
5649
  withThreadLock: () => withThreadLock,
5206
5650
  writeThread: () => writeThread
5207
5651
  });
5208
- function nowIso() {
5652
+ function nowIso2() {
5209
5653
  return (/* @__PURE__ */ new Date()).toISOString();
5210
5654
  }
5211
5655
  function resolveThreadEffort(raw) {
@@ -5294,9 +5738,9 @@ function normalizeThread(raw) {
5294
5738
  };
5295
5739
  }
5296
5740
  function createEmptyThread(partial) {
5297
- const ts = nowIso();
5741
+ const ts = nowIso2();
5298
5742
  return {
5299
- id: (0, import_node_crypto5.randomUUID)(),
5743
+ id: (0, import_node_crypto6.randomUUID)(),
5300
5744
  sessionId: partial.sessionId ?? null,
5301
5745
  autonomy: partial.autonomy ?? "default",
5302
5746
  model: partial.model ?? null,
@@ -5349,7 +5793,7 @@ function createEmptyThread(partial) {
5349
5793
  }
5350
5794
  async function withThreadLock(id, fn) {
5351
5795
  const lockPath = threadLockPath(id);
5352
- (0, import_node_fs12.writeFileSync)(lockPath, "", { flag: "a" });
5796
+ (0, import_node_fs13.writeFileSync)(lockPath, "", { flag: "a" });
5353
5797
  let release;
5354
5798
  try {
5355
5799
  release = await import_proper_lockfile.default.lock(lockPath, {
@@ -5370,7 +5814,7 @@ function cacheForDir() {
5370
5814
  }
5371
5815
  function fileMtimeMs(path2) {
5372
5816
  try {
5373
- return (0, import_node_fs12.statSync)(path2).mtimeMs;
5817
+ return (0, import_node_fs13.statSync)(path2).mtimeMs;
5374
5818
  } catch {
5375
5819
  return null;
5376
5820
  }
@@ -5403,7 +5847,7 @@ function readThread(id) {
5403
5847
  const cache2 = cacheForDir();
5404
5848
  const cached2 = cache2.byId.get(id);
5405
5849
  if (cached2 && cache2.mtimeMs.get(id) === mtimeMs) return cached2;
5406
- const raw = (0, import_node_fs12.readFileSync)(path2, "utf8");
5850
+ const raw = (0, import_node_fs13.readFileSync)(path2, "utf8");
5407
5851
  const thread = normalizeThread(JSON.parse(raw));
5408
5852
  rememberThread(thread, mtimeMs);
5409
5853
  return thread;
@@ -5411,9 +5855,9 @@ function readThread(id) {
5411
5855
  function writeThread(thread, opts) {
5412
5856
  const path2 = threadFilePath(idPath(thread.id));
5413
5857
  const tmp = `${path2}.${process.pid}.tmp`;
5414
- const next = opts?.touch === false ? { ...thread } : { ...thread, updatedAt: nowIso() };
5415
- (0, import_node_fs12.writeFileSync)(tmp, JSON.stringify(next, null, 2), "utf8");
5416
- (0, import_node_fs12.renameSync)(tmp, path2);
5858
+ const next = opts?.touch === false ? { ...thread } : { ...thread, updatedAt: nowIso2() };
5859
+ (0, import_node_fs13.writeFileSync)(tmp, JSON.stringify(next, null, 2), "utf8");
5860
+ (0, import_node_fs13.renameSync)(tmp, path2);
5417
5861
  const mtimeMs = fileMtimeMs(path2) ?? Date.now();
5418
5862
  rememberThread(next, mtimeMs);
5419
5863
  selfWriteMtimes.set(thread.id, mtimeMs);
@@ -5427,12 +5871,12 @@ function idPath(id) {
5427
5871
  return id;
5428
5872
  }
5429
5873
  function isThreadRecordFile(nameOrPath) {
5430
- const name = (0, import_node_path15.basename)(nameOrPath);
5874
+ const name = (0, import_node_path16.basename)(nameOrPath);
5431
5875
  return name.endsWith(".json") && !name.endsWith(".live.json") && !name.endsWith(".setup.log.json");
5432
5876
  }
5433
5877
  function listThreads(opts) {
5434
5878
  const cache2 = cacheForDir();
5435
- const files = (0, import_node_fs12.readdirSync)(threadsDir()).filter(isThreadRecordFile);
5879
+ const files = (0, import_node_fs13.readdirSync)(threadsDir()).filter(isThreadRecordFile);
5436
5880
  const seen = /* @__PURE__ */ new Set();
5437
5881
  for (const f of files) {
5438
5882
  const id = f.replace(/\.json$/, "");
@@ -5455,30 +5899,29 @@ function listThreads(opts) {
5455
5899
  function deleteThreadRecord(id) {
5456
5900
  forgetThread(id);
5457
5901
  const path2 = threadFilePath(id);
5458
- if ((0, import_node_fs12.existsSync)(path2)) (0, import_node_fs12.unlinkSync)(path2);
5459
- const lock = threadLockPath(id);
5460
- if ((0, import_node_fs12.existsSync)(lock)) {
5461
- try {
5462
- (0, import_node_fs12.unlinkSync)(lock);
5463
- } catch {
5464
- }
5902
+ if ((0, import_node_fs13.existsSync)(path2)) (0, import_node_fs13.unlinkSync)(path2);
5903
+ try {
5904
+ if ((0, import_node_fs13.existsSync)(threadLockPath(id))) (0, import_node_fs13.unlinkSync)(threadLockPath(id));
5905
+ } catch {
5465
5906
  }
5466
5907
  try {
5467
- (0, import_node_fs12.rmSync)(cursorSdkStoreDir(id), { recursive: true, force: true });
5908
+ (0, import_node_fs13.rmSync)(cursorSdkStoreDir(id), { recursive: true, force: true });
5468
5909
  } catch {
5469
5910
  }
5911
+ deleteSchedulesForThread(id);
5470
5912
  }
5471
5913
  function updateThread(id, patch) {
5472
5914
  const current = readThread(id);
5473
5915
  if (!current) throw new Error(`Thread not found: ${id}`);
5474
- const next = { ...current, ...patch, id: current.id, updatedAt: nowIso() };
5916
+ const next = { ...current, ...patch, id: current.id, updatedAt: nowIso2() };
5475
5917
  if (next.status === "archived") {
5476
- next.archivedAt = typeof next.archivedAt === "string" && next.archivedAt.trim() || current.archivedAt || nowIso();
5918
+ next.archivedAt = typeof next.archivedAt === "string" && next.archivedAt.trim() || current.archivedAt || nowIso2();
5477
5919
  } else {
5478
5920
  delete next.archivedAt;
5479
5921
  }
5480
5922
  if (!next.ephemeralAutomationId?.trim()) delete next.ephemeralAutomationId;
5481
5923
  writeThread(next);
5924
+ if (patch.status === "archived" && current.status !== "archived") deleteSchedulesForThread(current.id);
5482
5925
  return next;
5483
5926
  }
5484
5927
  function appendMessage(id, message) {
@@ -5495,19 +5938,20 @@ function findThreadByRef(ref) {
5495
5938
  (t) => t.id === ref || t.id.startsWith(ref) || t.branchName === ref || t.title === ref
5496
5939
  ) ?? null;
5497
5940
  }
5498
- var import_node_crypto5, import_node_fs12, import_node_path15, import_proper_lockfile, listCache, selfWriteMtimes;
5941
+ var import_node_crypto6, import_node_fs13, import_node_path16, import_proper_lockfile, listCache, selfWriteMtimes;
5499
5942
  var init_thread_store = __esm({
5500
5943
  "src/store/thread-store.ts"() {
5501
5944
  "use strict";
5502
- import_node_crypto5 = require("crypto");
5945
+ import_node_crypto6 = require("crypto");
5503
5946
  init_worktree_labels();
5504
- import_node_fs12 = require("fs");
5505
- import_node_path15 = require("path");
5947
+ import_node_fs13 = require("fs");
5948
+ import_node_path16 = require("path");
5506
5949
  import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
5507
5950
  init_thinking_effort();
5508
5951
  init_claude_mcp();
5509
5952
  init_cursor_store();
5510
5953
  init_paths();
5954
+ init_schedules();
5511
5955
  listCache = null;
5512
5956
  selfWriteMtimes = /* @__PURE__ */ new Map();
5513
5957
  }
@@ -6939,9 +7383,6 @@ function lastTopLevelToolIsAskUser(parts) {
6939
7383
  }
6940
7384
  return false;
6941
7385
  }
6942
- function hasTopLevelTool(parts) {
6943
- return Boolean(parts?.some((part) => part.type === "tool" && !part.parentId));
6944
- }
6945
7386
  function questionsFromToolPart(part) {
6946
7387
  const fromInput = parsePlanQuestionsInput(part.input);
6947
7388
  if (fromInput.length) return fromInput;
@@ -6957,7 +7398,7 @@ function extractPendingPlanQuestions(parts) {
6957
7398
  if (!parts?.length) return null;
6958
7399
  for (let i = parts.length - 1; i >= 0; i--) {
6959
7400
  const p = parts[i];
6960
- if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
7401
+ if (p.type !== "tool" || p.parentId || !isAskUserToolName(p.name)) continue;
6961
7402
  const questions2 = questionsFromToolPart(p);
6962
7403
  if (!questions2.length) continue;
6963
7404
  return {
@@ -6997,18 +7438,23 @@ function askBlockStillWaiting(block, messages) {
6997
7438
  }
6998
7439
  function latestPendingPlanQuestions(input) {
6999
7440
  if (input.userReplied) return null;
7441
+ const dismissed = input.dismissedQuestionSignature?.trim();
7442
+ const accept = (pending) => {
7443
+ if (!pending) return null;
7444
+ if (dismissed && pending.signature === dismissed) return null;
7445
+ return pending;
7446
+ };
7000
7447
  const fromBlock = () => pendingFromAskBlock(input.agentBlock, input.dismissedQuestionSignature);
7001
- if (lastTopLevelToolIsAskUser(input.liveParts)) {
7002
- return extractPendingPlanQuestions(input.liveParts) ?? fromBlock();
7003
- }
7448
+ const liveAsk = accept(extractPendingPlanQuestions(input.liveParts));
7449
+ if (liveAsk) return liveAsk;
7004
7450
  const last = input.messages[input.messages.length - 1];
7005
- if (last?.role === "agent" && lastTopLevelToolIsAskUser(last.parts)) {
7006
- return extractPendingPlanQuestions(last.parts) ?? fromBlock();
7451
+ if (last?.role === "agent") {
7452
+ const fromTranscript = accept(extractPendingPlanQuestions(last.parts));
7453
+ if (fromTranscript) return fromTranscript;
7007
7454
  }
7008
- if (hasTopLevelTool(input.liveParts) || last?.role === "agent" && hasTopLevelTool(last.parts)) {
7455
+ if ((input.status === "running" || input.status === "queued") && !input.agentBlock?.onScreen) {
7009
7456
  return null;
7010
7457
  }
7011
- if (input.status === "running" || input.status === "queued") return null;
7012
7458
  const stamped = fromBlock();
7013
7459
  if (stamped && askBlockStillWaiting(input.agentBlock, input.messages)) {
7014
7460
  return stamped;
@@ -8038,11 +8484,11 @@ function materialize(mem) {
8038
8484
  };
8039
8485
  }
8040
8486
  function setupLogPath(threadId) {
8041
- return (0, import_node_path16.join)(threadsDir(), `${threadId}.setup.log.json`);
8487
+ return (0, import_node_path17.join)(threadsDir(), `${threadId}.setup.log.json`);
8042
8488
  }
8043
8489
  function setupLogKeyForWorktree(worktreePath) {
8044
8490
  const norm = normalizeWorktreePath(worktreePath);
8045
- return `wt-${(0, import_node_crypto6.createHash)("sha1").update(norm).digest("hex").slice(0, 16)}`;
8491
+ return `wt-${(0, import_node_crypto7.createHash)("sha1").update(norm).digest("hex").slice(0, 16)}`;
8046
8492
  }
8047
8493
  function emptySetupLog() {
8048
8494
  return { ...EMPTY };
@@ -8064,7 +8510,7 @@ function persistNow(threadId) {
8064
8510
  const mem = memory.get(threadId);
8065
8511
  if (!mem) return;
8066
8512
  try {
8067
- (0, import_node_fs13.writeFileSync)(
8513
+ (0, import_node_fs14.writeFileSync)(
8068
8514
  setupLogPath(threadId),
8069
8515
  `${JSON.stringify(materialize(mem))}
8070
8516
  `,
@@ -8085,9 +8531,9 @@ function schedulePersist(threadId) {
8085
8531
  }
8086
8532
  function loadFromDisk(threadId) {
8087
8533
  const path2 = setupLogPath(threadId);
8088
- if (!(0, import_node_fs13.existsSync)(path2)) return null;
8534
+ if (!(0, import_node_fs14.existsSync)(path2)) return null;
8089
8535
  try {
8090
- const raw = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
8536
+ const raw = JSON.parse((0, import_node_fs14.readFileSync)(path2, "utf8"));
8091
8537
  return {
8092
8538
  output: typeof raw.output === "string" ? raw.output : "",
8093
8539
  running: Boolean(raw.running),
@@ -8143,13 +8589,13 @@ function finishSetupLog(threadId, exitCode, source) {
8143
8589
  persistNow(threadId);
8144
8590
  return materialize(mem);
8145
8591
  }
8146
- var import_node_crypto6, import_node_fs13, import_node_path16, EMPTY, MAX_SETUP_LOG_CHARS, memory, persistTimers, PERSIST_DEBOUNCE_MS;
8592
+ var import_node_crypto7, import_node_fs14, import_node_path17, EMPTY, MAX_SETUP_LOG_CHARS, memory, persistTimers, PERSIST_DEBOUNCE_MS;
8147
8593
  var init_setup_log = __esm({
8148
8594
  "src/store/setup-log.ts"() {
8149
8595
  "use strict";
8150
- import_node_crypto6 = require("crypto");
8151
- import_node_fs13 = require("fs");
8152
- import_node_path16 = require("path");
8596
+ import_node_crypto7 = require("crypto");
8597
+ import_node_fs14 = require("fs");
8598
+ import_node_path17 = require("path");
8153
8599
  init_worktree_labels();
8154
8600
  init_paths();
8155
8601
  EMPTY = {
@@ -8194,11 +8640,11 @@ function safeScriptToken(scriptName) {
8194
8640
  return token || "dev";
8195
8641
  }
8196
8642
  function runLogPath(key) {
8197
- return (0, import_node_path17.join)(threadsDir(), `${key}.run.log.json`);
8643
+ return (0, import_node_path18.join)(threadsDir(), `${key}.run.log.json`);
8198
8644
  }
8199
8645
  function runLogKeyForWorktree(worktreePath, scriptName) {
8200
8646
  const norm = normalizeWorktreePath(worktreePath);
8201
- const hash = (0, import_node_crypto7.createHash)("sha1").update(norm).digest("hex").slice(0, 16);
8647
+ const hash = (0, import_node_crypto8.createHash)("sha1").update(norm).digest("hex").slice(0, 16);
8202
8648
  return `wt-${hash}-run-${safeScriptToken(scriptName)}`;
8203
8649
  }
8204
8650
  function emptyRunLog() {
@@ -8222,7 +8668,7 @@ function persistNow2(key) {
8222
8668
  const mem = memory2.get(key);
8223
8669
  if (!mem) return;
8224
8670
  try {
8225
- (0, import_node_fs14.writeFileSync)(runLogPath(key), `${JSON.stringify(materialize2(mem))}
8671
+ (0, import_node_fs15.writeFileSync)(runLogPath(key), `${JSON.stringify(materialize2(mem))}
8226
8672
  `, "utf8");
8227
8673
  } catch {
8228
8674
  }
@@ -8239,9 +8685,9 @@ function schedulePersist2(key) {
8239
8685
  }
8240
8686
  function loadFromDisk2(key) {
8241
8687
  const path2 = runLogPath(key);
8242
- if (!(0, import_node_fs14.existsSync)(path2)) return null;
8688
+ if (!(0, import_node_fs15.existsSync)(path2)) return null;
8243
8689
  try {
8244
- const raw = JSON.parse((0, import_node_fs14.readFileSync)(path2, "utf8"));
8690
+ const raw = JSON.parse((0, import_node_fs15.readFileSync)(path2, "utf8"));
8245
8691
  return {
8246
8692
  output: typeof raw.output === "string" ? raw.output : "",
8247
8693
  running: Boolean(raw.running),
@@ -8299,13 +8745,13 @@ function finishRunLog(key, exitCode) {
8299
8745
  function flushRunLog(key) {
8300
8746
  persistNow2(key);
8301
8747
  }
8302
- var import_node_crypto7, import_node_fs14, import_node_path17, EMPTY2, MAX_RUN_LOG_CHARS, DEFAULT_RUN_LOG_TAIL_CHARS, memory2, persistTimers2, PERSIST_DEBOUNCE_MS2;
8748
+ var import_node_crypto8, import_node_fs15, import_node_path18, EMPTY2, MAX_RUN_LOG_CHARS, DEFAULT_RUN_LOG_TAIL_CHARS, memory2, persistTimers2, PERSIST_DEBOUNCE_MS2;
8303
8749
  var init_run_log = __esm({
8304
8750
  "src/store/run-log.ts"() {
8305
8751
  "use strict";
8306
- import_node_crypto7 = require("crypto");
8307
- import_node_fs14 = require("fs");
8308
- import_node_path17 = require("path");
8752
+ import_node_crypto8 = require("crypto");
8753
+ import_node_fs15 = require("fs");
8754
+ import_node_path18 = require("path");
8309
8755
  init_worktree_labels();
8310
8756
  init_setup_log();
8311
8757
  init_paths();
@@ -8323,441 +8769,6 @@ var init_run_log = __esm({
8323
8769
  }
8324
8770
  });
8325
8771
 
8326
- // src/store/schedule-when.ts
8327
- function parseDurationMs(every) {
8328
- const m = every.trim().match(DURATION_RE);
8329
- if (!m) return null;
8330
- const n = Number.parseInt(m[1], 10);
8331
- if (!Number.isFinite(n) || n <= 0) return null;
8332
- const unit = m[2].toLowerCase();
8333
- return n * UNIT_MS[unit];
8334
- }
8335
- function dayIndex(token) {
8336
- const text7 = token.trim().toLowerCase();
8337
- if (/^[0-6]$/.test(text7)) return Number(text7);
8338
- return DAY_ALIASES[text7];
8339
- }
8340
- function normalizeWeekdays(days) {
8341
- const set = /* @__PURE__ */ new Set();
8342
- for (const day of days) {
8343
- if (!Number.isInteger(day) || day < 0 || day > 6) {
8344
- throw new Error("Days must be 0 (Sunday) through 6 (Saturday)");
8345
- }
8346
- set.add(day);
8347
- }
8348
- if (set.size === 0) throw new Error("Pick at least one day");
8349
- return [...set].sort((a, b) => a - b);
8350
- }
8351
- function parseWeeklyDays(raw) {
8352
- const text7 = raw.trim().toLowerCase();
8353
- if (!text7) throw new Error("Pick at least one day");
8354
- if (text7 === "daily" || text7 === "all" || text7 === "every day" || text7 === "everyday") {
8355
- return [0, 1, 2, 3, 4, 5, 6];
8356
- }
8357
- if (text7 === "weekdays" || text7 === "weekday") return [1, 2, 3, 4, 5];
8358
- if (text7 === "weekends" || text7 === "weekend") return [0, 6];
8359
- const days = [];
8360
- for (const part of text7.split(/[\s,]+/).filter(Boolean)) {
8361
- const range = part.split("-");
8362
- if (range.length === 2 && range[0] && range[1]) {
8363
- const start = dayIndex(range[0]);
8364
- const end = dayIndex(range[1]);
8365
- if (start === void 0 || end === void 0) {
8366
- throw new Error(`Unknown day: ${part} (use mon,tue, weekdays, weekends, or daily)`);
8367
- }
8368
- if (start <= end) {
8369
- for (let d = start; d <= end; d++) days.push(d);
8370
- } else {
8371
- for (let d = start; d <= 6; d++) days.push(d);
8372
- for (let d = 0; d <= end; d++) days.push(d);
8373
- }
8374
- continue;
8375
- }
8376
- const n = dayIndex(part);
8377
- if (n === void 0) {
8378
- throw new Error(`Unknown day: ${part} (use mon,tue, weekdays, weekends, or daily)`);
8379
- }
8380
- days.push(n);
8381
- }
8382
- return normalizeWeekdays(days);
8383
- }
8384
- function normalizeWeeklyTime(raw) {
8385
- const m = raw.trim().match(TIME_RE);
8386
- if (!m) throw new Error(`Invalid time: ${raw} (use HH:mm, 24-hour)`);
8387
- return `${m[1]}:${m[2]}`;
8388
- }
8389
- function formatWeeklyDays(days) {
8390
- const key = normalizeWeekdays(days).join(",");
8391
- if (key === "0,1,2,3,4,5,6") return "daily";
8392
- if (key === "1,2,3,4,5") return "weekdays";
8393
- if (key === "0,6") return "weekends";
8394
- return normalizeWeekdays(days).map((day) => WEEKDAY_LABELS[day]).join(", ");
8395
- }
8396
- function weeklyCronExpr(when) {
8397
- const time = normalizeWeeklyTime(when.time);
8398
- const [hh, mm] = time.split(":");
8399
- const days = normalizeWeekdays(when.days);
8400
- return `${Number(mm)} ${Number(hh)} * * ${days.join(",")}`;
8401
- }
8402
- function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
8403
- if (when.kind === "once") {
8404
- const at = new Date(when.at);
8405
- if (Number.isNaN(at.getTime())) {
8406
- throw new Error(`Invalid once-at datetime: ${when.at}`);
8407
- }
8408
- return at;
8409
- }
8410
- if (when.kind === "every") {
8411
- const ms = parseDurationMs(when.every);
8412
- if (!ms) {
8413
- throw new Error(`Invalid interval: ${when.every} (use 15m, 1h, 6h, 1d)`);
8414
- }
8415
- return new Date(from.getTime() + ms);
8416
- }
8417
- if (when.kind === "weekly") {
8418
- return computeNextRunAt(
8419
- { kind: "cron", expr: weeklyCronExpr(when), tz: when.tz },
8420
- from
8421
- );
8422
- }
8423
- try {
8424
- const job = new import_croner.Cron(when.expr, {
8425
- timezone: when.tz?.trim() || void 0,
8426
- paused: true
8427
- });
8428
- const next = job.nextRun(from);
8429
- if (!next) {
8430
- throw new Error(`Cron expression has no next run: ${when.expr}`);
8431
- }
8432
- return next;
8433
- } catch (err) {
8434
- const message = err instanceof Error ? err.message : String(err);
8435
- throw new Error(`Invalid cron: ${message}`);
8436
- }
8437
- }
8438
- function formatScheduleWhen(when) {
8439
- if (when.kind === "once") return `once at ${when.at}`;
8440
- if (when.kind === "every") return `every ${when.every}`;
8441
- if (when.kind === "weekly") {
8442
- const label = `${formatWeeklyDays(when.days)} at ${normalizeWeeklyTime(when.time)}`;
8443
- const tz2 = when.tz?.trim();
8444
- return tz2 ? `${label} (${tz2})` : label;
8445
- }
8446
- const tz = when.tz?.trim();
8447
- return tz ? `cron ${when.expr} (${tz})` : `cron ${when.expr}`;
8448
- }
8449
- function normalizeWhen(raw) {
8450
- if (!raw || typeof raw !== "object") {
8451
- throw new Error("Schedule is missing `when`");
8452
- }
8453
- const rec = raw;
8454
- const kind = rec.kind;
8455
- if (kind === "once" && typeof rec.at === "string") {
8456
- return { kind: "once", at: rec.at };
8457
- }
8458
- if (kind === "every" && typeof rec.every === "string") {
8459
- return { kind: "every", every: rec.every };
8460
- }
8461
- if (kind === "cron" && typeof rec.expr === "string") {
8462
- return {
8463
- kind: "cron",
8464
- expr: rec.expr,
8465
- tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
8466
- };
8467
- }
8468
- if (kind === "weekly" && Array.isArray(rec.days) && typeof rec.time === "string") {
8469
- return {
8470
- kind: "weekly",
8471
- days: normalizeWeekdays(rec.days.map((day) => Number(day))),
8472
- time: normalizeWeeklyTime(rec.time),
8473
- tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
8474
- };
8475
- }
8476
- throw new Error("Schedule `when` must be once, every, weekly, or cron");
8477
- }
8478
- var import_croner, WEEKDAY_LABELS, DURATION_RE, UNIT_MS, DAY_ALIASES, TIME_RE;
8479
- var init_schedule_when = __esm({
8480
- "src/store/schedule-when.ts"() {
8481
- "use strict";
8482
- import_croner = require("croner");
8483
- WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
8484
- DURATION_RE = /^(\d+)(s|m|h|d)$/i;
8485
- UNIT_MS = {
8486
- s: 1e3,
8487
- m: 6e4,
8488
- h: 36e5,
8489
- d: 864e5
8490
- };
8491
- DAY_ALIASES = {
8492
- sun: 0,
8493
- sunday: 0,
8494
- mon: 1,
8495
- monday: 1,
8496
- tue: 2,
8497
- tues: 2,
8498
- tuesday: 2,
8499
- wed: 3,
8500
- wednesday: 3,
8501
- thu: 4,
8502
- thur: 4,
8503
- thurs: 4,
8504
- thursday: 4,
8505
- fri: 5,
8506
- friday: 5,
8507
- sat: 6,
8508
- saturday: 6
8509
- };
8510
- TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)(?::[0-5]\d)?$/;
8511
- }
8512
- });
8513
-
8514
- // src/store/schedules.ts
8515
- function schedulesPath() {
8516
- return (0, import_node_path18.join)(appDataDir(), "schedules.json");
8517
- }
8518
- function defaultScheduleName(prompt) {
8519
- const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
8520
- if (!line) return "Untitled automation";
8521
- return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
8522
- }
8523
- function resolveScheduleThreadId(raw, env = process.env) {
8524
- const value = raw?.trim() || "";
8525
- if (!value) return null;
8526
- if (value.toLowerCase() === "self") {
8527
- return env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || env.SIDEBOARD_THREAD_ID?.trim() || null;
8528
- }
8529
- return value;
8530
- }
8531
- function nowIso2(d = /* @__PURE__ */ new Date()) {
8532
- return d.toISOString();
8533
- }
8534
- function normalizeOptionalBool(raw) {
8535
- if (raw === true || raw === false) return raw;
8536
- return null;
8537
- }
8538
- function normalizeAutonomy2(raw) {
8539
- return raw === "default" || raw === "full" ? raw : null;
8540
- }
8541
- function normalizeAttachments(raw) {
8542
- if (!Array.isArray(raw)) return [];
8543
- const out = [];
8544
- for (const item of raw) {
8545
- if (!item || typeof item !== "object") continue;
8546
- const row2 = item;
8547
- if (typeof row2.id !== "string" || !row2.id.trim()) continue;
8548
- if (typeof row2.name !== "string" || !row2.name) continue;
8549
- if (typeof row2.content !== "string") continue;
8550
- if (!ATTACHMENT_KINDS.has(row2.kind)) continue;
8551
- const next = {
8552
- id: row2.id,
8553
- name: row2.name,
8554
- kind: row2.kind,
8555
- content: row2.content
8556
- };
8557
- if (typeof row2.path === "string" && row2.path) next.path = row2.path;
8558
- if (typeof row2.previewDataUrl === "string" && row2.previewDataUrl) {
8559
- next.previewDataUrl = row2.previewDataUrl;
8560
- }
8561
- out.push(next);
8562
- }
8563
- return out;
8564
- }
8565
- function normalizeTask(raw) {
8566
- const when = normalizeWhen(raw.when);
8567
- const agent = raw.agent && isOrchestratorCapableAgent(raw.agent) ? raw.agent : null;
8568
- return {
8569
- id: raw.id,
8570
- name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : defaultScheduleName(raw.prompt),
8571
- prompt: String(raw.prompt ?? ""),
8572
- enabled: Boolean(raw.enabled),
8573
- when,
8574
- threadId: raw.threadId?.trim() || null,
8575
- agent,
8576
- model: raw.model?.trim() || null,
8577
- effort: normalizeThinkingEffort(raw.effort),
8578
- fast: normalizeOptionalBool(raw.fast),
8579
- planMode: normalizeOptionalBool(raw.planMode),
8580
- autonomy: normalizeAutonomy2(raw.autonomy),
8581
- attachments: normalizeAttachments(raw.attachments),
8582
- archiveChat: raw.archiveChat !== false,
8583
- nextRunAt: raw.nextRunAt,
8584
- lastRunAt: raw.lastRunAt ?? null,
8585
- lastThreadId: raw.lastThreadId ?? null,
8586
- lastError: raw.lastError ?? null,
8587
- createdBy: raw.createdBy === "mcp" || raw.createdBy === "cli" || raw.createdBy === "ui" ? raw.createdBy : "ui",
8588
- createdAt: raw.createdAt,
8589
- updatedAt: raw.updatedAt
8590
- };
8591
- }
8592
- function readAll() {
8593
- const path2 = schedulesPath();
8594
- if (!(0, import_node_fs15.existsSync)(path2)) return [];
8595
- try {
8596
- const parsed = JSON.parse((0, import_node_fs15.readFileSync)(path2, "utf8"));
8597
- if (!Array.isArray(parsed)) return [];
8598
- return parsed.map((row2) => {
8599
- try {
8600
- return normalizeTask(row2);
8601
- } catch {
8602
- return null;
8603
- }
8604
- }).filter((row2) => Boolean(row2?.id && row2.prompt));
8605
- } catch {
8606
- return [];
8607
- }
8608
- }
8609
- function writeAll(rows) {
8610
- writePrivateFile(schedulesPath(), `${JSON.stringify(rows, null, 2)}
8611
- `);
8612
- }
8613
- function buildTask(input, now) {
8614
- const prompt = input.prompt.trim();
8615
- if (!prompt) throw new Error("Schedule prompt is required");
8616
- const when = normalizeWhen(input.when);
8617
- const nextRunAt = computeNextRunAt(when, now).toISOString();
8618
- const ts = nowIso2(now);
8619
- const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
8620
- return {
8621
- id: (0, import_node_crypto8.randomUUID)(),
8622
- name: input.name?.trim() || defaultScheduleName(prompt),
8623
- prompt,
8624
- enabled: input.enabled !== false,
8625
- when,
8626
- threadId: input.threadId?.trim() || null,
8627
- agent,
8628
- model: input.model?.trim() || null,
8629
- effort: input.effort != null ? normalizeThinkingEffort(input.effort) : null,
8630
- fast: normalizeOptionalBool(input.fast),
8631
- planMode: normalizeOptionalBool(input.planMode),
8632
- autonomy: normalizeAutonomy2(input.autonomy),
8633
- attachments: normalizeAttachments(input.attachments),
8634
- archiveChat: input.archiveChat !== false,
8635
- nextRunAt,
8636
- lastRunAt: null,
8637
- lastThreadId: null,
8638
- lastError: null,
8639
- createdBy: input.createdBy,
8640
- createdAt: ts,
8641
- updatedAt: ts
8642
- };
8643
- }
8644
- function listSchedules() {
8645
- return readAll().sort((a, b) => a.nextRunAt.localeCompare(b.nextRunAt));
8646
- }
8647
- function hasEnabledSchedules() {
8648
- return listSchedules().some((row2) => row2.enabled);
8649
- }
8650
- function scheduleNeedsCaffeinate(task, now = /* @__PURE__ */ new Date()) {
8651
- if (!task.enabled) return false;
8652
- const at = new Date(task.nextRunAt);
8653
- if (Number.isNaN(at.getTime())) return false;
8654
- return at.getTime() > now.getTime();
8655
- }
8656
- function ensureCaffeinateForFutureSchedule(task) {
8657
- if (!scheduleNeedsCaffeinate(task)) return;
8658
- if (caffeinateWhileSchedulesEnabled()) return;
8659
- updateAdvancedSettings({ caffeinateWhileSchedules: true });
8660
- }
8661
- function getSchedule(id) {
8662
- const key = id.trim();
8663
- if (!key) return null;
8664
- return readAll().find(
8665
- (row2) => row2.id === key || row2.id.startsWith(key) && key.length >= 8
8666
- ) ?? null;
8667
- }
8668
- function createSchedule(input) {
8669
- const task = buildTask(input, /* @__PURE__ */ new Date());
8670
- const rows = readAll();
8671
- rows.push(task);
8672
- writeAll(rows);
8673
- ensureCaffeinateForFutureSchedule(task);
8674
- return task;
8675
- }
8676
- function updateSchedule(id, patch) {
8677
- const rows = readAll();
8678
- const idx = rows.findIndex((row2) => row2.id === id || id.length >= 8 && row2.id.startsWith(id));
8679
- if (idx < 0) throw new Error(`Automation not found: ${id}`);
8680
- const prev = rows[idx];
8681
- const when = patch.when ? normalizeWhen(patch.when) : prev.when;
8682
- const prompt = patch.prompt !== void 0 ? patch.prompt.trim() : prev.prompt;
8683
- if (!prompt) throw new Error("Schedule prompt is required");
8684
- const whenChanged = patch.when !== void 0;
8685
- const next = {
8686
- ...prev,
8687
- name: patch.name !== void 0 ? patch.name.trim() || defaultScheduleName(prompt) : prev.name,
8688
- prompt,
8689
- enabled: patch.enabled ?? prev.enabled,
8690
- when,
8691
- threadId: patch.threadId !== void 0 ? patch.threadId?.trim() || null : prev.threadId,
8692
- agent: patch.agent !== void 0 ? patch.agent ? coerceOrchestratorAgent(patch.agent) : null : prev.agent,
8693
- model: patch.model !== void 0 ? patch.model?.trim() || null : prev.model,
8694
- effort: patch.effort !== void 0 ? normalizeThinkingEffort(patch.effort) : prev.effort,
8695
- fast: patch.fast !== void 0 ? normalizeOptionalBool(patch.fast) : prev.fast,
8696
- planMode: patch.planMode !== void 0 ? normalizeOptionalBool(patch.planMode) : prev.planMode,
8697
- autonomy: patch.autonomy !== void 0 ? normalizeAutonomy2(patch.autonomy) : prev.autonomy,
8698
- attachments: patch.attachments !== void 0 ? normalizeAttachments(patch.attachments) : prev.attachments,
8699
- archiveChat: patch.archiveChat !== void 0 ? patch.archiveChat !== false : prev.archiveChat,
8700
- nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
8701
- lastError: whenChanged ? null : prev.lastError,
8702
- updatedAt: nowIso2()
8703
- };
8704
- rows[idx] = next;
8705
- writeAll(rows);
8706
- ensureCaffeinateForFutureSchedule(next);
8707
- return next;
8708
- }
8709
- function deleteSchedule(id) {
8710
- const rows = readAll();
8711
- const next = rows.filter(
8712
- (row2) => row2.id !== id && !(id.length >= 8 && row2.id.startsWith(id))
8713
- );
8714
- if (next.length === rows.length) throw new Error(`Automation not found: ${id}`);
8715
- writeAll(next);
8716
- }
8717
- function recordScheduleRun(id, result) {
8718
- const rows = readAll();
8719
- const idx = rows.findIndex((row2) => row2.id === id);
8720
- if (idx < 0) throw new Error(`Automation not found: ${id}`);
8721
- const prev = rows[idx];
8722
- const firedAt = result.firedAt ?? /* @__PURE__ */ new Date();
8723
- const next = {
8724
- ...prev,
8725
- enabled: prev.when.kind === "once" ? false : prev.enabled,
8726
- nextRunAt: prev.when.kind === "once" ? prev.nextRunAt : computeNextRunAt(prev.when, firedAt).toISOString(),
8727
- lastRunAt: nowIso2(firedAt),
8728
- lastThreadId: result.lastThreadId !== void 0 ? result.lastThreadId : prev.lastThreadId,
8729
- lastError: result.lastError ?? null,
8730
- updatedAt: nowIso2(firedAt)
8731
- };
8732
- rows[idx] = next;
8733
- writeAll(rows);
8734
- return next;
8735
- }
8736
- var import_node_crypto8, import_node_fs15, import_node_path18, ATTACHMENT_KINDS;
8737
- var init_schedules = __esm({
8738
- "src/store/schedules.ts"() {
8739
- "use strict";
8740
- import_node_crypto8 = require("crypto");
8741
- import_node_fs15 = require("fs");
8742
- import_node_path18 = require("path");
8743
- init_schedule_when();
8744
- init_orchestrator_capable();
8745
- init_thinking_effort();
8746
- init_paths();
8747
- init_private_file();
8748
- init_app_settings();
8749
- init_schedule_when();
8750
- ATTACHMENT_KINDS = /* @__PURE__ */ new Set([
8751
- "transcript",
8752
- "file",
8753
- "issue",
8754
- "workspace",
8755
- "diff-comment",
8756
- "code-ref"
8757
- ]);
8758
- }
8759
- });
8760
-
8761
8772
  // src/store/desktop-host.ts
8762
8773
  function desktopHostPidPath() {
8763
8774
  return (0, import_node_path19.join)(appDataDir(), "desktop-host.pid");
@@ -9165,6 +9176,33 @@ function needsCoordinatorAction(state) {
9165
9176
  function endedOnAskUser(parts) {
9166
9177
  return lastTopLevelToolIsAskUser(parts);
9167
9178
  }
9179
+ function turnWaitingOnAskUser(parts, dismissedQuestionSignature) {
9180
+ if (!parts?.length) return false;
9181
+ let ask = null;
9182
+ for (let i = parts.length - 1; i >= 0; i--) {
9183
+ const part = parts[i];
9184
+ if (part.type === "tool" && !part.parentId && isAskUserToolName(part.name)) {
9185
+ ask = part;
9186
+ break;
9187
+ }
9188
+ }
9189
+ if (!ask) return false;
9190
+ const dismissed = dismissedQuestionSignature?.trim();
9191
+ if (!dismissed) return true;
9192
+ const pending = extractPendingPlanQuestions([ask]);
9193
+ if (!pending?.signature) return false;
9194
+ return pending.signature !== dismissed;
9195
+ }
9196
+ function userRepliedAfterAgent(messages, lastAgent) {
9197
+ if (!messages || !lastAgent) return false;
9198
+ const index = messages.lastIndexOf(lastAgent);
9199
+ if (index < 0) return false;
9200
+ for (let i = index + 1; i < messages.length; i++) {
9201
+ const message = messages[i];
9202
+ if (message.role === "user" && message.origin !== "continue") return true;
9203
+ }
9204
+ return false;
9205
+ }
9168
9206
  function deriveTaskState(input) {
9169
9207
  if (input.stillRunning) {
9170
9208
  return input.status === "queued" ? "submitted" : "working";
@@ -9176,7 +9214,7 @@ function deriveTaskState(input) {
9176
9214
  case "stopped":
9177
9215
  return "canceled";
9178
9216
  default:
9179
- return endedOnAskUser(input.lastAgentParts) ? "input-required" : "completed";
9217
+ return !input.userAnswered && turnWaitingOnAskUser(input.lastAgentParts, input.dismissedQuestionSignature) ? "input-required" : "completed";
9180
9218
  }
9181
9219
  }
9182
9220
  var init_task_state = __esm({
@@ -13153,7 +13191,7 @@ var init_coordinator_prompt = __esm({
13153
13191
  "- get_viewer_context / update_viewer_context \u2014 read or replace Settings \u2192 Agents (account) and Settings \u2192 Projects (per-repo) context. You can update a project: pass scope=project and repoPath from list_projects (this cwd is not a project). update_viewer_context requires confirmed=true after ask_user. Never write context unprompted.",
13154
13192
  "- list_env / set_env / delete_env \u2014 Settings \u2192 Environment. scope=project requires repoPath from list_projects and stores the key under that project prefix (the project name, uppercased, plus _). APPLE_ID for sideboard is SIDEBOARD_APPLE_ID. scope=account is shared by every project and is not prefixed. list_env returns names only.",
13155
13193
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for phone remote / away-from-keyboard work, overnight schedules, or when the user will be away. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
13156
- "- list_schedules / create_schedule / update_schedule / delete_schedule / run_schedule \u2014 automations that send a prompt to a chat (chatId or self) or start a new Global chat (omit chatId). One-shot `at`, interval `every` (15m/1h/6h/1d), clock time `time` + `days` (07:30 and weekdays, or mon,wed), or 5-field `cron`. Recurring jobs without chatId open a new chat each run and archive it when that run finishes unless archiveChat is false (leave the chat open to keep talking). History keeps the last 5 archived runs for that automation. Worktree agents have the same tools (`chatId=self` continues that coding chat). Jobs fire only while Sideboard.app is running; sleep skips until wake. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while automations are enabled (or call set_caffeinate). The desktop lists them in the sidebar after Board.",
13194
+ "- list_schedules / create_schedule / update_schedule / delete_schedule / run_schedule \u2014 automations that send a prompt to a chat (chatId or self) or start a new Global chat (omit chatId). A job bound to a chat is removed when that chat is archived. Omit chatId only for a Global automation that should outlive the chat. One-shot `at`, interval `every` (15m/1h/6h/1d), clock time `time` + `days` (07:30 and weekdays, or mon,wed), or 5-field `cron`. Recurring jobs without chatId open a new chat each run and archive it when that run finishes unless archiveChat is false (leave the chat open to keep talking). History keeps the last 5 archived runs for that automation. Worktree agents have the same tools (`chatId=self` continues that coding chat and is removed when that chat is archived). Jobs fire only while Sideboard.app is running; sleep skips until wake. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while automations are enabled (or call set_caffeinate). The desktop lists them in the sidebar after Board.",
13157
13195
  "Projects:",
13158
13196
  "- add_project / remove_project \u2014 register or unregister a git repo",
13159
13197
  "Workspaces and chats:",
@@ -25616,6 +25654,9 @@ function decisionBlockFromParts(parts, planMode, at = (/* @__PURE__ */ new Date(
25616
25654
  at
25617
25655
  );
25618
25656
  }
25657
+ function attentionOutranksAsk(existing) {
25658
+ return existing?.source === "reported" || existing?.source === "permission";
25659
+ }
25619
25660
  function withStampedQuestions(decision, existing) {
25620
25661
  if (decision.source !== "ask_user" || decision.questions?.length) return decision;
25621
25662
  if (existing?.source !== "ask_user" || !existing.questions?.length) return decision;
@@ -25629,12 +25670,37 @@ function agentBlockAfterTurn(input) {
25629
25670
  if (input.failed) return null;
25630
25671
  const decision = decisionBlockFromParts(input.parts, input.planMode);
25631
25672
  if (decision) {
25673
+ if (decision.source === "ask_user" && attentionOutranksAsk(input.existing)) {
25674
+ return input.existing ?? null;
25675
+ }
25632
25676
  const next = withStampedQuestions(decision, input.existing);
25633
25677
  if (isDismissedAskUserBlock(next, input.dismissedQuestionSignature)) {
25634
25678
  return input.existing?.source === "reported" ? input.existing : null;
25635
25679
  }
25636
25680
  return next;
25637
25681
  }
25682
+ const pending = extractPendingPlanQuestions(input.parts);
25683
+ if (pending && attentionOutranksAsk(input.existing)) {
25684
+ return input.existing ?? null;
25685
+ }
25686
+ if (pending) {
25687
+ const kept = withStampedQuestions(
25688
+ {
25689
+ ...makeAgentBlock(
25690
+ "ask_user",
25691
+ formatAskUserNotifyMessage(pending.questions),
25692
+ input.existing?.source === "ask_user" ? input.existing.at : void 0
25693
+ ),
25694
+ onScreen: true,
25695
+ questionSignature: pending.signature,
25696
+ questions: pending.questions
25697
+ },
25698
+ input.existing
25699
+ );
25700
+ if (!isDismissedAskUserBlock(kept, input.dismissedQuestionSignature)) return kept;
25701
+ } else if (input.existing?.source === "ask_user" && input.existing.onScreen && input.existing.questions?.length && !isDismissedAskUserBlock(input.existing, input.dismissedQuestionSignature)) {
25702
+ return input.existing;
25703
+ }
25638
25704
  if (input.existing && input.existing.source !== "reported" && lastTopLevelTool(input.parts)) {
25639
25705
  return null;
25640
25706
  }
@@ -25663,11 +25729,15 @@ function askBlocksEqual(a, b) {
25663
25729
  if (!a || !b) return false;
25664
25730
  return a.source === b.source && a.reason === b.reason && a.at === b.at && a.onScreen === b.onScreen && a.questionSignature === b.questionSignature;
25665
25731
  }
25666
- function askBlockAfterLiveParts(existing, parts) {
25732
+ function askBlockAfterLiveParts(existing, parts, dismissedQuestionSignature) {
25733
+ if (attentionOutranksAsk(existing)) return existing ?? null;
25734
+ if (isDismissedAskUserBlock(existing, dismissedQuestionSignature)) return null;
25735
+ const dismissed = dismissedQuestionSignature?.trim() ?? "";
25736
+ let pending = extractPendingPlanQuestions(parts);
25737
+ if (dismissed && pending?.signature === dismissed) pending = null;
25667
25738
  const last = lastTopLevelTool(parts);
25668
25739
  if (last?.type === "tool" && isAskUserToolName(last.name)) {
25669
25740
  if (existing?.source === "ask_user" && existing.onScreen) return existing;
25670
- const pending = extractPendingPlanQuestions(parts);
25671
25741
  const questions2 = pending?.questions ?? existing?.questions;
25672
25742
  if (!questions2?.length) {
25673
25743
  return existing?.source === "ask_user" ? { ...existing, onScreen: true } : existing ?? null;
@@ -25680,13 +25750,32 @@ function askBlockAfterLiveParts(existing, parts) {
25680
25750
  questions: questions2
25681
25751
  };
25682
25752
  }
25753
+ if (pending) {
25754
+ if (existing?.source === "ask_user" && existing.onScreen && existing.questions?.length) {
25755
+ return existing;
25756
+ }
25757
+ const at = existing?.source === "ask_user" ? existing.at : void 0;
25758
+ return {
25759
+ ...makeAgentBlock("ask_user", formatAskUserNotifyMessage(pending.questions), at),
25760
+ onScreen: true,
25761
+ questionSignature: pending.signature,
25762
+ questions: pending.questions
25763
+ };
25764
+ }
25765
+ if (existing?.source === "ask_user" && existing.onScreen && existing.questions?.length) {
25766
+ return existing;
25767
+ }
25683
25768
  if (last && existing?.source === "ask_user") return null;
25684
25769
  return existing ?? null;
25685
25770
  }
25686
25771
  function syncAskUserBlockFromLive(threadId, parts) {
25687
25772
  const thread = readThread(threadId);
25688
25773
  if (!thread || thread.status === "archived") return;
25689
- const next = askBlockAfterLiveParts(thread.agentBlock, parts);
25774
+ const next = askBlockAfterLiveParts(
25775
+ thread.agentBlock,
25776
+ parts,
25777
+ thread.dismissedQuestionSignature
25778
+ );
25690
25779
  if (askBlocksEqual(thread.agentBlock, next)) return;
25691
25780
  updateThread(threadId, { agentBlock: next });
25692
25781
  }
@@ -31373,7 +31462,7 @@ function formatWorktreeDirective(thread, opts) {
31373
31462
  lines.push(formatDevAccessHint(opts?.activeRuns));
31374
31463
  lines.push("");
31375
31464
  lines.push(
31376
- "Automations (sidebar, after Board): `create_schedule` / `list_schedules` (`at` / `every` / `cron` / `time`+`days`). `archiveChat=false` keeps the new chat. `chatId=self` continues this chat; omit chatId for a new Global chat. Fires only while Sideboard.app is running. A future job turns on Settings \u2192 Advanced \u2192 Caffeinate while automations are enabled."
31465
+ "Automations (sidebar, after Board): `create_schedule` / `list_schedules` (`at` / `every` / `cron` / `time`+`days`). `archiveChat=false` keeps the new chat. `chatId=self` is removed when this chat is archived. Fires only while Sideboard.app is running. A future job turns on Settings \u2192 Advanced \u2192 Caffeinate while automations are enabled."
31377
31466
  );
31378
31467
  lines.push(
31379
31468
  "`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
@@ -31393,7 +31482,7 @@ function connectedOrchestratorTurnReminder(thread, find) {
31393
31482
  return formatConnectedOrchestratorReminder(connectedOrchestrator(thread, find));
31394
31483
  }
31395
31484
  function formatWorktreeReminder() {
31396
- return "Sideboard workspace: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push. Automations: create_schedule (at / every / cron, or time + days such as weekdays at 07:30; archiveChat=false leaves the new chat open; chatId=self for this chat). set_workspace_tags updates labels on this workspace (omit ref).";
31485
+ return "Sideboard workspace: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push. Automations: create_schedule (at / every / cron, or time + days such as weekdays at 07:30; archiveChat=false leaves the new chat open; chatId=self ends with the chat). set_workspace_tags updates labels on this workspace (omit ref).";
31397
31486
  }
31398
31487
  function formatClaudeSubagentReminder() {
31399
31488
  return 'Claude subagents: if Task/Agent workers will edit files, write a short plan in this chat and continue in the same turn. Do not ask the user whether to isolate, and do not set worktree.bgIsolation to none. One writer may edit this workspace. Every other writer is an Agent with isolation "worktree" and a disjoint file list (read-only explore agents may overlap). While those agents run, leave their files alone. Merge each returned branch into this workspace, then remove the temporary worktree. You stay in this cwd. If Claude says another write-capable agent is in this directory, or that writes to the shared checkout are blocked, re-spawn that agent with isolation "worktree" or make the edit yourself. That note is another worker in this chat, or a worker Claude still has marked running. Do not mention it to the user.';
@@ -34715,7 +34804,9 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
34715
34804
  const taskState2 = deriveTaskState({
34716
34805
  status: thread.status,
34717
34806
  stillRunning,
34718
- lastAgentParts: lastAgent?.parts
34807
+ lastAgentParts: lastAgent?.parts,
34808
+ dismissedQuestionSignature: thread.dismissedQuestionSignature,
34809
+ userAnswered: userRepliedAfterAgent(thread.messages, lastAgent)
34719
34810
  });
34720
34811
  const block = thread.agentBlock?.reason?.trim() ? thread.agentBlock : null;
34721
34812
  return {
@@ -35451,7 +35542,15 @@ async function fireSchedule(id) {
35451
35542
  const deps = fireHooks ?? await defaultDeps();
35452
35543
  const prompt = formatScheduledPrompt(schedule.name, schedule.prompt);
35453
35544
  const target = schedule.threadId ? deps.findThread(schedule.threadId) : null;
35454
- if (target && target.status !== "archived") {
35545
+ if (schedule.threadId && (!target || target.status === "archived")) {
35546
+ deleteSchedule(schedule.id);
35547
+ return {
35548
+ ...schedule,
35549
+ enabled: false,
35550
+ lastError: "Removed because its chat is archived or gone."
35551
+ };
35552
+ }
35553
+ if (target) {
35455
35554
  const sent = await deps.send(target.id, prompt, {
35456
35555
  attachments: schedule.attachments,
35457
35556
  options: scheduleSendOptions(schedule)
@@ -35461,8 +35560,7 @@ async function fireSchedule(id) {
35461
35560
  lastError: null
35462
35561
  });
35463
35562
  }
35464
- const pinReplacement = Boolean(schedule.threadId) && schedule.when.kind !== "once";
35465
- const archiveFresh = schedule.archiveChat !== false && !pinReplacement;
35563
+ const archiveFresh = schedule.archiveChat !== false;
35466
35564
  const created = await deps.startOrchestration({
35467
35565
  goal: prompt,
35468
35566
  agent: pickAgent(schedule),
@@ -35475,9 +35573,6 @@ async function fireSchedule(id) {
35475
35573
  ...archiveFresh ? { ephemeralAutomationId: schedule.id } : {},
35476
35574
  ephemeralTitle: schedule.name
35477
35575
  });
35478
- if (pinReplacement) {
35479
- updateSchedule(schedule.id, { threadId: created.id });
35480
- }
35481
35576
  return recordScheduleRun(schedule.id, {
35482
35577
  lastThreadId: created.id,
35483
35578
  lastError: null
@@ -35894,6 +35989,7 @@ __export(index_exports, {
35894
35989
  defaultScheduleName: () => defaultScheduleName,
35895
35990
  deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
35896
35991
  deleteSchedule: () => deleteSchedule,
35992
+ deleteSchedulesForThread: () => deleteSchedulesForThread,
35897
35993
  deleteThreadRecord: () => deleteThreadRecord,
35898
35994
  deriveTaskState: () => deriveTaskState,
35899
35995
  desktopHostPidPath: () => desktopHostPidPath,
@@ -45361,7 +45457,7 @@ function whenFromUpdate(id, args) {
45361
45457
  }
45362
45458
  function createdHint(schedule) {
45363
45459
  const awake = scheduleNeedsCaffeinate(schedule) ? " Caffeinate while automations are enabled is now on so the Mac can stay awake until this job is due." : "";
45364
- const fresh = schedule.threadId ? "" : schedule.archiveChat === false ? " This job leaves the new chat open so you can keep talking." : " A new Global chat is archived when the run finishes (History keeps the last 5 runs). Pass archiveChat=false to leave it open.";
45460
+ const fresh = schedule.threadId ? " This job is removed when that chat is archived." : schedule.archiveChat === false ? " This job leaves the new chat open so you can keep talking." : " A new Global chat is archived when the run finishes (History keeps the last 5 runs). Pass archiveChat=false to leave it open.";
45365
45461
  return `Fires while Sideboard.app is running.${fresh}${awake}`;
45366
45462
  }
45367
45463
  function registerScheduleTools(server) {
@@ -45379,7 +45475,7 @@ function registerScheduleTools(server) {
45379
45475
  );
45380
45476
  server.tool(
45381
45477
  "create_schedule",
45382
- "Create a local automation that, when due, sends a prompt to an existing chat (chatId) or starts a new Global orchestration chat (omit chatId). Pass chatId=self to continue this chat. If that chat is missing or archived when the job fires, Sideboard starts a new Global chat (recurring jobs then continue that chat). Exactly one of at (ISO datetime), every (15m/1h/6h/1d), time (HH:mm, with days such as weekdays or mon,wed), or cron (5-field). Recurring jobs without chatId open a new Global chat each run and archive it when that run finishes unless archiveChat is false (leave the chat open to keep talking, for example a morning start-my-day run). History keeps the last 5 archived runs for that automation. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while automations are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
45478
+ "Create a local automation that, when due, sends a prompt to an existing chat (chatId) or starts a new Global orchestration chat (omit chatId). Pass chatId=self to continue this chat. A job bound to a chat is removed when that chat is archived or gone; it does not start a new Global chat. Omit chatId only for a Global automation that should outlive this chat. Exactly one of at (ISO datetime), every (15m/1h/6h/1d), time (HH:mm, with days such as weekdays or mon,wed), or cron (5-field). Recurring jobs without chatId open a new Global chat each run and archive it when that run finishes unless archiveChat is false (leave the chat open to keep talking, for example a morning start-my-day run). History keeps the last 5 archived runs for that automation. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while automations are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
45383
45479
  {
45384
45480
  prompt: import_zod10.z.string().describe("User message / goal queued when the automation fires"),
45385
45481
  name: import_zod10.z.string().optional(),
@@ -45393,7 +45489,7 @@ function registerScheduleTools(server) {
45393
45489
  "Archive a new Global chat when the run finishes. Default true. Pass false to leave the chat open."
45394
45490
  ),
45395
45491
  chatId: import_zod10.z.string().optional().describe(
45396
- 'Existing chat id (orchestration or workspace), or "self" for this chat. Omit to create a new Global chat on fire.'
45492
+ 'Existing chat id, or "self" for this chat. That job is removed when the chat is archived. Omit only for a Global automation that should outlive this chat.'
45397
45493
  ),
45398
45494
  threadId: import_zod10.z.string().optional().describe("Legacy alias for chatId."),
45399
45495
  agent: import_zod10.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
@@ -45445,7 +45541,7 @@ function registerScheduleTools(server) {
45445
45541
  tz: import_zod10.z.string().optional(),
45446
45542
  archiveChat: import_zod10.z.boolean().optional().describe("Archive a new Global chat when the run finishes. False leaves it open."),
45447
45543
  chatId: import_zod10.z.string().optional().describe(
45448
- 'Existing chat (orchestration or workspace), "self", or empty string to create a new Global chat each run'
45544
+ 'Existing chat, "self", or empty string for a Global chat each run. A bound job is removed when that chat is archived.'
45449
45545
  ),
45450
45546
  threadId: import_zod10.z.string().optional().describe("Legacy alias for chatId."),
45451
45547
  agent: import_zod10.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
@@ -50039,6 +50135,7 @@ async function pollSlackOutboundWatches(opts) {
50039
50135
  defaultScheduleName,
50040
50136
  deleteBranchOnPurgeEnabled,
50041
50137
  deleteSchedule,
50138
+ deleteSchedulesForThread,
50042
50139
  deleteThreadRecord,
50043
50140
  deriveTaskState,
50044
50141
  desktopHostPidPath,