@sideboard-ai/core 0.1.318 → 0.1.320

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 (61) hide show
  1. package/dist/{abletime-TKAFNXCM.js → abletime-CRRLEYTE.js} +6 -4
  2. package/dist/{abletime-KWOBUL46.js → abletime-ICW6Q2AB.js} +6 -4
  3. package/dist/{abletime-oauth-S4AXA4WR.js → abletime-oauth-P3KPPJE4.js} +3 -3
  4. package/dist/{abletime-oauth-IUIAGLFJ.js → abletime-oauth-Q76XBSPG.js} +3 -3
  5. package/dist/{abletime-rest-RWN4EPY3.js → abletime-rest-DNFAYYLP.js} +3 -3
  6. package/dist/{abletime-rest-ORBELZQO.js → abletime-rest-DQ3PNKXP.js} +3 -3
  7. package/dist/{agents-KABJUDPI.js → agents-CJZYJOWP.js} +7 -7
  8. package/dist/{agents-2AR7I6B6.js → agents-F2E2HACB.js} +7 -7
  9. package/dist/{app-settings-CZVFMOI5.js → app-settings-KMPVI2B7.js} +1 -1
  10. package/dist/{app-settings-V6YPYY57.js → app-settings-P3H4SEIK.js} +1 -1
  11. package/dist/{chunk-YEIHCSZS.js → chunk-4GZC6DDH.js} +2 -0
  12. package/dist/{chunk-XOZNEAWF.js → chunk-56M6VEXI.js} +3 -3
  13. package/dist/{chunk-7SCVGGF6.js → chunk-5OQONGHO.js} +5 -5
  14. package/dist/{chunk-A6C67DY5.js → chunk-5S3YP5KH.js} +4 -4
  15. package/dist/{chunk-I5CLCWUK.js → chunk-72PUPPV2.js} +9 -5
  16. package/dist/{chunk-BXTISNKW.js → chunk-B7UL36WH.js} +428 -99
  17. package/dist/{chunk-W5QZWAUB.js → chunk-BTIXTH4N.js} +9 -5
  18. package/dist/{chunk-AESO2KJS.js → chunk-CB5Z5C3J.js} +2 -2
  19. package/dist/{chunk-2HOZMSKQ.js → chunk-DFUXW2IT.js} +2 -2
  20. package/dist/{chunk-ST2UQBJT.js → chunk-DJZRDTZY.js} +6 -6
  21. package/dist/{chunk-TUJFCJJU.js → chunk-EDXTP2VR.js} +2 -2
  22. package/dist/{chunk-I5TESHDN.js → chunk-EQ52JR3Q.js} +5 -5
  23. package/dist/{chunk-FDXHNROD.js → chunk-EQ5JY3MJ.js} +2 -2
  24. package/dist/{chunk-E52SQENB.js → chunk-FZKAAAEB.js} +3 -1
  25. package/dist/{chunk-VZAJVX36.js → chunk-HHPTGIKS.js} +2 -2
  26. package/dist/{chunk-23ZUL2EX.js → chunk-IDPFLXRY.js} +429 -100
  27. package/dist/{chunk-GCHSJCCX.js → chunk-IFU4W4C4.js} +3 -1
  28. package/dist/{chunk-IAPO7ZFG.js → chunk-LLIEU2ZB.js} +5 -5
  29. package/dist/{chunk-MBYGWNLQ.js → chunk-OAFBHCSM.js} +1 -1
  30. package/dist/{chunk-NRSC4N5X.js → chunk-P74GXA5V.js} +6 -6
  31. package/dist/{chunk-A46CIARU.js → chunk-PNLGWAYE.js} +2 -2
  32. package/dist/{chunk-53OCOGND.js → chunk-QGY3UNNL.js} +2 -2
  33. package/dist/{chunk-7Q327QF2.js → chunk-QIC2JXHW.js} +5 -5
  34. package/dist/{chunk-YALFZKSC.js → chunk-RHJPP26T.js} +11 -11
  35. package/dist/{chunk-AZ4GEEGL.js → chunk-UK2KESBN.js} +2 -2
  36. package/dist/{chunk-7ADEYEMH.js → chunk-V76MQ7MH.js} +5 -5
  37. package/dist/{chunk-HOLB6ZFZ.js → chunk-XMHMVEIY.js} +2 -0
  38. package/dist/{chunk-GH7UMULC.js → chunk-ZYZBCLJR.js} +6 -6
  39. package/dist/{conductor-MG2RAQNT.js → conductor-FPFY6Z5S.js} +6 -6
  40. package/dist/{conductor-NIUBJLTS.js → conductor-JA5W3L7C.js} +6 -6
  41. package/dist/{coordinator-prompt-3CT3D5UM.js → coordinator-prompt-M75TAN43.js} +4 -4
  42. package/dist/{coordinator-prompt-X74F3JIC.js → coordinator-prompt-NUC2ZXMJ.js} +4 -4
  43. package/dist/{global-workspace-BUWMRNW5.js → global-workspace-5A4H34AF.js} +4 -4
  44. package/dist/{global-workspace-FTNJEML3.js → global-workspace-SCLQUNUI.js} +4 -4
  45. package/dist/index.cjs +861 -475
  46. package/dist/index.d.cts +56 -13
  47. package/dist/index.d.ts +56 -13
  48. package/dist/index.js +54 -31
  49. package/dist/mcp/run-stdio.cjs +459 -95
  50. package/dist/mcp/run-stdio.js +23 -23
  51. package/dist/{optional-cli-EMSZBYDE.js → optional-cli-2V3SLME5.js} +3 -3
  52. package/dist/{optional-cli-6GGESWGE.js → optional-cli-MRNQKVIR.js} +4 -4
  53. package/dist/{orchestrator-N54V3B76.js → orchestrator-7BDZ5GWD.js} +10 -10
  54. package/dist/{orchestrator-2SXK4U65.js → orchestrator-NPFWU3XU.js} +10 -10
  55. package/dist/{thread-store-PLNIWFZ4.js → thread-store-JWHVXLDX.js} +1 -1
  56. package/dist/{thread-store-7BBA63KV.js → thread-store-QHYN2ZMQ.js} +1 -1
  57. package/dist/{workspaces-2V5UYPYC.js → workspaces-5QA3YM4L.js} +4 -4
  58. package/dist/{workspaces-MGKEAYNM.js → workspaces-KWMBOTQD.js} +4 -4
  59. package/dist/{worktree-THAXW5U7.js → worktree-7EATHVYQ.js} +3 -3
  60. package/dist/{worktree-XJKLDYOL.js → worktree-O6BANO2I.js} +3 -3
  61. package/package.json +1 -1
