@sideboard-ai/core 0.1.322 → 0.1.324

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 (55) hide show
  1. package/dist/{abletime-CRRLEYTE.js → abletime-5ZY3ZUUO.js} +4 -4
  2. package/dist/{abletime-ICW6Q2AB.js → abletime-CU2UPMJF.js} +4 -4
  3. package/dist/{abletime-oauth-P3KPPJE4.js → abletime-oauth-4TTCR3CT.js} +3 -3
  4. package/dist/{abletime-oauth-Q76XBSPG.js → abletime-oauth-7EOSBEFM.js} +3 -3
  5. package/dist/{abletime-rest-DQ3PNKXP.js → abletime-rest-3TG4G7LW.js} +3 -3
  6. package/dist/{abletime-rest-DNFAYYLP.js → abletime-rest-KOKE7V4B.js} +3 -3
  7. package/dist/{agents-ROFDNE7Z.js → agents-LUSTCMDN.js} +5 -5
  8. package/dist/{agents-PFVYC36J.js → agents-YK65UDLT.js} +5 -5
  9. package/dist/{app-settings-KMPVI2B7.js → app-settings-I5HM5RMP.js} +1 -1
  10. package/dist/{app-settings-P3H4SEIK.js → app-settings-XIF4EC4K.js} +1 -1
  11. package/dist/{chunk-BPDVHB4U.js → chunk-5GDJSFCT.js} +2 -2
  12. package/dist/{chunk-RHJKZOPQ.js → chunk-74OPNF7U.js} +2 -2
  13. package/dist/{chunk-XDQSHSL6.js → chunk-7D5QIU3N.js} +4 -4
  14. package/dist/{chunk-45UB2J47.js → chunk-7MQSYPAY.js} +160 -46
  15. package/dist/{chunk-BTIXTH4N.js → chunk-7PVT73H6.js} +3 -3
  16. package/dist/{chunk-RGUZPMTF.js → chunk-BYUCNIXK.js} +4 -4
  17. package/dist/{chunk-LD2DXHBP.js → chunk-DF5BM5OY.js} +182 -50
  18. package/dist/{chunk-FZKAAAEB.js → chunk-EJ66LEKH.js} +1 -0
  19. package/dist/{chunk-DFUXW2IT.js → chunk-EKU7FQVG.js} +2 -2
  20. package/dist/{chunk-5OQONGHO.js → chunk-FR3SAYUL.js} +5 -5
  21. package/dist/{chunk-UWJUARFA.js → chunk-FUFNMUNR.js} +1 -1
  22. package/dist/{chunk-TEFFR6PB.js → chunk-INBUV7HY.js} +2 -2
  23. package/dist/{chunk-H6GXYV5Q.js → chunk-JE3JLHMO.js} +6 -7
  24. package/dist/{chunk-JNEFEA2A.js → chunk-JH5SMVM5.js} +4 -4
  25. package/dist/{chunk-7JV5QP5K.js → chunk-MZIZTSR2.js} +2 -2
  26. package/dist/{chunk-HHPTGIKS.js → chunk-NXUN4U5P.js} +2 -2
  27. package/dist/{chunk-V76MQ7MH.js → chunk-ODH3XHMI.js} +5 -5
  28. package/dist/{chunk-IFU4W4C4.js → chunk-OIHRC2ZJ.js} +1 -0
  29. package/dist/{chunk-56M6VEXI.js → chunk-Q3YJZPQI.js} +3 -3
  30. package/dist/{chunk-UX6EDDGR.js → chunk-QOS4465Y.js} +1 -1
  31. package/dist/{chunk-YWBLQBSN.js → chunk-ROOZDDFA.js} +1 -1
  32. package/dist/{chunk-BANN5HUX.js → chunk-RR4WCGNB.js} +4 -4
  33. package/dist/{chunk-72PUPPV2.js → chunk-Y3WREIPQ.js} +3 -3
  34. package/dist/{chunk-SPMIZSKH.js → chunk-ZIRQZUZY.js} +6 -7
  35. package/dist/{conductor-XLXJU24U.js → conductor-JFABMXDA.js} +4 -4
  36. package/dist/{conductor-CNT3VL3S.js → conductor-SPZOTC7W.js} +4 -4
  37. package/dist/{coordinator-prompt-D5EE2YTV.js → coordinator-prompt-K66RGNWK.js} +3 -3
  38. package/dist/{coordinator-prompt-TU24TQ4I.js → coordinator-prompt-Z6UFHFDH.js} +3 -3
  39. package/dist/{global-workspace-743TPQNF.js → global-workspace-A6UO6PJV.js} +3 -3
  40. package/dist/{global-workspace-UPMXDQM7.js → global-workspace-RHEO55B4.js} +3 -3
  41. package/dist/index.cjs +270 -61
  42. package/dist/index.d.cts +48 -20
  43. package/dist/index.d.ts +48 -20
  44. package/dist/index.js +113 -36
  45. package/dist/mcp/run-stdio.cjs +245 -61
  46. package/dist/mcp/run-stdio.js +97 -31
  47. package/dist/{optional-cli-USD6PROE.js → optional-cli-RMMS5L6F.js} +3 -3
  48. package/dist/{optional-cli-2YDQQCCD.js → optional-cli-Y3Z3GIPZ.js} +4 -4
  49. package/dist/{orchestrator-VQ4HRYVB.js → orchestrator-2O6OJQLX.js} +8 -8
  50. package/dist/{orchestrator-B4STIW4B.js → orchestrator-7VTAUY24.js} +8 -8
  51. package/dist/{workspaces-47WQL5IG.js → workspaces-42E6AGXD.js} +3 -3
  52. package/dist/{workspaces-75FD4QUR.js → workspaces-NRKEXOYR.js} +3 -3
  53. package/dist/{worktree-G3E6WP66.js → worktree-4NGXCCUO.js} +2 -2
  54. package/dist/{worktree-PDNPL5CQ.js → worktree-ZDA2UJ7C.js} +2 -2
  55. package/package.json +1 -1
