@sideboard-ai/core 0.1.319 → 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-L3ODNTBT.js → abletime-CRRLEYTE.js} +4 -4
  2. package/dist/{abletime-DVYO4IVS.js → abletime-ICW6Q2AB.js} +4 -4
  3. package/dist/{abletime-oauth-2VWQCXGT.js → abletime-oauth-P3KPPJE4.js} +3 -3
  4. package/dist/{abletime-oauth-6DTX7LB7.js → abletime-oauth-Q76XBSPG.js} +3 -3
  5. package/dist/{abletime-rest-PYQM234B.js → abletime-rest-DNFAYYLP.js} +3 -3
  6. package/dist/{abletime-rest-7NICZYOC.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-5OM7UCTQ.js → chunk-56M6VEXI.js} +3 -3
  13. package/dist/{chunk-ZM43NTJJ.js → chunk-5OQONGHO.js} +5 -5
  14. package/dist/{chunk-A6C67DY5.js → chunk-5S3YP5KH.js} +4 -4
  15. package/dist/{chunk-QPLQXWND.js → chunk-72PUPPV2.js} +3 -3
  16. package/dist/{chunk-OO3R6CS2.js → chunk-B7UL36WH.js} +214 -37
  17. package/dist/{chunk-WLZHRNE6.js → chunk-BTIXTH4N.js} +3 -3
  18. package/dist/{chunk-AESO2KJS.js → chunk-CB5Z5C3J.js} +2 -2
  19. package/dist/{chunk-AUJ4XB6H.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-JD7CC6SW.js → chunk-HHPTGIKS.js} +2 -2
  26. package/dist/{chunk-U2N32EA6.js → chunk-IDPFLXRY.js} +215 -38
  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-LXBZQP2Q.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 +589 -402
  46. package/dist/index.d.cts +29 -4
  47. package/dist/index.d.ts +29 -4
  48. package/dist/index.js +28 -28
  49. package/dist/mcp/run-stdio.cjs +219 -32
  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-H3DSZ5RM.js → orchestrator-7BDZ5GWD.js} +10 -10
  54. package/dist/{orchestrator-NCA4LSCC.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");
@@ -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, {
@@ -26296,17 +26426,23 @@ async function isolateQuotaFailover(run2) {
26296
26426
  }
26297
26427
  }
26298
26428
  function createQuotaFailoverChat(from, fallbackAgent, limitText, opts) {
26429
+ const ephemeralAutomationId = from.ephemeralAutomationId?.trim();
26430
+ const ephemeralTitle = ephemeralAutomationId ? from.title.trim() : "";
26299
26431
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
26300
26432
  const tab = createChatTab({
26301
26433
  fromThreadId: from.id,
26302
26434
  agent: fallbackAgent,
26303
26435
  model: null,
26304
26436
  attachments: [handoff],
26437
+ ...ephemeralTitle ? { title: ephemeralTitle } : {},
26305
26438
  ...opts && "accountId" in opts ? { accountId: opts.accountId } : {}
26306
26439
  });
26307
26440
  const patch = {
26308
26441
  quotaContinuedFromId: from.id,
26309
- 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 } : {}
26310
26446
  };
26311
26447
  if (isOrchestratorThread(from)) {
26312
26448
  patch.parentThreadId = from.id;
@@ -28723,7 +28859,7 @@ function formatWorktreeDirective(thread, opts) {
28723
28859
  lines.push(formatDevAccessHint(opts?.activeRuns));
28724
28860
  lines.push("");
28725
28861
  lines.push(
28726
- "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."
28727
28863
  );
28728
28864
  lines.push(
28729
28865
  "`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
@@ -28743,7 +28879,7 @@ function connectedOrchestratorTurnReminder(thread, find) {
28743
28879
  return formatConnectedOrchestratorReminder(connectedOrchestrator(thread, find));
28744
28880
  }
28745
28881
  function formatWorktreeReminder() {
28746
- 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).";
28747
28883
  }
28748
28884
  function formatClaudeSubagentReminder() {
28749
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.';
@@ -29971,10 +30107,11 @@ async function startOrchestration(opts) {
29971
30107
  planMode: opts.planMode,
29972
30108
  attachments: opts.attachments
29973
30109
  });
30110
+ const ready2 = markEphemeralAutomation(thread2.id, opts);
29974
30111
  if (goal) {
29975
- return orch.send(thread2.id, goal);
30112
+ return orch.send(ready2.id, goal);
29976
30113
  }
29977
- return thread2;
30114
+ return ready2;
29978
30115
  }
29979
30116
  const { titleFromPrompt: titleFromPrompt2 } = await Promise.resolve().then(() => (init_title(), title_exports));
29980
30117
  const title = titleFromPrompt2(goal) || "Orchestration";
@@ -30009,10 +30146,11 @@ async function startOrchestration(opts) {
30009
30146
  sourceType: "orchestration",
30010
30147
  sourceRef: goal
30011
30148
  });
30149
+ const ready = markEphemeralAutomation(updated.id, opts);
30012
30150
  if (goal) {
30013
- return orch.send(updated.id, goal);
30151
+ return orch.send(ready.id, goal);
30014
30152
  }
30015
- return updated;
30153
+ return ready;
30016
30154
  }
