@sideboard-ai/core 0.1.157 → 0.1.159

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 (38) hide show
  1. package/dist/{abletime-BEIDJG7W.js → abletime-HOQBDQC4.js} +2 -2
  2. package/dist/{abletime-GWZE6OYM.js → abletime-LK7GV556.js} +2 -2
  3. package/dist/{agents-LCDXN2B2.js → agents-5HY76BCR.js} +4 -4
  4. package/dist/{agents-C55LAEUR.js → agents-67ZBH5OE.js} +5 -5
  5. package/dist/{app-settings-VRQI3FIE.js → app-settings-BJ5GFAE5.js} +13 -17
  6. package/dist/{app-settings-H3INSR67.js → app-settings-YWJ4MVYM.js} +13 -17
  7. package/dist/{chunk-NVXVEGGM.js → chunk-5U4NZLBH.js} +4 -0
  8. package/dist/{chunk-DVJBO3M7.js → chunk-AGLPYXDT.js} +1 -1
  9. package/dist/{chunk-KQ4JOGUH.js → chunk-DWUOKF3W.js} +11 -5
  10. package/dist/{chunk-D7EGJ3EI.js → chunk-EQIJ5MHR.js} +119 -110
  11. package/dist/{chunk-3BWVOUQG.js → chunk-FLG4A5FJ.js} +119 -110
  12. package/dist/{chunk-2SDMENAL.js → chunk-JV4VNFMS.js} +1 -1
  13. package/dist/{chunk-LQMFCS54.js → chunk-KEDHHXKW.js} +3 -13
  14. package/dist/{chunk-CJQLPPEJ.js → chunk-MEZXWELT.js} +4 -14
  15. package/dist/{chunk-CVR4RJ6G.js → chunk-O7RWWPMR.js} +12 -13
  16. package/dist/{chunk-GWEEGE6L.js → chunk-OYOXLY6B.js} +404 -115
  17. package/dist/{chunk-4I4VKAPZ.js → chunk-SIMFKJIB.js} +13 -14
  18. package/dist/{chunk-SUCJAWV4.js → chunk-TGU3LSDG.js} +402 -113
  19. package/dist/{chunk-N27GVFZY.js → chunk-UBMCEZRE.js} +1 -1
  20. package/dist/{chunk-TIJ6QVX5.js → chunk-V4R6L2ZL.js} +15 -6
  21. package/dist/{coordinator-prompt-AM47SATF.js → coordinator-prompt-7NKY7QRK.js} +4 -4
  22. package/dist/{coordinator-prompt-26IJU2AV.js → coordinator-prompt-CNI6AK65.js} +3 -3
  23. package/dist/{global-workspace-QV7CC7SK.js → global-workspace-FKAJZHO5.js} +4 -4
  24. package/dist/{global-workspace-5BIW26YR.js → global-workspace-IJYCUO4M.js} +3 -3
  25. package/dist/index.cjs +1037 -648
  26. package/dist/index.d.cts +71 -57
  27. package/dist/index.d.ts +71 -57
  28. package/dist/index.js +339 -250
  29. package/dist/mcp/run-stdio.cjs +967 -575
  30. package/dist/mcp/run-stdio.js +283 -187
  31. package/dist/{orchestrator-BUH3LJLL.js → orchestrator-KFLS5BTP.js} +5 -5
  32. package/dist/{orchestrator-KQO4MXBI.js → orchestrator-NSVP7DJH.js} +7 -7
  33. package/dist/{plan-file-OZEBFSGQ.js → plan-file-I24VJVP2.js} +2 -2
  34. package/dist/{workspaces-NYSM2EPX.js → workspaces-D2M3AM2M.js} +3 -3
  35. package/dist/{workspaces-Q2VCHIAS.js → workspaces-OFSDJ7PZ.js} +4 -4
  36. package/dist/{worktree-PEWDAX5O.js → worktree-BQE5KLRY.js} +3 -3
  37. package/dist/{worktree-T4SWFSTT.js → worktree-EA6AD6D6.js} +2 -2
  38. package/package.json +1 -1
@@ -24,7 +24,7 @@ import {
24
24
  stripBrightsyNdjsonNoise,
25
25
  sumUsageList,
26
26
  toolDescription
27
- } from "./chunk-TIJ6QVX5.js";
27
+ } from "./chunk-V4R6L2ZL.js";
28
28
  import {
29
29
  SLACK_REPLY_FORMATTING,
30
30
  addWorkspace,
@@ -50,7 +50,7 @@ import {
50
50
  stageAbsolutePathsAsAttachments,
51
51
  stageBuffersAsAttachments,
52
52
  syncWorkspacesFromThreads
53
- } from "./chunk-CVR4RJ6G.js";
53
+ } from "./chunk-O7RWWPMR.js";
54
54
  import {
55
55
  addPrStackLayer,
56
56
  allocateTeamName,
@@ -98,10 +98,12 @@ import {
98
98
  withRepoGitLock,
99
99
  worktreeDisplayLabelForGroup,
100
100
  worktreeNameFromPath
101
- } from "./chunk-LQMFCS54.js";
101
+ } from "./chunk-KEDHHXKW.js";
102
102
  import {
103
103
  ATTACHMENTS_DIR,
104
- LEGACY_ATTACHMENTS_DIR
104
+ DETACHED_JOBS_DIR,
105
+ LEGACY_ATTACHMENTS_DIR,
106
+ LEGACY_DETACHED_JOBS_DIR
105
107
  } from "./chunk-JRH62XC4.js";
106
108
  import {
107
109
  httpFetch
@@ -126,6 +128,8 @@ import {
126
128
  } from "./chunk-HEII7W3Q.js";
127
129
  import {
128
130
  childEnvWithAppSettings,
131
+ formatViewerContextDirective,
132
+ formatViewerContextReminder,
129
133
  getIssueSource,
130
134
  isAbleTimeConnected,
131
135
  isLinearConnected,
@@ -138,10 +142,11 @@ import {
138
142
  resolveNewThreadOptions,
139
143
  resolveThreadDefaults,
140
144
  resolveVaultKey,
145
+ resolveViewerProfileForRepo,
141
146
  saveAppSettings,
142
147
  updateAdvancedSettings,
143
148
  writeSecureJson
144
- } from "./chunk-3BWVOUQG.js";
149
+ } from "./chunk-FLG4A5FJ.js";
145
150
  import {
146
151
  stripNestedElectronEnv
147
152
  } from "./chunk-4TR3HZFT.js";
@@ -459,7 +464,7 @@ async function continueSourceThread(threadId, prompt) {
459
464
  await continueOnReply(threadId, prompt);
460
465
  return;
461
466
  }
462
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-BUH3LJLL.js");
467
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-KFLS5BTP.js");
463
468
  await getOrchestrator2().send(threadId, prompt);
464
469
  } catch {
465
470
  }
@@ -719,7 +724,7 @@ async function pollSlackOutboundWatches(opts) {
719
724
  }