@@ -10512,7 +10512,7 @@ function coordinatorTurnReminder(opts) {
10512
10512
  const goal = opts.goal?.trim();
10513
10513
  return [
10514
10514
  "Sideboard Orchestration (mandatory):",
10515
- "- You oversee worktree agents from a synthetic empty cwd (not a git repo). Follow AGENTS.md / CLAUDE.md.",
10515
+ "- You oversee worktree agents from a synthetic empty cwd (not a git repo). Follow AGENTS.md (CLAUDE.md imports it).",
10516
10516
  `- YOUR orchestration chat id is ${opts.parentId} \u2014 pass parentChatId="${opts.parentId}" on create_workspace, or omit it.`,
10517
10517
  goal ? `- Goal / title: ${goal}` : null,
10518
10518
  accountDefaultsPlaybookLine(),
@@ -10576,10 +10576,9 @@ function ensureGlobalCoordinatorCwd(opts) {
10576
10576
  "Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_chat`). If the user gave a goal (Greptile 5/5, CI green), pass that goal through so they watch-fix-push until it lands \u2014 do not start that loop on a plain push, and do not tell the human to poll. Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
10577
10577
  ].join("\n");
10578
10578
  try {
10579
- (0, import_node_fs21.writeFileSync)((0, import_node_path24.join)(dir, "CLAUDE.md"), `${body}
10580
- `, "utf8");
10581
10579
  (0, import_node_fs21.writeFileSync)((0, import_node_path24.join)(dir, "AGENTS.md"), `${body}
10582
10580
  `, "utf8");
10581
+ (0, import_node_fs21.writeFileSync)((0, import_node_path24.join)(dir, "CLAUDE.md"), "@AGENTS.md\n", "utf8");
10583
10582
  } catch {
10584
10583
  }
10585
10584
  return dir;
@@ -10603,7 +10602,7 @@ function coordinatorSystemPrompt(opts) {
10603
10602
  ...intro,
10604
10603
  "You operate across ALL registered workspaces below.",
10605
10604
  "You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
10606
- "Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
10605
+ "Follow AGENTS.md in this cwd for the fleet playbook. CLAUDE.md is `@AGENTS.md` so Cursor does not load a second copy.",
10607
10606
  `YOUR orchestration chat id is ${opts.parentId} \u2014 pass parentChatId="${opts.parentId}" on create_workspace, or omit parentChatId (Sideboard binds it). Never invent a uuid.`,
10608
10607
  `Goal: ${opts.goal}`,
10609
10608
  "Registered workspaces:",
@@ -10641,7 +10640,7 @@ var init_coordinator_prompt = __esm({
10641
10640
  "- 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.",
10642
10641
  "- 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.",
10643
10642
  "- 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.",
10644
- "- 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), or 5-field `cron`. Recurring jobs without chatId open a new chat each run and archive it when that run finishes (History keeps the latest run 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.",
10643
+ "- 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.",
10645
10644
  "Projects:",
10646
10645
  "- add_project / remove_project \u2014 register or unregister a git repo",
10647
10646
  "Workspaces and chats:",
@@ -20515,12 +20514,12 @@ function ephemeralRunReadyToArchive(thread) {
20515
20514
  }
20516
20515
  function markEphemeralAutomation(threadId, opts) {
20517
20516
  const id = opts.ephemeralAutomationId?.trim();
20517
+ const title = opts.ephemeralTitle?.trim();
20518
20518
  const current = readThread(threadId);
20519
20519
  if (!current) throw new Error(`Thread not found: ${threadId}`);
20520
- if (!id) return current;
20521
- const title = opts.ephemeralTitle?.trim();
20520
+ if (!id && !title) return current;
20522
20521
  return updateThread(threadId, {
20523
- ephemeralAutomationId: id,
20522
+ ...id ? { ephemeralAutomationId: id } : {},
20524
20523
  ...title ? { title, userSetTitle: true } : {}
20525
20524
  });
20526
20525
  }
@@ -20555,11 +20554,19 @@ function clearEphemeralAutomation(threadId) {
20555
20554
  function forgetOlderEphemeralRuns(automationId, keepId) {
20556
20555
  const id = automationId.trim();
20557
20556
  if (!id || !keepId) return [];
20557
+ const archived = listThreads({ includeArchived: true }).filter(
20558
+ (thread) => thread.status === "archived" && thread.ephemeralAutomationId === id
20559
+ );
20560
+ const ranked = [...archived].sort((a, b) => {
20561
+ if (a.id === keepId) return -1;
20562
+ if (b.id === keepId) return 1;
20563
+ return archivedUpdatedMs(b) - archivedUpdatedMs(a);
20564
+ });
20565
+ const keep = new Set(ranked.slice(0, EPHEMERAL_AUTOMATION_KEEP).map((thread) => thread.id));
20566
+ keep.add(keepId);
20558
20567
  const removed = [];
20559
- for (const thread of listThreads({ includeArchived: true })) {
20560
- if (thread.id === keepId) continue;
20561
- if (thread.status !== "archived") continue;
20562
- if (thread.ephemeralAutomationId !== id) continue;
20568
+ for (const thread of archived) {
20569
+ if (keep.has(thread.id)) continue;
20563
20570
  try {
20564
20571
  deleteThreadRecord(thread.id);
20565
20572
  removed.push(thread.id);
@@ -20568,10 +20575,13 @@ function forgetOlderEphemeralRuns(automationId, keepId) {
20568
20575
  }
20569
20576
  return removed;
20570
20577
  }
20578
+ var EPHEMERAL_AUTOMATION_KEEP;
20571
20579
  var init_ephemeral_automation = __esm({
20572
20580
  "src/orchestrator/ephemeral-automation.ts"() {
20573
20581
  "use strict";
20582
+ init_history_retention();
20574
20583
  init_thread_store();
20584
+ EPHEMERAL_AUTOMATION_KEEP = 5;
20575
20585
  }
20576
20586
  });
20577
20587
 
@@ -26727,7 +26737,7 @@ function formatWorktreeDirective(thread, opts) {
26727
26737
  lines.push(formatDevAccessHint(opts?.activeRuns));
26728
26738
  lines.push("");
26729
26739
  lines.push(
26730
- "Automations (sidebar, after Board): `create_schedule` / `list_schedules` (`at` / `every` / `cron`). `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."
26740
+ "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."
26731
26741
  );
26732
26742
  lines.push(
26733
26743
  "`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
@@ -26747,7 +26757,7 @@ function connectedOrchestratorTurnReminder(thread, find) {
26747
26757
  return formatConnectedOrchestratorReminder(connectedOrchestrator(thread, find));
26748
26758
  }
26749
26759
  function formatWorktreeReminder() {
26750
- 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; chatId=self for this chat). set_workspace_tags updates labels on this workspace (omit ref).";
26760
+ 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).";
26751
26761
  }
26752
26762
  function formatClaudeSubagentReminder() {
26753
26763
  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.';
@@ -27858,10 +27868,7 @@ var init_caffeinate_hold = __esm({
27858
27868
  }
27859
27869
  });
27860
27870
 
27861
- // src/store/schedules.ts
27862
- function schedulesPath() {
27863
- return (0, import_node_path65.join)(appDataDir(), "schedules.json");
27864
- }
27871
+ // src/store/schedule-when.ts
27865
27872
  function parseDurationMs(every) {
27866
27873
  const m = every.trim().match(DURATION_RE);
27867
27874
  if (!m) return null;
@@ -27870,18 +27877,65 @@ function parseDurationMs(every) {
27870
27877
  const unit = m[2].toLowerCase();
27871
27878
  return n * UNIT_MS[unit];
27872
27879
  }
27873
- function defaultScheduleName(prompt) {
27874
- const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
27875
- if (!line) return "Untitled automation";
27876
- return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
27877
- }
27878
- function resolveScheduleThreadId(raw, env = process.env) {
27879
- const value = raw?.trim() || "";
27880
- if (!value) return null;
27881
- if (value.toLowerCase() === "self") {
27882
- return env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || env.SIDEBOARD_THREAD_ID?.trim() || null;
27880
+ function dayIndex(token) {
27881
+ const text7 = token.trim().toLowerCase();
27882
+ if (/^[0-6]$/.test(text7)) return Number(text7);
27883
+ return DAY_ALIASES[text7];
27884
+ }
27885
+ function normalizeWeekdays(days) {
27886
+ const set = /* @__PURE__ */ new Set();
27887
+ for (const day of days) {
27888
+ if (!Number.isInteger(day) || day < 0 || day > 6) {
27889
+ throw new Error("Days must be 0 (Sunday) through 6 (Saturday)");
27890
+ }
27891
+ set.add(day);
27892
+ }
27893
+ if (set.size === 0) throw new Error("Pick at least one day");
27894
+ return [...set].sort((a, b) => a - b);
27895
+ }
27896
+ function parseWeeklyDays(raw) {
27897
+ const text7 = raw.trim().toLowerCase();
27898
+ if (!text7) throw new Error("Pick at least one day");
27899
+ if (text7 === "daily" || text7 === "all" || text7 === "every day" || text7 === "everyday") {
27900
+ return [0, 1, 2, 3, 4, 5, 6];
27901
+ }
27902
+ if (text7 === "weekdays" || text7 === "weekday") return [1, 2, 3, 4, 5];
27903
+ if (text7 === "weekends" || text7 === "weekend") return [0, 6];
27904
+ const days = [];
27905
+ for (const part of text7.split(/[\s,]+/).filter(Boolean)) {
27906
+ const range = part.split("-");
27907
+ if (range.length === 2 && range[0] && range[1]) {
27908
+ const start = dayIndex(range[0]);
27909
+ const end = dayIndex(range[1]);
27910
+ if (start === void 0 || end === void 0) {
27911
+ throw new Error(`Unknown day: ${part} (use mon,tue, weekdays, weekends, or daily)`);
27912
+ }
27913
+ if (start <= end) {
27914
+ for (let d = start; d <= end; d++) days.push(d);
27915
+ } else {
27916
+ for (let d = start; d <= 6; d++) days.push(d);
27917
+ for (let d = 0; d <= end; d++) days.push(d);
27918
+ }
27919
+ continue;
27920
+ }
27921
+ const n = dayIndex(part);
27922
+ if (n === void 0) {
27923
+ throw new Error(`Unknown day: ${part} (use mon,tue, weekdays, weekends, or daily)`);
27924
+ }
27925
+ days.push(n);
27883
27926
  }
27884
- return value;
27927
+ return normalizeWeekdays(days);
27928
+ }
27929
+ function normalizeWeeklyTime(raw) {
27930
+ const m = raw.trim().match(TIME_RE);
27931
+ if (!m) throw new Error(`Invalid time: ${raw} (use HH:mm, 24-hour)`);
27932
+ return `${m[1]}:${m[2]}`;
27933
+ }
27934
+ function weeklyCronExpr(when) {
27935
+ const time = normalizeWeeklyTime(when.time);
27936
+ const [hh, mm] = time.split(":");
27937
+ const days = normalizeWeekdays(when.days);
27938
+ return `${Number(mm)} ${Number(hh)} * * ${days.join(",")}`;
27885
27939
  }
27886
27940
  function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
27887
27941
  if (when.kind === "once") {
@@ -27898,6 +27952,12 @@ function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
27898
27952
  }
27899
27953
  return new Date(from.getTime() + ms);
27900
27954
  }
27955
+ if (when.kind === "weekly") {
27956
+ return computeNextRunAt(
27957
+ { kind: "cron", expr: weeklyCronExpr(when), tz: when.tz },
27958
+ from
27959
+ );
27960
+ }
27901
27961
  try {
27902
27962
  const job = new import_croner.Cron(when.expr, {
27903
27963
  timezone: when.tz?.trim() || void 0,
@@ -27913,9 +27973,6 @@ function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
27913
27973
  throw new Error(`Invalid cron: ${message}`);
27914
27974
  }
27915
27975
  }
27916
- function nowIso2(d = /* @__PURE__ */ new Date()) {
27917
- return d.toISOString();
27918
- }
27919
27976
  function normalizeWhen(raw) {
27920
27977
  if (!raw || typeof raw !== "object") {
27921
27978
  throw new Error("Schedule is missing `when`");
@@ -27935,7 +27992,70 @@ function normalizeWhen(raw) {
27935
27992
  tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
27936
27993
  };
27937
27994
  }
27938
- throw new Error("Schedule `when` must be once, every, or cron");
27995
+ if (kind === "weekly" && Array.isArray(rec.days) && typeof rec.time === "string") {
27996
+ return {
27997
+ kind: "weekly",
27998
+ days: normalizeWeekdays(rec.days.map((day) => Number(day))),
27999
+ time: normalizeWeeklyTime(rec.time),
28000
+ tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
28001
+ };
28002
+ }
28003
+ throw new Error("Schedule `when` must be once, every, weekly, or cron");
28004
+ }
28005
+ var import_croner, DURATION_RE, UNIT_MS, DAY_ALIASES, TIME_RE;
28006
+ var init_schedule_when = __esm({
28007
+ "src/store/schedule-when.ts"() {
28008
+ "use strict";
28009
+ import_croner = require("croner");
28010
+ DURATION_RE = /^(\d+)(s|m|h|d)$/i;
28011
+ UNIT_MS = {
28012
+ s: 1e3,
28013
+ m: 6e4,
28014
+ h: 36e5,
28015
+ d: 864e5
28016
+ };
28017
+ DAY_ALIASES = {
28018
+ sun: 0,
28019
+ sunday: 0,
28020
+ mon: 1,
28021
+ monday: 1,
28022
+ tue: 2,
28023
+ tues: 2,
28024
+ tuesday: 2,
28025
+ wed: 3,
28026
+ wednesday: 3,
28027
+ thu: 4,
28028
+ thur: 4,
28029
+ thurs: 4,
28030
+ thursday: 4,
28031
+ fri: 5,
28032
+ friday: 5,
28033
+ sat: 6,
28034
+ saturday: 6
28035
+ };
28036
+ TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)(?::[0-5]\d)?$/;
28037
+ }
28038
+ });
28039
+
28040
+ // src/store/schedules.ts
28041
+ function schedulesPath() {
28042
+ return (0, import_node_path65.join)(appDataDir(), "schedules.json");
28043
+ }
28044
+ function defaultScheduleName(prompt) {
28045
+ const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
28046
+ if (!line) return "Untitled automation";
28047
+ return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
28048
+ }
28049
+ function resolveScheduleThreadId(raw, env = process.env) {
28050
+ const value = raw?.trim() || "";
28051
+ if (!value) return null;
28052
+ if (value.toLowerCase() === "self") {
28053
+ return env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || env.SIDEBOARD_THREAD_ID?.trim() || null;
28054
+ }
28055
+ return value;
28056
+ }
28057
+ function nowIso2(d = /* @__PURE__ */ new Date()) {
28058
+ return d.toISOString();
27939
28059
  }
27940
28060
  function normalizeOptionalBool(raw) {
27941
28061
  if (raw === true || raw === false) return raw;
@@ -27985,6 +28105,7 @@ function normalizeTask(raw) {
27985
28105
  planMode: normalizeOptionalBool(raw.planMode),
27986
28106
  autonomy: normalizeAutonomy2(raw.autonomy),
27987
28107
  attachments: normalizeAttachments(raw.attachments),
28108
+ archiveChat: raw.archiveChat !== false,
27988
28109
  nextRunAt: raw.nextRunAt,
27989
28110
  lastRunAt: raw.lastRunAt ?? null,
27990
28111
  lastThreadId: raw.lastThreadId ?? null,
@@ -28036,6 +28157,7 @@ function buildTask(input, now) {
28036
28157
  planMode: normalizeOptionalBool(input.planMode),
28037
28158
  autonomy: normalizeAutonomy2(input.autonomy),
28038
28159
  attachments: normalizeAttachments(input.attachments),
28160
+ archiveChat: input.archiveChat !== false,
28039
28161
  nextRunAt,
28040
28162
  lastRunAt: null,
28041
28163
  lastThreadId: null,
@@ -28097,6 +28219,7 @@ function updateSchedule(id, patch) {
28097
28219
  planMode: patch.planMode !== void 0 ? normalizeOptionalBool(patch.planMode) : prev.planMode,
28098
28220
  autonomy: patch.autonomy !== void 0 ? normalizeAutonomy2(patch.autonomy) : prev.autonomy,
28099
28221
  attachments: patch.attachments !== void 0 ? normalizeAttachments(patch.attachments) : prev.attachments,
28222
+ archiveChat: patch.archiveChat !== void 0 ? patch.archiveChat !== false : prev.archiveChat,
28100
28223
  nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
28101
28224
  lastError: whenChanged ? null : prev.lastError,
28102
28225
  updatedAt: nowIso2()
@@ -28133,26 +28256,20 @@ function recordScheduleRun(id, result) {
28133
28256
  writeAll2(rows);
28134
28257
  return next;
28135
28258
  }
28136
- var import_node_crypto17, import_node_fs66, import_node_path65, import_croner, DURATION_RE, UNIT_MS, ATTACHMENT_KINDS;
28259
+ var import_node_crypto17, import_node_fs66, import_node_path65, ATTACHMENT_KINDS;
28137
28260
  var init_schedules = __esm({
28138
28261
  "src/store/schedules.ts"() {
28139
28262
  "use strict";
28140
28263
  import_node_crypto17 = require("crypto");
28141
28264
  import_node_fs66 = require("fs");
28142
28265
  import_node_path65 = require("path");
28143
- import_croner = require("croner");
28266
+ init_schedule_when();
28144
28267
  init_orchestrator_capable();
28145
28268
  init_thinking_effort();
28146
28269
  init_paths();
28147
28270
  init_private_file();
28148
28271
  init_app_settings();
28149
- DURATION_RE = /^(\d+)(s|m|h|d)$/i;
28150
- UNIT_MS = {
28151
- s: 1e3,
28152
- m: 6e4,
28153
- h: 36e5,
28154
- d: 864e5
28155
- };
28272
+ init_schedule_when();
28156
28273
  ATTACHMENT_KINDS = /* @__PURE__ */ new Set([
28157
28274
  "transcript",
28158
28275
  "file",
@@ -28237,6 +28354,7 @@ async function fireSchedule(id) {
28237
28354
  });
28238
28355
  }
28239
28356
  const pinReplacement = Boolean(schedule.threadId) && schedule.when.kind !== "once";
28357
+ const archiveFresh = schedule.archiveChat !== false && !pinReplacement;
28240
28358
  const created = await deps.startOrchestration({
28241
28359
  goal: prompt,
28242
28360
  agent: pickAgent(schedule),
@@ -28246,7 +28364,8 @@ async function fireSchedule(id) {
28246
28364
  ...schedule.planMode != null ? { planMode: schedule.planMode } : {},
28247
28365
  ...schedule.autonomy ? { autonomy: schedule.autonomy } : {},
28248
28366
  attachments: schedule.attachments,
28249
- ...pinReplacement ? {} : { ephemeralAutomationId: schedule.id, ephemeralTitle: schedule.name }
28367
+ ...archiveFresh ? { ephemeralAutomationId: schedule.id } : {},
28368
+ ephemeralTitle: schedule.name
28250
28369
  });
28251
28370
  if (pinReplacement) {
28252
28371
  updateSchedule(schedule.id, { threadId: created.id });
@@ -34791,18 +34910,79 @@ function fail4(err) {
34791
34910
  );
34792
34911
  }
34793
34912
  function parseWhen(args) {
34794
- const count = [args.at, args.every, args.cron].filter((v) => v?.trim()).length;
34795
- if (count !== 1) {
34796
- throw new Error("Pass exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron");
34913
+ const at = args.at?.trim();
34914
+ const every = args.every?.trim();
34915
+ const cron = args.cron?.trim();
34916
+ const time = args.time?.trim();
34917
+ const days = args.days?.trim();
34918
+ if (days && !time && !at && !every && !cron) {
34919
+ throw new Error("days needs time (HH:mm), for example time=07:30 days=weekdays");
34920
+ }
34921
+ const picked = [at, every, cron, time].filter(Boolean);
34922
+ if (picked.length !== 1) {
34923
+ throw new Error(
34924
+ "Pass exactly one of at (ISO datetime), every (15m/1h/6h/1d), time (HH:mm), or cron"
34925
+ );
34926
+ }
34927
+ if (at) return { kind: "once", at };
34928
+ if (every) return { kind: "every", every };
34929
+ if (time) {
34930
+ return {
34931
+ kind: "weekly",
34932
+ time,
34933
+ days: parseWeeklyDays(days || "daily"),
34934
+ tz: args.tz?.trim() || void 0
34935
+ };
34797
34936
  }
34798
- if (args.at?.trim()) return { kind: "once", at: args.at.trim() };
34799
- if (args.every?.trim()) return { kind: "every", every: args.every.trim() };
34800
34937
  return {
34801
34938
  kind: "cron",
34802
- expr: args.cron.trim(),
34939
+ expr: cron,
34803
34940
  tz: args.tz?.trim() || void 0
34804
34941
  };
34805
34942
  }
34943
+ function whenFromUpdate(id, args) {
34944
+ const at = args.at?.trim();
34945
+ const every = args.every?.trim();
34946
+ const cron = args.cron?.trim();
34947
+ const time = args.time?.trim();
34948
+ const days = args.days?.trim();
34949
+ const tzSet = args.tz !== void 0;
34950
+ if (!at && !every && !cron && !time && !days && !tzSet) return void 0;
34951
+ if ((time || days) && !at && !every && !cron) {
34952
+ const current = getSchedule(id);
34953
+ const weekly = current?.when.kind === "weekly" ? current.when : null;
34954
+ if (!weekly && !(time && days)) {
34955
+ const kind = current?.when.kind ?? "missing";
34956
+ throw new Error(
34957
+ `time and days change a days-at-a-time automation. This job is ${kind}. Pass time and days together to switch it, or at / every / cron to replace the cadence.`
34958
+ );
34959
+ }
34960
+ const clock = time || weekly?.time;
34961
+ if (!clock) {
34962
+ throw new Error("time (HH:mm) is required, for example time=07:30 days=weekdays");
34963
+ }
34964
+ const prevTz = current?.when.kind === "weekly" || current?.when.kind === "cron" ? current.when.tz : void 0;
34965
+ return {
34966
+ kind: "weekly",
34967
+ time: clock,
34968
+ days: days ? parseWeeklyDays(days) : weekly?.days ?? parseWeeklyDays("daily"),
34969
+ tz: tzSet ? args.tz?.trim() || void 0 : prevTz
34970
+ };
34971
+ }
34972
+ if (!at && !every && !cron && !time && !days && tzSet) {
34973
+ const current = getSchedule(id);
34974
+ if (current?.when.kind === "weekly" || current?.when.kind === "cron") {
34975
+ return { ...current.when, tz: args.tz?.trim() || void 0 };
34976
+ }
34977
+ return void 0;
34978
+ }
34979
+ return parseWhen(args);
34980
+ }
34981
+ function createdHint(schedule) {
34982
+ const awake = scheduleNeedsCaffeinate(schedule) ? " Caffeinate while automations are enabled is now on so the Mac can stay awake until this job is due." : "";
34983
+ 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.";
34984
+ return `Fires while Sideboard.app is running.${fresh}${awake}`;
34985
+ }
34806
34986
  function registerScheduleTools(server) {
34807
34987
  server.tool(
34808
34988
  "list_schedules",
@@ -34818,14 +34998,19 @@ function registerScheduleTools(server) {
34818
34998
  );
34819
34999
  server.tool(
34820
35000
  "create_schedule",
34821
- "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), or cron (5-field). Recurring jobs without chatId open a new Global chat each run and archive it when that run finishes. History keeps the latest run 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.",
35001
+ "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.",
34822
35002
  {
34823
35003
  prompt: import_zod10.z.string().describe("User message / goal queued when the automation fires"),
34824
35004
  name: import_zod10.z.string().optional(),
34825
35005
  at: import_zod10.z.string().optional().describe("ISO datetime for a one-shot"),
34826
35006
  every: import_zod10.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
35007
+ time: import_zod10.z.string().optional().describe("HH:mm (24-hour) clock time. Omit days for every day."),
35008
+ days: import_zod10.z.string().optional().describe("daily, weekdays, weekends, or days such as mon,wed,fri or mon-fri. Use with time."),
34827
35009
  cron: import_zod10.z.string().optional().describe("5-field cron expression"),
34828
- tz: import_zod10.z.string().optional().describe("IANA timezone for cron (default: system)"),
35010
+ tz: import_zod10.z.string().optional().describe("IANA timezone for cron or time (default: system)"),
35011
+ archiveChat: import_zod10.z.boolean().optional().describe(
35012
+ "Archive a new Global chat when the run finishes. Default true. Pass false to leave the chat open."
35013
+ ),
34829
35014
  chatId: import_zod10.z.string().optional().describe(
34830
35015
  'Existing chat id (orchestration or workspace), or "self" for this chat. Omit to create a new Global chat on fire.'
34831
35016
  ),
@@ -34852,11 +35037,12 @@ function registerScheduleTools(server) {
34852
35037
  threadId,
34853
35038
  agent: args.agent,
34854
35039
  model: args.model,
35040
+ archiveChat: args.archiveChat,
34855
35041
  createdBy: "mcp"
34856
35042
  });
34857
35043
  return text6({
34858
35044
  schedule,
34859
- hint: scheduleNeedsCaffeinate(schedule) ? "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run and archive it when that run finishes (History keeps the latest). Caffeinate while automations are enabled is now on so the Mac can stay awake until this job is due." : "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run and archive it when that run finishes (History keeps the latest)."
35045
+ hint: createdHint(schedule)
34860
35046
  });
34861
35047
  } catch (err) {
34862
35048
  return fail4(err);
@@ -34865,15 +35051,18 @@ function registerScheduleTools(server) {
34865
35051
  );
34866
35052
  server.tool(
34867
35053
  "update_schedule",
34868
- "Update a local automation (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
35054
+ "Update a local automation (prompt, cadence, target chat, archiveChat, enabled). Pass id from list_schedules.",
34869
35055
  {
34870
35056
  id: import_zod10.z.string(),
34871
35057
  prompt: import_zod10.z.string().optional(),
34872
35058
  name: import_zod10.z.string().optional(),
34873
35059
  at: import_zod10.z.string().optional(),
34874
35060
  every: import_zod10.z.string().optional(),
35061
+ time: import_zod10.z.string().optional().describe("HH:mm clock time. Use with days, or alone to change the time of a weekly job."),
35062
+ days: import_zod10.z.string().optional().describe("daily, weekdays, weekends, or mon,wed,fri. Use with time, or alone on an existing weekly job."),
34875
35063
  cron: import_zod10.z.string().optional(),
34876
35064
  tz: import_zod10.z.string().optional(),
35065
+ archiveChat: import_zod10.z.boolean().optional().describe("Archive a new Global chat when the run finishes. False leaves it open."),
34877
35066
  chatId: import_zod10.z.string().optional().describe(
34878
35067
  'Existing chat (orchestration or workspace), "self", or empty string to create a new Global chat each run'
34879
35068
  ),
@@ -34884,13 +35073,7 @@ function registerScheduleTools(server) {
34884
35073
  },
34885
35074
  async (args) => {
34886
35075
  try {
34887
- const cadenceSet = [args.at, args.every, args.cron].some((v) => v?.trim());
34888
- const when = cadenceSet ? parseWhen({
34889
- at: args.at,
34890
- every: args.every,
34891
- cron: args.cron,
34892
- tz: args.tz
34893
- }) : void 0;
35076
+ const when = whenFromUpdate(args.id, args);
34894
35077
  let threadId;
34895
35078
  const rawChatId = args.chatId ?? args.threadId;
34896
35079
  if (rawChatId !== void 0) {
@@ -34910,7 +35093,8 @@ function registerScheduleTools(server) {
34910
35093
  threadId,
34911
35094
  agent: args.agent,
34912
35095
  enabled: args.enabled,
34913
- model: args.model
35096
+ model: args.model,
35097
+ archiveChat: args.archiveChat
34914
35098
  });
34915
35099
  return text6({ schedule });
34916
35100
  } catch (err) {