30017
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;
30018
30156
  var init_orchestrator = __esm({
@@ -30053,6 +30191,7 @@ var init_orchestrator = __esm({
30053
30191
  init_child_halt();
30054
30192
  init_stop_reason();
30055
30193
  init_steer_request();
30194
+ init_ephemeral_automation();
30056
30195
  init_wait_for_job();
30057
30196
  init_agent_purpose_title();
30058
30197
  init_create();
@@ -30890,6 +31029,7 @@ var init_orchestrator = __esm({
30890
31029
  } finally {
30891
31030
  this.draining.delete(threadId);
30892
31031
  }
31032
+ await archiveEphemeralAutomationIfReady(threadId, (id) => this.archive(id));
30893
31033
  }
30894
31034
  async runTurn(threadId, prompt) {
30895
31035
  const existing = readThread(threadId);
@@ -32723,6 +32863,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
32723
32863
  if (thread.status !== "archived") {
32724
32864
  throw new Error("Thread is not archived");
32725
32865
  }
32866
+ clearEphemeralAutomation(thread.id);
32726
32867
  if (isGlobalThread(thread)) {
32727
32868
  const { globalAgentCwd: globalAgentCwd2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
32728
32869
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
@@ -32814,17 +32955,52 @@ async function defaultDeps() {
32814
32955
  const orch = getOrchestrator2();
32815
32956
  return {
32816
32957
  findThread: (id) => findThreadByRef(id),
32817
- 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
+ },
32818
32976
  startOrchestration: (opts) => startOrchestration2(opts)
32819
32977
  };
32820
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
+ }
32821
32997
  function pickAgent(schedule) {
32822
32998
  if (schedule.agent) return schedule.agent;
32823
32999
  return coerceOrchestratorAgent(resolveOrchestratorDefaults().agent);
32824
33000
  }
32825
33001
  async function fireSchedule(id) {
32826
33002
  const schedule = getSchedule(id);
32827
- if (!schedule) throw new Error(`Schedule not found: ${id}`);
33003
+ if (!schedule) throw new Error(`Automation not found: ${id}`);
32828
33004
  if (firing.has(schedule.id)) return schedule;
32829
33005
  firing.add(schedule.id);
32830
33006
  try {
@@ -32832,18 +33008,28 @@ async function fireSchedule(id) {
32832
33008
  const prompt = formatScheduledPrompt(schedule.name, schedule.prompt);
32833
33009
  const target = schedule.threadId ? deps.findThread(schedule.threadId) : null;
32834
33010
  if (target && target.status !== "archived") {
32835
- 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
+ });
32836
33015
  return recordScheduleRun(schedule.id, {
32837
33016
  lastThreadId: sent.id,
32838
33017
  lastError: null
32839
33018
  });
32840
33019
  }
33020
+ const pinReplacement = Boolean(schedule.threadId) && schedule.when.kind !== "once";
32841
33021
  const created = await deps.startOrchestration({
32842
33022
  goal: prompt,
32843
33023
  agent: pickAgent(schedule),
32844
- 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 }
32845
33031
  });
32846
- if (schedule.threadId && schedule.when.kind !== "once") {
33032
+ if (pinReplacement) {
32847
33033
  updateSchedule(schedule.id, { threadId: created.id });
32848
33034
  }
32849
33035
  return recordScheduleRun(schedule.id, {
@@ -32913,6 +33099,7 @@ var init_schedule_runner = __esm({
32913
33099
  init_desktop_host();
32914
33100
  init_app_settings();
32915
33101
  init_schedules();
33102
+ init_stage_files();
32916
33103
  init_thread_store();
32917
33104
  SET_TIMEOUT_MAX = 2147483647;
32918
33105
  fireHooks = null;
@@ -43639,7 +43826,7 @@ function parseWhen(args) {
43639
43826
  function registerScheduleTools(server) {
43640
43827
  server.tool(
43641
43828
  "list_schedules",
43642
- "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.",
43643
43830
  {},
43644
43831
  async () => {
43645
43832
  try {
@@ -43651,9 +43838,9 @@ function registerScheduleTools(server) {
43651
43838
  );
43652
43839
  server.tool(
43653
43840
  "create_schedule",
43654
- "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.",
43655
43842
  {
43656
- 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"),
43657
43844
  name: import_zod10.z.string().optional(),
43658
43845
  at: import_zod10.z.string().optional().describe("ISO datetime for a one-shot"),
43659
43846
  every: import_zod10.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
@@ -43689,7 +43876,7 @@ function registerScheduleTools(server) {
43689
43876
  });
43690
43877
  return text6({
43691
43878
  schedule,
43692
- 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)."
43693
43880
  });
43694
43881
  } catch (err) {
43695
43882
  return fail4(err);
@@ -43698,7 +43885,7 @@ function registerScheduleTools(server) {
43698
43885
  );
43699
43886
  server.tool(
43700
43887
  "update_schedule",
43701
- "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.",
43702
43889
  {
43703
43890
  id: import_zod10.z.string(),
43704
43891
  prompt: import_zod10.z.string().optional(),
@@ -43753,7 +43940,7 @@ function registerScheduleTools(server) {
43753
43940
  );
43754
43941
  server.tool(
43755
43942
  "delete_schedule",
43756
- "Delete a local schedule. Pass id from list_schedules.",
43943
+ "Delete a local automation. Pass id from list_schedules.",
43757
43944
  { id: import_zod10.z.string() },
43758
43945
  async ({ id }) => {
43759
43946
  try {
@@ -43766,12 +43953,12 @@ function registerScheduleTools(server) {
43766
43953
  );
43767
43954
  server.tool(
43768
43955
  "run_schedule",
43769
- "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.",
43770
43957
  { id: import_zod10.z.string() },
43771
43958
  async ({ id }) => {
43772
43959
  try {
43773
43960
  if (!getSchedule(id)) {
43774
- return fail4(new Error(`Schedule not found: ${id}`));
43961
+ return fail4(new Error(`Automation not found: ${id}`));
43775
43962
  }
43776
43963
  const schedule = await fireSchedule(id);
43777
43964
  return text6({ schedule });