@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
package/dist/index.cjs CHANGED
@@ -1925,7 +1925,9 @@ function planHistoryRetention(archived, thresholds = {}) {
1925
1925
  if (archivedUpdatedMs(thread) < cutoff) expiredIds.add(thread.id);
1926
1926
  }
1927
1927
  }
1928
- const remaining = oldestFirst.filter((t) => !expiredIds.has(t.id));
1928
+ const remaining = oldestFirst.filter(
1929
+ (t) => !expiredIds.has(t.id) && !t.ephemeralAutomationId?.trim()
1930
+ );
1929
1931
  const overflow = remaining.length > maxCount ? remaining.slice(0, remaining.length - maxCount) : [];
1930
1932
  const stripIds = [];
1931
1933
  const purgeIds = [...expiredIds];
@@ -5257,6 +5259,7 @@ function normalizeThread(raw) {
5257
5259
  quotaContinuedFromId: raw.quotaContinuedFromId ?? null,
5258
5260
  accountId: raw.accountId === null ? null : typeof raw.accountId === "string" && raw.accountId.trim() ? raw.accountId.trim() : void 0,
5259
5261
  archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0,
5262
+ ephemeralAutomationId: typeof raw.ephemeralAutomationId === "string" && raw.ephemeralAutomationId.trim() ? raw.ephemeralAutomationId.trim() : void 0,
5260
5263
  compactedMessages: Array.isArray(raw.compactedMessages) ? raw.compactedMessages : void 0
5261
5264
  };
5262
5265
  }
@@ -5443,6 +5446,7 @@ function updateThread(id, patch) {
5443
5446
  } else {
5444
5447
  delete next.archivedAt;
5445
5448
  }
5449
+ if (!next.ephemeralAutomationId?.trim()) delete next.ephemeralAutomationId;
5446
5450
  writeThread(next);
5447
5451
  return next;
5448
5452
  }