720
725
 
721
726
  // src/orchestrator/orchestrator.ts
722
- import { existsSync as existsSync17 } from "fs";
727
+ import { existsSync as existsSync18 } from "fs";
723
728
 
724
729
  // src/agents/spawn.ts
725
730
  import { createInterface } from "readline";
@@ -739,9 +744,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
739
744
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
740
745
  );
741
746
  }
742
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-5BIW26YR.js");
747
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-IJYCUO4M.js");
743
748
  if (isGlobalThread2(thread)) {
744
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-26IJU2AV.js");
749
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-CNI6AK65.js");
745
750
  ensureGlobalCoordinatorCwd2(
746
751
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
747
752
  );
@@ -2564,8 +2569,232 @@ function notifyParentOfChildHalt(child, status, send) {
2564
2569
  return true;
2565
2570
  }
2566
2571
 
2572
+ // src/mcp/wait-for-job.ts
2573
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2574
+ import { dirname as dirname3, join as join10 } from "path";
2575
+
2576
+ // src/mcp/wait-for-turn.ts
2577
+ var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
2578
+ function mcpWaitForTurnTimeoutMs(requested) {
2579
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
2580
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
2581
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
2582
+ }
2583
+ var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
2584
+ var MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume it failed to start.";
2585
+ function mcpWaitStillRunningHint(status) {
2586
+ return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
2587
+ }
2588
+ var MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
2589
+ var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
2590
+ var MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
2591
+ function mcpWaitFinishedHint(status) {
2592
+ if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
2593
+ if (status === "broken") return MCP_WAIT_BROKEN_HINT;
2594
+ if (status === "error") return MCP_WAIT_ERROR_HINT;
2595
+ return void 0;
2596
+ }
2597
+
2598
+ // src/mcp/wait-for-job.ts
2599
+ var MAX_JOB_CONTINUES = 8;
2600
+ var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. present_artifact type=log with the same artifact_id and content=delta (new lines only). Then call wait_for_job again. Do not end the turn or tell the user you will let them know later.";
2601
+ var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
2602
+ function mcpWaitForJobTimeoutMs(requested) {
2603
+ return mcpWaitForTurnTimeoutMs(requested);
2604
+ }
2605
+ function sanitizeDetachedJobId(id) {
2606
+ const s = id.trim();
2607
+ if (!JOB_ID_RE.test(s)) {
2608
+ throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
2609
+ }
2610
+ return s;
2611
+ }
2612
+ function jobAlive(pid) {
2613
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2614
+ try {
2615
+ process.kill(pid, 0);
2616
+ return true;
2617
+ } catch {
2618
+ return false;
2619
+ }
2620
+ }
2621
+ function readIntFile(file) {
2622
+ if (!existsSync8(file)) return null;
2623
+ const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
2624
+ return Number.isInteger(n) ? n : null;
2625
+ }
2626
+ function jobDir(root, id, legacy = false) {
2627
+ return join10(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
2628
+ }
2629
+ function resolveJobDir(root, id) {
2630
+ const modern = jobDir(root, id, false);
2631
+ if (existsSync8(modern)) return modern;
2632
+ const legacy = jobDir(root, id, true);
2633
+ if (existsSync8(legacy)) return legacy;
2634
+ return modern;
2635
+ }
2636
+ function listJobIdsIn(root, rel) {
2637
+ const dir = join10(root, rel);
2638
+ if (!existsSync8(dir)) return [];
2639
+ return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
2640
+ }
2641
+ function listRunningDetachedJobs(worktreePath) {
2642
+ const root = worktreePath.trim();
2643
+ if (!root) return [];
2644
+ const ids = /* @__PURE__ */ new Set([
2645
+ ...listJobIdsIn(root, DETACHED_JOBS_DIR),
2646
+ ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
2647
+ ]);
2648
+ const running = [];
2649
+ for (const id of ids) {
2650
+ const dir = resolveJobDir(root, id);
2651
+ const pid = readIntFile(join10(dir, "pid"));
2652
+ if (pid != null && jobAlive(pid)) running.push(id);
2653
+ }
2654
+ return running.sort();
2655
+ }
2656
+ function looksLikeDeferredDonePromise(text) {
2657
+ const t = (text ?? "").trim();
2658
+ if (!t) return false;
2659
+ return /\b(i['’]?ll|i will)\s+let you know\b/i.test(t) || /\blet you know when\b/i.test(t) || /\b(check back|ping me)\s+when\b/i.test(t) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(t);
2660
+ }
2661
+ function formatJobStillRunningContinuePrompt(jobIds) {
2662
+ const ids = jobIds.join(", ");
2663
+ return [
2664
+ `Detached job still running: ${ids}.`,
2665
+ "Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
2666
+ "Then report the result. Do not tell the user you will let them know later."
2667
+ ].join(" ");
2668
+ }
2669
+ function formatDeferredDoneContinuePrompt() {
2670
+ return [
2671
+ "You ended the turn after promising to report later, but no detached job is running.",
2672
+ "If tests/pack/deploy still need to run: start once with detached-job.js, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
2673
+ "Do not say you will let the user know later."
2674
+ ].join(" ");
2675
+ }
2676
+ function turnWatchedDetachedJob(parts) {
2677
+ return parts.some((p) => {
2678
+ if (p.type !== "tool") return false;
2679
+ if (/wait_for_job$/i.test(p.name ?? "")) return true;
2680
+ const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
2681
+ return /detached-job\.js\b/i.test(blob);
2682
+ });
2683
+ }
2684
+ function planJobContinue(opts) {
2685
+ if (opts.isOrchestrator) return { action: "none" };
2686
+ if (opts.agent === "brightsy") return { action: "none" };
2687
+ if (opts.queueLength > 0) return { action: "none" };
2688
+ if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
2689
+ const farewell = looksLikeDeferredDonePromise(opts.chatText);
2690
+ if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
2691
+ return {
2692
+ action: "wait",
2693
+ jobIds: opts.runningJobIds,
2694
+ prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
2695
+ };
2696
+ }
2697
+ if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
2698
+ return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
2699
+ }
2700
+ return { action: "none" };
2701
+ }
2702
+ function tailProgress(logFile, maxLines = 12) {
2703
+ if (!existsSync8(logFile)) return "(no log yet)";
2704
+ const lines = readFileSync7(logFile, "utf8").split("\n");
2705
+ return lines.slice(-maxLines).join("\n");
2706
+ }
2707
+ function readLogLines(file) {
2708
+ if (!existsSync8(file)) return [];
2709
+ const lines = readFileSync7(file, "utf8").split("\n");
2710
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
2711
+ return lines;
2712
+ }
2713
+ function takeDelta(logFile, cursorFile) {
2714
+ const lines = readLogLines(logFile);
2715
+ const cursor = readIntFile(cursorFile) ?? 0;
2716
+ const start = Math.min(Math.max(0, cursor), lines.length);
2717
+ return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
2718
+ }
2719
+ function snapshotJob(dir) {
2720
+ const pid = readIntFile(join10(dir, "pid"));
2721
+ const running = pid != null && jobAlive(pid);
2722
+ return {
2723
+ pid,
2724
+ running,
2725
+ exitCode: readIntFile(join10(dir, "exit")),
2726
+ log: join10(dir, "log"),
2727
+ cursor: join10(dir, "present.cursor"),
2728
+ progress: tailProgress(join10(dir, "log"))
2729
+ };
2730
+ }
2731
+ function toResult(id, snap, extra) {
2732
+ const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
2733
+ const ok = !snap.running && snap.exitCode === 0;
2734
+ const stillRunning = snap.running && !ok;
2735
+ const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
2736
+ try {
2737
+ mkdirSync3(dirname3(snap.cursor), { recursive: true });
2738
+ writeFileSync4(snap.cursor, `${nextCursor}
2739
+ `);
2740
+ } catch {
2741
+ }
2742
+ const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
2743
+ return {
2744
+ stillRunning,
2745
+ ok,
2746
+ failed: Boolean(failed && !stillRunning && !ok),
2747
+ status,
2748
+ id,
2749
+ pid: snap.pid,
2750
+ exitCode: snap.exitCode ?? void 0,
2751
+ delta,
2752
+ progress: extra?.progress ?? snap.progress,
2753
+ hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
2754
+ };
2755
+ }
2756
+ async function sleepMs(ms) {
2757
+ await new Promise((resolve) => setTimeout(resolve, ms));
2758
+ }
2759
+ async function waitForDetachedJob(cwd, id, opts) {
2760
+ const jobId = sanitizeDetachedJobId(id);
2761
+ const root = cwd.trim() || process.cwd();
2762
+ const dir = resolveJobDir(root, jobId);
2763
+ const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
2764
+ const sleep = opts?.sleep ?? sleepMs;
2765
+ if (!existsSync8(dir)) {
2766
+ return {
2767
+ stillRunning: false,
2768
+ ok: false,
2769
+ failed: true,
2770
+ status: "failed",
2771
+ id: jobId,
2772
+ delta: "",
2773
+ progress: "No detached job. Start one first.",
2774
+ hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
2775
+ };
2776
+ }
2777
+ const deadline = Date.now() + timeoutMs;
2778
+ let snap = snapshotJob(dir);
2779
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2780
+ if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
2781
+ return toResult(jobId, snap, {
2782
+ failed: true,
2783
+ progress: "No detached job. Start one first."
2784
+ });
2785
+ }
2786
+ while (Date.now() < deadline) {
2787
+ snap = snapshotJob(dir);
2788
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2789
+ if (!snap.running) return toResult(jobId, snap, { failed: true });
2790
+ await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
2791
+ }
2792
+ snap = snapshotJob(dir);
2793
+ return toResult(jobId, snap);
2794
+ }
2795
+
2567
2796
  // src/threads/create.ts
2568
- import { existsSync as existsSync8 } from "fs";
2797
+ import { existsSync as existsSync9 } from "fs";
2569
2798
 
2570
2799
  // src/detect/detect.ts
2571
2800
  async function detectAgents() {
@@ -3127,7 +3356,7 @@ function reuseLiveThread(input, repoPath, match) {
3127
3356
  }
3128
3357
  async function createThread(input, _onSetupLine) {
3129
3358
  const repoPath = await resolveRepoRoot(input.repoPath);
3130
- if (!existsSync8(repoPath)) {
3359
+ if (!existsSync9(repoPath)) {
3131
3360
  throw new Error(`Repo not found: ${repoPath}`);
3132
3361
  }
3133
3362
  const reused = reuseLiveThread(input, repoPath, {
@@ -3144,7 +3373,7 @@ async function createThread(input, _onSetupLine) {
3144
3373
  });
3145
3374
  await requireAgent(resolved.agent);
3146
3375
  if (input.cowboy) {
3147
- const { cowboyModeEnabled } = await import("./app-settings-H3INSR67.js");
3376
+ const { cowboyModeEnabled } = await import("./app-settings-YWJ4MVYM.js");
3148
3377
  if (!cowboyModeEnabled()) {
3149
3378
  throw new Error(
3150
3379
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -3202,7 +3431,7 @@ async function createThread(input, _onSetupLine) {
3202
3431
  let sourceIsFork = false;
3203
3432
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
3204
3433
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
3205
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-GWZE6OYM.js");
3434
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-LK7GV556.js");
3206
3435
  const task = await ensureAbleTimeTask({
3207
3436
  title: title?.trim() || "Untitled work",
3208
3437
  description: input.prompt?.trim() || void 0
@@ -3299,7 +3528,7 @@ async function createThread(input, _onSetupLine) {
3299
3528
  return readThread(thread.id) ?? thread;
3300
3529
  }
3301
3530
  async function listLinearIssues(agent, repoPath) {
3302
- const { getAdapter: getAdapter2 } = await import("./agents-LCDXN2B2.js");
3531
+ const { getAdapter: getAdapter2 } = await import("./agents-5HY76BCR.js");
3303
3532
  await requireAgent(agent, { requireLinear: true });
3304
3533
  const adapter = getAdapter2(agent);
3305
3534
  if (!adapter.listLinearIssues) {
@@ -3322,11 +3551,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
3322
3551
 
3323
3552
  // src/store/turn-live.ts
3324
3553
  import {
3325
- existsSync as existsSync9,
3554
+ existsSync as existsSync10,
3326
3555
  renameSync,
3327
3556
  unlinkSync as unlinkSync2,
3328
- writeFileSync as writeFileSync4,
3329
- readFileSync as readFileSync7
3557
+ writeFileSync as writeFileSync5,
3558
+ readFileSync as readFileSync8
3330
3559
  } from "fs";
3331
3560
  var buffers = /* @__PURE__ */ new Map();
3332
3561
  var FLUSH_MS = 800;
@@ -3403,7 +3632,7 @@ function writeTurnLive(threadId, progress) {
3403
3632
  const path = threadLivePath(threadId);
3404
3633
  const tmp = `${path}.${process.pid}.tmp`;
3405
3634
  try {
3406
- writeFileSync4(tmp, JSON.stringify(progress), "utf8");
3635
+ writeFileSync5(tmp, JSON.stringify(progress), "utf8");
3407
3636
  renameSync(tmp, path);
3408
3637
  } catch {
3409
3638
  try {
@@ -3414,9 +3643,9 @@ function writeTurnLive(threadId, progress) {
3414
3643
  }
3415
3644
  function readTurnLive(threadId) {
3416
3645
  const path = threadLivePath(threadId);
3417
- if (!existsSync9(path)) return null;
3646
+ if (!existsSync10(path)) return null;
3418
3647
  try {
3419
- const raw = JSON.parse(readFileSync7(path, "utf8"));
3648
+ const raw = JSON.parse(readFileSync8(path, "utf8"));
3420
3649
  if (!raw || typeof raw.summary !== "string") return null;
3421
3650
  return raw;
3422
3651
  } catch {
@@ -3428,7 +3657,7 @@ function clearTurnLive(threadId) {
3428
3657
  if (buf?.timer) clearTimeout(buf.timer);
3429
3658
  buffers.delete(threadId);
3430
3659
  const path = threadLivePath(threadId);
3431
- if (!existsSync9(path)) return;
3660
+ if (!existsSync10(path)) return;
3432
3661
  try {
3433
3662
  unlinkSync2(path);
3434
3663
  } catch {
@@ -3616,26 +3845,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
3616
3845
  import { execFileSync } from "child_process";
3617
3846
  import {
3618
3847
  copyFileSync as copyFileSync2,
3619
- existsSync as existsSync10,
3848
+ existsSync as existsSync11,
3620
3849
  mkdtempSync,
3621
- readdirSync as readdirSync3,
3622
- readFileSync as readFileSync8,
3850
+ readdirSync as readdirSync4,
3851
+ readFileSync as readFileSync9,
3623
3852
  rmSync
3624
3853
  } from "fs";
3625
3854
  import { tmpdir } from "os";
3626
- import { join as join10 } from "path";
3855
+ import { join as join11 } from "path";
3627
3856
  import { createRequire } from "module";
3628
- var CONDUCTOR_APP_SUPPORT = join10(
3857
+ var CONDUCTOR_APP_SUPPORT = join11(
3629
3858
  process.env.HOME ?? "",
3630
3859
  "Library",
3631
3860
  "Application Support",
3632
3861
  "com.conductor.app"
3633
3862
  );
3634
- var CONDUCTOR_DB = join10(CONDUCTOR_APP_SUPPORT, "conductor.db");
3635
- var CURSOR_SDK_STORE = join10(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3863
+ var CONDUCTOR_DB = join11(CONDUCTOR_APP_SUPPORT, "conductor.db");
3864
+ var CURSOR_SDK_STORE = join11(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3636
3865
  function thisModuleFile() {
3637
3866
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
3638
- return cjsFile || process.argv[1] || join10(process.cwd(), "package.json");
3867
+ return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
3639
3868
  }
3640
3869
  function openReadonlySqlite(file) {
3641
3870
  const req = createRequire(thisModuleFile());
@@ -3653,21 +3882,21 @@ function mapAgentType(raw) {
3653
3882
  return null;
3654
3883
  }
3655
3884
  function resolveConductorCursorAgentId(workspacePath) {
3656
- if (!workspacePath || !existsSync10(CURSOR_SDK_STORE)) return null;
3885
+ if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
3657
3886
  const normalized = workspacePath.replace(/\/$/, "");
3658
3887
  let best = null;
3659
3888
  let hashes;
3660
3889
  try {
3661
- hashes = readdirSync3(CURSOR_SDK_STORE);
3890
+ hashes = readdirSync4(CURSOR_SDK_STORE);
3662
3891
  } catch {
3663
3892
  return null;
3664
3893
  }
3665
3894
  for (const hash of hashes) {
3666
- const agentsFile = join10(CURSOR_SDK_STORE, hash, "agents.ndjson");
3667
- if (!existsSync10(agentsFile)) continue;
3895
+ const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
3896
+ if (!existsSync11(agentsFile)) continue;
3668
3897
  let text;
3669
3898
  try {
3670
- text = readFileSync8(agentsFile, "utf8");
3899
+ text = readFileSync9(agentsFile, "utf8");
3671
3900
  } catch {
3672
3901
  continue;
3673
3902
  }
@@ -3691,7 +3920,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3691
3920
  return best?.agentId ?? null;
3692
3921
  }
3693
3922
  async function adoptThread(input) {
3694
- if (!existsSync10(input.worktreePath)) {
3923
+ if (!existsSync11(input.worktreePath)) {
3695
3924
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3696
3925
  }
3697
3926
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -3710,7 +3939,7 @@ async function adoptThread(input) {
3710
3939
  messages: input.messages ?? []
3711
3940
  });
3712
3941
  writeThread(thread);
3713
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NYSM2EPX.js");
3942
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-D2M3AM2M.js");
3714
3943
  await ensureWorkspace2(repoPath);
3715
3944
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-X54WSOXM.js");
3716
3945
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
@@ -3720,16 +3949,16 @@ function conductorDbPath() {
3720
3949
  return CONDUCTOR_DB;
3721
3950
  }
3722
3951
  function listConductorWorkspaces() {
3723
- if (!existsSync10(CONDUCTOR_DB)) {
3952
+ if (!existsSync11(CONDUCTOR_DB)) {
3724
3953
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3725
3954
  }
3726
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3727
- const snapshot = join10(tmp, "conductor.db");
3955
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3956
+ const snapshot = join11(tmp, "conductor.db");
3728
3957
  try {
3729
3958
  copyFileSync2(CONDUCTOR_DB, snapshot);
3730
3959
  for (const suffix of ["-wal", "-shm"]) {
3731
3960
  const src = `${CONDUCTOR_DB}${suffix}`;
3732
- if (existsSync10(src)) {
3961
+ if (existsSync11(src)) {
3733
3962
  try {
3734
3963
  copyFileSync2(src, `${snapshot}${suffix}`);
3735
3964
  } catch {
@@ -3811,16 +4040,16 @@ function listConductorWorkspaces() {
3811
4040
  }
3812
4041
  }
3813
4042
  function importConductorWorkspace(workspaceId) {
3814
- if (!existsSync10(CONDUCTOR_DB)) {
4043
+ if (!existsSync11(CONDUCTOR_DB)) {
3815
4044
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3816
4045
  }
3817
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3818
- const snapshot = join10(tmp, "conductor.db");
4046
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
4047
+ const snapshot = join11(tmp, "conductor.db");
3819
4048
  try {
3820
4049
  copyFileSync2(CONDUCTOR_DB, snapshot);
3821
4050
  for (const suffix of ["-wal", "-shm"]) {
3822
4051
  const src = `${CONDUCTOR_DB}${suffix}`;
3823
- if (existsSync10(src)) {
4052
+ if (existsSync11(src)) {
3824
4053
  try {
3825
4054
  copyFileSync2(src, `${snapshot}${suffix}`);
3826
4055
  } catch {
@@ -3840,7 +4069,7 @@ function importConductorWorkspace(workspaceId) {
3840
4069
  ).get(workspaceId);
3841
4070
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3842
4071
  const worktreePath = String(row.workspacePath);
3843
- if (!existsSync10(worktreePath)) {
4072
+ if (!existsSync11(worktreePath)) {
3844
4073
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3845
4074
  }
3846
4075
  let sessionId = null;
@@ -3911,7 +4140,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
3911
4140
  }
3912
4141
 
3913
4142
  // src/threads/stack-layers.ts
3914
- import { existsSync as existsSync11 } from "fs";
4143
+ import { existsSync as existsSync12 } from "fs";
3915
4144
  function stackIdFrom(stack) {
3916
4145
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3917
4146
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -3974,7 +4203,7 @@ async function openStackLayer(input, _onSetupLine) {
3974
4203
  let createdWorktree = false;
3975
4204
  const trees = await listWorktrees(repoPath);
3976
4205
  const checkedOut = trees.find((w) => w.branch === branchName);
3977
- if (checkedOut?.path && existsSync11(checkedOut.path)) {
4206
+ if (checkedOut?.path && existsSync12(checkedOut.path)) {
3978
4207
  if (input.reuseExistingWorktree !== false) {
3979
4208
  worktreePath = checkedOut.path;
3980
4209
  } else {
@@ -4116,7 +4345,7 @@ async function initStackFromThread(input, onSetupLine) {
4116
4345
  async function createPrStack(input, onSetupLine) {
4117
4346
  await requireAgent(input.agent);
4118
4347
  const repoPath = await resolveRepoRoot(input.repoPath);
4119
- if (!existsSync11(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4348
+ if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4120
4349
  if (!input.branches.length) throw new Error("At least one branch name required");
4121
4350
  const status = await detectGhStack(repoPath);
4122
4351
  if (!status.available) throw new Error(status.reason);
@@ -4183,7 +4412,7 @@ async function createPrStack(input, onSetupLine) {
4183
4412
  }
4184
4413
  }
4185
4414
  const claimed = new Set(threads.map((t) => t.worktreePath));
4186
- if (!claimed.has(bootstrap.worktreePath) && existsSync11(bootstrap.worktreePath)) {
4415
+ if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
4187
4416
  try {
4188
4417
  await removeWorktree(repoPath, bootstrap.worktreePath, {
4189
4418
  deleteBranch: bootstrap.branchName
@@ -4205,10 +4434,10 @@ function stackAgentDefaultsFrom(input) {
4205
4434
  }
4206
4435
 
4207
4436
  // src/diff/diff.ts
4208
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4209
- import { dirname as dirname3, join as join11 } from "path";
4437
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
4438
+ import { dirname as dirname4, join as join12 } from "path";
4210
4439
  async function inspectGitWorktree(worktreePath) {
4211
- if (!worktreePath || !existsSync12(worktreePath)) return "missing_worktree";
4440
+ if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
4212
4441
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
4213
4442
  reject: false
4214
4443
  });
@@ -4216,7 +4445,7 @@ async function inspectGitWorktree(worktreePath) {
4216
4445
  return "ok";
4217
4446
  }
4218
4447
  async function initializeGitRepository(worktreePath) {
4219
- if (!worktreePath || !existsSync12(worktreePath)) {
4448
+ if (!worktreePath || !existsSync13(worktreePath)) {
4220
4449
  throw new Error("Worktree not found");
4221
4450
  }
4222
4451
  const status = await inspectGitWorktree(worktreePath);
@@ -4352,11 +4581,11 @@ new file mode 100644
4352
4581
  };
4353
4582
  }
4354
4583
  async function untrackedPatch(worktreePath, path, maxHunk) {
4355
- const abs = join11(worktreePath, path);
4584
+ const abs = join12(worktreePath, path);
4356
4585
  try {
4357
4586
  const st = statSync2(abs);
4358
4587
  if (st.isFile() && st.size > maxHunk) {
4359
- const buf = readFileSync9(abs).subarray(0, maxHunk);
4588
+ const buf = readFileSync10(abs).subarray(0, maxHunk);
4360
4589
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
4361
4590
  }
4362
4591
  } catch {
@@ -4857,7 +5086,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
4857
5086
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4858
5087
  assertSafeRelativePath(relativePath);
4859
5088
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
4860
- const abs = join11(worktreePath, relativePath);
5089
+ const abs = join12(worktreePath, relativePath);
4861
5090
  const st = statSync2(abs);
4862
5091
  if (!st.isFile()) {
4863
5092
  throw new Error(`Not a file: ${relativePath}`);
@@ -4867,7 +5096,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4867
5096
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
4868
5097
  );
4869
5098
  }
4870
- const buf = readFileSync9(abs);
5099
+ const buf = readFileSync10(abs);
4871
5100
  return {
4872
5101
  path: relativePath,
4873
5102
  contentBase64: buf.toString("base64"),
@@ -4877,12 +5106,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4877
5106
  function readWorktreeFile(worktreePath, relativePath, opts) {
4878
5107
  assertSafeRelativePath(relativePath);
4879
5108
  const maxBytes = opts?.maxBytes ?? 2e5;
4880
- const abs = join11(worktreePath, relativePath);
5109
+ const abs = join12(worktreePath, relativePath);
4881
5110
  const st = statSync2(abs);
4882
5111
  if (!st.isFile()) {
4883
5112
  throw new Error(`Not a file: ${relativePath}`);
4884
5113
  }
4885
- const buf = readFileSync9(abs);
5114
+ const buf = readFileSync10(abs);
4886
5115
  if (isImageRelativePath(relativePath)) {
4887
5116
  const maxImageBytes = Math.max(maxBytes, 15e6);
4888
5117
  const truncated2 = buf.length > maxImageBytes;
@@ -4925,9 +5154,9 @@ function assertSafeRelativePath(relativePath) {
4925
5154
  }
4926
5155
  function writeWorktreeFile(worktreePath, relativePath, content) {
4927
5156
  assertSafeRelativePath(relativePath);
4928
- const abs = join11(worktreePath, relativePath);
4929
- mkdirSync3(dirname3(abs), { recursive: true });
4930
- writeFileSync5(abs, content, "utf8");
5157
+ const abs = join12(worktreePath, relativePath);
5158
+ mkdirSync4(dirname4(abs), { recursive: true });
5159
+ writeFileSync6(abs, content, "utf8");
4931
5160
  return { path: relativePath };
4932
5161
  }
4933
5162
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -5052,9 +5281,9 @@ async function confirmLand(thread, opts) {
5052
5281
  }
5053
5282
 
5054
5283
  // src/skills/discover.ts
5055
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5284
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
5056
5285
  import { homedir } from "os";
5057
- import { join as join12 } from "path";
5286
+ import { join as join13 } from "path";
5058
5287
 
5059
5288
  // src/skills/bundled/long-running.ts
5060
5289
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -5065,7 +5294,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
5065
5294
 
5066
5295
  A Sideboard or Cursor **worktree turn** SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. \`block_until_ms: 0\` is not enough \u2014 the child stays in that group.
5067
5296
 
5068
- Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as MCP \`wait_for_turn\`) until \`stillRunning\` is false.
5297
+ Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP \`wait_for_job\`, same idea as \`wait_for_turn\`) until \`stillRunning\` is false. Do not end the turn with \u201CI\u2019ll let you know.\u201D
5069
5298
 
5070
5299
  ## Tool
5071
5300
 
@@ -5075,7 +5304,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
5075
5304
  # Start (exits in ~1s; job survives this turn)
5076
5305
  node <detached-job.js> start <id> -- <command> [args...]
5077
5306
 
5078
- # Wait \u2014 returns within ~45s even if the job is still going
5307
+ # Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
5308
+ # Shell fallback:
5079
5309
  node <detached-job.js> wait <id>
5080
5310
 
5081
5311
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -5088,7 +5318,7 @@ node <detached-job.js> status <id>
5088
5318
 
5089
5319
  Wait JSON:
5090
5320
 
5091
- - \`stillRunning: true\` \u2192 exit 2 \u2192 **call wait again**. Progress is in \`progress\` / \`phase\`. Do not start a second job. Do not ping the user.
5321
+ - \`stillRunning: true\` \u2192 **call wait_for_job again** (or shell wait). Progress is in \`progress\` / \`phase\`. Do not start a second job. Do not ping the user.
5092
5322
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
5093
5323
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
5094
5324
 
@@ -5118,7 +5348,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
5118
5348
 
5119
5349
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
5120
5350
  2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
5121
- 3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
5351
+ 3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
5122
5352
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
5123
5353
 
5124
5354
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -5183,7 +5413,7 @@ function parseFrontmatter(content) {
5183
5413
  }
5184
5414
  function readSkill(skillMd, source) {
5185
5415
  try {
5186
- const content = readFileSync10(skillMd, "utf8");
5416
+ const content = readFileSync11(skillMd, "utf8");
5187
5417
  const { name: fmName, description } = parseFrontmatter(content);
5188
5418
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
5189
5419
  const name = fmName || dirName;
@@ -5202,17 +5432,17 @@ function readSkill(skillMd, source) {
5202
5432
  }
5203
5433
  }
5204
5434
  function scanSkillsDir(dir, source, out) {
5205
- if (!existsSync13(dir)) return;
5435
+ if (!existsSync14(dir)) return;
5206
5436
  let entries;
5207
5437
  try {
5208
- entries = readdirSync4(dir);
5438
+ entries = readdirSync5(dir);
5209
5439
  } catch {
5210
5440
  return;
5211
5441
  }
5212
5442
  for (const entry of entries) {
5213
5443
  if (entry.startsWith(".")) continue;
5214
- const skillMd = join12(dir, entry, "SKILL.md");
5215
- if (!existsSync13(skillMd)) continue;
5444
+ const skillMd = join13(dir, entry, "SKILL.md");
5445
+ if (!existsSync14(skillMd)) continue;
5216
5446
  try {
5217
5447
  if (!statSync3(skillMd).isFile()) continue;
5218
5448
  } catch {
@@ -5223,22 +5453,22 @@ function scanSkillsDir(dir, source, out) {
5223
5453
  }
5224
5454
  }
5225
5455
  function scanClaudePluginSkills(pluginsRoot, out) {
5226
- if (!existsSync13(pluginsRoot)) return;
5456
+ if (!existsSync14(pluginsRoot)) return;
5227
5457
  const walk = (dir, depth, lookingForSkillsDir) => {
5228
5458
  if (depth > 7) return;
5229
5459
  let entries;
5230
5460
  try {
5231
- entries = readdirSync4(dir);
5461
+ entries = readdirSync5(dir);
5232
5462
  } catch {
5233
5463
  return;
5234
5464
  }
5235
5465
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
5236
- const skill = readSkill(join12(dir, "SKILL.md"), "cli");
5466
+ const skill = readSkill(join13(dir, "SKILL.md"), "cli");
5237
5467
  if (skill) out.push(skill);
5238
5468
  }
5239
5469
  for (const entry of entries) {
5240
5470
  if (entry === "node_modules" || entry === ".git") continue;
5241
- const full = join12(dir, entry);
5471
+ const full = join13(dir, entry);
5242
5472
  try {
5243
5473
  if (!statSync3(full).isDirectory()) continue;
5244
5474
  } catch {
@@ -5258,17 +5488,17 @@ function discoverSkills(worktreePath) {
5258
5488
  const home = homedir();
5259
5489
  const collected = [];
5260
5490
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
5261
- scanSkillsDir(join12(worktreePath, rel), "workspace", collected);
5491
+ scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
5262
5492
  }
5263
5493
  for (const abs of [
5264
- join12(home, ".claude/skills"),
5265
- join12(home, ".cursor/skills"),
5266
- join12(home, ".sideboard/skills"),
5267
- join12(home, ".brightsy/skills")
5494
+ join13(home, ".claude/skills"),
5495
+ join13(home, ".cursor/skills"),
5496
+ join13(home, ".sideboard/skills"),
5497
+ join13(home, ".brightsy/skills")
5268
5498
  ]) {
5269
5499
  scanSkillsDir(abs, "user", collected);
5270
5500
  }
5271
- scanClaudePluginSkills(join12(home, ".claude/plugins"), collected);
5501
+ scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
5272
5502
  collected.push(...bundledSkills());
5273
5503
  const rank = {
5274
5504
  workspace: 0,
@@ -5294,7 +5524,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
5294
5524
  \u2026(truncated)` : body;
5295
5525
  }
5296
5526
  }
5297
- const raw = readFileSync10(skillPath, "utf8");
5527
+ const raw = readFileSync11(skillPath, "utf8");
5298
5528
  if (raw.startsWith("---")) {
5299
5529
  const end = raw.indexOf("\n---", 3);
5300
5530
  if (end >= 0) {
@@ -5387,27 +5617,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
5387
5617
  }
5388
5618
 
5389
5619
  // src/agents/instructions.ts
5390
- import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5391
- import { join as join14 } from "path";
5620
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
5621
+ import { join as join15 } from "path";
5392
5622
 
5393
5623
  // src/skills/detached-job-path.ts
5394
- import { existsSync as existsSync14 } from "fs";
5395
- import { dirname as dirname4, join as join13 } from "path";
5624
+ import { existsSync as existsSync15 } from "fs";
5625
+ import { dirname as dirname5, join as join14 } from "path";
5396
5626
  import { fileURLToPath } from "url";
5397
5627
  function packagedDetachedJobPath() {
5398
5628
  const dir = packagedMcpDir();
5399
5629
  if (!dir) return null;
5400
- const script = join13(dir, "scripts", "detached-job.js");
5401
- return existsSync14(script) ? script : null;
5630
+ const script = join14(dir, "scripts", "detached-job.js");
5631
+ return existsSync15(script) ? script : null;
5402
5632
  }
5403
5633
  function resolveDetachedJobScript() {
5404
5634
  const packaged = packagedDetachedJobPath();
5405
5635
  if (packaged) return packaged;
5406
- let dir = dirname4(fileURLToPath(import.meta.url));
5636
+ let dir = dirname5(fileURLToPath(import.meta.url));
5407
5637
  for (let i = 0; i < 8; i++) {
5408
- const candidate = join13(dir, "scripts", "detached-job.js");
5409
- if (existsSync14(candidate)) return candidate;
5410
- const parent = dirname4(dir);
5638
+ const candidate = join14(dir, "scripts", "detached-job.js");
5639
+ if (existsSync15(candidate)) return candidate;
5640
+ const parent = dirname5(dir);
5411
5641
  if (parent === dir) break;
5412
5642
  dir = parent;
5413
5643
  }
@@ -5635,14 +5865,15 @@ function formatLongRunningDirective(opts) {
5635
5865
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
5636
5866
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5637
5867
  "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
5638
- `- Loop \`${invoke} wait <id>\` (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait again. Do not resend the full log or HTML.`,
5868
+ `- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
5869
+ "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
5639
5870
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
5640
5871
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5641
5872
  ].join("\n");
5642
5873
  }
5643
5874
  function formatLongRunningReminder(opts) {
5644
5875
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5645
- return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
5876
+ return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait), present_artifact type=log (delta). Do not say you will let the user know later \u2014 stay in the turn.`;
5646
5877
  }
5647
5878
  function formatArtifactDirective() {
5648
5879
  return [
@@ -5693,11 +5924,11 @@ function loadAgentInstructions(worktreePath, agent) {
5693
5924
  const out = [];
5694
5925
  for (const rel of candidates) {
5695
5926
  if (seenPaths.has(rel)) continue;
5696
- const abs = join14(worktreePath, rel);
5697
- if (!existsSync15(abs)) continue;
5927
+ const abs = join15(worktreePath, rel);
5928
+ if (!existsSync16(abs)) continue;
5698
5929
  try {
5699
5930
  if (!statSync4(abs).isFile()) continue;
5700
- let content = readFileSync11(abs, "utf8");
5931
+ let content = readFileSync12(abs, "utf8");
5701
5932
  if (!content.trim()) continue;
5702
5933
  if (content.length > MAX_CHARS_PER_FILE) {
5703
5934
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -6017,8 +6248,8 @@ async function syncThreadBranchFromGit(threadId) {
6017
6248
 
6018
6249
  // src/store/schedules.ts
6019
6250
  import { randomUUID as randomUUID4 } from "crypto";
6020
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6021
- import { join as join15 } from "path";
6251
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
6252
+ import { join as join16 } from "path";
6022
6253
  import { Cron } from "croner";
6023
6254
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
6024
6255
  var UNIT_MS = {
@@ -6028,7 +6259,7 @@ var UNIT_MS = {
6028
6259
  d: 864e5
6029
6260
  };
6030
6261
  function schedulesPath() {
6031
- return join15(appDataDir(), "schedules.json");
6262
+ return join16(appDataDir(), "schedules.json");
6032
6263
  }
6033
6264
  function parseDurationMs(every) {
6034
6265
  const m = every.trim().match(DURATION_RE);
@@ -6134,9 +6365,9 @@ function normalizeTask(raw) {
6134
6365
  }
6135
6366
  function readAll() {
6136
6367
  const path = schedulesPath();
6137
- if (!existsSync16(path)) return [];
6368
+ if (!existsSync17(path)) return [];
6138
6369
  try {
6139
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
6370
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
6140
6371
  if (!Array.isArray(parsed)) return [];
6141
6372
  return parsed.map((row) => {
6142
6373
  try {
@@ -6262,7 +6493,7 @@ function formatScheduledPrompt(name, prompt) {
6262
6493
  ${prompt}`;
6263
6494
  }
6264
6495
  async function defaultDeps() {
6265
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-BUH3LJLL.js");
6496
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-KFLS5BTP.js");
6266
6497
  const orch = getOrchestrator2();
6267
6498
  return {
6268
6499
  findThread: (id) => findThreadByRef(id),
@@ -6419,6 +6650,9 @@ var Orchestrator = class {
6419
6650
  * finish or a user send so a later crash can recover again.
6420
6651
  */
6421
6652
  crashContinued = /* @__PURE__ */ new Set();
6653
+ /** Auto-continues after a worktree turn ended while a detached job still runs. */
6654
+ jobContinueCount = /* @__PURE__ */ new Map();
6655
+ jobContinueNudged = /* @__PURE__ */ new Set();
6422
6656
  maxConcurrent;
6423
6657
  runningCount = 0;
6424
6658
  constructor(opts) {
@@ -6502,7 +6736,7 @@ var Orchestrator = class {
6502
6736
  }
6503
6737
  continue;
6504
6738
  }
6505
- if (!existsSync17(thread.worktreePath)) {
6739
+ if (!existsSync18(thread.worktreePath)) {
6506
6740
  setStatus(thread.id, "broken", "Worktree missing on disk");
6507
6741
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
6508
6742
  continue;
@@ -6521,7 +6755,7 @@ var Orchestrator = class {
6521
6755
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
6522
6756
  });
6523
6757
  }
6524
- const { autoCleanupOrphansEnabled } = await import("./app-settings-H3INSR67.js");
6758
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-YWJ4MVYM.js");
6525
6759
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
6526
6760
  await cleanupOrphanWorktrees({ repoPaths });
6527
6761
  }
@@ -6717,6 +6951,37 @@ var Orchestrator = class {
6717
6951
  this.emit({ type: "queue_changed", threadId, queue });
6718
6952
  this.haltDrain.delete(threadId);
6719
6953
  }
6954
+ /**
6955
+ * Worktree agent ended the turn after “I’ll let you know” (or left a
6956
+ * detached test/pack job running). Queue a continue so the chat does not
6957
+ * go idle with nobody watching the log.
6958
+ */
6959
+ maybeEnqueueJobContinue(threadId, chatText, parts = []) {
6960
+ if (this.haltDrain.has(threadId)) return;
6961
+ const thread = readThread(threadId);
6962
+ if (!thread || thread.status === "archived") return;
6963
+ const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
6964
+ const decision = planJobContinue({
6965
+ runningJobIds,
6966
+ chatText,
6967
+ queueLength: thread.queue.length,
6968
+ continueCount: this.jobContinueCount.get(threadId) ?? 0,
6969
+ alreadyNudged: this.jobContinueNudged.has(threadId),
6970
+ isOrchestrator: isOrchestratorThread(thread),
6971
+ agent: thread.agent,
6972
+ watchedJob: turnWatchedDetachedJob(parts)
6973
+ });
6974
+ if (decision.action === "none") {
6975
+ if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
6976
+ return;
6977
+ }
6978
+ if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
6979
+ else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
6980
+ const queue = [decision.prompt, ...thread.queue];
6981
+ updateThread(threadId, { queue });
6982
+ this.emit({ type: "queue_changed", threadId, queue });
6983
+ this.haltDrain.delete(threadId);
6984
+ }
6720
6985
  getThreads(includeArchived = false) {
6721
6986
  return listThreads({ includeArchived });
6722
6987
  }
@@ -6761,7 +7026,7 @@ var Orchestrator = class {
6761
7026
  }
6762
7027
  await setup;
6763
7028
  if (skipSetup) return;
6764
- const { autoRunAfterSetupEnabled } = await import("./app-settings-H3INSR67.js");
7029
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-YWJ4MVYM.js");
6765
7030
  if (autoRunAfterSetupEnabled()) {
6766
7031
  try {
6767
7032
  await this.startDev(threadId);
@@ -6816,6 +7081,8 @@ var Orchestrator = class {
6816
7081
  }
6817
7082
  const queue = [...current.queue, prompt];
6818
7083
  this.crashContinued.delete(thread.id);
7084
+ this.jobContinueCount.delete(thread.id);
7085
+ this.jobContinueNudged.delete(thread.id);
6819
7086
  this.haltDrain.delete(thread.id);
6820
7087
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
6821
7088
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -7038,6 +7305,7 @@ var Orchestrator = class {
7038
7305
  ticketId: issueTicket?.id,
7039
7306
  ticketProvider: issueTicket?.provider
7040
7307
  }) : null;
7308
+ const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
7041
7309
  const slackReplyContext = formatSlackRepliesForTurn(
7042
7310
  pendingSlackExternalReplies(thread.messages)
7043
7311
  );
@@ -7047,6 +7315,7 @@ var Orchestrator = class {
7047
7315
  worktreeReminder,
7048
7316
  optionalServicesReminder,
7049
7317
  issueToolsReminder,
7318
+ viewerContextReminder,
7050
7319
  artifactReminder,
7051
7320
  longRunningReminder,
7052
7321
  slackReplyContext,
@@ -7068,7 +7337,7 @@ var Orchestrator = class {
7068
7337
  }
7069
7338
  const isBrightsy = fresh.agent === "brightsy";
7070
7339
  const isOrchestration = isOrchestratorThread(fresh);
7071
- const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-H3INSR67.js");
7340
+ const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-YWJ4MVYM.js");
7072
7341
  const gitAuthMode = getGithubGitAuthMode();
7073
7342
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
7074
7343
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -7087,6 +7356,18 @@ var Orchestrator = class {
7087
7356
  ticketId: freshIssueTicket?.id,
7088
7357
  ticketProvider: freshIssueTicket?.provider
7089
7358
  });
7359
+ let viewerContextDirective = null;
7360
+ if (!isBrightsy && !isOrchestration) {
7361
+ try {
7362
+ viewerContextDirective = formatViewerContextDirective(
7363
+ resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
7364
+ );
7365
+ } catch {
7366
+ viewerContextDirective = formatViewerContextDirective(
7367
+ resolveViewerProfileForRepo()
7368
+ );
7369
+ }
7370
+ }
7090
7371
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
7091
7372
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
7092
7373
  customPrompt: settings?.prompts?.renameBranch
@@ -7118,6 +7399,7 @@ var Orchestrator = class {
7118
7399
  worktreeDirective,
7119
7400
  optionalServicesDirective,
7120
7401
  issueToolsDirective,
7402
+ viewerContextDirective,
7121
7403
  artifactDirective,
7122
7404
  longRunningDirective,
7123
7405
  renameBranchDirective,
@@ -7228,6 +7510,7 @@ var Orchestrator = class {
7228
7510
  worktreeDirective,
7229
7511
  optionalServicesDirective,
7230
7512
  issueToolsDirective,
7513
+ viewerContextDirective,
7231
7514
  artifactDirective,
7232
7515
  longRunningDirective,
7233
7516
  renameBranchDirective,
@@ -7330,6 +7613,7 @@ var Orchestrator = class {
7330
7613
  this.emit({ type: "turn_finished", threadId, exitCode });
7331
7614
  if (exitCode === 0) {
7332
7615
  this.crashContinued.delete(threadId);
7616
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
7333
7617
  } else {
7334
7618
  const blob = [chatText, detail].filter(Boolean).join("\n");
7335
7619
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -7965,7 +8249,7 @@ var Orchestrator = class {
7965
8249
  updateThread(thread.id, { title: meta.title });
7966
8250
  }
7967
8251
  }
7968
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-H3INSR67.js");
8252
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-YWJ4MVYM.js");
7969
8253
  const latest = this.requireThread(thread.id);
7970
8254
  if (!shouldAutoArchiveOnPrMerge({
7971
8255
  previousPrState: prevState || null,
@@ -8282,7 +8566,7 @@ var Orchestrator = class {
8282
8566
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
8283
8567
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
8284
8568
  try {
8285
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NYSM2EPX.js");
8569
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-D2M3AM2M.js");
8286
8570
  await ensureWorkspace2(thread.repoPath);
8287
8571
  } catch {
8288
8572
  }
@@ -8309,7 +8593,7 @@ var Orchestrator = class {
8309
8593
  await runArchiveScript(thread.repoPath, thread.worktreePath);
8310
8594
  } catch {
8311
8595
  }
8312
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-H3INSR67.js");
8596
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-YWJ4MVYM.js");
8313
8597
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
8314
8598
  await removeWorktree(thread.repoPath, thread.worktreePath, {
8315
8599
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -8330,13 +8614,13 @@ var Orchestrator = class {
8330
8614
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
8331
8615
  return restored2;
8332
8616
  }
8333
- if (!existsSync17(thread.worktreePath)) {
8617
+ if (!existsSync18(thread.worktreePath)) {
8334
8618
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
8335
8619
  throw new Error(
8336
8620
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
8337
8621
  );
8338
8622
  }
8339
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-T4SWFSTT.js");
8623
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-EA6AD6D6.js");
8340
8624
  const slug = thread.worktreePath.split("/").pop();
8341
8625
  const dest = thread.worktreePath;
8342
8626
  await withRepoGitLock(thread.repoPath, async () => {
@@ -8446,7 +8730,7 @@ async function startOrchestration(opts) {
8446
8730
  sourceRef: "default",
8447
8731
  ...createOpts
8448
8732
  }).catch(async () => {
8449
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-T4SWFSTT.js");
8733
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-EA6AD6D6.js");
8450
8734
  const repo = await resolveRepoRoot2(repoPath);
8451
8735
  const def = await resolveDefaultBranch2(repo);
8452
8736
  return orch.createThread({
@@ -8592,6 +8876,11 @@ export {
8592
8876
  worktreeCleanupSettings,
8593
8877
  applyThreadIntoMain,
8594
8878
  cloneRepoIntoSideboard,
8879
+ mcpWaitForTurnTimeoutMs,
8880
+ mcpWaitStillRunningHint,
8881
+ mcpWaitFinishedHint,
8882
+ mcpWaitForJobTimeoutMs,
8883
+ waitForDetachedJob,
8595
8884
  detectAgents,
8596
8885
  requireAgent,
8597
8886
  isHomeBoardThread,