@@ -1405,13 +1405,13 @@ function toolDescription(name, input) {
1405
1405
  else if (/notify_orchestrator$/i.test(name)) return "Notify orchestrator";
1406
1406
  else if (/set_workspace_tags$/i.test(name)) return "Set workspace tags";
1407
1407
  if (/(?:list|set|delete)_env$/i.test(name)) return /list_env$/i.test(name) ? "List env vars" : /set_env$/i.test(name) ? "Set env var" : "Delete env var";
1408
- if (/list_schedules$/i.test(name)) return "List schedules";
1408
+ if (/list_schedules$/i.test(name)) return "List automations";
1409
1409
  if (/create_schedule$/i.test(name)) {
1410
- return str(input?.name) ? `Schedule ${str(input?.name)}` : "Create schedule";
1410
+ return str(input?.name) ? `Automation ${str(input?.name)}` : "Create automation";
1411
1411
  }
1412
- if (/update_schedule$/i.test(name)) return "Update schedule";
1413
- if (/delete_schedule$/i.test(name)) return "Delete schedule";
1414
- if (/run_schedule$/i.test(name)) return "Run schedule";
1412
+ if (/update_schedule$/i.test(name)) return "Update automation";
1413
+ if (/delete_schedule$/i.test(name)) return "Delete automation";
1414
+ if (/run_schedule$/i.test(name)) return "Run automation";
1415
1415
  if (isSkillToolName(name)) {
1416
1416
  const cmd = skillCommandFromInput(input);
1417
1417
  return cmd ? `Using /${cmd}` : "Using skill";
@@ -4532,7 +4532,9 @@ function planHistoryRetention(archived, thresholds = {}) {
4532
4532
  if (archivedUpdatedMs(thread) < cutoff) expiredIds.add(thread.id);
4533
4533
  }
4534
4534
  }
4535
- const remaining = oldestFirst.filter((t) => !expiredIds.has(t.id));
4535
+ const remaining = oldestFirst.filter(
4536
+ (t) => !expiredIds.has(t.id) && !t.ephemeralAutomationId?.trim()
4537
+ );
4536
4538
  const overflow = remaining.length > maxCount ? remaining.slice(0, remaining.length - maxCount) : [];
4537
4539
  const stripIds = [];
4538
4540
  const purgeIds = [...expiredIds];
@@ -7638,6 +7640,7 @@ function normalizeThread(raw) {
7638
7640
  quotaContinuedFromId: raw.quotaContinuedFromId ?? null,
7639
7641
  accountId: raw.accountId === null ? null : typeof raw.accountId === "string" && raw.accountId.trim() ? raw.accountId.trim() : void 0,
7640
7642
  archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0,
7643
+ ephemeralAutomationId: typeof raw.ephemeralAutomationId === "string" && raw.ephemeralAutomationId.trim() ? raw.ephemeralAutomationId.trim() : void 0,
7641
7644
  compactedMessages: Array.isArray(raw.compactedMessages) ? raw.compactedMessages : void 0
7642
7645
  };
7643
7646
  }
@@ -7824,6 +7827,7 @@ function updateThread(id, patch) {
7824
7827
  } else {
7825
7828
  delete next.archivedAt;
7826
7829
  }
7830
+ if (!next.ephemeralAutomationId?.trim()) delete next.ephemeralAutomationId;
7827
7831
  writeThread(next);
7828
7832
  return next;
7829
7833
  }