@@ -6093,13 +6097,13 @@ function toolDescription(name, input) {
6093
6097
  else if (/notify_orchestrator$/i.test(name)) return "Notify orchestrator";
6094
6098
  else if (/set_workspace_tags$/i.test(name)) return "Set workspace tags";
6095
6099
  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";
6096
- if (/list_schedules$/i.test(name)) return "List schedules";
6100
+ if (/list_schedules$/i.test(name)) return "List automations";
6097
6101
  if (/create_schedule$/i.test(name)) {
6098
- return str2(input?.name) ? `Schedule ${str2(input?.name)}` : "Create schedule";
6102
+ return str2(input?.name) ? `Automation ${str2(input?.name)}` : "Create automation";
6099
6103
  }
6100
- if (/update_schedule$/i.test(name)) return "Update schedule";
6101
- if (/delete_schedule$/i.test(name)) return "Delete schedule";
6102
- if (/run_schedule$/i.test(name)) return "Run schedule";
6104
+ if (/update_schedule$/i.test(name)) return "Update automation";
6105
+ if (/delete_schedule$/i.test(name)) return "Delete automation";
6106
+ if (/run_schedule$/i.test(name)) return "Run automation";
6103
6107
  if (isSkillToolName(name)) {
6104
6108
  const cmd = skillCommandFromInput(input);
6105
6109
  return cmd ? `Using /${cmd}` : "Using skill";
@@ -8302,7 +8306,7 @@ function parseDurationMs(every) {
8302
8306
  }
8303
8307
  function defaultScheduleName(prompt) {
8304
8308
  const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
8305
- if (!line) return "Untitled schedule";
8309
+ if (!line) return "Untitled automation";
8306
8310
  return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
8307
8311
  }
8308
8312
  function resolveScheduleThreadId(raw, env = process.env) {
@@ -8373,6 +8377,37 @@ function normalizeWhen(raw) {
8373
8377
  }
8374
8378
  throw new Error("Schedule `when` must be once, every, or cron");
8375
8379
  }
8380
+ function normalizeOptionalBool(raw) {
8381
+ if (raw === true || raw === false) return raw;
8382
+ return null;
8383
+ }
8384
+ function normalizeAutonomy2(raw) {
8385
+ return raw === "default" || raw === "full" ? raw : null;
8386
+ }
8387
+ function normalizeAttachments(raw) {
8388
+ if (!Array.isArray(raw)) return [];
8389
+ const out = [];
8390
+ for (const item of raw) {
8391
+ if (!item || typeof item !== "object") continue;
8392
+ const row2 = item;
8393
+ if (typeof row2.id !== "string" || !row2.id.trim()) continue;
8394
+ if (typeof row2.name !== "string" || !row2.name) continue;
8395
+ if (typeof row2.content !== "string") continue;
8396
+ if (!ATTACHMENT_KINDS.has(row2.kind)) continue;
8397
+ const next = {
8398
+ id: row2.id,
8399
+ name: row2.name,
8400
+ kind: row2.kind,
8401
+ content: row2.content
8402
+ };
8403
+ if (typeof row2.path === "string" && row2.path) next.path = row2.path;
8404
+ if (typeof row2.previewDataUrl === "string" && row2.previewDataUrl) {
8405
+ next.previewDataUrl = row2.previewDataUrl;
8406
+ }
8407
+ out.push(next);
8408
+ }
8409
+ return out;
8410
+ }
8376
8411
  function normalizeTask(raw) {
8377
8412
  const when = normalizeWhen(raw.when);
8378
8413
  const agent = raw.agent && isOrchestratorCapableAgent(raw.agent) ? raw.agent : null;
@@ -8385,6 +8420,11 @@ function normalizeTask(raw) {
8385
8420
  threadId: raw.threadId?.trim() || null,
8386
8421
  agent,
8387
8422
  model: raw.model?.trim() || null,
8423
+ effort: normalizeThinkingEffort(raw.effort),
8424
+ fast: normalizeOptionalBool(raw.fast),
8425
+ planMode: normalizeOptionalBool(raw.planMode),
8426
+ autonomy: normalizeAutonomy2(raw.autonomy),
8427
+ attachments: normalizeAttachments(raw.attachments),
8388
8428
  nextRunAt: raw.nextRunAt,
8389
8429
  lastRunAt: raw.lastRunAt ?? null,
8390
8430
  lastThreadId: raw.lastThreadId ?? null,
@@ -8431,6 +8471,11 @@ function buildTask(input, now) {
8431
8471
  threadId: input.threadId?.trim() || null,
8432
8472
  agent,
8433
8473
  model: input.model?.trim() || null,
8474
+ effort: input.effort != null ? normalizeThinkingEffort(input.effort) : null,
8475
+ fast: normalizeOptionalBool(input.fast),
8476
+ planMode: normalizeOptionalBool(input.planMode),
8477
+ autonomy: normalizeAutonomy2(input.autonomy),
8478
+ attachments: normalizeAttachments(input.attachments),
8434
8479
  nextRunAt,
8435
8480
  lastRunAt: null,
8436
8481
  lastThreadId: null,
@@ -8475,7 +8520,7 @@ function createSchedule(input) {
8475
8520
  function updateSchedule(id, patch) {
8476
8521
  const rows = readAll();
8477
8522
  const idx = rows.findIndex((row2) => row2.id === id || id.length >= 8 && row2.id.startsWith(id));
8478
- if (idx < 0) throw new Error(`Schedule not found: ${id}`);
8523
+ if (idx < 0) throw new Error(`Automation not found: ${id}`);
8479
8524
  const prev = rows[idx];
8480
8525
  const when = patch.when ? normalizeWhen(patch.when) : prev.when;
8481
8526
  const prompt = patch.prompt !== void 0 ? patch.prompt.trim() : prev.prompt;
@@ -8490,6 +8535,11 @@ function updateSchedule(id, patch) {
8490
8535
  threadId: patch.threadId !== void 0 ? patch.threadId?.trim() || null : prev.threadId,
8491
8536
  agent: patch.agent !== void 0 ? patch.agent ? coerceOrchestratorAgent(patch.agent) : null : prev.agent,
8492
8537
  model: patch.model !== void 0 ? patch.model?.trim() || null : prev.model,
8538
+ effort: patch.effort !== void 0 ? normalizeThinkingEffort(patch.effort) : prev.effort,
8539
+ fast: patch.fast !== void 0 ? normalizeOptionalBool(patch.fast) : prev.fast,
8540
+ planMode: patch.planMode !== void 0 ? normalizeOptionalBool(patch.planMode) : prev.planMode,
8541
+ autonomy: patch.autonomy !== void 0 ? normalizeAutonomy2(patch.autonomy) : prev.autonomy,
8542
+ attachments: patch.attachments !== void 0 ? normalizeAttachments(patch.attachments) : prev.attachments,
8493
8543
  nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
8494
8544
  lastError: whenChanged ? null : prev.lastError,
8495
8545
  updatedAt: nowIso2()
@@ -8504,13 +8554,13 @@ function deleteSchedule(id) {
8504
8554
  const next = rows.filter(
8505
8555
  (row2) => row2.id !== id && !(id.length >= 8 && row2.id.startsWith(id))
8506
8556
  );
8507
- if (next.length === rows.length) throw new Error(`Schedule not found: ${id}`);
8557
+ if (next.length === rows.length) throw new Error(`Automation not found: ${id}`);
8508
8558
  writeAll(next);
8509
8559
  }
8510
8560
  function recordScheduleRun(id, result) {
8511
8561
  const rows = readAll();
8512
8562
  const idx = rows.findIndex((row2) => row2.id === id);
8513
- if (idx < 0) throw new Error(`Schedule not found: ${id}`);
8563
+ if (idx < 0) throw new Error(`Automation not found: ${id}`);
8514
8564
  const prev = rows[idx];
8515
8565
  const firedAt = result.firedAt ?? /* @__PURE__ */ new Date();
8516
8566
  const next = {
@@ -8526,7 +8576,7 @@ function recordScheduleRun(id, result) {
8526
8576
  writeAll(rows);
8527
8577
  return next;
8528
8578
  }
8529
- var import_node_crypto8, import_node_fs15, import_node_path18, import_croner, DURATION_RE, UNIT_MS;
8579
+ var import_node_crypto8, import_node_fs15, import_node_path18, import_croner, DURATION_RE, UNIT_MS, ATTACHMENT_KINDS;
8530
8580
  var init_schedules = __esm({
8531
8581
  "src/store/schedules.ts"() {
8532
8582
  "use strict";
@@ -8535,6 +8585,7 @@ var init_schedules = __esm({
8535
8585
  import_node_path18 = require("path");
8536
8586
  import_croner = require("croner");
8537
8587
  init_orchestrator_capable();
8588
+ init_thinking_effort();
8538
8589
  init_paths();
8539
8590
  init_private_file();
8540
8591
  init_app_settings();
@@ -8545,6 +8596,14 @@ var init_schedules = __esm({
8545
8596
  h: 36e5,
8546
8597
  d: 864e5
8547
8598
  };
8599
+ ATTACHMENT_KINDS = /* @__PURE__ */ new Set([
8600
+ "transcript",
8601
+ "file",
8602
+ "issue",
8603
+ "workspace",
8604
+ "diff-comment",
8605
+ "code-ref"
8606
+ ]);
8548
8607
  }
8549
8608
  });
8550
8609
 
@@ -8601,6 +8660,303 @@ var init_desktop_host = __esm({
8601
8660
  }
8602
8661
  });
8603
8662
 
8663
+ // src/paths/workspace-scratch.ts
8664
+ function attachmentsGitignoreBody() {
8665
+ return ATTACHMENTS_GITIGNORE;
8666
+ }
8667
+ function isWorkspaceScratchPath(relativePath) {
8668
+ const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
8669
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === CLI_DIR || p.startsWith(`${CLI_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
8670
+ }
8671
+ var ATTACHMENTS_DIR, CLI_DIR, DETACHED_JOBS_DIR, LEGACY_ATTACHMENTS_DIR, LEGACY_DETACHED_JOBS_DIR, ATTACHMENTS_GITIGNORE;
8672
+ var init_workspace_scratch = __esm({
8673
+ "src/paths/workspace-scratch.ts"() {
8674
+ "use strict";
8675
+ ATTACHMENTS_DIR = ".context/attachments";
8676
+ CLI_DIR = ".context/cli";
8677
+ DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
8678
+ LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
8679
+ LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
8680
+ ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
8681
+ *
8682
+ !.gitignore
8683
+ `;
8684
+ }
8685
+ });
8686
+
8687
+ // src/composer/stage-files.ts
8688
+ function fileExtension(filePath) {
8689
+ const base = (0, import_node_path20.basename)(filePath).toLowerCase();
8690
+ return base.includes(".") ? base.split(".").pop() || "" : "";
8691
+ }
8692
+ function isImageFilePath(filePath) {
8693
+ return IMAGE_EXTENSIONS.has(fileExtension(filePath));
8694
+ }
8695
+ function imageMimeType(filePath) {
8696
+ return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
8697
+ }
8698
+ function ensureAttachmentsDir(worktreePath) {
8699
+ const dir = (0, import_node_path20.join)(worktreePath, ATTACHMENTS_DIR);
8700
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
8701
+ const gi = (0, import_node_path20.join)(dir, ".gitignore");
8702
+ if (!(0, import_node_fs17.existsSync)(gi)) {
8703
+ (0, import_node_fs17.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
8704
+ }
8705
+ return dir;
8706
+ }
8707
+ function uniqueAttachmentName(dir, originalName) {
8708
+ const safe = originalName.replace(/[/\\]/g, "_") || "file";
8709
+ if (!(0, import_node_fs17.existsSync)((0, import_node_path20.join)(dir, safe))) return safe;
8710
+ const ext = (0, import_node_path20.extname)(safe);
8711
+ const stem = ext ? safe.slice(0, -ext.length) : safe;
8712
+ for (let i = 1; i < 1e4; i++) {
8713
+ const candidate = `${stem}-${i}${ext}`;
8714
+ if (!(0, import_node_fs17.existsSync)((0, import_node_path20.join)(dir, candidate))) return candidate;
8715
+ }
8716
+ return `${stem}-${(0, import_node_crypto9.randomUUID)()}${ext}`;
8717
+ }
8718
+ function previewDataUrlFromBuf(filePath, buf) {
8719
+ if (!isImageFilePath(filePath)) return void 0;
8720
+ if (buf.length > MAX_PREVIEW_BYTES) return void 0;
8721
+ return `data:${imageMimeType(filePath)};base64,${buf.toString("base64")}`;
8722
+ }
8723
+ function attachmentFromBuffer(name, buf, opts) {
8724
+ const previewDataUrl = previewDataUrlFromBuf(name, buf);
8725
+ if (isImageFilePath(name)) {
8726
+ const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
8727
+ return {
8728
+ id: (0, import_node_crypto9.randomUUID)(),
8729
+ name,
8730
+ kind: "file",
8731
+ path: opts.path,
8732
+ previewDataUrl,
8733
+ content: [
8734
+ `Image attached: ${pathHint}`,
8735
+ opts.path ? `Use the Read tool on \`${opts.path}\` to view this image.` : "The image is shown in the composer; copy it into the worktree if you need to inspect pixels."
8736
+ ].join("\n")
8737
+ };
8738
+ }
8739
+ if (buf.length > MAX_INLINE_BYTES) {
8740
+ return {
8741
+ id: (0, import_node_crypto9.randomUUID)(),
8742
+ name,
8743
+ kind: "file",
8744
+ path: opts.path,
8745
+ content: opts.path ? `(file too large to attach inline: \`${opts.path}\`, ${buf.length} bytes \u2014 use the Read tool)` : `(file too large to attach inline: ${opts.sourceLabel || name}, ${buf.length} bytes)`
8746
+ };
8747
+ }
8748
+ if (buf.includes(0)) {
8749
+ return {
8750
+ id: (0, import_node_crypto9.randomUUID)(),
8751
+ name,
8752
+ kind: "file",
8753
+ path: opts.path,
8754
+ content: opts.path ? `(binary file at \`${opts.path}\` \u2014 use tools to inspect)` : `(binary file attached by path only: ${opts.sourceLabel || name})`
8755
+ };
8756
+ }
8757
+ return {
8758
+ id: (0, import_node_crypto9.randomUUID)(),
8759
+ name,
8760
+ kind: "file",
8761
+ path: opts.path,
8762
+ content: buf.toString("utf8")
8763
+ };
8764
+ }
8765
+ function attachmentFromAbsolutePath(absolutePath) {
8766
+ const name = (0, import_node_path20.basename)(absolutePath);
8767
+ try {
8768
+ const st = (0, import_node_fs17.statSync)(absolutePath);
8769
+ if (!st.isFile()) {
8770
+ return {
8771
+ id: (0, import_node_crypto9.randomUUID)(),
8772
+ name,
8773
+ kind: "file",
8774
+ content: `(not a file: ${absolutePath})`
8775
+ };
8776
+ }
8777
+ const buf = (0, import_node_fs17.readFileSync)(absolutePath);
8778
+ return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
8779
+ } catch (err) {
8780
+ return {
8781
+ id: (0, import_node_crypto9.randomUUID)(),
8782
+ name,
8783
+ kind: "file",
8784
+ content: `(could not read ${absolutePath}: ${err instanceof Error ? err.message : String(err)})`
8785
+ };
8786
+ }
8787
+ }
8788
+ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
8789
+ if (absolutePaths.length === 0) return [];
8790
+ const dir = ensureAttachmentsDir(worktreePath);
8791
+ const out = [];
8792
+ for (const abs of absolutePaths) {
8793
+ const originalName = (0, import_node_path20.basename)(abs);
8794
+ try {
8795
+ const st = (0, import_node_fs17.statSync)(abs);
8796
+ if (!st.isFile()) continue;
8797
+ const name = uniqueAttachmentName(dir, originalName);
8798
+ const destAbs = (0, import_node_path20.join)(dir, name);
8799
+ (0, import_node_fs17.copyFileSync)(abs, destAbs);
8800
+ const rel = `${ATTACHMENTS_DIR}/${name}`;
8801
+ const buf = (0, import_node_fs17.readFileSync)(destAbs);
8802
+ out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
8803
+ } catch (err) {
8804
+ out.push({
8805
+ id: (0, import_node_crypto9.randomUUID)(),
8806
+ name: originalName,
8807
+ kind: "file",
8808
+ content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
8809
+ });
8810
+ }
8811
+ }
8812
+ return out;
8813
+ }
8814
+ function stageBuffersAsAttachments(worktreePath, buffers2) {
8815
+ if (buffers2.length === 0) return [];
8816
+ const dir = ensureAttachmentsDir(worktreePath);
8817
+ const out = [];
8818
+ for (const item of buffers2) {
8819
+ const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
8820
+ try {
8821
+ const buf = Buffer.from(item.dataBase64, "base64");
8822
+ const name = uniqueAttachmentName(dir, originalName);
8823
+ const destAbs = (0, import_node_path20.join)(dir, name);
8824
+ (0, import_node_fs17.writeFileSync)(destAbs, buf);
8825
+ const rel = `${ATTACHMENTS_DIR}/${name}`;
8826
+ out.push(attachmentFromBuffer(name, buf, { path: rel }));
8827
+ } catch (err) {
8828
+ out.push({
8829
+ id: (0, import_node_crypto9.randomUUID)(),
8830
+ name: originalName,
8831
+ kind: "file",
8832
+ content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
8833
+ });
8834
+ }
8835
+ }
8836
+ return out;
8837
+ }
8838
+ function attachmentsFromBuffers(buffers2) {
8839
+ return buffers2.map((item) => {
8840
+ const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
8841
+ try {
8842
+ const buf = Buffer.from(item.dataBase64, "base64");
8843
+ return attachmentFromBuffer(name, buf, { sourceLabel: name });
8844
+ } catch (err) {
8845
+ return {
8846
+ id: (0, import_node_crypto9.randomUUID)(),
8847
+ name,
8848
+ kind: "file",
8849
+ content: `(could not attach ${name}: ${err instanceof Error ? err.message : String(err)})`
8850
+ };
8851
+ }
8852
+ });
8853
+ }
8854
+ function isWorktreeRelativePath(p) {
8855
+ if (!p || p.includes("..")) return false;
8856
+ if (p.startsWith("/")) return false;
8857
+ if (/^[A-Za-z]:[\\/]/.test(p)) return false;
8858
+ return true;
8859
+ }
8860
+ function dataUrlToBase64(url) {
8861
+ if (!url) return null;
8862
+ const m = /^data:[^;]+;base64,(.+)$/s.exec(url);
8863
+ return m?.[1] ?? null;
8864
+ }
8865
+ function persistPendingFileAttachments(worktreePath, attachments) {
8866
+ if (attachments.length === 0) return attachments;
8867
+ const keep = [];
8868
+ const buffers2 = [];
8869
+ for (const att of attachments) {
8870
+ if (att.kind !== "file") {
8871
+ keep.push(att);
8872
+ continue;
8873
+ }
8874
+ if (att.path && isWorktreeRelativePath(att.path)) {
8875
+ keep.push(att);
8876
+ continue;
8877
+ }
8878
+ const fromPreview = dataUrlToBase64(att.previewDataUrl);
8879
+ if (fromPreview) {
8880
+ buffers2.push({ name: att.name, dataBase64: fromPreview });
8881
+ continue;
8882
+ }
8883
+ if (att.content && !IMAGE_HINT_RE.test(att.content) && !PLACEHOLDER_CONTENT_RE.test(att.content)) {
8884
+ buffers2.push({
8885
+ name: att.name,
8886
+ dataBase64: Buffer.from(att.content, "utf8").toString("base64")
8887
+ });
8888
+ continue;
8889
+ }
8890
+ keep.push(att);
8891
+ }
8892
+ if (buffers2.length === 0) return attachments;
8893
+ return [...keep, ...stageBuffersAsAttachments(worktreePath, buffers2)];
8894
+ }
8895
+ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
8896
+ const out = [];
8897
+ for (const rel of relativePaths) {
8898
+ if (!rel || rel.includes("..") || rel.startsWith("/")) {
8899
+ out.push({
8900
+ id: (0, import_node_crypto9.randomUUID)(),
8901
+ name: (0, import_node_path20.basename)(rel) || "file",
8902
+ kind: "file",
8903
+ content: `(invalid path: ${rel})`
8904
+ });
8905
+ continue;
8906
+ }
8907
+ const name = (0, import_node_path20.basename)(rel);
8908
+ try {
8909
+ const abs = (0, import_node_path20.join)(worktreePath, rel);
8910
+ const st = (0, import_node_fs17.statSync)(abs);
8911
+ if (!st.isFile()) continue;
8912
+ const buf = (0, import_node_fs17.readFileSync)(abs);
8913
+ out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
8914
+ } catch (err) {
8915
+ out.push({
8916
+ id: (0, import_node_crypto9.randomUUID)(),
8917
+ name,
8918
+ kind: "file",
8919
+ content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
8920
+ });
8921
+ }
8922
+ }
8923
+ return out;
8924
+ }
8925
+ var import_node_fs17, import_node_path20, import_node_crypto9, IMAGE_EXTENSIONS, IMAGE_MIME_BY_EXT, MAX_INLINE_BYTES, MAX_PREVIEW_BYTES, IMAGE_HINT_RE, PLACEHOLDER_CONTENT_RE;
8926
+ var init_stage_files = __esm({
8927
+ "src/composer/stage-files.ts"() {
8928
+ "use strict";
8929
+ import_node_fs17 = require("fs");
8930
+ import_node_path20 = require("path");
8931
+ import_node_crypto9 = require("crypto");
8932
+ init_workspace_scratch();
8933
+ IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
8934
+ "png",
8935
+ "jpg",
8936
+ "jpeg",
8937
+ "gif",
8938
+ "webp",
8939
+ "svg",
8940
+ "bmp",
8941
+ "ico"
8942
+ ]);
8943
+ IMAGE_MIME_BY_EXT = {
8944
+ png: "image/png",
8945
+ jpg: "image/jpeg",
8946
+ jpeg: "image/jpeg",
8947
+ gif: "image/gif",
8948
+ webp: "image/webp",
8949
+ svg: "image/svg+xml",
8950
+ bmp: "image/bmp",
8951
+ ico: "image/x-icon"
8952
+ };
8953
+ MAX_INLINE_BYTES = 4e5;
8954
+ MAX_PREVIEW_BYTES = 5e6;
8955
+ IMAGE_HINT_RE = /^Image attached:/;
8956
+ PLACEHOLDER_CONTENT_RE = /^\((could not |file too large|binary file|not a file|invalid path)/;
8957
+ }
8958
+ });
8959
+
8604
8960
  // src/threads/injected-notices.ts
8605
8961
  function isSlackExternalReplyText(text7) {
8606
8962
  return text7.startsWith("Slack reply from ") && text7.includes("not a command");
@@ -8685,18 +9041,18 @@ function mcpWaitForTurnTimeoutMs(requested) {
8685
9041
  if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
8686
9042
  return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
8687
9043
  }
8688
- function mcpWaitTaskHint(taskState, status, blockedReason) {
9044
+ function mcpWaitTaskHint(taskState2, status, blockedReason) {
8689
9045
  const why = blockedReason?.trim();
8690
- if (why && taskState !== "failed" && taskState !== "canceled") {
8691
- if (taskState === "submitted" || taskState === "working") {
9046
+ if (why && taskState2 !== "failed" && taskState2 !== "canceled") {
9047
+ if (taskState2 === "submitted" || taskState2 === "working") {
8692
9048
  return `Child is blocked: ${why} It has not finished the turn. Do not send_to_chat a check-in.`;
8693
9049
  }
8694
- if (taskState === "input-required") {
9050
+ if (taskState2 === "input-required") {
8695
9051
  return `Child is blocked: ${why} Wait for the user in that chat \u2014 do not send_to_chat a check-in.`;
8696
9052
  }
8697
9053
  return `Child is blocked: ${why} Help if this is your child; otherwise tell the user. Do not send_to_chat a check-in.`;
8698
9054
  }
8699
- switch (taskState) {
9055
+ switch (taskState2) {
8700
9056
  case "submitted":
8701
9057
  return MCP_WAIT_QUEUED_HINT;
8702
9058
  case "working":
@@ -8752,17 +9108,17 @@ var init_wait_for_turn = __esm({
8752
9108
 
8753
9109
  // src/agents/path.ts
8754
9110
  function prependPathDir(env, dir) {
8755
- if (!dir || !(0, import_node_fs17.existsSync)(dir)) return;
9111
+ if (!dir || !(0, import_node_fs18.existsSync)(dir)) return;
8756
9112
  const current = env.PATH ?? "";
8757
- const parts = current.split(import_node_path20.delimiter).filter(Boolean);
9113
+ const parts = current.split(import_node_path21.delimiter).filter(Boolean);
8758
9114
  if (parts.includes(dir)) {
8759
9115
  env.PATH = current;
8760
9116
  return;
8761
9117
  }
8762
- env.PATH = [dir, ...parts].join(import_node_path20.delimiter);
9118
+ env.PATH = [dir, ...parts].join(import_node_path21.delimiter);
8763
9119
  }
8764
9120
  function conductorBundledBinDir(home = process.env.HOME || process.env.USERPROFILE || (0, import_node_os6.homedir)()) {
8765
- return (0, import_node_path20.join)(home, "Library", "Application Support", "com.conductor.app", "bin");
9121
+ return (0, import_node_path21.join)(home, "Library", "Application Support", "com.conductor.app", "bin");
8766
9122
  }
8767
9123
  function isConductorBundledCli(filePath) {
8768
9124
  const p = (filePath ?? "").replace(/\\/g, "/");
@@ -8771,21 +9127,21 @@ function isConductorBundledCli(filePath) {
8771
9127
  function ensureAgentPath(env = process.env) {
8772
9128
  const home = env.HOME || env.USERPROFILE || (0, import_node_os6.homedir)();
8773
9129
  const current = env.PATH ?? "";
8774
- const parts = current.split(import_node_path20.delimiter).filter(Boolean);
9130
+ const parts = current.split(import_node_path21.delimiter).filter(Boolean);
8775
9131
  const seen = new Set(parts);
8776
9132
  const extras = [
8777
- ...EXTRA_BIN_DIRS.map((rel) => (0, import_node_path20.join)(home, rel)),
9133
+ ...EXTRA_BIN_DIRS.map((rel) => (0, import_node_path21.join)(home, rel)),
8778
9134
  "/opt/homebrew/bin",
8779
9135
  "/usr/local/bin",
8780
9136
  // Keep after Homebrew/npm so a user-installed CLI still wins.
8781
9137
  conductorBundledBinDir(home)
8782
9138
  ];
8783
9139
  for (const dir of extras.reverse()) {
8784
- if (!dir || seen.has(dir) || !(0, import_node_fs17.existsSync)(dir)) continue;
9140
+ if (!dir || seen.has(dir) || !(0, import_node_fs18.existsSync)(dir)) continue;
8785
9141
  parts.unshift(dir);
8786
9142
  seen.add(dir);
8787
9143
  }
8788
- const next = parts.join(import_node_path20.delimiter);
9144
+ const next = parts.join(import_node_path21.delimiter);
8789
9145
  env.PATH = next;
8790
9146
  return next;
8791
9147
  }
@@ -8793,7 +9149,7 @@ function enrichPathWithNpmGlobalBin(env = process.env) {
8793
9149
  ensureAgentPath(env);
8794
9150
  const prefix = npmGlobalPrefix(env);
8795
9151
  if (prefix) {
8796
- const binDir = process.platform === "win32" ? prefix : (0, import_node_path20.join)(prefix, "bin");
9152
+ const binDir = process.platform === "win32" ? prefix : (0, import_node_path21.join)(prefix, "bin");
8797
9153
  prependPathDir(env, binDir);
8798
9154
  }
8799
9155
  return env.PATH ?? "";
@@ -8814,8 +9170,8 @@ function npmGlobalPrefix(env = process.env) {
8814
9170
  function npmGlobalNodeModules(env = process.env) {
8815
9171
  const prefix = npmGlobalPrefix(env);
8816
9172
  if (!prefix) return null;
8817
- const dir = process.platform === "win32" ? (0, import_node_path20.join)(prefix, "node_modules") : (0, import_node_path20.join)(prefix, "lib", "node_modules");
8818
- return (0, import_node_fs17.existsSync)(dir) ? dir : null;
9173
+ const dir = process.platform === "win32" ? (0, import_node_path21.join)(prefix, "node_modules") : (0, import_node_path21.join)(prefix, "lib", "node_modules");
9174
+ return (0, import_node_fs18.existsSync)(dir) ? dir : null;
8819
9175
  }
8820
9176
  function posixShellSingleQuote(value) {
8821
9177
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -8838,14 +9194,14 @@ function withExportedPath(command, pathValue) {
8838
9194
  if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
8839
9195
  return `export PATH=${posixShellSingleQuote(pathValue)} && ${trimmed}`;
8840
9196
  }
8841
- var import_node_fs17, import_node_child_process3, import_node_os6, import_node_path20, EXTRA_BIN_DIRS;
9197
+ var import_node_fs18, import_node_child_process3, import_node_os6, import_node_path21, EXTRA_BIN_DIRS;
8842
9198
  var init_path = __esm({
8843
9199
  "src/agents/path.ts"() {
8844
9200
  "use strict";
8845
- import_node_fs17 = require("fs");
9201
+ import_node_fs18 = require("fs");
8846
9202
  import_node_child_process3 = require("child_process");
8847
9203
  import_node_os6 = require("os");
8848
- import_node_path20 = require("path");
9204
+ import_node_path21 = require("path");
8849
9205
  EXTRA_BIN_DIRS = [
8850
9206
  ".local/bin",
8851
9207
  ".cargo/bin",
@@ -8866,23 +9222,23 @@ var init_path = __esm({
8866
9222
  function githubAgentAuthDir() {
8867
9223
  const override = process.env.SIDEBOARD_GIT_AUTH_DIR?.trim();
8868
9224
  if (override) return override;
8869
- return (0, import_node_path21.join)((0, import_node_os7.homedir)(), ".sideboard-git-auth");
9225
+ return (0, import_node_path22.join)((0, import_node_os7.homedir)(), ".sideboard-git-auth");
8870
9226
  }
8871
9227
  function githubCredentialStorePath() {
8872
- return (0, import_node_path21.join)(githubAgentAuthDir(), "git-credentials");
9228
+ return (0, import_node_path22.join)(githubAgentAuthDir(), "git-credentials");
8873
9229
  }
8874
9230
  function githubGhConfigDir() {
8875
- return (0, import_node_path21.join)(githubAgentAuthDir(), "gh");
9231
+ return (0, import_node_path22.join)(githubAgentAuthDir(), "gh");
8876
9232
  }
8877
9233
  function writePrivateFile2(file, body) {
8878
- (0, import_node_fs18.mkdirSync)((0, import_node_path21.dirname)(file), { recursive: true, mode: 448 });
9234
+ (0, import_node_fs19.mkdirSync)((0, import_node_path22.dirname)(file), { recursive: true, mode: 448 });
8879
9235
  try {
8880
- (0, import_node_fs18.chmodSync)((0, import_node_path21.dirname)(file), 448);
9236
+ (0, import_node_fs19.chmodSync)((0, import_node_path22.dirname)(file), 448);
8881
9237
  } catch {
8882
9238
  }
8883
- (0, import_node_fs18.writeFileSync)(file, body, { encoding: "utf8", mode: 384 });
9239
+ (0, import_node_fs19.writeFileSync)(file, body, { encoding: "utf8", mode: 384 });
8884
9240
  try {
8885
- (0, import_node_fs18.chmodSync)(file, 384);
9241
+ (0, import_node_fs19.chmodSync)(file, 384);
8886
9242
  } catch {
8887
9243
  }
8888
9244
  }
@@ -8907,19 +9263,19 @@ function materializeGithubAgentAuth(token, user) {
8907
9263
  const trimmed = token.trim();
8908
9264
  if (!trimmed) return;
8909
9265
  const root = githubAgentAuthDir();
8910
- (0, import_node_fs18.mkdirSync)(root, { recursive: true, mode: 448 });
9266
+ (0, import_node_fs19.mkdirSync)(root, { recursive: true, mode: 448 });
8911
9267
  try {
8912
- (0, import_node_fs18.chmodSync)(root, 448);
9268
+ (0, import_node_fs19.chmodSync)(root, 448);
8913
9269
  } catch {
8914
9270
  }
8915
9271
  writePrivateFile2(githubCredentialStorePath(), gitCredentialStoreContents(trimmed));
8916
9272
  const ghDir = githubGhConfigDir();
8917
- (0, import_node_fs18.mkdirSync)(ghDir, { recursive: true, mode: 448 });
8918
- writePrivateFile2((0, import_node_path21.join)(ghDir, "hosts.yml"), ghHostsYml(trimmed, user));
8919
- writePrivateFile2((0, import_node_path21.join)(ghDir, "config.yml"), "git_protocol: https\nprompt: disabled\n");
9273
+ (0, import_node_fs19.mkdirSync)(ghDir, { recursive: true, mode: 448 });
9274
+ writePrivateFile2((0, import_node_path22.join)(ghDir, "hosts.yml"), ghHostsYml(trimmed, user));
9275
+ writePrivateFile2((0, import_node_path22.join)(ghDir, "config.yml"), "git_protocol: https\nprompt: disabled\n");
8920
9276
  }
8921
9277
  function githubAgentAuthReady() {
8922
- return (0, import_node_fs18.existsSync)(githubCredentialStorePath()) && (0, import_node_fs18.existsSync)((0, import_node_path21.join)(githubGhConfigDir(), "hosts.yml"));
9278
+ return (0, import_node_fs19.existsSync)(githubCredentialStorePath()) && (0, import_node_fs19.existsSync)((0, import_node_path22.join)(githubGhConfigDir(), "hosts.yml"));
8923
9279
  }
8924
9280
  function githubCredentialHelperGitConfig() {
8925
9281
  const file = githubCredentialStorePath();
@@ -8934,13 +9290,13 @@ function githubGhConfigEnv() {
8934
9290
  GH_PROMPT_DISABLED: "1"
8935
9291
  };
8936
9292
  }
8937
- var import_node_fs18, import_node_os7, import_node_path21;
9293
+ var import_node_fs19, import_node_os7, import_node_path22;
8938
9294
  var init_github_agent_auth = __esm({
8939
9295
  "src/git/github-agent-auth.ts"() {
8940
9296
  "use strict";
8941
- import_node_fs18 = require("fs");
9297
+ import_node_fs19 = require("fs");
8942
9298
  import_node_os7 = require("os");
8943
- import_node_path21 = require("path");
9299
+ import_node_path22 = require("path");
8944
9300
  }
8945
9301
  });
8946
9302
 
@@ -8949,11 +9305,11 @@ function isIndexLockError(text7) {
8949
9305
  return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text7);
8950
9306
  }
8951
9307
  function clearStaleIndexLock(gitDir, maxAgeMs = STALE_INDEX_LOCK_MS, now = Date.now()) {
8952
- const lockPath = (0, import_node_path22.join)(gitDir, "index.lock");
9308
+ const lockPath = (0, import_node_path23.join)(gitDir, "index.lock");
8953
9309
  try {
8954
- if (!(0, import_node_fs19.existsSync)(lockPath)) return null;
8955
- if (now - (0, import_node_fs19.statSync)(lockPath).mtimeMs < maxAgeMs) return null;
8956
- (0, import_node_fs19.unlinkSync)(lockPath);
9310
+ if (!(0, import_node_fs20.existsSync)(lockPath)) return null;
9311
+ if (now - (0, import_node_fs20.statSync)(lockPath).mtimeMs < maxAgeMs) return null;
9312
+ (0, import_node_fs20.unlinkSync)(lockPath);
8957
9313
  return lockPath;
8958
9314
  } catch {
8959
9315
  return null;
@@ -8968,12 +9324,12 @@ function clearStaleIndexLocks(gitDirs, maxAgeMs = STALE_INDEX_LOCK_MS) {
8968
9324
  }
8969
9325
  return removed;
8970
9326
  }
8971
- var import_node_fs19, import_node_path22, STALE_INDEX_LOCK_MS;
9327
+ var import_node_fs20, import_node_path23, STALE_INDEX_LOCK_MS;
8972
9328
  var init_stale_lock = __esm({
8973
9329
  "src/git/stale-lock.ts"() {
8974
9330
  "use strict";
8975
- import_node_fs19 = require("fs");
8976
- import_node_path22 = require("path");
9331
+ import_node_fs20 = require("fs");
9332
+ import_node_path23 = require("path");
8977
9333
  STALE_INDEX_LOCK_MS = 2e4;
8978
9334
  }
8979
9335
  });
@@ -9224,11 +9580,11 @@ async function ensureWorktreeSideboardIgnored(worktreePath) {
9224
9580
  if (gitDir.exitCode !== 0) return;
9225
9581
  const dir = gitDir.stdout.trim();
9226
9582
  if (!dir) return;
9227
- const excludePath = (0, import_node_path23.join)(dir, "info", "exclude");
9228
- (0, import_node_fs20.mkdirSync)((0, import_node_path23.dirname)(excludePath), { recursive: true });
9583
+ const excludePath = (0, import_node_path24.join)(dir, "info", "exclude");
9584
+ (0, import_node_fs21.mkdirSync)((0, import_node_path24.dirname)(excludePath), { recursive: true });
9229
9585
  let body = "";
9230
9586
  try {
9231
- body = (0, import_node_fs20.readFileSync)(excludePath, "utf8");
9587
+ body = (0, import_node_fs21.readFileSync)(excludePath, "utf8");
9232
9588
  } catch {
9233
9589
  body = "";
9234
9590
  }
@@ -9244,7 +9600,7 @@ async function ensureWorktreeSideboardIgnored(worktreePath) {
9244
9600
  ` : body;
9245
9601
  const header = body.includes(EXCLUDE_COMMENT) ? "" : `${EXCLUDE_COMMENT}
9246
9602
  `;
9247
- (0, import_node_fs20.writeFileSync)(excludePath, `${prefix}${header}${missing.join("\n")}
9603
+ (0, import_node_fs21.writeFileSync)(excludePath, `${prefix}${header}${missing.join("\n")}
9248
9604
  `, "utf8");
9249
9605
  }
9250
9606
  const listed = await git(["ls-files", "-z", "--", ".sideboard"], worktreePath, {
@@ -9259,12 +9615,12 @@ async function ensureWorktreeSideboardIgnored(worktreePath) {
9259
9615
  timeoutMs: 8e3
9260
9616
  });
9261
9617
  }
9262
- var import_node_fs20, import_node_path23, WORKTREE_SIDEBOARD_EXCLUDE, WORKTREE_CONTEXT_EXCLUDE, EXCLUDE_COMMENT;
9618
+ var import_node_fs21, import_node_path24, WORKTREE_SIDEBOARD_EXCLUDE, WORKTREE_CONTEXT_EXCLUDE, EXCLUDE_COMMENT;
9263
9619
  var init_worktree_exclude = __esm({
9264
9620
  "src/git/worktree-exclude.ts"() {
9265
9621
  "use strict";
9266
- import_node_fs20 = require("fs");
9267
- import_node_path23 = require("path");
9622
+ import_node_fs21 = require("fs");
9623
+ import_node_path24 = require("path");
9268
9624
  init_run();
9269
9625
  WORKTREE_SIDEBOARD_EXCLUDE = ".sideboard/";
9270
9626
  WORKTREE_CONTEXT_EXCLUDE = ".context/";
@@ -9385,7 +9741,7 @@ async function warmGithubAgentAuth(opts) {
9385
9741
  }
9386
9742
  function normalizeWritableRoot(raw) {
9387
9743
  const trimmed = raw.trim().replace(/\/+$/, "");
9388
- return trimmed && (0, import_node_path24.isAbsolute)(trimmed) ? trimmed : null;
9744
+ return trimmed && (0, import_node_path25.isAbsolute)(trimmed) ? trimmed : null;
9389
9745
  }
9390
9746
  async function resolveCodexGitWritableRoots(cwd) {
9391
9747
  const roots = /* @__PURE__ */ new Set();
@@ -9463,11 +9819,11 @@ function formatGitAuthModeDirective(mode) {
9463
9819
  ].join("\n");
9464
9820
  }
9465
9821
  }
9466
- var import_node_path24, HTTPS_REWRITE, TOKEN_TTL_MS, tokenMemo, GITHUB_CHILD_TOKEN_KEYS;
9822
+ var import_node_path25, HTTPS_REWRITE, TOKEN_TTL_MS, tokenMemo, GITHUB_CHILD_TOKEN_KEYS;
9467
9823
  var init_git_auth_mode = __esm({
9468
9824
  "src/git/git-auth-mode.ts"() {
9469
9825
  "use strict";
9470
- import_node_path24 = require("path");
9826
+ import_node_path25 = require("path");
9471
9827
  init_app_settings();
9472
9828
  init_github_agent_auth();
9473
9829
  init_run();
@@ -10096,46 +10452,22 @@ var init_stack = __esm({
10096
10452
  }
10097
10453
  });
10098
10454
 
10099
- // src/paths/workspace-scratch.ts
10100
- function attachmentsGitignoreBody() {
10101
- return ATTACHMENTS_GITIGNORE;
10102
- }
10103
- function isWorkspaceScratchPath(relativePath) {
10104
- const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
10105
- return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === CLI_DIR || p.startsWith(`${CLI_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
10106
- }
10107
- var ATTACHMENTS_DIR, CLI_DIR, DETACHED_JOBS_DIR, LEGACY_ATTACHMENTS_DIR, LEGACY_DETACHED_JOBS_DIR, ATTACHMENTS_GITIGNORE;
10108
- var init_workspace_scratch = __esm({
10109
- "src/paths/workspace-scratch.ts"() {
10110
- "use strict";
10111
- ATTACHMENTS_DIR = ".context/attachments";
10112
- CLI_DIR = ".context/cli";
10113
- DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
10114
- LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
10115
- LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
10116
- ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
10117
- *
10118
- !.gitignore
10119
- `;
10120
- }
10121
- });
10122
-
10123
10455
  // src/git/team-slugs.ts
10124
10456
  function sameRepoPath2(a, b) {
10125
10457
  return normalizeWorktreePath(a) === normalizeWorktreePath(b);
10126
10458
  }
10127
10459
  function listLocalPlaceholderBranchSlugs(repoPath) {
10128
10460
  const slugs = [];
10129
- const headsDir = (0, import_node_path25.join)(repoPath, ".git", "refs", "heads");
10130
- const threadDir = (0, import_node_path25.join)(headsDir, "thread");
10461
+ const headsDir = (0, import_node_path26.join)(repoPath, ".git", "refs", "heads");
10462
+ const threadDir = (0, import_node_path26.join)(headsDir, "thread");
10131
10463
  try {
10132
- if ((0, import_node_fs21.existsSync)(threadDir)) {
10133
- for (const name of (0, import_node_fs21.readdirSync)(threadDir)) {
10464
+ if ((0, import_node_fs22.existsSync)(threadDir)) {
10465
+ for (const name of (0, import_node_fs22.readdirSync)(threadDir)) {
10134
10466
  if (!name.startsWith(".")) slugs.push(normalizeTakenSlug(name));
10135
10467
  }
10136
10468
  }
10137
- if ((0, import_node_fs21.existsSync)(headsDir)) {
10138
- for (const entry of (0, import_node_fs21.readdirSync)(headsDir, { withFileTypes: true })) {
10469
+ if ((0, import_node_fs22.existsSync)(headsDir)) {
10470
+ for (const entry of (0, import_node_fs22.readdirSync)(headsDir, { withFileTypes: true })) {
10139
10471
  if (entry.isFile() && !entry.name.startsWith(".")) {
10140
10472
  slugs.push(normalizeTakenSlug(entry.name));
10141
10473
  }
@@ -10149,8 +10481,8 @@ function listLocalPlaceholderBranchSlugs(repoPath) {
10149
10481
  function collectTakenTeamSlugs(repoPath) {
10150
10482
  const taken = /* @__PURE__ */ new Set();
10151
10483
  const root = worktreesRoot(repoPath);
10152
- if ((0, import_node_fs21.existsSync)(root)) {
10153
- for (const entry of (0, import_node_fs21.readdirSync)(root, { withFileTypes: true })) {
10484
+ if ((0, import_node_fs22.existsSync)(root)) {
10485
+ for (const entry of (0, import_node_fs22.readdirSync)(root, { withFileTypes: true })) {
10154
10486
  if (entry.isDirectory() && entry.name !== ".DS_Store") {
10155
10487
  for (const slug of takenSlugsFromThread({ worktreePath: entry.name })) {
10156
10488
  taken.add(slug);
@@ -10175,18 +10507,18 @@ function allocateTeamSlug(repoPath) {
10175
10507
  const taken = collectTakenTeamSlugs(repoPath);
10176
10508
  for (let attempt = 0; attempt < 32; attempt++) {
10177
10509
  const team = allocateTeamName(taken);
10178
- const path2 = (0, import_node_path25.join)(worktreesRoot(repoPath), team.slug);
10179
- if (!(0, import_node_fs21.existsSync)(path2)) return team;
10510
+ const path2 = (0, import_node_path26.join)(worktreesRoot(repoPath), team.slug);
10511
+ if (!(0, import_node_fs22.existsSync)(path2)) return team;
10180
10512
  taken.add(team.slug);
10181
10513
  }
10182
10514
  throw new Error("No available soccer team worktree directories left");
10183
10515
  }
10184
- var import_node_fs21, import_node_path25;
10516
+ var import_node_fs22, import_node_path26;
10185
10517
  var init_team_slugs = __esm({
10186
10518
  "src/git/team-slugs.ts"() {
10187
10519
  "use strict";
10188
- import_node_fs21 = require("fs");
10189
- import_node_path25 = require("path");
10520
+ import_node_fs22 = require("fs");
10521
+ import_node_path26 = require("path");
10190
10522
  init_paths();
10191
10523
  init_thread_store();
10192
10524
  init_teams();
@@ -10302,7 +10634,7 @@ async function resolveRepoRoot(cwd) {
10302
10634
  }
10303
10635
  function canonicalizeRepoPath(path2) {
10304
10636
  try {
10305
- return (0, import_node_fs22.realpathSync)(path2);
10637
+ return (0, import_node_fs23.realpathSync)(path2);
10306
10638
  } catch {
10307
10639
  return path2.replace(/\/+$/, "");
10308
10640
  }
@@ -11220,8 +11552,8 @@ async function refreshOriginAndMaybeFastForwardMain(repoPath, sourceRef) {
11220
11552
  }
11221
11553
  async function createThreadWorktree(opts) {
11222
11554
  let branchName = opts.slug;
11223
- const worktreePath = (0, import_node_path26.join)(worktreesRoot(opts.repoPath), opts.slug);
11224
- if ((0, import_node_fs22.existsSync)(worktreePath)) {
11555
+ const worktreePath = (0, import_node_path27.join)(worktreesRoot(opts.repoPath), opts.slug);
11556
+ if ((0, import_node_fs23.existsSync)(worktreePath)) {
11225
11557
  throw new Error(`Worktree already exists at ${worktreePath}`);
11226
11558
  }
11227
11559
  await ensureGhPreferOrigin(opts.repoPath);
@@ -11262,8 +11594,8 @@ ${add.stdout}`;
11262
11594
  async function createExistingBranchWorktree(opts) {
11263
11595
  const branchName = opts.branchName.trim();
11264
11596
  if (!branchName) throw new Error("branch name required");
11265
- const worktreePath = (0, import_node_path26.join)(worktreesRoot(opts.repoPath), opts.slug);
11266
- if ((0, import_node_fs22.existsSync)(worktreePath)) {
11597
+ const worktreePath = (0, import_node_path27.join)(worktreesRoot(opts.repoPath), opts.slug);
11598
+ if ((0, import_node_fs23.existsSync)(worktreePath)) {
11267
11599
  throw new Error(`Worktree already exists at ${worktreePath}`);
11268
11600
  }
11269
11601
  await ensureGhPreferOrigin(opts.repoPath);
@@ -11602,11 +11934,11 @@ async function createOrUpdatePr(worktreePath, opts) {
11602
11934
  const headRef = ghHeadRef(slug, opts.head);
11603
11935
  const branchOnly = opts.head.trim().replace(/^refs\/heads\//, "");
11604
11936
  const bodyText = clampGithubPrBody(opts.body ?? opts.title);
11605
- const bodyFile = (0, import_node_path26.join)(
11937
+ const bodyFile = (0, import_node_path27.join)(
11606
11938
  (0, import_node_os8.tmpdir)(),
11607
- `sideboard-pr-body-${process.pid}-${(0, import_node_crypto9.randomBytes)(6).toString("hex")}.md`
11939
+ `sideboard-pr-body-${process.pid}-${(0, import_node_crypto10.randomBytes)(6).toString("hex")}.md`
11608
11940
  );
11609
- (0, import_node_fs22.writeFileSync)(bodyFile, bodyText.endsWith("\n") ? bodyText : `${bodyText}
11941
+ (0, import_node_fs23.writeFileSync)(bodyFile, bodyText.endsWith("\n") ? bodyText : `${bodyText}
11610
11942
  `, "utf8");
11611
11943
  const bodyArgs = ["--body-file", bodyFile];
11612
11944
  try {
@@ -11620,7 +11952,7 @@ async function createOrUpdatePr(worktreePath, opts) {
11620
11952
  });
11621
11953
  } finally {
11622
11954
  try {
11623
- (0, import_node_fs22.unlinkSync)(bodyFile);
11955
+ (0, import_node_fs23.unlinkSync)(bodyFile);
11624
11956
  } catch {
11625
11957
  }
11626
11958
  }
@@ -11700,14 +12032,14 @@ function suggestSlug(source) {
11700
12032
  const stamp = Date.now().toString(36).slice(-4);
11701
12033
  return `${base || "thread"}-${stamp}`;
11702
12034
  }
11703
- var import_node_crypto9, import_node_fs22, import_node_os8, import_node_path26, PR_INFO_JSON_FIELDS, PR_DETAILS_JSON_FIELDS;
12035
+ var import_node_crypto10, import_node_fs23, import_node_os8, import_node_path27, PR_INFO_JSON_FIELDS, PR_DETAILS_JSON_FIELDS;
11704
12036
  var init_worktree = __esm({
11705
12037
  "src/git/worktree.ts"() {
11706
12038
  "use strict";
11707
- import_node_crypto9 = require("crypto");
11708
- import_node_fs22 = require("fs");
12039
+ import_node_crypto10 = require("crypto");
12040
+ import_node_fs23 = require("fs");
11709
12041
  import_node_os8 = require("os");
11710
- import_node_path26 = require("path");
12042
+ import_node_path27 = require("path");
11711
12043
  init_paths();
11712
12044
  init_worktree_labels();
11713
12045
  init_gh_errors();
@@ -11797,279 +12129,6 @@ var init_cloud_connect_constants = __esm({
11797
12129
  }
11798
12130
  });
11799
12131
 
11800
- // src/composer/stage-files.ts
11801
- function fileExtension(filePath) {
11802
- const base = (0, import_node_path27.basename)(filePath).toLowerCase();
11803
- return base.includes(".") ? base.split(".").pop() || "" : "";
11804
- }
11805
- function isImageFilePath(filePath) {
11806
- return IMAGE_EXTENSIONS.has(fileExtension(filePath));
11807
- }
11808
- function imageMimeType(filePath) {
11809
- return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
11810
- }
11811
- function ensureAttachmentsDir(worktreePath) {
11812
- const dir = (0, import_node_path27.join)(worktreePath, ATTACHMENTS_DIR);
11813
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
11814
- const gi = (0, import_node_path27.join)(dir, ".gitignore");
11815
- if (!(0, import_node_fs23.existsSync)(gi)) {
11816
- (0, import_node_fs23.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
11817
- }
11818
- return dir;
11819
- }
11820
- function uniqueAttachmentName(dir, originalName) {
11821
- const safe = originalName.replace(/[/\\]/g, "_") || "file";
11822
- if (!(0, import_node_fs23.existsSync)((0, import_node_path27.join)(dir, safe))) return safe;
11823
- const ext = (0, import_node_path27.extname)(safe);
11824
- const stem = ext ? safe.slice(0, -ext.length) : safe;
11825
- for (let i = 1; i < 1e4; i++) {
11826
- const candidate = `${stem}-${i}${ext}`;
11827
- if (!(0, import_node_fs23.existsSync)((0, import_node_path27.join)(dir, candidate))) return candidate;
11828
- }
11829
- return `${stem}-${(0, import_node_crypto10.randomUUID)()}${ext}`;
11830
- }
11831
- function previewDataUrlFromBuf(filePath, buf) {
11832
- if (!isImageFilePath(filePath)) return void 0;
11833
- if (buf.length > MAX_PREVIEW_BYTES) return void 0;
11834
- return `data:${imageMimeType(filePath)};base64,${buf.toString("base64")}`;
11835
- }
11836
- function attachmentFromBuffer(name, buf, opts) {
11837
- const previewDataUrl = previewDataUrlFromBuf(name, buf);
11838
- if (isImageFilePath(name)) {
11839
- const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
11840
- return {
11841
- id: (0, import_node_crypto10.randomUUID)(),
11842
- name,
11843
- kind: "file",
11844
- path: opts.path,
11845
- previewDataUrl,
11846
- content: [
11847
- `Image attached: ${pathHint}`,
11848
- opts.path ? `Use the Read tool on \`${opts.path}\` to view this image.` : "The image is shown in the composer; copy it into the worktree if you need to inspect pixels."
11849
- ].join("\n")
11850
- };
11851
- }
11852
- if (buf.length > MAX_INLINE_BYTES) {
11853
- return {
11854
- id: (0, import_node_crypto10.randomUUID)(),
11855
- name,
11856
- kind: "file",
11857
- path: opts.path,
11858
- content: opts.path ? `(file too large to attach inline: \`${opts.path}\`, ${buf.length} bytes \u2014 use the Read tool)` : `(file too large to attach inline: ${opts.sourceLabel || name}, ${buf.length} bytes)`
11859
- };
11860
- }
11861
- if (buf.includes(0)) {
11862
- return {
11863
- id: (0, import_node_crypto10.randomUUID)(),
11864
- name,
11865
- kind: "file",
11866
- path: opts.path,
11867
- content: opts.path ? `(binary file at \`${opts.path}\` \u2014 use tools to inspect)` : `(binary file attached by path only: ${opts.sourceLabel || name})`
11868
- };
11869
- }
11870
- return {
11871
- id: (0, import_node_crypto10.randomUUID)(),
11872
- name,
11873
- kind: "file",
11874
- path: opts.path,
11875
- content: buf.toString("utf8")
11876
- };
11877
- }
11878
- function attachmentFromAbsolutePath(absolutePath) {
11879
- const name = (0, import_node_path27.basename)(absolutePath);
11880
- try {
11881
- const st = (0, import_node_fs23.statSync)(absolutePath);
11882
- if (!st.isFile()) {
11883
- return {
11884
- id: (0, import_node_crypto10.randomUUID)(),
11885
- name,
11886
- kind: "file",
11887
- content: `(not a file: ${absolutePath})`
11888
- };
11889
- }
11890
- const buf = (0, import_node_fs23.readFileSync)(absolutePath);
11891
- return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
11892
- } catch (err) {
11893
- return {
11894
- id: (0, import_node_crypto10.randomUUID)(),
11895
- name,
11896
- kind: "file",
11897
- content: `(could not read ${absolutePath}: ${err instanceof Error ? err.message : String(err)})`
11898
- };
11899
- }
11900
- }
11901
- function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
11902
- if (absolutePaths.length === 0) return [];
11903
- const dir = ensureAttachmentsDir(worktreePath);
11904
- const out = [];
11905
- for (const abs of absolutePaths) {
11906
- const originalName = (0, import_node_path27.basename)(abs);
11907
- try {
11908
- const st = (0, import_node_fs23.statSync)(abs);
11909
- if (!st.isFile()) continue;
11910
- const name = uniqueAttachmentName(dir, originalName);
11911
- const destAbs = (0, import_node_path27.join)(dir, name);
11912
- (0, import_node_fs23.copyFileSync)(abs, destAbs);
11913
- const rel = `${ATTACHMENTS_DIR}/${name}`;
11914
- const buf = (0, import_node_fs23.readFileSync)(destAbs);
11915
- out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
11916
- } catch (err) {
11917
- out.push({
11918
- id: (0, import_node_crypto10.randomUUID)(),
11919
- name: originalName,
11920
- kind: "file",
11921
- content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
11922
- });
11923
- }
11924
- }
11925
- return out;
11926
- }
11927
- function stageBuffersAsAttachments(worktreePath, buffers2) {
11928
- if (buffers2.length === 0) return [];
11929
- const dir = ensureAttachmentsDir(worktreePath);
11930
- const out = [];
11931
- for (const item of buffers2) {
11932
- const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
11933
- try {
11934
- const buf = Buffer.from(item.dataBase64, "base64");
11935
- const name = uniqueAttachmentName(dir, originalName);
11936
- const destAbs = (0, import_node_path27.join)(dir, name);
11937
- (0, import_node_fs23.writeFileSync)(destAbs, buf);
11938
- const rel = `${ATTACHMENTS_DIR}/${name}`;
11939
- out.push(attachmentFromBuffer(name, buf, { path: rel }));
11940
- } catch (err) {
11941
- out.push({
11942
- id: (0, import_node_crypto10.randomUUID)(),
11943
- name: originalName,
11944
- kind: "file",
11945
- content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
11946
- });
11947
- }
11948
- }
11949
- return out;
11950
- }
11951
- function attachmentsFromBuffers(buffers2) {
11952
- return buffers2.map((item) => {
11953
- const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
11954
- try {
11955
- const buf = Buffer.from(item.dataBase64, "base64");
11956
- return attachmentFromBuffer(name, buf, { sourceLabel: name });
11957
- } catch (err) {
11958
- return {
11959
- id: (0, import_node_crypto10.randomUUID)(),
11960
- name,
11961
- kind: "file",
11962
- content: `(could not attach ${name}: ${err instanceof Error ? err.message : String(err)})`
11963
- };
11964
- }
11965
- });
11966
- }
11967
- function isWorktreeRelativePath(p) {
11968
- if (!p || p.includes("..")) return false;
11969
- if (p.startsWith("/")) return false;
11970
- if (/^[A-Za-z]:[\\/]/.test(p)) return false;
11971
- return true;
11972
- }
11973
- function dataUrlToBase64(url) {
11974
- if (!url) return null;
11975
- const m = /^data:[^;]+;base64,(.+)$/s.exec(url);
11976
- return m?.[1] ?? null;
11977
- }
11978
- function persistPendingFileAttachments(worktreePath, attachments) {
11979
- if (attachments.length === 0) return attachments;
11980
- const keep = [];
11981
- const buffers2 = [];
11982
- for (const att of attachments) {
11983
- if (att.kind !== "file") {
11984
- keep.push(att);
11985
- continue;
11986
- }
11987
- if (att.path && isWorktreeRelativePath(att.path)) {
11988
- keep.push(att);
11989
- continue;
11990
- }
11991
- const fromPreview = dataUrlToBase64(att.previewDataUrl);
11992
- if (fromPreview) {
11993
- buffers2.push({ name: att.name, dataBase64: fromPreview });
11994
- continue;
11995
- }
11996
- if (att.content && !IMAGE_HINT_RE.test(att.content) && !PLACEHOLDER_CONTENT_RE.test(att.content)) {
11997
- buffers2.push({
11998
- name: att.name,
11999
- dataBase64: Buffer.from(att.content, "utf8").toString("base64")
12000
- });
12001
- continue;
12002
- }
12003
- keep.push(att);
12004
- }
12005
- if (buffers2.length === 0) return attachments;
12006
- return [...keep, ...stageBuffersAsAttachments(worktreePath, buffers2)];
12007
- }
12008
- function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
12009
- const out = [];
12010
- for (const rel of relativePaths) {
12011
- if (!rel || rel.includes("..") || rel.startsWith("/")) {
12012
- out.push({
12013
- id: (0, import_node_crypto10.randomUUID)(),
12014
- name: (0, import_node_path27.basename)(rel) || "file",
12015
- kind: "file",
12016
- content: `(invalid path: ${rel})`
12017
- });
12018
- continue;
12019
- }
12020
- const name = (0, import_node_path27.basename)(rel);
12021
- try {
12022
- const abs = (0, import_node_path27.join)(worktreePath, rel);
12023
- const st = (0, import_node_fs23.statSync)(abs);
12024
- if (!st.isFile()) continue;
12025
- const buf = (0, import_node_fs23.readFileSync)(abs);
12026
- out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
12027
- } catch (err) {
12028
- out.push({
12029
- id: (0, import_node_crypto10.randomUUID)(),
12030
- name,
12031
- kind: "file",
12032
- content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
12033
- });
12034
- }
12035
- }
12036
- return out;
12037
- }
12038
- var import_node_fs23, import_node_path27, import_node_crypto10, IMAGE_EXTENSIONS, IMAGE_MIME_BY_EXT, MAX_INLINE_BYTES, MAX_PREVIEW_BYTES, IMAGE_HINT_RE, PLACEHOLDER_CONTENT_RE;
12039
- var init_stage_files = __esm({
12040
- "src/composer/stage-files.ts"() {
12041
- "use strict";
12042
- import_node_fs23 = require("fs");
12043
- import_node_path27 = require("path");
12044
- import_node_crypto10 = require("crypto");
12045
- init_workspace_scratch();
12046
- IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
12047
- "png",
12048
- "jpg",
12049
- "jpeg",
12050
- "gif",
12051
- "webp",
12052
- "svg",
12053
- "bmp",
12054
- "ico"
12055
- ]);
12056
- IMAGE_MIME_BY_EXT = {
12057
- png: "image/png",
12058
- jpg: "image/jpeg",
12059
- jpeg: "image/jpeg",
12060
- gif: "image/gif",
12061
- webp: "image/webp",
12062
- svg: "image/svg+xml",
12063
- bmp: "image/bmp",
12064
- ico: "image/x-icon"
12065
- };
12066
- MAX_INLINE_BYTES = 4e5;
12067
- MAX_PREVIEW_BYTES = 5e6;
12068
- IMAGE_HINT_RE = /^Image attached:/;
12069
- PLACEHOLDER_CONTENT_RE = /^\((could not |file too large|binary file|not a file|invalid path)/;
12070
- }
12071
- });
12072
-
12073
12132
  // src/store/workspaces.ts
12074
12133
  var workspaces_exports = {};
12075
12134
  __export(workspaces_exports, {
@@ -12558,7 +12617,7 @@ var init_coordinator_prompt = __esm({
12558
12617
  "- 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.",
12559
12618
  "- 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.",
12560
12619
  "- 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.",
12561
- "- 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).",
12620
+ "- 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.",
12562
12621
  "Projects:",
12563
12622
  "- add_project / remove_project \u2014 register or unregister a git repo",
12564
12623
  "Workspaces and chats:",
@@ -23328,6 +23387,77 @@ var init_steer_request = __esm({
23328
23387
  }
23329
23388
  });
23330
23389
 
23390
+ // src/orchestrator/ephemeral-automation.ts
23391
+ function ephemeralRunReadyToArchive(thread) {
23392
+ if (!thread.ephemeralAutomationId?.trim()) return false;
23393
+ if (thread.status !== "idle" && thread.status !== "error") return false;
23394
+ if (thread.queue.length > 0) return false;
23395
+ if (thread.agentBlock?.reason?.trim()) return false;
23396
+ if (thread.quotaResumeAt) return false;
23397
+ return true;
23398
+ }
23399
+ function markEphemeralAutomation(threadId, opts) {
23400
+ const id = opts.ephemeralAutomationId?.trim();
23401
+ const current = readThread(threadId);
23402
+ if (!current) throw new Error(`Thread not found: ${threadId}`);
23403
+ if (!id) return current;
23404
+ const title = opts.ephemeralTitle?.trim();
23405
+ return updateThread(threadId, {
23406
+ ephemeralAutomationId: id,
23407
+ ...title ? { title, userSetTitle: true } : {}
23408
+ });
23409
+ }
23410
+ async function archiveEphemeralAutomationIfReady(threadId, archive) {
23411
+ const keep = await withThreadLock(threadId, async () => {
23412
+ const thread = readThread(threadId);
23413
+ if (!thread || !ephemeralRunReadyToArchive(thread)) return null;
23414
+ const automationId = thread.ephemeralAutomationId?.trim();
23415
+ if (!automationId) return null;
23416
+ await archive(thread.id);
23417
+ return {
23418
+ automationId,
23419
+ keepId: archivedContinuationId(automationId, threadId) ?? threadId
23420
+ };
23421
+ });
23422
+ if (keep) forgetOlderEphemeralRuns(keep.automationId, keep.keepId);
23423
+ }
23424
+ function archivedContinuationId(automationId, sourceId) {
23425
+ for (const thread of listThreads({ includeArchived: true })) {
23426
+ if (thread.id === sourceId || thread.status !== "archived") continue;
23427
+ if (thread.ephemeralAutomationId !== automationId) continue;
23428
+ if (thread.quotaContinuedFromId !== sourceId) continue;
23429
+ return thread.id;
23430
+ }
23431
+ return null;
23432
+ }
23433
+ function clearEphemeralAutomation(threadId) {
23434
+ const thread = readThread(threadId);
23435
+ if (!thread?.ephemeralAutomationId?.trim()) return;
23436
+ updateThread(threadId, { ephemeralAutomationId: void 0 });
23437
+ }
23438
+ function forgetOlderEphemeralRuns(automationId, keepId) {
23439
+ const id = automationId.trim();
23440
+ if (!id || !keepId) return [];
23441
+ const removed = [];
23442
+ for (const thread of listThreads({ includeArchived: true })) {
23443
+ if (thread.id === keepId) continue;
23444
+ if (thread.status !== "archived") continue;
23445
+ if (thread.ephemeralAutomationId !== id) continue;
23446
+ try {
23447
+ deleteThreadRecord(thread.id);
23448
+ removed.push(thread.id);
23449
+ } catch {
23450
+ }
23451
+ }
23452
+ return removed;
23453
+ }
23454
+ var init_ephemeral_automation = __esm({
23455
+ "src/orchestrator/ephemeral-automation.ts"() {
23456
+ "use strict";
23457
+ init_thread_store();
23458
+ }
23459
+ });
23460
+
23331
23461
  // src/threads/title.ts
23332
23462
  var title_exports = {};
23333
23463
  __export(title_exports, {
@@ -24886,6 +25016,7 @@ var init_abletime_projects = __esm({
24886
25016
  var abletime_exports = {};
24887
25017
  __export(abletime_exports, {
24888
25018
  ableTimeDependencyTarget: () => ableTimeDependencyTarget,
25019
+ ableTimeStateIsClosed: () => ableTimeStateIsClosed,
24889
25020
  commentAbleTimeTask: () => commentAbleTimeTask,
24890
25021
  createAbleTimeTask: () => createAbleTimeTask,
24891
25022
  downloadAbleTimeAttachment: () => downloadAbleTimeAttachment,
@@ -25039,9 +25170,12 @@ function issueAttachmentForAbleTimeTask(task) {
25039
25170
  ].filter(Boolean).join("\n")
25040
25171
  };
25041
25172
  }
25173
+ function ableTimeStateIsClosed(state) {
25174
+ const value = (state ?? "").trim().toLowerCase();
25175
+ return Boolean(value && CLOSED_STATES.has(value));
25176
+ }
25042
25177
  function isOpenTask(task) {
25043
- const state = (task.state ?? "").toLowerCase();
25044
- return !state || !CLOSED_STATES.has(state);
25178
+ return !ableTimeStateIsClosed(task.state);
25045
25179
  }
25046
25180
  async function getAbleTimeOrientation(opts) {
25047
25181
  const raw = await callAbleTimeTool("orientation", {}, opts);
@@ -26292,17 +26426,23 @@ async function isolateQuotaFailover(run2) {
26292
26426
  }
26293
26427
  }
26294
26428
  function createQuotaFailoverChat(from, fallbackAgent, limitText, opts) {
26429
+ const ephemeralAutomationId = from.ephemeralAutomationId?.trim();
26430
+ const ephemeralTitle = ephemeralAutomationId ? from.title.trim() : "";
26295
26431
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
26296
26432
  const tab = createChatTab({
26297
26433
  fromThreadId: from.id,
26298
26434
  agent: fallbackAgent,
26299
26435
  model: null,
26300
26436
  attachments: [handoff],
26437
+ ...ephemeralTitle ? { title: ephemeralTitle } : {},
26301
26438
  ...opts && "accountId" in opts ? { accountId: opts.accountId } : {}
26302
26439
  });
26303
26440
  const patch = {
26304
26441
  quotaContinuedFromId: from.id,
26305
- sourceRef: from.sourceRef
26442
+ sourceRef: from.sourceRef,
26443
+ // The original drain archives as idle. This sibling has to carry the one-off
26444
+ // so it archives when the continued turn finishes and History keeps it.
26445
+ ...ephemeralAutomationId ? { ephemeralAutomationId } : {}
26306
26446
  };
26307
26447
  if (isOrchestratorThread(from)) {
26308
26448
  patch.parentThreadId = from.id;
@@ -28329,6 +28469,41 @@ var init_expand = __esm({
28329
28469
  }
28330
28470
  });
28331
28471
 
28472
+ // src/composer/agent-has-replied.ts
28473
+ function joinSteerPrompt(base, addition) {
28474
+ const next = addition.trim();
28475
+ if (!next) return base;
28476
+ const head = base.replace(/\s+$/, "");
28477
+ if (!head) return next;
28478
+ return `${head}
28479
+ ${next}`;
28480
+ }
28481
+ function trailingUnansweredUserText(messages) {
28482
+ for (let i = messages.length - 1; i >= 0; i--) {
28483
+ const message = messages[i];
28484
+ if (!message || message.origin === "continue" || message.role === "summary") continue;
28485
+ if (message.role === "agent") {
28486
+ const text7 = (message.text ?? "").trim();
28487
+ if (text7 || (message.parts?.length ?? 0) > 0) return null;
28488
+ continue;
28489
+ }
28490
+ if (message.role === "user") return message.text;
28491
+ }
28492
+ return null;
28493
+ }
28494
+ function livePartsShowAgentReply(parts) {
28495
+ for (const part of parts) {
28496
+ if (part.type === "tool") return true;
28497
+ if ((part.type === "text" || part.type === "thinking") && part.text.trim()) return true;
28498
+ }
28499
+ return false;
28500
+ }
28501
+ var init_agent_has_replied = __esm({
28502
+ "src/composer/agent-has-replied.ts"() {
28503
+ "use strict";
28504
+ }
28505
+ });
28506
+
28332
28507
  // src/composer/consume-attachments.ts
28333
28508
  function consumeComposerAttachments(thread) {
28334
28509
  return {
@@ -28386,6 +28561,142 @@ var init_consume_attachments = __esm({
28386
28561
  }
28387
28562
  });
28388
28563
 
28564
+ // src/orchestrator/enqueue-thread-send.ts
28565
+ function resolveSendFollowUp(thread, requested) {
28566
+ if (requested === "steer" || requested === "queue") return requested;
28567
+ return isOrchestratorThread(thread) ? followUpBehavior() : "queue";
28568
+ }
28569
+ function rewriteTrailingUserPrompt(threadId, from, to) {
28570
+ if (from === to) return;
28571
+ const thread = readThread(threadId);
28572
+ if (!thread) return;
28573
+ const messages = thread.messages.slice();
28574
+ for (let i = messages.length - 1; i >= 0; i--) {
28575
+ const message = messages[i];
28576
+ if (!message || message.origin === "continue" || message.role === "summary") continue;
28577
+ if (message.role === "user" && message.text === from) {
28578
+ messages[i] = { ...message, text: to };
28579
+ updateThread(threadId, { messages });
28580
+ }
28581
+ return;
28582
+ }
28583
+ }
28584
+ function reuseJoinedUserBubble(reused, threadId, last, promptText) {
28585
+ return reused.delete(threadId) && last?.role === "user" && last.origin !== "continue" && last.text === promptText;
28586
+ }
28587
+ function promptForJoinedSteer(input) {
28588
+ if (input.latestPrompt === input.promptText) return input.agentPrompt;
28589
+ rewriteTrailingUserPrompt(input.threadId, input.promptText, input.latestPrompt);
28590
+ const { agentPrompt: expandedLatest } = expandComposerPrompt(
28591
+ input.worktreePath,
28592
+ expandCanonicalGitRequest(input.latestPrompt),
28593
+ { attachments: input.attachments }
28594
+ );
28595
+ return input.expandedPrompt && input.agentPrompt.endsWith(input.expandedPrompt) ? input.agentPrompt.slice(0, -input.expandedPrompt.length) + expandedLatest : `${input.agentPrompt}
28596
+
28597
+ ${expandedLatest}`;
28598
+ }
28599
+ function clearSendBookkeeping(host, threadId) {
28600
+ host.crashContinued.delete(threadId);
28601
+ host.jobContinueCount.delete(threadId);
28602
+ host.jobContinueNudged.delete(threadId);
28603
+ host.haltDrain.delete(threadId);
28604
+ }
28605
+ async function enqueueThreadSend(host, threadRef, prompt, opts) {
28606
+ const thread = host.requireThread(threadRef);
28607
+ if (thread.status === "archived") {
28608
+ throw new Error(`Thread is archived: ${thread.id}`);
28609
+ }
28610
+ let shouldSteer = false;
28611
+ const sent = await withThreadLock(thread.id, async () => {
28612
+ const current = host.requireThread(thread.id);
28613
+ if (current.status === "archived") {
28614
+ throw new Error(`Thread is archived: ${thread.id}`);
28615
+ }
28616
+ const followUp = resolveSendFollowUp(current, opts?.followUp);
28617
+ const explicit = opts?.attachments;
28618
+ const consumed = explicit !== void 0 ? { attachments: current.attachments, consumed: explicit } : consumeComposerAttachments(current);
28619
+ const inFlightEarly = host.activeTurns.has(thread.id) || host.startingTurns.has(thread.id) || current.status === "running";
28620
+ const agentHasRepliedEarly = livePartsShowAgentReply(readTurnLiveParts(current.id));
28621
+ const draft = host.openSteerPrompt.get(current.id);
28622
+ const queuedHead = !inFlightEarly && current.status === "queued" && current.queue.length > 0 ? current.queue[0] : null;
28623
+ const unanswered = followUp === "steer" && !agentHasRepliedEarly ? draft?.text ?? (inFlightEarly ? trailingUnansweredUserText(current.messages) : queuedHead) : null;
28624
+ if (unanswered != null && (draft ? !draft.spawned : queuedHead != null)) {
28625
+ clearSendBookkeeping(host, thread.id);
28626
+ const combined = joinSteerPrompt(unanswered, prompt);
28627
+ if (draft && !draft.spawned) {
28628
+ draft.text = combined;
28629
+ rewriteTrailingUserPrompt(current.id, unanswered, combined);
28630
+ } else {
28631
+ const queue = current.queue.slice();
28632
+ queue[0] = combined;
28633
+ const queueAttachments = alignQueueAttachments(queue.length, current.queueAttachments);
28634
+ if (consumed.consumed.length > 0) {
28635
+ queueAttachments[0] = [...queueAttachments[0] ?? [], ...consumed.consumed];
28636
+ }
28637
+ const patch2 = { queue, queueAttachments };
28638
+ if (explicit === void 0 && consumed.consumed.length > 0) {
28639
+ patch2.attachments = consumed.attachments;
28640
+ }
28641
+ updateThread(current.id, patch2);
28642
+ host.emit({ type: "queue_changed", threadId: current.id, queue });
28643
+ }
28644
+ if (thisProcessShouldDrainAgentQueues() && !inFlightEarly) {
28645
+ host.armDrain(current.id);
28646
+ }
28647
+ return host.requireThread(current.id);
28648
+ }
28649
+ const promptToQueue = unanswered != null ? joinSteerPrompt(unanswered, prompt) : prompt;
28650
+ if (unanswered != null) {
28651
+ if (draft) draft.text = promptToQueue;
28652
+ rewriteTrailingUserPrompt(current.id, unanswered, promptToQueue);
28653
+ host.reusedUserPrompt.add(current.id);
28654
+ }
28655
+ const queued = appendQueuedItem(
28656
+ current.queue,
28657
+ current.queueAttachments,
28658
+ promptToQueue,
28659
+ consumed.consumed
28660
+ );
28661
+ clearSendBookkeeping(host, thread.id);
28662
+ const inFlight = host.activeTurns.has(thread.id) || host.startingTurns.has(thread.id) || current.status === "running";
28663
+ shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
28664
+ const patch = queued;
28665
+ if (explicit === void 0 && consumed.consumed.length > 0) {
28666
+ patch.attachments = consumed.attachments;
28667
+ }
28668
+ if (!inFlight) patch.status = "queued";
28669
+ const pid = current.agentPid;
28670
+ if (typeof pid === "number" && pid > 0 && !host.pidAlive(pid)) {
28671
+ patch.agentPid = null;
28672
+ }
28673
+ updateThread(thread.id, patch);
28674
+ host.emit({ type: "queue_changed", threadId: thread.id, queue: queued.queue });
28675
+ if (!inFlight) {
28676
+ host.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
28677
+ }
28678
+ if (thisProcessShouldDrainAgentQueues() && !shouldSteer) {
28679
+ host.armDrain(thread.id);
28680
+ }
28681
+ return host.requireThread(thread.id);
28682
+ });
28683
+ return { thread: sent, shouldSteer };
28684
+ }
28685
+ var init_enqueue_thread_send = __esm({
28686
+ "src/orchestrator/enqueue-thread-send.ts"() {
28687
+ "use strict";
28688
+ init_agent_git_actions();
28689
+ init_agent_has_replied();
28690
+ init_expand();
28691
+ init_consume_attachments();
28692
+ init_desktop_host();
28693
+ init_thread_store();
28694
+ init_turn_live();
28695
+ init_app_settings();
28696
+ init_global_workspace();
28697
+ }
28698
+ });
28699
+
28389
28700
  // src/skills/detached-job-path.ts
28390
28701
  function compiledModuleDir() {
28391
28702
  const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
@@ -28548,7 +28859,7 @@ function formatWorktreeDirective(thread, opts) {
28548
28859
  lines.push(formatDevAccessHint(opts?.activeRuns));
28549
28860
  lines.push("");
28550
28861
  lines.push(
28551
- "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."
28862
+ "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."
28552
28863
  );
28553
28864
  lines.push(
28554
28865
  "`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
@@ -28568,7 +28879,7 @@ function connectedOrchestratorTurnReminder(thread, find) {
28568
28879
  return formatConnectedOrchestratorReminder(connectedOrchestrator(thread, find));
28569
28880
  }
28570
28881
  function formatWorktreeReminder() {
28571
- 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).";
28882
+ 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).";
28572
28883
  }
28573
28884
  function formatClaudeSubagentReminder() {
28574
28885
  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.';
@@ -29756,10 +30067,6 @@ async function waitForPidExit(pid, timeoutMs) {
29756
30067
  }
29757
30068
  return true;
29758
30069
  }
29759
- function resolveSendFollowUp(thread, requested) {
29760
- if (requested === "steer" || requested === "queue") return requested;
29761
- return isOrchestratorThread(thread) ? followUpBehavior() : "queue";
29762
- }
29763
30070
  function resolveOrchChildFollowUp(requested) {
29764
30071
  if (requested === "steer" || requested === "queue") return requested;
29765
30072
  return followUpBehavior();
@@ -29800,10 +30107,11 @@ async function startOrchestration(opts) {
29800
30107
  planMode: opts.planMode,
29801
30108
  attachments: opts.attachments
29802
30109
  });
30110
+ const ready2 = markEphemeralAutomation(thread2.id, opts);
29803
30111
  if (goal) {
29804
- return orch.send(thread2.id, goal);
30112
+ return orch.send(ready2.id, goal);
29805
30113
  }
29806
- return thread2;
30114
+ return ready2;
29807
30115
  }
29808
30116
  const { titleFromPrompt: titleFromPrompt2 } = await Promise.resolve().then(() => (init_title(), title_exports));
29809
30117
  const title = titleFromPrompt2(goal) || "Orchestration";
@@ -29838,10 +30146,11 @@ async function startOrchestration(opts) {
29838
30146
  sourceType: "orchestration",
29839
30147
  sourceRef: goal
29840
30148
  });
30149
+ const ready = markEphemeralAutomation(updated.id, opts);
29841
30150
  if (goal) {
29842
- return orch.send(updated.id, goal);
30151
+ return orch.send(ready.id, goal);
29843
30152
  }
29844
- return updated;
30153
+ return ready;
29845
30154
  }
29846
30155
  var import_node_events, import_node_crypto19, import_node_fs67, LIVE_TURN_SPAWN_GRACE_MS, RUN_SCRIPT_DESKTOP_ADOPT_MS, STALE_AGENT_PID_WAIT_MS, LIGHT_RECONCILE_THROTTLE_MS, Orchestrator, singleton2;
29847
30156
  var init_orchestrator = __esm({
@@ -29882,6 +30191,7 @@ var init_orchestrator = __esm({
29882
30191
  init_child_halt();
29883
30192
  init_stop_reason();
29884
30193
  init_steer_request();
30194
+ init_ephemeral_automation();
29885
30195
  init_wait_for_job();
29886
30196
  init_agent_purpose_title();
29887
30197
  init_create();
@@ -29909,6 +30219,7 @@ var init_orchestrator = __esm({
29909
30219
  init_read_worktree_tail();
29910
30220
  init_discover();
29911
30221
  init_expand();
30222
+ init_enqueue_thread_send();
29912
30223
  init_consume_attachments();
29913
30224
  init_stage_files();
29914
30225
  init_context_compact();
@@ -29926,6 +30237,7 @@ var init_orchestrator = __esm({
29926
30237
  init_caffeinate_hold();
29927
30238
  init_schedule_runner();
29928
30239
  init_coordinator_prompt();
30240
+ init_enqueue_thread_send();
29929
30241
  LIVE_TURN_SPAWN_GRACE_MS = 15e3;
29930
30242
  RUN_SCRIPT_DESKTOP_ADOPT_MS = 2e4;
29931
30243
  STALE_AGENT_PID_WAIT_MS = 2500;
@@ -29937,6 +30249,13 @@ var init_orchestrator = __esm({
29937
30249
  draining = /* @__PURE__ */ new Set();
29938
30250
  /** Threads past setStatus(running) but not yet in activeTurns (spawn in flight). */
29939
30251
  startingTurns = /* @__PURE__ */ new Set();
30252
+ /**
30253
+ * Prompt for the turn that has not been answered yet. Steer follow-ups before
30254
+ * any agent output are joined onto this text instead of starting a second bubble.
30255
+ */
30256
+ openSteerPrompt = /* @__PURE__ */ new Map();
30257
+ /** Next runTurn should keep the user bubble already rewritten to this prompt. */
30258
+ reusedUserPrompt = /* @__PURE__ */ new Set();
29940
30259
  /** Last sync thread-file read for false-stop heal (throttled per thread). */
29941
30260
  lastReconcileHealAt = /* @__PURE__ */ new Map();
29942
30261
  /** Per-repo timestamp of the last `reconcile(repo, { light: true })`. */
@@ -30547,54 +30866,29 @@ var init_orchestrator = __esm({
30547
30866
  return thread;
30548
30867
  }
30549
30868
  async send(threadRef, prompt, opts) {
30550
- const thread = this.requireThread(threadRef);
30551
- if (thread.status === "archived") {
30552
- throw new Error(`Thread is archived: ${thread.id}`);
30553
- }
30554
- let shouldSteer = false;
30555
- const sent = await withThreadLock(thread.id, async () => {
30556
- const current = this.requireThread(thread.id);
30557
- if (current.status === "archived") {
30558
- throw new Error(`Thread is archived: ${thread.id}`);
30559
- }
30560
- const followUp = resolveSendFollowUp(current, opts?.followUp);
30561
- const explicit = opts?.attachments;
30562
- const consumed = explicit !== void 0 ? { attachments: current.attachments, consumed: explicit } : consumeComposerAttachments(current);
30563
- const queued = appendQueuedItem(
30564
- current.queue,
30565
- current.queueAttachments,
30566
- prompt,
30567
- consumed.consumed
30568
- );
30569
- this.crashContinued.delete(thread.id);
30570
- this.jobContinueCount.delete(thread.id);
30571
- this.jobContinueNudged.delete(thread.id);
30572
- this.haltDrain.delete(thread.id);
30573
- const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
30574
- shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
30575
- const patch = queued;
30576
- if (explicit === void 0 && consumed.consumed.length > 0) {
30577
- patch.attachments = consumed.attachments;
30578
- }
30579
- if (!inFlight) patch.status = "queued";
30580
- const pid = current.agentPid;
30581
- if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
30582
- patch.agentPid = null;
30583
- }
30584
- updateThread(thread.id, patch);
30585
- this.emit({ type: "queue_changed", threadId: thread.id, queue: queued.queue });
30586
- if (!inFlight) {
30587
- this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
30588
- }
30589
- if (thisProcessShouldDrainAgentQueues() && !shouldSteer) {
30590
- this.armDrain(thread.id);
30591
- }
30592
- return this.requireThread(thread.id);
30593
- });
30594
- if (shouldSteer && sent.queue.length > 0) {
30595
- return this.sendQueuedMessageNow(sent.id, sent.queue.length - 1);
30869
+ const sent = await enqueueThreadSend(
30870
+ {
30871
+ activeTurns: this.activeTurns,
30872
+ startingTurns: this.startingTurns,
30873
+ openSteerPrompt: this.openSteerPrompt,
30874
+ reusedUserPrompt: this.reusedUserPrompt,
30875
+ crashContinued: this.crashContinued,
30876
+ jobContinueCount: this.jobContinueCount,
30877
+ jobContinueNudged: this.jobContinueNudged,
30878
+ haltDrain: this.haltDrain,
30879
+ emit: (event) => this.emit(event),
30880
+ armDrain: (id) => this.armDrain(id),
30881
+ requireThread: (ref) => this.requireThread(ref),
30882
+ pidAlive: (pid) => isPidAlive(pid)
30883
+ },
30884
+ threadRef,
30885
+ prompt,
30886
+ opts
30887
+ );
30888
+ if (sent.shouldSteer && sent.thread.queue.length > 0) {
30889
+ return this.sendQueuedMessageNow(sent.thread.id, sent.thread.queue.length - 1);
30596
30890
  }
30597
- return sent;
30891
+ return sent.thread;
30598
30892
  }
30599
30893
  async fanOut(threadRefs, prompt, opts) {
30600
30894
  const results = [];
@@ -30735,6 +31029,7 @@ var init_orchestrator = __esm({
30735
31029
  } finally {
30736
31030
  this.draining.delete(threadId);
30737
31031
  }
31032
+ await archiveEphemeralAutomationIfReady(threadId, (id) => this.archive(id));
30738
31033
  }
30739
31034
  async runTurn(threadId, prompt) {
30740
31035
  const existing = readThread(threadId);
@@ -30751,6 +31046,10 @@ var init_orchestrator = __esm({
30751
31046
  this.emit({ type: "status_changed", threadId, status: "running" });
30752
31047
  this.emit({ type: "turn_started", threadId, prompt });
30753
31048
  try {
31049
+ this.openSteerPrompt.set(threadId, {
31050
+ text: typeof prompt === "string" ? prompt : "",
31051
+ spawned: false
31052
+ });
30754
31053
  try {
30755
31054
  const baseline = await captureTurnBaseline(thread.worktreePath);
30756
31055
  if (baseline) this.turnBaselines.set(threadId, baseline);
@@ -30776,18 +31075,21 @@ var init_orchestrator = __esm({
30776
31075
  this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
30777
31076
  thread = this.requireThread(threadId);
30778
31077
  }
30779
- const promptText = typeof prompt === "string" ? prompt : "";
31078
+ const promptText = this.openSteerPrompt.get(threadId)?.text ?? (typeof prompt === "string" ? prompt : "");
30780
31079
  const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
30781
31080
  const parked = takePendingTurnAttachments(thread);
30782
31081
  const sentAttachments = !autoContinue && parked.length > 0 ? parked : void 0;
30783
31082
  if (!autoContinue) clearAgentBlock(threadId);
30784
- appendMessage(threadId, {
30785
- role: "user",
30786
- text: promptText,
30787
- ...sentAttachments ? { attachments: sentAttachments } : {},
30788
- ...autoContinue ? { origin: "continue" } : {},
30789
- ts: (/* @__PURE__ */ new Date()).toISOString()
30790
- });
31083
+ const lastShown = this.requireThread(threadId).messages.at(-1);
31084
+ if (!reuseJoinedUserBubble(this.reusedUserPrompt, threadId, lastShown, promptText)) {
31085
+ appendMessage(threadId, {
31086
+ role: "user",
31087
+ text: promptText,
31088
+ ...sentAttachments ? { attachments: sentAttachments } : {},
31089
+ ...autoContinue ? { origin: "continue" } : {},
31090
+ ts: (/* @__PURE__ */ new Date()).toISOString()
31091
+ });
31092
+ }
30791
31093
  if (!autoContinue) {
30792
31094
  const live = this.requireThread(threadId);
30793
31095
  const stamp = agentPurposeStamp({
@@ -30986,11 +31288,22 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
30986
31288
  ].filter(Boolean).join("\n\n---\n\n");
30987
31289
  const stderrTail = [];
30988
31290
  traceTurn("runTurn.beforeSpawn", { threadId, agent: fresh.agent });
31291
+ const steerDraft = this.openSteerPrompt.get(threadId);
31292
+ if (steerDraft) steerDraft.spawned = true;
31293
+ const promptForAgent = promptForJoinedSteer({
31294
+ threadId,
31295
+ promptText,
31296
+ latestPrompt: steerDraft?.text ?? promptText,
31297
+ agentPrompt,
31298
+ expandedPrompt,
31299
+ worktreePath: fresh.worktreePath,
31300
+ attachments: autoContinue ? [] : sentAttachments ?? []
31301
+ });
30989
31302
  const handle = await spawnAgentTurn(
30990
31303
  fresh,
30991
31304
  {
30992
31305
  cachedPrefix,
30993
- prompt: agentPrompt,
31306
+ prompt: promptForAgent,
30994
31307
  systemPrompt: turnReminders,
30995
31308
  isolateCodexPlugins: fresh.isolateCodexPlugins === true
30996
31309
  },
@@ -31118,7 +31431,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
31118
31431
  retryThread,
31119
31432
  {
31120
31433
  cachedPrefix: retryPrefix,
31121
- prompt: agentPrompt,
31434
+ prompt: promptForAgent,
31122
31435
  systemPrompt: turnReminders,
31123
31436
  isolateCodexPlugins: isolateCodexPluginRetry || retryThread.isolateCodexPlugins === true
31124
31437
  },
@@ -31278,6 +31591,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
31278
31591
  } finally {
31279
31592
  this.startingTurns.delete(threadId);
31280
31593
  this.activeTurns.delete(threadId);
31594
+ this.openSteerPrompt.delete(threadId);
31281
31595
  this.processes.delete(`${threadId}:agent`);
31282
31596
  this.stoppedTurns.delete(threadId);
31283
31597
  this.runningCount = Math.max(0, this.runningCount - 1);
@@ -31954,7 +32268,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
31954
32268
  const live = stillRunning ? readTurnLive(thread.id) : null;
31955
32269
  const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
31956
32270
  const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
31957
- const taskState = deriveTaskState({
32271
+ const taskState2 = deriveTaskState({
31958
32272
  status: thread.status,
31959
32273
  stillRunning,
31960
32274
  lastAgentParts: lastAgent?.parts
@@ -31963,7 +32277,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
31963
32277
  return {
31964
32278
  text: text7,
31965
32279
  status: thread.status,
31966
- taskState,
32280
+ taskState: taskState2,
31967
32281
  sessionId: thread.sessionId,
31968
32282
  lastError,
31969
32283
  stillRunning,
@@ -32549,6 +32863,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
32549
32863
  if (thread.status !== "archived") {
32550
32864
  throw new Error("Thread is not archived");
32551
32865
  }
32866
+ clearEphemeralAutomation(thread.id);
32552
32867
  if (isGlobalThread(thread)) {
32553
32868
  const { globalAgentCwd: globalAgentCwd2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
32554
32869
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
@@ -32640,17 +32955,52 @@ async function defaultDeps() {
32640
32955
  const orch = getOrchestrator2();
32641
32956
  return {
32642
32957
  findThread: (id) => findThreadByRef(id),
32643
- send: (id, prompt) => orch.send(id, prompt),
32958
+ send: async (id, prompt, opts) => {
32959
+ if (opts?.options && Object.keys(opts.options).length > 0) {
32960
+ try {
32961
+ orch.setThreadOptions(id, opts.options);
32962
+ } catch {
32963
+ const rest = scheduleOptionsAfterRefusedAgentSwitch(opts.options);
32964
+ if (Object.keys(rest).length > 0) {
32965
+ try {
32966
+ orch.setThreadOptions(id, rest);
32967
+ } catch {
32968
+ }
32969
+ }
32970
+ }
32971
+ }
32972
+ const thread = findThreadByRef(id);
32973
+ const attachments = thread ? persistPendingFileAttachments(thread.worktreePath, opts?.attachments ?? []) : opts?.attachments ?? [];
32974
+ return orch.send(id, prompt, { attachments });
32975
+ },
32644
32976
  startOrchestration: (opts) => startOrchestration2(opts)
32645
32977
  };
32646
32978
  }
32979
+ function scheduleOptionsAfterRefusedAgentSwitch(options) {
32980
+ const { agent: _agent, model: _model, ...rest } = options;
32981
+ return rest;
32982
+ }
32983
+ function scheduleSendOptions(schedule) {
32984
+ const patch = {};
32985
+ if (schedule.agent) {
32986
+ patch.agent = schedule.agent;
32987
+ patch.model = schedule.model;
32988
+ } else if (schedule.model) {
32989
+ patch.model = schedule.model;
32990
+ }
32991
+ if (schedule.effort) patch.effort = schedule.effort;
32992
+ if (schedule.fast != null) patch.fast = schedule.fast;
32993
+ if (schedule.planMode != null) patch.planMode = schedule.planMode;
32994
+ if (schedule.autonomy) patch.autonomy = schedule.autonomy;
32995
+ return Object.keys(patch).length > 0 ? patch : void 0;
32996
+ }
32647
32997
  function pickAgent(schedule) {
32648
32998
  if (schedule.agent) return schedule.agent;
32649
32999
  return coerceOrchestratorAgent(resolveOrchestratorDefaults().agent);
32650
33000
  }
32651
33001
  async function fireSchedule(id) {
32652
33002
  const schedule = getSchedule(id);
32653
- if (!schedule) throw new Error(`Schedule not found: ${id}`);
33003
+ if (!schedule) throw new Error(`Automation not found: ${id}`);
32654
33004
  if (firing.has(schedule.id)) return schedule;
32655
33005
  firing.add(schedule.id);
32656
33006
  try {
@@ -32658,18 +33008,28 @@ async function fireSchedule(id) {
32658
33008
  const prompt = formatScheduledPrompt(schedule.name, schedule.prompt);
32659
33009
  const target = schedule.threadId ? deps.findThread(schedule.threadId) : null;
32660
33010
  if (target && target.status !== "archived") {
32661
- const sent = await deps.send(target.id, prompt);
33011
+ const sent = await deps.send(target.id, prompt, {
33012
+ attachments: schedule.attachments,
33013
+ options: scheduleSendOptions(schedule)
33014
+ });
32662
33015
  return recordScheduleRun(schedule.id, {
32663
33016
  lastThreadId: sent.id,
32664
33017
  lastError: null
32665
33018
  });
32666
33019
  }
33020
+ const pinReplacement = Boolean(schedule.threadId) && schedule.when.kind !== "once";
32667
33021
  const created = await deps.startOrchestration({
32668
33022
  goal: prompt,
32669
33023
  agent: pickAgent(schedule),
32670
- model: schedule.model
33024
+ model: schedule.model,
33025
+ ...schedule.effort ? { effort: schedule.effort } : {},
33026
+ ...schedule.fast != null ? { fast: schedule.fast } : {},
33027
+ ...schedule.planMode != null ? { planMode: schedule.planMode } : {},
33028
+ ...schedule.autonomy ? { autonomy: schedule.autonomy } : {},
33029
+ attachments: schedule.attachments,
33030
+ ...pinReplacement ? {} : { ephemeralAutomationId: schedule.id, ephemeralTitle: schedule.name }
32671
33031
  });
32672
- if (schedule.threadId && schedule.when.kind !== "once") {
33032
+ if (pinReplacement) {
32673
33033
  updateSchedule(schedule.id, { threadId: created.id });
32674
33034
  }
32675
33035
  return recordScheduleRun(schedule.id, {
@@ -32739,6 +33099,7 @@ var init_schedule_runner = __esm({
32739
33099
  init_desktop_host();
32740
33100
  init_app_settings();
32741
33101
  init_schedules();
33102
+ init_stage_files();
32742
33103
  init_thread_store();
32743
33104
  SET_TIMEOUT_MAX = 2147483647;
32744
33105
  fireHooks = null;
@@ -32907,6 +33268,7 @@ __export(index_exports, {
32907
33268
  ableTimeOAuthLocalBounceUrl: () => ableTimeOAuthLocalBounceUrl,
32908
33269
  ableTimeOAuthRedirectUri: () => ableTimeOAuthRedirectUri,
32909
33270
  ableTimeOAuthResource: () => ableTimeOAuthResource,
33271
+ ableTimeStateIsClosed: () => ableTimeStateIsClosed,
32910
33272
  ableTimeUsesRest: () => ableTimeUsesRest,
32911
33273
  abletimeMcpRequest: () => abletimeMcpRequest,
32912
33274
  abletimeMcpUrl: () => abletimeMcpUrl,
@@ -33389,6 +33751,7 @@ __export(index_exports, {
33389
33751
  issueMatchesAssignee: () => issueMatchesAssignee,
33390
33752
  issueSourceLabel: () => issueSourceLabel,
33391
33753
  issueTicketFromThread: () => issueTicketFromThread,
33754
+ joinSteerPrompt: () => joinSteerPrompt,
33392
33755
  killDetachedRunTree: () => killDetachedRunTree,
33393
33756
  killListenersOnPorts: () => killListenersOnPorts,
33394
33757
  killStaleWorktreeListeners: () => killStaleWorktreeListeners,
@@ -33447,6 +33810,7 @@ __export(index_exports, {
33447
33810
  listWorktrees: () => listWorktrees,
33448
33811
  listenerCwd: () => listenerCwd,
33449
33812
  liveActivitySummary: () => liveActivitySummary,
33813
+ livePartsShowAgentReply: () => livePartsShowAgentReply,
33450
33814
  loadAppSettings: () => loadAppSettings,
33451
33815
  loadBrightsyConfig: () => loadBrightsyConfig,
33452
33816
  loadConductorSettings: () => loadConductorSettings,
@@ -33784,6 +34148,7 @@ __export(index_exports, {
33784
34148
  toolDetail: () => toolDetail,
33785
34149
  toolFilePath: () => toolFilePath,
33786
34150
  totalTokens: () => totalTokens,
34151
+ trailingUnansweredUserText: () => trailingUnansweredUserText,
33787
34152
  trustSystemCertificates: () => trustSystemCertificates,
33788
34153
  tryReservePort: () => tryReservePort,
33789
34154
  tryReservePorts: () => tryReservePorts,
@@ -41754,6 +42119,7 @@ init_discover();
41754
42119
  init_detached_job_path();
41755
42120
  init_long_running();
41756
42121
  init_expand();
42122
+ init_agent_has_replied();
41757
42123
 
41758
42124
  // src/composer/diff-comment.ts
41759
42125
  function lineRangeLabel(lines) {
@@ -43460,7 +43826,7 @@ function parseWhen(args) {
43460
43826
  function registerScheduleTools(server) {
43461
43827
  server.tool(
43462
43828
  "list_schedules",
43463
- "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).",
43829
+ "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.",
43464
43830
  {},
43465
43831
  async () => {
43466
43832
  try {
@@ -43472,9 +43838,9 @@ function registerScheduleTools(server) {
43472
43838
  );
43473
43839
  server.tool(
43474
43840
  "create_schedule",
43475
- "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.",
43841
+ "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.",
43476
43842
  {
43477
- prompt: import_zod10.z.string().describe("User message / goal queued when the schedule fires"),
43843
+ prompt: import_zod10.z.string().describe("User message / goal queued when the automation fires"),
43478
43844
  name: import_zod10.z.string().optional(),
43479
43845
  at: import_zod10.z.string().optional().describe("ISO datetime for a one-shot"),
43480
43846
  every: import_zod10.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
@@ -43510,7 +43876,7 @@ function registerScheduleTools(server) {
43510
43876
  });
43511
43877
  return text6({
43512
43878
  schedule,
43513
- 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."
43879
+ 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)."
43514
43880
  });
43515
43881
  } catch (err) {
43516
43882
  return fail4(err);
@@ -43519,7 +43885,7 @@ function registerScheduleTools(server) {
43519
43885
  );
43520
43886
  server.tool(
43521
43887
  "update_schedule",
43522
- "Update a local schedule (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
43888
+ "Update a local automation (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
43523
43889
  {
43524
43890
  id: import_zod10.z.string(),
43525
43891
  prompt: import_zod10.z.string().optional(),
@@ -43574,7 +43940,7 @@ function registerScheduleTools(server) {
43574
43940
  );
43575
43941
  server.tool(
43576
43942
  "delete_schedule",
43577
- "Delete a local schedule. Pass id from list_schedules.",
43943
+ "Delete a local automation. Pass id from list_schedules.",
43578
43944
  { id: import_zod10.z.string() },
43579
43945
  async ({ id }) => {
43580
43946
  try {
@@ -43587,12 +43953,12 @@ function registerScheduleTools(server) {
43587
43953
  );
43588
43954
  server.tool(
43589
43955
  "run_schedule",
43590
- "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.",
43956
+ "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.",
43591
43957
  { id: import_zod10.z.string() },
43592
43958
  async ({ id }) => {
43593
43959
  try {
43594
43960
  if (!getSchedule(id)) {
43595
- return fail4(new Error(`Schedule not found: ${id}`));
43961
+ return fail4(new Error(`Automation not found: ${id}`));
43596
43962
  }
43597
43963
  const schedule = await fireSchedule(id);
43598
43964
  return text6({ schedule });
@@ -45568,6 +45934,7 @@ init_profile();
45568
45934
  init_home_board();
45569
45935
 
45570
45936
  // src/integrations/abletime-blocked.ts
45937
+ init_abletime();
45571
45938
  init_abletime_mcp();
45572
45939
  init_abletime_rest();
45573
45940
  function asRecord18(value) {
@@ -45592,6 +45959,9 @@ function firstRecord2(record, keys) {
45592
45959
  }
45593
45960
  return null;
45594
45961
  }
45962
+ function taskState(record) {
45963
+ return firstString5(record, ["state", "board_state", "boardState", "status", "taskState"]);
45964
+ }
45595
45965
  function identifierFromUnknown(value) {
45596
45966
  if (typeof value === "string" || typeof value === "number") {
45597
45967
  const id2 = String(value).trim();
@@ -45603,6 +45973,7 @@ function identifierFromUnknown(value) {
45603
45973
  for (const key of ["nodes", "data", "tasks", "items"]) {
45604
45974
  if (Array.isArray(rec[key])) return identifierFromUnknown(rec[key]);
45605
45975
  }
45976
+ if (ableTimeStateIsClosed(taskState(rec))) return [];
45606
45977
  const id = firstString5(rec, ["identifier", "reference", "ref", "key", "code", "number", "id"]);
45607
45978
  return id ? [id] : [];
45608
45979
  }
@@ -45637,6 +46008,8 @@ async function getAbleTimeIssueBlockedBy(id, opts) {
45637
46008
  continue;
45638
46009
  }
45639
46010
  const parent = await callAbleTimeTool("get_task", { id: blockerId, task_id: blockerId }, opts);
46011
+ const parentRecord = firstRecord2(asRecord18(parent), ["task", "data"]) ?? asRecord18(parent);
46012
+ if (ableTimeStateIsClosed(taskState(parentRecord))) continue;
45640
46013
  const ref = taskReference(parent);
45641
46014
  if (!ref || looksLikeAbleTimeUlid(ref)) {
45642
46015
  throw new Error(`AbleTime blocker has no reference: ${blockerId}`);
@@ -45661,6 +46034,8 @@ function githubBlockedByIdentifiers(payload) {
45661
46034
  const rec = node && typeof node === "object" && !Array.isArray(node) ? node : null;
45662
46035
  const number = Number(rec?.number ?? node);
45663
46036
  if (!Number.isFinite(number) || number <= 0) continue;
46037
+ const state = String(rec?.state ?? "").trim().toUpperCase();
46038
+ if (state === "CLOSED") continue;
45664
46039
  const id = `#${number}`;
45665
46040
  const key = id.toLowerCase();
45666
46041
  if (seen.has(key)) continue;
@@ -45673,7 +46048,7 @@ var GITHUB_BLOCKED_BY_QUERY = `
45673
46048
  query($owner: String!, $name: String!, $number: Int!) {
45674
46049
  repository(owner: $owner, name: $name) {
45675
46050
  issue(number: $number) {
45676
- blockedBy(first: 25) { nodes { number } }
46051
+ blockedBy(first: 25) { nodes { number state } }
45677
46052
  }
45678
46053
  }
45679
46054
  }
@@ -45721,15 +46096,21 @@ async function getGitHubIssueBlockedBy(id, opts) {
45721
46096
  }
45722
46097
 
45723
46098
  // src/integrations/linear-blocked.ts
46099
+ var RESOLVED_LINEAR_STATE = /* @__PURE__ */ new Set(["completed", "canceled", "cancelled", "duplicate"]);
46100
+ function linearBlockerStillBlocks(issue) {
46101
+ const type = (issue?.state?.type ?? "").trim().toLowerCase();
46102
+ if (!type) return true;
46103
+ return !RESOLVED_LINEAR_STATE.has(type);
46104
+ }
45724
46105
  var ISSUE_BLOCKED_BY_QUERY = `
45725
46106
  query SideboardIssueBlockedBy($id: String!) {
45726
46107
  issue(id: $id) {
45727
46108
  identifier
45728
46109
  relations(first: 25) {
45729
- nodes { id type relatedIssue { id identifier } }
46110
+ nodes { id type relatedIssue { id identifier state { type } } }
45730
46111
  }
45731
46112
  inverseRelations(first: 25) {
45732
- nodes { id type issue { id identifier } }
46113
+ nodes { id type issue { id identifier state { type } } }
45733
46114
  }
45734
46115
  }
45735
46116
  }
@@ -45740,6 +46121,7 @@ function linearBlockedByIdentifiers(relations) {
45740
46121
  for (const rel of relations) {
45741
46122
  const type = (rel.type ?? "").trim().toLowerCase().replace(/[\s_-]+/g, "");
45742
46123
  if (type !== "blockedby" && type !== "blocked") continue;
46124
+ if (!linearBlockerStillBlocks(rel.issue)) continue;
45743
46125
  const id = (rel.issue?.identifier || rel.issue?.id || "").trim();
45744
46126
  const key = id.toLowerCase();
45745
46127
  if (!id || seen.has(key)) continue;
@@ -47931,6 +48313,7 @@ async function pollSlackOutboundWatches(opts) {
47931
48313
  ableTimeOAuthLocalBounceUrl,
47932
48314
  ableTimeOAuthRedirectUri,
47933
48315
  ableTimeOAuthResource,
48316
+ ableTimeStateIsClosed,
47934
48317
  ableTimeUsesRest,
47935
48318
  abletimeMcpRequest,
47936
48319
  abletimeMcpUrl,
@@ -48413,6 +48796,7 @@ async function pollSlackOutboundWatches(opts) {
48413
48796
  issueMatchesAssignee,
48414
48797
  issueSourceLabel,
48415
48798
  issueTicketFromThread,
48799
+ joinSteerPrompt,
48416
48800
  killDetachedRunTree,
48417
48801
  killListenersOnPorts,
48418
48802
  killStaleWorktreeListeners,
@@ -48471,6 +48855,7 @@ async function pollSlackOutboundWatches(opts) {
48471
48855
  listWorktrees,
48472
48856
  listenerCwd,
48473
48857
  liveActivitySummary,
48858
+ livePartsShowAgentReply,
48474
48859
  loadAppSettings,
48475
48860
  loadBrightsyConfig,
48476
48861
  loadConductorSettings,
@@ -48808,6 +49193,7 @@ async function pollSlackOutboundWatches(opts) {
48808
49193
  toolDetail,
48809
49194
  toolFilePath,
48810
49195
  totalTokens,
49196
+ trailingUnansweredUserText,
48811
49197
  trustSystemCertificates,
48812
49198
  tryReservePort,
48813
49199
  tryReservePorts,