@@ -10250,7 +10254,7 @@ var init_coordinator_prompt = __esm({
10250
10254
  "- 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.",
10251
10255
  "- 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.",
10252
10256
  "- 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.",
10253
- "- list_schedules / create_schedule / update_schedule / delete_schedule / run_schedule \u2014 local jobs 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. 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 schedules are enabled (or call set_caffeinate).",
10257
+ "- 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.",
10254
10258
  "Projects:",
10255
10259
  "- add_project / remove_project \u2014 register or unregister a git repo",
10256
10260
  "Workspaces and chats:",
@@ -20060,6 +20064,77 @@ var init_steer_request = __esm({
20060
20064
  }
20061
20065
  });
20062
20066
 
20067
+ // src/orchestrator/ephemeral-automation.ts
20068
+ function ephemeralRunReadyToArchive(thread) {
20069
+ if (!thread.ephemeralAutomationId?.trim()) return false;
20070
+ if (thread.status !== "idle" && thread.status !== "error") return false;
20071
+ if (thread.queue.length > 0) return false;
20072
+ if (thread.agentBlock?.reason?.trim()) return false;
20073
+ if (thread.quotaResumeAt) return false;
20074
+ return true;
20075
+ }
20076
+ function markEphemeralAutomation(threadId, opts) {
20077
+ const id = opts.ephemeralAutomationId?.trim();
20078
+ const current = readThread(threadId);
20079
+ if (!current) throw new Error(`Thread not found: ${threadId}`);
20080
+ if (!id) return current;
20081
+ const title = opts.ephemeralTitle?.trim();
20082
+ return updateThread(threadId, {
20083
+ ephemeralAutomationId: id,
20084
+ ...title ? { title, userSetTitle: true } : {}
20085
+ });
20086
+ }
20087
+ async function archiveEphemeralAutomationIfReady(threadId, archive) {
20088
+ const keep = await withThreadLock(threadId, async () => {
20089
+ const thread = readThread(threadId);
20090
+ if (!thread || !ephemeralRunReadyToArchive(thread)) return null;
20091
+ const automationId = thread.ephemeralAutomationId?.trim();
20092
+ if (!automationId) return null;
20093
+ await archive(thread.id);
20094
+ return {
20095
+ automationId,
20096
+ keepId: archivedContinuationId(automationId, threadId) ?? threadId
20097
+ };
20098
+ });
20099
+ if (keep) forgetOlderEphemeralRuns(keep.automationId, keep.keepId);
20100
+ }
20101
+ function archivedContinuationId(automationId, sourceId) {
20102
+ for (const thread of listThreads({ includeArchived: true })) {
20103
+ if (thread.id === sourceId || thread.status !== "archived") continue;
20104
+ if (thread.ephemeralAutomationId !== automationId) continue;
20105
+ if (thread.quotaContinuedFromId !== sourceId) continue;
20106
+ return thread.id;
20107
+ }
20108
+ return null;
20109
+ }
20110
+ function clearEphemeralAutomation(threadId) {
20111
+ const thread = readThread(threadId);
20112
+ if (!thread?.ephemeralAutomationId?.trim()) return;
20113
+ updateThread(threadId, { ephemeralAutomationId: void 0 });
20114
+ }
20115
+ function forgetOlderEphemeralRuns(automationId, keepId) {
20116
+ const id = automationId.trim();
20117
+ if (!id || !keepId) return [];
20118
+ const removed = [];
20119
+ for (const thread of listThreads({ includeArchived: true })) {
20120
+ if (thread.id === keepId) continue;
20121
+ if (thread.status !== "archived") continue;
20122
+ if (thread.ephemeralAutomationId !== id) continue;
20123
+ try {
20124
+ deleteThreadRecord(thread.id);
20125
+ removed.push(thread.id);
20126
+ } catch {
20127
+ }
20128
+ }
20129
+ return removed;
20130
+ }
20131
+ var init_ephemeral_automation = __esm({
20132
+ "src/orchestrator/ephemeral-automation.ts"() {
20133
+ "use strict";
20134
+ init_thread_store();
20135
+ }
20136
+ });
20137
+
20063
20138
  // src/threads/title.ts
20064
20139
  var title_exports = {};
20065
20140
  __export(title_exports, {
@@ -22170,6 +22245,7 @@ var init_abletime_projects = __esm({
22170
22245
  var abletime_exports = {};
22171
22246
  __export(abletime_exports, {
22172
22247
  ableTimeDependencyTarget: () => ableTimeDependencyTarget,
22248
+ ableTimeStateIsClosed: () => ableTimeStateIsClosed,
22173
22249
  commentAbleTimeTask: () => commentAbleTimeTask,
22174
22250
  createAbleTimeTask: () => createAbleTimeTask,
22175
22251
  downloadAbleTimeAttachment: () => downloadAbleTimeAttachment,
@@ -22323,9 +22399,12 @@ function issueAttachmentForAbleTimeTask(task) {
22323
22399
  ].filter(Boolean).join("\n")
22324
22400
  };
22325
22401
  }
22402
+ function ableTimeStateIsClosed(state) {
22403
+ const value = (state ?? "").trim().toLowerCase();
22404
+ return Boolean(value && CLOSED_STATES.has(value));
22405
+ }
22326
22406
  function isOpenTask(task) {
22327
- const state = (task.state ?? "").toLowerCase();
22328
- return !state || !CLOSED_STATES.has(state);
22407
+ return !ableTimeStateIsClosed(task.state);
22329
22408
  }
22330
22409
  async function getAbleTimeOrientation(opts) {
22331
22410
  const raw = await callAbleTimeTool("orientation", {}, opts);
@@ -23301,6 +23380,9 @@ function writeTurnLive(threadId, progress) {
23301
23380
  }
23302
23381
  }
23303
23382
  }
23383
+ function readTurnLiveParts(threadId) {
23384
+ return buffers.get(threadId)?.parts.slice() ?? [];
23385
+ }
23304
23386
  function readTurnLive(threadId) {
23305
23387
  const path = threadLivePath(threadId);
23306
23388
  if (!(0, import_node_fs55.existsSync)(path)) return null;
@@ -23561,17 +23643,23 @@ async function isolateQuotaFailover(run2) {
23561
23643
  }
23562
23644
  }
23563
23645
  function createQuotaFailoverChat(from, fallbackAgent, limitText, opts) {
23646
+ const ephemeralAutomationId = from.ephemeralAutomationId?.trim();
23647
+ const ephemeralTitle = ephemeralAutomationId ? from.title.trim() : "";
23564
23648
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
23565
23649
  const tab = createChatTab({
23566
23650
  fromThreadId: from.id,
23567
23651
  agent: fallbackAgent,
23568
23652
  model: null,
23569
23653
  attachments: [handoff],
23654
+ ...ephemeralTitle ? { title: ephemeralTitle } : {},
23570
23655
  ...opts && "accountId" in opts ? { accountId: opts.accountId } : {}
23571
23656
  });
23572
23657
  const patch = {
23573
23658
  quotaContinuedFromId: from.id,
23574
- sourceRef: from.sourceRef
23659
+ sourceRef: from.sourceRef,
23660
+ // The original drain archives as idle. This sibling has to carry the one-off
23661
+ // so it archives when the continued turn finishes and History keeps it.
23662
+ ...ephemeralAutomationId ? { ephemeralAutomationId } : {}
23575
23663
  };
23576
23664
  if (isOrchestratorThread(from)) {
23577
23665
  patch.parentThreadId = from.id;
@@ -25585,6 +25673,41 @@ var init_expand = __esm({
25585
25673
  }
25586
25674
  });
25587
25675
 
25676
+ // src/composer/agent-has-replied.ts
25677
+ function joinSteerPrompt(base, addition) {
25678
+ const next = addition.trim();
25679
+ if (!next) return base;
25680
+ const head = base.replace(/\s+$/, "");
25681
+ if (!head) return next;
25682
+ return `${head}
25683
+ ${next}`;
25684
+ }
25685
+ function trailingUnansweredUserText(messages) {
25686
+ for (let i = messages.length - 1; i >= 0; i--) {
25687
+ const message = messages[i];
25688
+ if (!message || message.origin === "continue" || message.role === "summary") continue;
25689
+ if (message.role === "agent") {
25690
+ const text7 = (message.text ?? "").trim();
25691
+ if (text7 || (message.parts?.length ?? 0) > 0) return null;
25692
+ continue;
25693
+ }
25694
+ if (message.role === "user") return message.text;
25695
+ }
25696
+ return null;
25697
+ }
25698
+ function livePartsShowAgentReply(parts) {
25699
+ for (const part of parts) {
25700
+ if (part.type === "tool") return true;
25701
+ if ((part.type === "text" || part.type === "thinking") && part.text.trim()) return true;
25702
+ }
25703
+ return false;
25704
+ }
25705
+ var init_agent_has_replied = __esm({
25706
+ "src/composer/agent-has-replied.ts"() {
25707
+ "use strict";
25708
+ }
25709
+ });
25710
+
25588
25711
  // src/composer/consume-attachments.ts
25589
25712
  function consumeComposerAttachments(thread) {
25590
25713
  return {
@@ -25642,6 +25765,142 @@ var init_consume_attachments = __esm({
25642
25765
  }
25643
25766
  });
25644
25767
 
25768
+ // src/orchestrator/enqueue-thread-send.ts
25769
+ function resolveSendFollowUp(thread, requested) {
25770
+ if (requested === "steer" || requested === "queue") return requested;
25771
+ return isOrchestratorThread(thread) ? followUpBehavior() : "queue";
25772
+ }
25773
+ function rewriteTrailingUserPrompt(threadId, from, to) {
25774
+ if (from === to) return;
25775
+ const thread = readThread(threadId);
25776
+ if (!thread) return;
25777
+ const messages = thread.messages.slice();
25778
+ for (let i = messages.length - 1; i >= 0; i--) {
25779
+ const message = messages[i];
25780
+ if (!message || message.origin === "continue" || message.role === "summary") continue;
25781
+ if (message.role === "user" && message.text === from) {
25782
+ messages[i] = { ...message, text: to };
25783
+ updateThread(threadId, { messages });
25784
+ }
25785
+ return;
25786
+ }
25787
+ }
25788
+ function reuseJoinedUserBubble(reused, threadId, last, promptText) {
25789
+ return reused.delete(threadId) && last?.role === "user" && last.origin !== "continue" && last.text === promptText;
25790
+ }
25791
+ function promptForJoinedSteer(input) {
25792
+ if (input.latestPrompt === input.promptText) return input.agentPrompt;
25793
+ rewriteTrailingUserPrompt(input.threadId, input.promptText, input.latestPrompt);
25794
+ const { agentPrompt: expandedLatest } = expandComposerPrompt(
25795
+ input.worktreePath,
25796
+ expandCanonicalGitRequest(input.latestPrompt),
25797
+ { attachments: input.attachments }
25798
+ );
25799
+ return input.expandedPrompt && input.agentPrompt.endsWith(input.expandedPrompt) ? input.agentPrompt.slice(0, -input.expandedPrompt.length) + expandedLatest : `${input.agentPrompt}
25800
+
25801
+ ${expandedLatest}`;
25802
+ }
25803
+ function clearSendBookkeeping(host, threadId) {
25804
+ host.crashContinued.delete(threadId);
25805
+ host.jobContinueCount.delete(threadId);
25806
+ host.jobContinueNudged.delete(threadId);
25807
+ host.haltDrain.delete(threadId);
25808
+ }
25809
+ async function enqueueThreadSend(host, threadRef, prompt, opts) {
25810
+ const thread = host.requireThread(threadRef);
25811
+ if (thread.status === "archived") {
25812
+ throw new Error(`Thread is archived: ${thread.id}`);
25813
+ }
25814
+ let shouldSteer = false;
25815
+ const sent = await withThreadLock(thread.id, async () => {
25816
+ const current = host.requireThread(thread.id);
25817
+ if (current.status === "archived") {
25818
+ throw new Error(`Thread is archived: ${thread.id}`);
25819
+ }
25820
+ const followUp = resolveSendFollowUp(current, opts?.followUp);
25821
+ const explicit = opts?.attachments;
25822
+ const consumed = explicit !== void 0 ? { attachments: current.attachments, consumed: explicit } : consumeComposerAttachments(current);
25823
+ const inFlightEarly = host.activeTurns.has(thread.id) || host.startingTurns.has(thread.id) || current.status === "running";
25824
+ const agentHasRepliedEarly = livePartsShowAgentReply(readTurnLiveParts(current.id));
25825
+ const draft = host.openSteerPrompt.get(current.id);
25826
+ const queuedHead = !inFlightEarly && current.status === "queued" && current.queue.length > 0 ? current.queue[0] : null;
25827
+ const unanswered = followUp === "steer" && !agentHasRepliedEarly ? draft?.text ?? (inFlightEarly ? trailingUnansweredUserText(current.messages) : queuedHead) : null;
25828
+ if (unanswered != null && (draft ? !draft.spawned : queuedHead != null)) {
25829
+ clearSendBookkeeping(host, thread.id);
25830
+ const combined = joinSteerPrompt(unanswered, prompt);
25831
+ if (draft && !draft.spawned) {
25832
+ draft.text = combined;
25833
+ rewriteTrailingUserPrompt(current.id, unanswered, combined);
25834
+ } else {
25835
+ const queue = current.queue.slice();
25836
+ queue[0] = combined;
25837
+ const queueAttachments = alignQueueAttachments(queue.length, current.queueAttachments);
25838
+ if (consumed.consumed.length > 0) {
25839
+ queueAttachments[0] = [...queueAttachments[0] ?? [], ...consumed.consumed];
25840
+ }
25841
+ const patch2 = { queue, queueAttachments };
25842
+ if (explicit === void 0 && consumed.consumed.length > 0) {
25843
+ patch2.attachments = consumed.attachments;
25844
+ }
25845
+ updateThread(current.id, patch2);
25846
+ host.emit({ type: "queue_changed", threadId: current.id, queue });
25847
+ }
25848
+ if (thisProcessShouldDrainAgentQueues() && !inFlightEarly) {
25849
+ host.armDrain(current.id);
25850
+ }
25851
+ return host.requireThread(current.id);
25852
+ }
25853
+ const promptToQueue = unanswered != null ? joinSteerPrompt(unanswered, prompt) : prompt;
25854
+ if (unanswered != null) {
25855
+ if (draft) draft.text = promptToQueue;
25856
+ rewriteTrailingUserPrompt(current.id, unanswered, promptToQueue);
25857
+ host.reusedUserPrompt.add(current.id);
25858
+ }
25859
+ const queued = appendQueuedItem(
25860
+ current.queue,
25861
+ current.queueAttachments,
25862
+ promptToQueue,
25863
+ consumed.consumed
25864
+ );
25865
+ clearSendBookkeeping(host, thread.id);
25866
+ const inFlight = host.activeTurns.has(thread.id) || host.startingTurns.has(thread.id) || current.status === "running";
25867
+ shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
25868
+ const patch = queued;
25869
+ if (explicit === void 0 && consumed.consumed.length > 0) {
25870
+ patch.attachments = consumed.attachments;
25871
+ }
25872
+ if (!inFlight) patch.status = "queued";
25873
+ const pid = current.agentPid;
25874
+ if (typeof pid === "number" && pid > 0 && !host.pidAlive(pid)) {
25875
+ patch.agentPid = null;
25876
+ }
25877
+ updateThread(thread.id, patch);
25878
+ host.emit({ type: "queue_changed", threadId: thread.id, queue: queued.queue });
25879
+ if (!inFlight) {
25880
+ host.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
25881
+ }
25882
+ if (thisProcessShouldDrainAgentQueues() && !shouldSteer) {
25883
+ host.armDrain(thread.id);
25884
+ }
25885
+ return host.requireThread(thread.id);
25886
+ });
25887
+ return { thread: sent, shouldSteer };
25888
+ }
25889
+ var init_enqueue_thread_send = __esm({
25890
+ "src/orchestrator/enqueue-thread-send.ts"() {
25891
+ "use strict";
25892
+ init_agent_git_actions();
25893
+ init_agent_has_replied();
25894
+ init_expand();
25895
+ init_consume_attachments();
25896
+ init_desktop_host();
25897
+ init_thread_store();
25898
+ init_turn_live();
25899
+ init_app_settings();
25900
+ init_global_workspace();
25901
+ }
25902
+ });
25903
+
25645
25904
  // src/skills/detached-job-path.ts
25646
25905
  function compiledModuleDir() {
25647
25906
  const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
@@ -25804,7 +26063,7 @@ function formatWorktreeDirective(thread, opts) {
25804
26063
  lines.push(formatDevAccessHint(opts?.activeRuns));
25805
26064
  lines.push("");
25806
26065
  lines.push(
25807
- "Schedules: `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 schedules are enabled."
26066
+ "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."
25808
26067
  );
25809
26068
  lines.push(
25810
26069
  "`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
@@ -25824,7 +26083,7 @@ function connectedOrchestratorTurnReminder(thread, find) {
25824
26083
  return formatConnectedOrchestratorReminder(connectedOrchestrator(thread, find));
25825
26084
  }
25826
26085
  function formatWorktreeReminder() {
25827
- 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. Schedules: create_schedule (at / every / cron; chatId=self for this chat). set_workspace_tags updates labels on this workspace (omit ref).";
26086
+ 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).";
25828
26087
  }
25829
26088
  function formatClaudeSubagentReminder() {
25830
26089
  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.';
@@ -26947,7 +27206,7 @@ function parseDurationMs(every) {
26947
27206
  }
26948
27207
  function defaultScheduleName(prompt) {
26949
27208
  const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
26950
- if (!line) return "Untitled schedule";
27209
+ if (!line) return "Untitled automation";
26951
27210
  return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
26952
27211
  }
26953
27212
  function resolveScheduleThreadId(raw, env = process.env) {
@@ -27012,6 +27271,37 @@ function normalizeWhen(raw) {
27012
27271
  }
27013
27272
  throw new Error("Schedule `when` must be once, every, or cron");
27014
27273
  }
27274
+ function normalizeOptionalBool(raw) {
27275
+ if (raw === true || raw === false) return raw;
27276
+ return null;
27277
+ }
27278
+ function normalizeAutonomy2(raw) {
27279
+ return raw === "default" || raw === "full" ? raw : null;
27280
+ }
27281
+ function normalizeAttachments(raw) {
27282
+ if (!Array.isArray(raw)) return [];
27283
+ const out = [];
27284
+ for (const item of raw) {
27285
+ if (!item || typeof item !== "object") continue;
27286
+ const row = item;
27287
+ if (typeof row.id !== "string" || !row.id.trim()) continue;
27288
+ if (typeof row.name !== "string" || !row.name) continue;
27289
+ if (typeof row.content !== "string") continue;
27290
+ if (!ATTACHMENT_KINDS.has(row.kind)) continue;
27291
+ const next = {
27292
+ id: row.id,
27293
+ name: row.name,
27294
+ kind: row.kind,
27295
+ content: row.content
27296
+ };
27297
+ if (typeof row.path === "string" && row.path) next.path = row.path;
27298
+ if (typeof row.previewDataUrl === "string" && row.previewDataUrl) {
27299
+ next.previewDataUrl = row.previewDataUrl;
27300
+ }
27301
+ out.push(next);
27302
+ }
27303
+ return out;
27304
+ }
27015
27305
  function normalizeTask(raw) {
27016
27306
  const when = normalizeWhen(raw.when);
27017
27307
  const agent = raw.agent && isOrchestratorCapableAgent(raw.agent) ? raw.agent : null;
@@ -27024,6 +27314,11 @@ function normalizeTask(raw) {
27024
27314
  threadId: raw.threadId?.trim() || null,
27025
27315
  agent,
27026
27316
  model: raw.model?.trim() || null,
27317
+ effort: normalizeThinkingEffort(raw.effort),
27318
+ fast: normalizeOptionalBool(raw.fast),
27319
+ planMode: normalizeOptionalBool(raw.planMode),
27320
+ autonomy: normalizeAutonomy2(raw.autonomy),
27321
+ attachments: normalizeAttachments(raw.attachments),
27027
27322
  nextRunAt: raw.nextRunAt,
27028
27323
  lastRunAt: raw.lastRunAt ?? null,
27029
27324
  lastThreadId: raw.lastThreadId ?? null,
@@ -27070,6 +27365,11 @@ function buildTask(input, now) {
27070
27365
  threadId: input.threadId?.trim() || null,
27071
27366
  agent,
27072
27367
  model: input.model?.trim() || null,
27368
+ effort: input.effort != null ? normalizeThinkingEffort(input.effort) : null,
27369
+ fast: normalizeOptionalBool(input.fast),
27370
+ planMode: normalizeOptionalBool(input.planMode),
27371
+ autonomy: normalizeAutonomy2(input.autonomy),
27372
+ attachments: normalizeAttachments(input.attachments),
27073
27373
  nextRunAt,
27074
27374
  lastRunAt: null,
27075
27375
  lastThreadId: null,
@@ -27111,7 +27411,7 @@ function createSchedule(input) {
27111
27411
  function updateSchedule(id, patch) {
27112
27412
  const rows = readAll2();
27113
27413
  const idx = rows.findIndex((row) => row.id === id || id.length >= 8 && row.id.startsWith(id));
27114
- if (idx < 0) throw new Error(`Schedule not found: ${id}`);
27414
+ if (idx < 0) throw new Error(`Automation not found: ${id}`);
27115
27415
  const prev = rows[idx];
27116
27416
  const when = patch.when ? normalizeWhen(patch.when) : prev.when;
27117
27417
  const prompt = patch.prompt !== void 0 ? patch.prompt.trim() : prev.prompt;
@@ -27126,6 +27426,11 @@ function updateSchedule(id, patch) {
27126
27426
  threadId: patch.threadId !== void 0 ? patch.threadId?.trim() || null : prev.threadId,
27127
27427
  agent: patch.agent !== void 0 ? patch.agent ? coerceOrchestratorAgent(patch.agent) : null : prev.agent,
27128
27428
  model: patch.model !== void 0 ? patch.model?.trim() || null : prev.model,
27429
+ effort: patch.effort !== void 0 ? normalizeThinkingEffort(patch.effort) : prev.effort,
27430
+ fast: patch.fast !== void 0 ? normalizeOptionalBool(patch.fast) : prev.fast,
27431
+ planMode: patch.planMode !== void 0 ? normalizeOptionalBool(patch.planMode) : prev.planMode,
27432
+ autonomy: patch.autonomy !== void 0 ? normalizeAutonomy2(patch.autonomy) : prev.autonomy,
27433
+ attachments: patch.attachments !== void 0 ? normalizeAttachments(patch.attachments) : prev.attachments,
27129
27434
  nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
27130
27435
  lastError: whenChanged ? null : prev.lastError,
27131
27436
  updatedAt: nowIso2()
@@ -27140,13 +27445,13 @@ function deleteSchedule(id) {
27140
27445
  const next = rows.filter(
27141
27446
  (row) => row.id !== id && !(id.length >= 8 && row.id.startsWith(id))
27142
27447
  );
27143
- if (next.length === rows.length) throw new Error(`Schedule not found: ${id}`);
27448
+ if (next.length === rows.length) throw new Error(`Automation not found: ${id}`);
27144
27449
  writeAll2(next);
27145
27450
  }
27146
27451
  function recordScheduleRun(id, result) {
27147
27452
  const rows = readAll2();
27148
27453
  const idx = rows.findIndex((row) => row.id === id);
27149
- if (idx < 0) throw new Error(`Schedule not found: ${id}`);
27454
+ if (idx < 0) throw new Error(`Automation not found: ${id}`);
27150
27455
  const prev = rows[idx];
27151
27456
  const firedAt = result.firedAt ?? /* @__PURE__ */ new Date();
27152
27457
  const next = {
@@ -27162,7 +27467,7 @@ function recordScheduleRun(id, result) {
27162
27467
  writeAll2(rows);
27163
27468
  return next;
27164
27469
  }
27165
- var import_node_crypto17, import_node_fs65, import_node_path64, import_croner, DURATION_RE, UNIT_MS;
27470
+ var import_node_crypto17, import_node_fs65, import_node_path64, import_croner, DURATION_RE, UNIT_MS, ATTACHMENT_KINDS;
27166
27471
  var init_schedules = __esm({
27167
27472
  "src/store/schedules.ts"() {
27168
27473
  "use strict";
@@ -27171,6 +27476,7 @@ var init_schedules = __esm({
27171
27476
  import_node_path64 = require("path");
27172
27477
  import_croner = require("croner");
27173
27478
  init_orchestrator_capable();
27479
+ init_thinking_effort();
27174
27480
  init_paths();
27175
27481
  init_private_file();
27176
27482
  init_app_settings();
@@ -27181,6 +27487,14 @@ var init_schedules = __esm({
27181
27487
  h: 36e5,
27182
27488
  d: 864e5
27183
27489
  };
27490
+ ATTACHMENT_KINDS = /* @__PURE__ */ new Set([
27491
+ "transcript",
27492
+ "file",
27493
+ "issue",
27494
+ "workspace",
27495
+ "diff-comment",
27496
+ "code-ref"
27497
+ ]);
27184
27498
  }
27185
27499
  });
27186
27500
 
@@ -27194,17 +27508,52 @@ async function defaultDeps() {
27194
27508
  const orch = getOrchestrator2();
27195
27509
  return {
27196
27510
  findThread: (id) => findThreadByRef(id),
27197
- send: (id, prompt) => orch.send(id, prompt),
27511
+ send: async (id, prompt, opts) => {
27512
+ if (opts?.options && Object.keys(opts.options).length > 0) {
27513
+ try {
27514
+ orch.setThreadOptions(id, opts.options);
27515
+ } catch {
27516
+ const rest = scheduleOptionsAfterRefusedAgentSwitch(opts.options);
27517
+ if (Object.keys(rest).length > 0) {
27518
+ try {
27519
+ orch.setThreadOptions(id, rest);
27520
+ } catch {
27521
+ }
27522
+ }
27523
+ }
27524
+ }
27525
+ const thread = findThreadByRef(id);
27526
+ const attachments = thread ? persistPendingFileAttachments(thread.worktreePath, opts?.attachments ?? []) : opts?.attachments ?? [];
27527
+ return orch.send(id, prompt, { attachments });
27528
+ },
27198
27529
  startOrchestration: (opts) => startOrchestration2(opts)
27199
27530
  };
27200
27531
  }
27532
+ function scheduleOptionsAfterRefusedAgentSwitch(options) {
27533
+ const { agent: _agent, model: _model, ...rest } = options;
27534
+ return rest;
27535
+ }
27536
+ function scheduleSendOptions(schedule) {
27537
+ const patch = {};
27538
+ if (schedule.agent) {
27539
+ patch.agent = schedule.agent;
27540
+ patch.model = schedule.model;
27541
+ } else if (schedule.model) {
27542
+ patch.model = schedule.model;
27543
+ }
27544
+ if (schedule.effort) patch.effort = schedule.effort;
27545
+ if (schedule.fast != null) patch.fast = schedule.fast;
27546
+ if (schedule.planMode != null) patch.planMode = schedule.planMode;
27547
+ if (schedule.autonomy) patch.autonomy = schedule.autonomy;
27548
+ return Object.keys(patch).length > 0 ? patch : void 0;
27549
+ }
27201
27550
  function pickAgent(schedule) {
27202
27551
  if (schedule.agent) return schedule.agent;
27203
27552
  return coerceOrchestratorAgent(resolveOrchestratorDefaults().agent);
27204
27553
  }
27205
27554
  async function fireSchedule(id) {
27206
27555
  const schedule = getSchedule(id);
27207
- if (!schedule) throw new Error(`Schedule not found: ${id}`);
27556
+ if (!schedule) throw new Error(`Automation not found: ${id}`);
27208
27557
  if (firing.has(schedule.id)) return schedule;
27209
27558
  firing.add(schedule.id);
27210
27559
  try {
@@ -27212,18 +27561,28 @@ async function fireSchedule(id) {
27212
27561
  const prompt = formatScheduledPrompt(schedule.name, schedule.prompt);
27213
27562
  const target = schedule.threadId ? deps.findThread(schedule.threadId) : null;
27214
27563
  if (target && target.status !== "archived") {
27215
- const sent = await deps.send(target.id, prompt);
27564
+ const sent = await deps.send(target.id, prompt, {
27565
+ attachments: schedule.attachments,
27566
+ options: scheduleSendOptions(schedule)
27567
+ });
27216
27568
  return recordScheduleRun(schedule.id, {
27217
27569
  lastThreadId: sent.id,
27218
27570
  lastError: null
27219
27571
  });
27220
27572
  }
27573
+ const pinReplacement = Boolean(schedule.threadId) && schedule.when.kind !== "once";
27221
27574
  const created = await deps.startOrchestration({
27222
27575
  goal: prompt,
27223
27576
  agent: pickAgent(schedule),
27224
- model: schedule.model
27577
+ model: schedule.model,
27578
+ ...schedule.effort ? { effort: schedule.effort } : {},
27579
+ ...schedule.fast != null ? { fast: schedule.fast } : {},
27580
+ ...schedule.planMode != null ? { planMode: schedule.planMode } : {},
27581
+ ...schedule.autonomy ? { autonomy: schedule.autonomy } : {},
27582
+ attachments: schedule.attachments,
27583
+ ...pinReplacement ? {} : { ephemeralAutomationId: schedule.id, ephemeralTitle: schedule.name }
27225
27584
  });
27226
- if (schedule.threadId && schedule.when.kind !== "once") {
27585
+ if (pinReplacement) {
27227
27586
  updateSchedule(schedule.id, { threadId: created.id });
27228
27587
  }
27229
27588
  return recordScheduleRun(schedule.id, {
@@ -27293,6 +27652,7 @@ var init_schedule_runner = __esm({
27293
27652
  init_desktop_host();
27294
27653
  init_app_settings();
27295
27654
  init_schedules();
27655
+ init_stage_files();
27296
27656
  init_thread_store();
27297
27657
  SET_TIMEOUT_MAX = 2147483647;
27298
27658
  fireHooks = null;
@@ -27385,10 +27745,6 @@ async function waitForPidExit(pid, timeoutMs) {
27385
27745
  }
27386
27746
  return true;
27387
27747
  }
27388
- function resolveSendFollowUp(thread, requested) {
27389
- if (requested === "steer" || requested === "queue") return requested;
27390
- return isOrchestratorThread(thread) ? followUpBehavior() : "queue";
27391
- }
27392
27748
  function resolveOrchChildFollowUp(requested) {
27393
27749
  if (requested === "steer" || requested === "queue") return requested;
27394
27750
  return followUpBehavior();
@@ -27429,10 +27785,11 @@ async function startOrchestration(opts) {
27429
27785
  planMode: opts.planMode,
27430
27786
  attachments: opts.attachments
27431
27787
  });
27788
+ const ready2 = markEphemeralAutomation(thread2.id, opts);
27432
27789
  if (goal) {
27433
- return orch.send(thread2.id, goal);
27790
+ return orch.send(ready2.id, goal);
27434
27791
  }
27435
- return thread2;
27792
+ return ready2;
27436
27793
  }
27437
27794
  const { titleFromPrompt: titleFromPrompt2 } = await Promise.resolve().then(() => (init_title(), title_exports));
27438
27795
  const title = titleFromPrompt2(goal) || "Orchestration";
@@ -27467,10 +27824,11 @@ async function startOrchestration(opts) {
27467
27824
  sourceType: "orchestration",
27468
27825
  sourceRef: goal
27469
27826
  });
27827
+ const ready = markEphemeralAutomation(updated.id, opts);
27470
27828
  if (goal) {
27471
- return orch.send(updated.id, goal);
27829
+ return orch.send(ready.id, goal);
27472
27830
  }
27473
- return updated;
27831
+ return ready;
27474
27832
  }
27475
27833
  var import_node_events, import_node_crypto18, import_node_fs67, LIVE_TURN_SPAWN_GRACE_MS, RUN_SCRIPT_DESKTOP_ADOPT_MS, STALE_AGENT_PID_WAIT_MS, LIGHT_RECONCILE_THROTTLE_MS, Orchestrator, singleton2;
27476
27834
  var init_orchestrator = __esm({
@@ -27511,6 +27869,7 @@ var init_orchestrator = __esm({
27511
27869
  init_child_halt();
27512
27870
  init_stop_reason();
27513
27871
  init_steer_request();
27872
+ init_ephemeral_automation();
27514
27873
  init_wait_for_job();
27515
27874
  init_agent_purpose_title();
27516
27875
  init_create();
@@ -27538,6 +27897,7 @@ var init_orchestrator = __esm({
27538
27897
  init_read_worktree_tail();
27539
27898
  init_discover();
27540
27899
  init_expand();
27900
+ init_enqueue_thread_send();
27541
27901
  init_consume_attachments();
27542
27902
  init_stage_files();
27543
27903
  init_context_compact();
@@ -27555,6 +27915,7 @@ var init_orchestrator = __esm({
27555
27915
  init_caffeinate_hold();
27556
27916
  init_schedule_runner();
27557
27917
  init_coordinator_prompt();
27918
+ init_enqueue_thread_send();
27558
27919
  LIVE_TURN_SPAWN_GRACE_MS = 15e3;
27559
27920
  RUN_SCRIPT_DESKTOP_ADOPT_MS = 2e4;
27560
27921
  STALE_AGENT_PID_WAIT_MS = 2500;
@@ -27566,6 +27927,13 @@ var init_orchestrator = __esm({
27566
27927
  draining = /* @__PURE__ */ new Set();
27567
27928
  /** Threads past setStatus(running) but not yet in activeTurns (spawn in flight). */
27568
27929
  startingTurns = /* @__PURE__ */ new Set();
27930
+ /**
27931
+ * Prompt for the turn that has not been answered yet. Steer follow-ups before
27932
+ * any agent output are joined onto this text instead of starting a second bubble.
27933
+ */
27934
+ openSteerPrompt = /* @__PURE__ */ new Map();
27935
+ /** Next runTurn should keep the user bubble already rewritten to this prompt. */
27936
+ reusedUserPrompt = /* @__PURE__ */ new Set();
27569
27937
  /** Last sync thread-file read for false-stop heal (throttled per thread). */
27570
27938
  lastReconcileHealAt = /* @__PURE__ */ new Map();
27571
27939
  /** Per-repo timestamp of the last `reconcile(repo, { light: true })`. */
@@ -28176,54 +28544,29 @@ var init_orchestrator = __esm({
28176
28544
  return thread;
28177
28545
  }
28178
28546
  async send(threadRef, prompt, opts) {
28179
- const thread = this.requireThread(threadRef);
28180
- if (thread.status === "archived") {
28181
- throw new Error(`Thread is archived: ${thread.id}`);
28182
- }
28183
- let shouldSteer = false;
28184
- const sent = await withThreadLock(thread.id, async () => {
28185
- const current = this.requireThread(thread.id);
28186
- if (current.status === "archived") {
28187
- throw new Error(`Thread is archived: ${thread.id}`);
28188
- }
28189
- const followUp = resolveSendFollowUp(current, opts?.followUp);
28190
- const explicit = opts?.attachments;
28191
- const consumed = explicit !== void 0 ? { attachments: current.attachments, consumed: explicit } : consumeComposerAttachments(current);
28192
- const queued = appendQueuedItem(
28193
- current.queue,
28194
- current.queueAttachments,
28195
- prompt,
28196
- consumed.consumed
28197
- );
28198
- this.crashContinued.delete(thread.id);
28199
- this.jobContinueCount.delete(thread.id);
28200
- this.jobContinueNudged.delete(thread.id);
28201
- this.haltDrain.delete(thread.id);
28202
- const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
28203
- shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
28204
- const patch = queued;
28205
- if (explicit === void 0 && consumed.consumed.length > 0) {
28206
- patch.attachments = consumed.attachments;
28207
- }
28208
- if (!inFlight) patch.status = "queued";
28209
- const pid = current.agentPid;
28210
- if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
28211
- patch.agentPid = null;
28212
- }
28213
- updateThread(thread.id, patch);
28214
- this.emit({ type: "queue_changed", threadId: thread.id, queue: queued.queue });
28215
- if (!inFlight) {
28216
- this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
28217
- }
28218
- if (thisProcessShouldDrainAgentQueues() && !shouldSteer) {
28219
- this.armDrain(thread.id);
28220
- }
28221
- return this.requireThread(thread.id);
28222
- });
28223
- if (shouldSteer && sent.queue.length > 0) {
28224
- return this.sendQueuedMessageNow(sent.id, sent.queue.length - 1);
28547
+ const sent = await enqueueThreadSend(
28548
+ {
28549
+ activeTurns: this.activeTurns,
28550
+ startingTurns: this.startingTurns,
28551
+ openSteerPrompt: this.openSteerPrompt,
28552
+ reusedUserPrompt: this.reusedUserPrompt,
28553
+ crashContinued: this.crashContinued,
28554
+ jobContinueCount: this.jobContinueCount,
28555
+ jobContinueNudged: this.jobContinueNudged,
28556
+ haltDrain: this.haltDrain,
28557
+ emit: (event) => this.emit(event),
28558
+ armDrain: (id) => this.armDrain(id),
28559
+ requireThread: (ref) => this.requireThread(ref),
28560
+ pidAlive: (pid) => isPidAlive(pid)
28561
+ },
28562
+ threadRef,
28563
+ prompt,
28564
+ opts
28565
+ );
28566
+ if (sent.shouldSteer && sent.thread.queue.length > 0) {
28567
+ return this.sendQueuedMessageNow(sent.thread.id, sent.thread.queue.length - 1);
28225
28568
  }
28226
- return sent;
28569
+ return sent.thread;
28227
28570
  }
28228
28571
  async fanOut(threadRefs, prompt, opts) {
28229
28572
  const results = [];
@@ -28364,6 +28707,7 @@ var init_orchestrator = __esm({
28364
28707
  } finally {
28365
28708
  this.draining.delete(threadId);
28366
28709
  }
28710
+ await archiveEphemeralAutomationIfReady(threadId, (id) => this.archive(id));
28367
28711
  }
28368
28712
  async runTurn(threadId, prompt) {
28369
28713
  const existing = readThread(threadId);
@@ -28380,6 +28724,10 @@ var init_orchestrator = __esm({
28380
28724
  this.emit({ type: "status_changed", threadId, status: "running" });
28381
28725
  this.emit({ type: "turn_started", threadId, prompt });
28382
28726
  try {
28727
+ this.openSteerPrompt.set(threadId, {
28728
+ text: typeof prompt === "string" ? prompt : "",
28729
+ spawned: false
28730
+ });
28383
28731
  try {
28384
28732
  const baseline = await captureTurnBaseline(thread.worktreePath);
28385
28733
  if (baseline) this.turnBaselines.set(threadId, baseline);
@@ -28405,18 +28753,21 @@ var init_orchestrator = __esm({
28405
28753
  this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
28406
28754
  thread = this.requireThread(threadId);
28407
28755
  }
28408
- const promptText = typeof prompt === "string" ? prompt : "";
28756
+ const promptText = this.openSteerPrompt.get(threadId)?.text ?? (typeof prompt === "string" ? prompt : "");
28409
28757
  const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
28410
28758
  const parked = takePendingTurnAttachments(thread);
28411
28759
  const sentAttachments = !autoContinue && parked.length > 0 ? parked : void 0;
28412
28760
  if (!autoContinue) clearAgentBlock(threadId);
28413
- appendMessage(threadId, {
28414
- role: "user",
28415
- text: promptText,
28416
- ...sentAttachments ? { attachments: sentAttachments } : {},
28417
- ...autoContinue ? { origin: "continue" } : {},
28418
- ts: (/* @__PURE__ */ new Date()).toISOString()
28419
- });
28761
+ const lastShown = this.requireThread(threadId).messages.at(-1);
28762
+ if (!reuseJoinedUserBubble(this.reusedUserPrompt, threadId, lastShown, promptText)) {
28763
+ appendMessage(threadId, {
28764
+ role: "user",
28765
+ text: promptText,
28766
+ ...sentAttachments ? { attachments: sentAttachments } : {},
28767
+ ...autoContinue ? { origin: "continue" } : {},
28768
+ ts: (/* @__PURE__ */ new Date()).toISOString()
28769
+ });
28770
+ }
28420
28771
  if (!autoContinue) {
28421
28772
  const live = this.requireThread(threadId);
28422
28773
  const stamp = agentPurposeStamp({
@@ -28615,11 +28966,22 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
28615
28966
  ].filter(Boolean).join("\n\n---\n\n");
28616
28967
  const stderrTail = [];
28617
28968
  traceTurn("runTurn.beforeSpawn", { threadId, agent: fresh.agent });
28969
+ const steerDraft = this.openSteerPrompt.get(threadId);
28970
+ if (steerDraft) steerDraft.spawned = true;
28971
+ const promptForAgent = promptForJoinedSteer({
28972
+ threadId,
28973
+ promptText,
28974
+ latestPrompt: steerDraft?.text ?? promptText,
28975
+ agentPrompt,
28976
+ expandedPrompt,
28977
+ worktreePath: fresh.worktreePath,
28978
+ attachments: autoContinue ? [] : sentAttachments ?? []
28979
+ });
28618
28980
  const handle = await spawnAgentTurn(
28619
28981
  fresh,
28620
28982
  {
28621
28983
  cachedPrefix,
28622
- prompt: agentPrompt,
28984
+ prompt: promptForAgent,
28623
28985
  systemPrompt: turnReminders,
28624
28986
  isolateCodexPlugins: fresh.isolateCodexPlugins === true
28625
28987
  },
@@ -28747,7 +29109,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
28747
29109
  retryThread,
28748
29110
  {
28749
29111
  cachedPrefix: retryPrefix,
28750
- prompt: agentPrompt,
29112
+ prompt: promptForAgent,
28751
29113
  systemPrompt: turnReminders,
28752
29114
  isolateCodexPlugins: isolateCodexPluginRetry || retryThread.isolateCodexPlugins === true
28753
29115
  },
@@ -28907,6 +29269,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
28907
29269
  } finally {
28908
29270
  this.startingTurns.delete(threadId);
28909
29271
  this.activeTurns.delete(threadId);
29272
+ this.openSteerPrompt.delete(threadId);
28910
29273
  this.processes.delete(`${threadId}:agent`);
28911
29274
  this.stoppedTurns.delete(threadId);
28912
29275
  this.runningCount = Math.max(0, this.runningCount - 1);
@@ -30178,6 +30541,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
30178
30541
  if (thread.status !== "archived") {
30179
30542
  throw new Error("Thread is not archived");
30180
30543
  }
30544
+ clearEphemeralAutomation(thread.id);
30181
30545
  if (isGlobalThread(thread)) {
30182
30546
  const { globalAgentCwd: globalAgentCwd2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
30183
30547
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
@@ -33730,7 +34094,7 @@ function parseWhen(args) {
33730
34094
  function registerScheduleTools(server) {
33731
34095
  server.tool(
33732
34096
  "list_schedules",
33733
- "List local Sideboard schedules that trigger a chat when due. Jobs fire only while Sideboard.app is running on this Mac (sleep skips until wake).",
34097
+ "List local Sideboard automations that trigger a chat when due. Jobs fire only while Sideboard.app is running on this Mac (sleep skips until wake). Shown in the sidebar after Board.",
33734
34098
  {},
33735
34099
  async () => {
33736
34100
  try {
@@ -33742,9 +34106,9 @@ function registerScheduleTools(server) {
33742
34106
  );
33743
34107
  server.tool(
33744
34108
  "create_schedule",
33745
- "Create a local schedule 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. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
34109
+ "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.",
33746
34110
  {
33747
- prompt: import_zod10.z.string().describe("User message / goal queued when the schedule fires"),
34111
+ prompt: import_zod10.z.string().describe("User message / goal queued when the automation fires"),
33748
34112
  name: import_zod10.z.string().optional(),
33749
34113
  at: import_zod10.z.string().optional().describe("ISO datetime for a one-shot"),
33750
34114
  every: import_zod10.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
@@ -33780,7 +34144,7 @@ function registerScheduleTools(server) {
33780
34144
  });
33781
34145
  return text6({
33782
34146
  schedule,
33783
- hint: scheduleNeedsCaffeinate(schedule) ? "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run. Caffeinate while schedules 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."
34147
+ 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)."
33784
34148
  });
33785
34149
  } catch (err) {
33786
34150
  return fail4(err);
@@ -33789,7 +34153,7 @@ function registerScheduleTools(server) {
33789
34153
  );
33790
34154
  server.tool(
33791
34155
  "update_schedule",
33792
- "Update a local schedule (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
34156
+ "Update a local automation (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
33793
34157
  {
33794
34158
  id: import_zod10.z.string(),
33795
34159
  prompt: import_zod10.z.string().optional(),
@@ -33844,7 +34208,7 @@ function registerScheduleTools(server) {
33844
34208
  );
33845
34209
  server.tool(
33846
34210
  "delete_schedule",
33847
- "Delete a local schedule. Pass id from list_schedules.",
34211
+ "Delete a local automation. Pass id from list_schedules.",
33848
34212
  { id: import_zod10.z.string() },
33849
34213
  async ({ id }) => {
33850
34214
  try {
@@ -33857,12 +34221,12 @@ function registerScheduleTools(server) {
33857
34221
  );
33858
34222
  server.tool(
33859
34223
  "run_schedule",
33860
- "Fire a schedule now (does not wait for nextRunAt). Queues the prompt on the target chat or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
34224
+ "Fire an automation now (does not wait for nextRunAt). Queues the prompt on the target chat or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
33861
34225
  { id: import_zod10.z.string() },
33862
34226
  async ({ id }) => {
33863
34227
  try {
33864
34228
  if (!getSchedule(id)) {
33865
- return fail4(new Error(`Schedule not found: ${id}`));
34229
+ return fail4(new Error(`Automation not found: ${id}`));
33866
34230
  }
33867
34231
  const schedule = await fireSchedule(id);
33868
34232
  return text6({ schedule });