@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
@@ -33,12 +33,12 @@ import {
33
33
  summarizeTurnStderr,
34
34
  toolDescription,
35
35
  turnFailChatText
36
- } from "./chunk-KQ4JOGUH.js";
36
+ } from "./chunk-DWUOKF3W.js";
37
37
  import {
38
38
  extractPresentedPlan,
39
39
  readPlanFile,
40
40
  writePlanFile
41
- } from "./chunk-2SDMENAL.js";
41
+ } from "./chunk-JV4VNFMS.js";
42
42
  import {
43
43
  releaseCaffeinateHoldForThread
44
44
  } from "./chunk-OCQPTUR7.js";
@@ -67,7 +67,7 @@ import {
67
67
  stageAbsolutePathsAsAttachments,
68
68
  stageBuffersAsAttachments,
69
69
  syncWorkspacesFromThreads
70
- } from "./chunk-4I4VKAPZ.js";
70
+ } from "./chunk-SIMFKJIB.js";
71
71
  import {
72
72
  addPrStackLayer,
73
73
  allocateTeamName,
@@ -115,11 +115,13 @@ import {
115
115
  withRepoGitLock,
116
116
  worktreeDisplayLabelForGroup,
117
117
  worktreeNameFromPath
118
- } from "./chunk-CJQLPPEJ.js";
118
+ } from "./chunk-MEZXWELT.js";
119
119
  import {
120
120
  ATTACHMENTS_DIR,
121
- LEGACY_ATTACHMENTS_DIR
122
- } from "./chunk-NVXVEGGM.js";
121
+ DETACHED_JOBS_DIR,
122
+ LEGACY_ATTACHMENTS_DIR,
123
+ LEGACY_DETACHED_JOBS_DIR
124
+ } from "./chunk-5U4NZLBH.js";
123
125
  import {
124
126
  appendMessage,
125
127
  createEmptyThread,
@@ -145,6 +147,8 @@ import {
145
147
  } from "./chunk-FNQNJOHY.js";
146
148
  import {
147
149
  childEnvWithAppSettings,
150
+ formatViewerContextDirective,
151
+ formatViewerContextReminder,
148
152
  getIssueSource,
149
153
  isAbleTimeConnected,
150
154
  isLinearConnected,
@@ -157,10 +161,11 @@ import {
157
161
  resolveNewThreadOptions,
158
162
  resolveThreadDefaults,
159
163
  resolveVaultKey,
164
+ resolveViewerProfileForRepo,
160
165
  stripNestedElectronEnv,
161
166
  updateAdvancedSettings,
162
167
  writeSecureJson
163
- } from "./chunk-D7EGJ3EI.js";
168
+ } from "./chunk-EQIJ5MHR.js";
164
169
  import {
165
170
  writePrivateFile
166
171
  } from "./chunk-NSTQ6QKD.js";
@@ -394,7 +399,7 @@ async function continueSourceThread(threadId, prompt) {
394
399
  await continueOnReply(threadId, prompt);
395
400
  return;
396
401
  }
397
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-KQO4MXBI.js");
402
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-NSVP7DJH.js");
398
403
  await getOrchestrator2().send(threadId, prompt);
399
404
  } catch {
400
405
  }
@@ -654,7 +659,7 @@ async function pollSlackOutboundWatches(opts) {
654
659
  }
655
660
 
656
661
  // src/orchestrator/orchestrator.ts
657
- import { existsSync as existsSync17 } from "fs";
662
+ import { existsSync as existsSync18 } from "fs";
658
663
 
659
664
  // src/agents/spawn.ts
660
665
  import { createInterface } from "readline";
@@ -735,9 +740,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
735
740
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
736
741
  );
737
742
  }
738
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-QV7CC7SK.js");
743
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-FKAJZHO5.js");
739
744
  if (isGlobalThread2(thread)) {
740
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-AM47SATF.js");
745
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-7NKY7QRK.js");
741
746
  ensureGlobalCoordinatorCwd2(
742
747
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
743
748
  );
@@ -2479,8 +2484,232 @@ function notifyParentOfChildHalt(child, status, send) {
2479
2484
  return true;
2480
2485
  }
2481
2486
 
2487
+ // src/mcp/wait-for-job.ts
2488
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2489
+ import { dirname as dirname3, join as join10 } from "path";
2490
+
2491
+ // src/mcp/wait-for-turn.ts
2492
+ var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
2493
+ function mcpWaitForTurnTimeoutMs(requested) {
2494
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
2495
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
2496
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
2497
+ }
2498
+ 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.";
2499
+ 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.";
2500
+ function mcpWaitStillRunningHint(status) {
2501
+ return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
2502
+ }
2503
+ 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.";
2504
+ var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
2505
+ 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.";
2506
+ function mcpWaitFinishedHint(status) {
2507
+ if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
2508
+ if (status === "broken") return MCP_WAIT_BROKEN_HINT;
2509
+ if (status === "error") return MCP_WAIT_ERROR_HINT;
2510
+ return void 0;
2511
+ }
2512
+
2513
+ // src/mcp/wait-for-job.ts
2514
+ var MAX_JOB_CONTINUES = 8;
2515
+ 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.";
2516
+ var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
2517
+ function mcpWaitForJobTimeoutMs(requested) {
2518
+ return mcpWaitForTurnTimeoutMs(requested);
2519
+ }
2520
+ function sanitizeDetachedJobId(id) {
2521
+ const s = id.trim();
2522
+ if (!JOB_ID_RE.test(s)) {
2523
+ throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
2524
+ }
2525
+ return s;
2526
+ }
2527
+ function jobAlive(pid) {
2528
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2529
+ try {
2530
+ process.kill(pid, 0);
2531
+ return true;
2532
+ } catch {
2533
+ return false;
2534
+ }
2535
+ }
2536
+ function readIntFile(file) {
2537
+ if (!existsSync8(file)) return null;
2538
+ const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
2539
+ return Number.isInteger(n) ? n : null;
2540
+ }
2541
+ function jobDir(root, id, legacy = false) {
2542
+ return join10(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
2543
+ }
2544
+ function resolveJobDir(root, id) {
2545
+ const modern = jobDir(root, id, false);
2546
+ if (existsSync8(modern)) return modern;
2547
+ const legacy = jobDir(root, id, true);
2548
+ if (existsSync8(legacy)) return legacy;
2549
+ return modern;
2550
+ }
2551
+ function listJobIdsIn(root, rel) {
2552
+ const dir = join10(root, rel);
2553
+ if (!existsSync8(dir)) return [];
2554
+ return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
2555
+ }
2556
+ function listRunningDetachedJobs(worktreePath) {
2557
+ const root = worktreePath.trim();
2558
+ if (!root) return [];
2559
+ const ids = /* @__PURE__ */ new Set([
2560
+ ...listJobIdsIn(root, DETACHED_JOBS_DIR),
2561
+ ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
2562
+ ]);
2563
+ const running = [];
2564
+ for (const id of ids) {
2565
+ const dir = resolveJobDir(root, id);
2566
+ const pid = readIntFile(join10(dir, "pid"));
2567
+ if (pid != null && jobAlive(pid)) running.push(id);
2568
+ }
2569
+ return running.sort();
2570
+ }
2571
+ function looksLikeDeferredDonePromise(text) {
2572
+ const t = (text ?? "").trim();
2573
+ if (!t) return false;
2574
+ 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);
2575
+ }
2576
+ function formatJobStillRunningContinuePrompt(jobIds) {
2577
+ const ids = jobIds.join(", ");
2578
+ return [
2579
+ `Detached job still running: ${ids}.`,
2580
+ "Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
2581
+ "Then report the result. Do not tell the user you will let them know later."
2582
+ ].join(" ");
2583
+ }
2584
+ function formatDeferredDoneContinuePrompt() {
2585
+ return [
2586
+ "You ended the turn after promising to report later, but no detached job is running.",
2587
+ "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.",
2588
+ "Do not say you will let the user know later."
2589
+ ].join(" ");
2590
+ }
2591
+ function turnWatchedDetachedJob(parts) {
2592
+ return parts.some((p) => {
2593
+ if (p.type !== "tool") return false;
2594
+ if (/wait_for_job$/i.test(p.name ?? "")) return true;
2595
+ const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
2596
+ return /detached-job\.js\b/i.test(blob);
2597
+ });
2598
+ }
2599
+ function planJobContinue(opts) {
2600
+ if (opts.isOrchestrator) return { action: "none" };
2601
+ if (opts.agent === "brightsy") return { action: "none" };
2602
+ if (opts.queueLength > 0) return { action: "none" };
2603
+ if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
2604
+ const farewell = looksLikeDeferredDonePromise(opts.chatText);
2605
+ if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
2606
+ return {
2607
+ action: "wait",
2608
+ jobIds: opts.runningJobIds,
2609
+ prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
2610
+ };
2611
+ }
2612
+ if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
2613
+ return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
2614
+ }
2615
+ return { action: "none" };
2616
+ }
2617
+ function tailProgress(logFile, maxLines = 12) {
2618
+ if (!existsSync8(logFile)) return "(no log yet)";
2619
+ const lines = readFileSync7(logFile, "utf8").split("\n");
2620
+ return lines.slice(-maxLines).join("\n");
2621
+ }
2622
+ function readLogLines(file) {
2623
+ if (!existsSync8(file)) return [];
2624
+ const lines = readFileSync7(file, "utf8").split("\n");
2625
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
2626
+ return lines;
2627
+ }
2628
+ function takeDelta(logFile, cursorFile) {
2629
+ const lines = readLogLines(logFile);
2630
+ const cursor = readIntFile(cursorFile) ?? 0;
2631
+ const start = Math.min(Math.max(0, cursor), lines.length);
2632
+ return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
2633
+ }
2634
+ function snapshotJob(dir) {
2635
+ const pid = readIntFile(join10(dir, "pid"));
2636
+ const running = pid != null && jobAlive(pid);
2637
+ return {
2638
+ pid,
2639
+ running,
2640
+ exitCode: readIntFile(join10(dir, "exit")),
2641
+ log: join10(dir, "log"),
2642
+ cursor: join10(dir, "present.cursor"),
2643
+ progress: tailProgress(join10(dir, "log"))
2644
+ };
2645
+ }
2646
+ function toResult(id, snap, extra) {
2647
+ const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
2648
+ const ok = !snap.running && snap.exitCode === 0;
2649
+ const stillRunning = snap.running && !ok;
2650
+ const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
2651
+ try {
2652
+ mkdirSync3(dirname3(snap.cursor), { recursive: true });
2653
+ writeFileSync4(snap.cursor, `${nextCursor}
2654
+ `);
2655
+ } catch {
2656
+ }
2657
+ const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
2658
+ return {
2659
+ stillRunning,
2660
+ ok,
2661
+ failed: Boolean(failed && !stillRunning && !ok),
2662
+ status,
2663
+ id,
2664
+ pid: snap.pid,
2665
+ exitCode: snap.exitCode ?? void 0,
2666
+ delta,
2667
+ progress: extra?.progress ?? snap.progress,
2668
+ hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
2669
+ };
2670
+ }
2671
+ async function sleepMs(ms) {
2672
+ await new Promise((resolve) => setTimeout(resolve, ms));
2673
+ }
2674
+ async function waitForDetachedJob(cwd, id, opts) {
2675
+ const jobId = sanitizeDetachedJobId(id);
2676
+ const root = cwd.trim() || process.cwd();
2677
+ const dir = resolveJobDir(root, jobId);
2678
+ const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
2679
+ const sleep = opts?.sleep ?? sleepMs;
2680
+ if (!existsSync8(dir)) {
2681
+ return {
2682
+ stillRunning: false,
2683
+ ok: false,
2684
+ failed: true,
2685
+ status: "failed",
2686
+ id: jobId,
2687
+ delta: "",
2688
+ progress: "No detached job. Start one first.",
2689
+ hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
2690
+ };
2691
+ }
2692
+ const deadline = Date.now() + timeoutMs;
2693
+ let snap = snapshotJob(dir);
2694
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2695
+ if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
2696
+ return toResult(jobId, snap, {
2697
+ failed: true,
2698
+ progress: "No detached job. Start one first."
2699
+ });
2700
+ }
2701
+ while (Date.now() < deadline) {
2702
+ snap = snapshotJob(dir);
2703
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2704
+ if (!snap.running) return toResult(jobId, snap, { failed: true });
2705
+ await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
2706
+ }
2707
+ snap = snapshotJob(dir);
2708
+ return toResult(jobId, snap);
2709
+ }
2710
+
2482
2711
  // src/threads/create.ts
2483
- import { existsSync as existsSync8 } from "fs";
2712
+ import { existsSync as existsSync9 } from "fs";
2484
2713
 
2485
2714
  // src/detect/detect.ts
2486
2715
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
@@ -3000,7 +3229,7 @@ function reuseLiveThread(input, repoPath, match) {
3000
3229
  }
3001
3230
  async function createThread(input, _onSetupLine) {
3002
3231
  const repoPath = await resolveRepoRoot(input.repoPath);
3003
- if (!existsSync8(repoPath)) {
3232
+ if (!existsSync9(repoPath)) {
3004
3233
  throw new Error(`Repo not found: ${repoPath}`);
3005
3234
  }
3006
3235
  const reused = reuseLiveThread(input, repoPath, {
@@ -3017,7 +3246,7 @@ async function createThread(input, _onSetupLine) {
3017
3246
  });
3018
3247
  await requireAgent(resolved.agent);
3019
3248
  if (input.cowboy) {
3020
- const { cowboyModeEnabled } = await import("./app-settings-VRQI3FIE.js");
3249
+ const { cowboyModeEnabled } = await import("./app-settings-BJ5GFAE5.js");
3021
3250
  if (!cowboyModeEnabled()) {
3022
3251
  throw new Error(
3023
3252
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -3075,7 +3304,7 @@ async function createThread(input, _onSetupLine) {
3075
3304
  let sourceIsFork = false;
3076
3305
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
3077
3306
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
3078
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-BEIDJG7W.js");
3307
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-HOQBDQC4.js");
3079
3308
  const task = await ensureAbleTimeTask({
3080
3309
  title: title?.trim() || "Untitled work",
3081
3310
  description: input.prompt?.trim() || void 0
@@ -3186,11 +3415,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
3186
3415
 
3187
3416
  // src/store/turn-live.ts
3188
3417
  import {
3189
- existsSync as existsSync9,
3418
+ existsSync as existsSync10,
3190
3419
  renameSync,
3191
3420
  unlinkSync as unlinkSync2,
3192
- writeFileSync as writeFileSync4,
3193
- readFileSync as readFileSync7
3421
+ writeFileSync as writeFileSync5,
3422
+ readFileSync as readFileSync8
3194
3423
  } from "fs";
3195
3424
  var buffers = /* @__PURE__ */ new Map();
3196
3425
  var FLUSH_MS = 800;
@@ -3267,7 +3496,7 @@ function writeTurnLive(threadId, progress) {
3267
3496
  const path = threadLivePath(threadId);
3268
3497
  const tmp = `${path}.${process.pid}.tmp`;
3269
3498
  try {
3270
- writeFileSync4(tmp, JSON.stringify(progress), "utf8");
3499
+ writeFileSync5(tmp, JSON.stringify(progress), "utf8");
3271
3500
  renameSync(tmp, path);
3272
3501
  } catch {
3273
3502
  try {
@@ -3278,9 +3507,9 @@ function writeTurnLive(threadId, progress) {
3278
3507
  }
3279
3508
  function readTurnLive(threadId) {
3280
3509
  const path = threadLivePath(threadId);
3281
- if (!existsSync9(path)) return null;
3510
+ if (!existsSync10(path)) return null;
3282
3511
  try {
3283
- const raw = JSON.parse(readFileSync7(path, "utf8"));
3512
+ const raw = JSON.parse(readFileSync8(path, "utf8"));
3284
3513
  if (!raw || typeof raw.summary !== "string") return null;
3285
3514
  return raw;
3286
3515
  } catch {
@@ -3292,7 +3521,7 @@ function clearTurnLive(threadId) {
3292
3521
  if (buf?.timer) clearTimeout(buf.timer);
3293
3522
  buffers.delete(threadId);
3294
3523
  const path = threadLivePath(threadId);
3295
- if (!existsSync9(path)) return;
3524
+ if (!existsSync10(path)) return;
3296
3525
  try {
3297
3526
  unlinkSync2(path);
3298
3527
  } catch {
@@ -3477,26 +3706,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
3477
3706
  import { execFileSync } from "child_process";
3478
3707
  import {
3479
3708
  copyFileSync as copyFileSync2,
3480
- existsSync as existsSync10,
3709
+ existsSync as existsSync11,
3481
3710
  mkdtempSync,
3482
- readdirSync as readdirSync3,
3483
- readFileSync as readFileSync8,
3711
+ readdirSync as readdirSync4,
3712
+ readFileSync as readFileSync9,
3484
3713
  rmSync
3485
3714
  } from "fs";
3486
3715
  import { tmpdir } from "os";
3487
- import { join as join10 } from "path";
3716
+ import { join as join11 } from "path";
3488
3717
  import { createRequire } from "module";
3489
- var CONDUCTOR_APP_SUPPORT = join10(
3718
+ var CONDUCTOR_APP_SUPPORT = join11(
3490
3719
  process.env.HOME ?? "",
3491
3720
  "Library",
3492
3721
  "Application Support",
3493
3722
  "com.conductor.app"
3494
3723
  );
3495
- var CONDUCTOR_DB = join10(CONDUCTOR_APP_SUPPORT, "conductor.db");
3496
- var CURSOR_SDK_STORE = join10(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3724
+ var CONDUCTOR_DB = join11(CONDUCTOR_APP_SUPPORT, "conductor.db");
3725
+ var CURSOR_SDK_STORE = join11(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3497
3726
  function thisModuleFile() {
3498
3727
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
3499
- return cjsFile || process.argv[1] || join10(process.cwd(), "package.json");
3728
+ return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
3500
3729
  }
3501
3730
  function openReadonlySqlite(file) {
3502
3731
  const req = createRequire(thisModuleFile());
@@ -3514,21 +3743,21 @@ function mapAgentType(raw) {
3514
3743
  return null;
3515
3744
  }
3516
3745
  function resolveConductorCursorAgentId(workspacePath) {
3517
- if (!workspacePath || !existsSync10(CURSOR_SDK_STORE)) return null;
3746
+ if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
3518
3747
  const normalized = workspacePath.replace(/\/$/, "");
3519
3748
  let best = null;
3520
3749
  let hashes;
3521
3750
  try {
3522
- hashes = readdirSync3(CURSOR_SDK_STORE);
3751
+ hashes = readdirSync4(CURSOR_SDK_STORE);
3523
3752
  } catch {
3524
3753
  return null;
3525
3754
  }
3526
3755
  for (const hash of hashes) {
3527
- const agentsFile = join10(CURSOR_SDK_STORE, hash, "agents.ndjson");
3528
- if (!existsSync10(agentsFile)) continue;
3756
+ const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
3757
+ if (!existsSync11(agentsFile)) continue;
3529
3758
  let text;
3530
3759
  try {
3531
- text = readFileSync8(agentsFile, "utf8");
3760
+ text = readFileSync9(agentsFile, "utf8");
3532
3761
  } catch {
3533
3762
  continue;
3534
3763
  }
@@ -3552,7 +3781,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3552
3781
  return best?.agentId ?? null;
3553
3782
  }
3554
3783
  async function adoptThread(input) {
3555
- if (!existsSync10(input.worktreePath)) {
3784
+ if (!existsSync11(input.worktreePath)) {
3556
3785
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3557
3786
  }
3558
3787
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -3571,23 +3800,23 @@ async function adoptThread(input) {
3571
3800
  messages: input.messages ?? []
3572
3801
  });
3573
3802
  writeThread(thread);
3574
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-Q2VCHIAS.js");
3803
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OFSDJ7PZ.js");
3575
3804
  await ensureWorkspace2(repoPath);
3576
3805
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-N5X5I422.js");
3577
3806
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
3578
3807
  return thread;
3579
3808
  }
3580
3809
  function listConductorWorkspaces() {
3581
- if (!existsSync10(CONDUCTOR_DB)) {
3810
+ if (!existsSync11(CONDUCTOR_DB)) {
3582
3811
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3583
3812
  }
3584
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3585
- const snapshot = join10(tmp, "conductor.db");
3813
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3814
+ const snapshot = join11(tmp, "conductor.db");
3586
3815
  try {
3587
3816
  copyFileSync2(CONDUCTOR_DB, snapshot);
3588
3817
  for (const suffix of ["-wal", "-shm"]) {
3589
3818
  const src = `${CONDUCTOR_DB}${suffix}`;
3590
- if (existsSync10(src)) {
3819
+ if (existsSync11(src)) {
3591
3820
  try {
3592
3821
  copyFileSync2(src, `${snapshot}${suffix}`);
3593
3822
  } catch {
@@ -3669,16 +3898,16 @@ function listConductorWorkspaces() {
3669
3898
  }
3670
3899
  }
3671
3900
  function importConductorWorkspace(workspaceId) {
3672
- if (!existsSync10(CONDUCTOR_DB)) {
3901
+ if (!existsSync11(CONDUCTOR_DB)) {
3673
3902
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3674
3903
  }
3675
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3676
- const snapshot = join10(tmp, "conductor.db");
3904
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3905
+ const snapshot = join11(tmp, "conductor.db");
3677
3906
  try {
3678
3907
  copyFileSync2(CONDUCTOR_DB, snapshot);
3679
3908
  for (const suffix of ["-wal", "-shm"]) {
3680
3909
  const src = `${CONDUCTOR_DB}${suffix}`;
3681
- if (existsSync10(src)) {
3910
+ if (existsSync11(src)) {
3682
3911
  try {
3683
3912
  copyFileSync2(src, `${snapshot}${suffix}`);
3684
3913
  } catch {
@@ -3698,7 +3927,7 @@ function importConductorWorkspace(workspaceId) {
3698
3927
  ).get(workspaceId);
3699
3928
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3700
3929
  const worktreePath = String(row.workspacePath);
3701
- if (!existsSync10(worktreePath)) {
3930
+ if (!existsSync11(worktreePath)) {
3702
3931
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3703
3932
  }
3704
3933
  let sessionId = null;
@@ -3769,7 +3998,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
3769
3998
  }
3770
3999
 
3771
4000
  // src/threads/stack-layers.ts
3772
- import { existsSync as existsSync11 } from "fs";
4001
+ import { existsSync as existsSync12 } from "fs";
3773
4002
  function stackIdFrom(stack) {
3774
4003
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3775
4004
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -3832,7 +4061,7 @@ async function openStackLayer(input, _onSetupLine) {
3832
4061
  let createdWorktree = false;
3833
4062
  const trees = await listWorktrees(repoPath);
3834
4063
  const checkedOut = trees.find((w) => w.branch === branchName);
3835
- if (checkedOut?.path && existsSync11(checkedOut.path)) {
4064
+ if (checkedOut?.path && existsSync12(checkedOut.path)) {
3836
4065
  if (input.reuseExistingWorktree !== false) {
3837
4066
  worktreePath = checkedOut.path;
3838
4067
  } else {
@@ -3974,7 +4203,7 @@ async function initStackFromThread(input, onSetupLine) {
3974
4203
  async function createPrStack(input, onSetupLine) {
3975
4204
  await requireAgent(input.agent);
3976
4205
  const repoPath = await resolveRepoRoot(input.repoPath);
3977
- if (!existsSync11(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4206
+ if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3978
4207
  if (!input.branches.length) throw new Error("At least one branch name required");
3979
4208
  const status = await detectGhStack(repoPath);
3980
4209
  if (!status.available) throw new Error(status.reason);
@@ -4041,7 +4270,7 @@ async function createPrStack(input, onSetupLine) {
4041
4270
  }
4042
4271
  }
4043
4272
  const claimed = new Set(threads.map((t) => t.worktreePath));
4044
- if (!claimed.has(bootstrap.worktreePath) && existsSync11(bootstrap.worktreePath)) {
4273
+ if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
4045
4274
  try {
4046
4275
  await removeWorktree(repoPath, bootstrap.worktreePath, {
4047
4276
  deleteBranch: bootstrap.branchName
@@ -4053,10 +4282,10 @@ async function createPrStack(input, onSetupLine) {
4053
4282
  }
4054
4283
 
4055
4284
  // src/diff/diff.ts
4056
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4057
- import { dirname as dirname3, join as join11 } from "path";
4285
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
4286
+ import { dirname as dirname4, join as join12 } from "path";
4058
4287
  async function inspectGitWorktree(worktreePath) {
4059
- if (!worktreePath || !existsSync12(worktreePath)) return "missing_worktree";
4288
+ if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
4060
4289
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
4061
4290
  reject: false
4062
4291
  });
@@ -4064,7 +4293,7 @@ async function inspectGitWorktree(worktreePath) {
4064
4293
  return "ok";
4065
4294
  }
4066
4295
  async function initializeGitRepository(worktreePath) {
4067
- if (!worktreePath || !existsSync12(worktreePath)) {
4296
+ if (!worktreePath || !existsSync13(worktreePath)) {
4068
4297
  throw new Error("Worktree not found");
4069
4298
  }
4070
4299
  const status = await inspectGitWorktree(worktreePath);
@@ -4200,11 +4429,11 @@ new file mode 100644
4200
4429
  };
4201
4430
  }
4202
4431
  async function untrackedPatch(worktreePath, path, maxHunk) {
4203
- const abs = join11(worktreePath, path);
4432
+ const abs = join12(worktreePath, path);
4204
4433
  try {
4205
4434
  const st = statSync2(abs);
4206
4435
  if (st.isFile() && st.size > maxHunk) {
4207
- const buf = readFileSync9(abs).subarray(0, maxHunk);
4436
+ const buf = readFileSync10(abs).subarray(0, maxHunk);
4208
4437
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
4209
4438
  }
4210
4439
  } catch {
@@ -4705,7 +4934,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
4705
4934
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4706
4935
  assertSafeRelativePath(relativePath);
4707
4936
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
4708
- const abs = join11(worktreePath, relativePath);
4937
+ const abs = join12(worktreePath, relativePath);
4709
4938
  const st = statSync2(abs);
4710
4939
  if (!st.isFile()) {
4711
4940
  throw new Error(`Not a file: ${relativePath}`);
@@ -4715,7 +4944,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4715
4944
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
4716
4945
  );
4717
4946
  }
4718
- const buf = readFileSync9(abs);
4947
+ const buf = readFileSync10(abs);
4719
4948
  return {
4720
4949
  path: relativePath,
4721
4950
  contentBase64: buf.toString("base64"),
@@ -4725,12 +4954,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4725
4954
  function readWorktreeFile(worktreePath, relativePath, opts) {
4726
4955
  assertSafeRelativePath(relativePath);
4727
4956
  const maxBytes = opts?.maxBytes ?? 2e5;
4728
- const abs = join11(worktreePath, relativePath);
4957
+ const abs = join12(worktreePath, relativePath);
4729
4958
  const st = statSync2(abs);
4730
4959
  if (!st.isFile()) {
4731
4960
  throw new Error(`Not a file: ${relativePath}`);
4732
4961
  }
4733
- const buf = readFileSync9(abs);
4962
+ const buf = readFileSync10(abs);
4734
4963
  if (isImageRelativePath(relativePath)) {
4735
4964
  const maxImageBytes = Math.max(maxBytes, 15e6);
4736
4965
  const truncated2 = buf.length > maxImageBytes;
@@ -4773,9 +5002,9 @@ function assertSafeRelativePath(relativePath) {
4773
5002
  }
4774
5003
  function writeWorktreeFile(worktreePath, relativePath, content) {
4775
5004
  assertSafeRelativePath(relativePath);
4776
- const abs = join11(worktreePath, relativePath);
4777
- mkdirSync3(dirname3(abs), { recursive: true });
4778
- writeFileSync5(abs, content, "utf8");
5005
+ const abs = join12(worktreePath, relativePath);
5006
+ mkdirSync4(dirname4(abs), { recursive: true });
5007
+ writeFileSync6(abs, content, "utf8");
4779
5008
  return { path: relativePath };
4780
5009
  }
4781
5010
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -4900,9 +5129,9 @@ async function confirmLand(thread, opts) {
4900
5129
  }
4901
5130
 
4902
5131
  // src/skills/discover.ts
4903
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5132
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
4904
5133
  import { homedir } from "os";
4905
- import { join as join12 } from "path";
5134
+ import { join as join13 } from "path";
4906
5135
 
4907
5136
  // src/skills/bundled/long-running.ts
4908
5137
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -4913,7 +5142,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
4913
5142
 
4914
5143
  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.
4915
5144
 
4916
- 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.
5145
+ 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
4917
5146
 
4918
5147
  ## Tool
4919
5148
 
@@ -4923,7 +5152,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
4923
5152
  # Start (exits in ~1s; job survives this turn)
4924
5153
  node <detached-job.js> start <id> -- <command> [args...]
4925
5154
 
4926
- # Wait \u2014 returns within ~45s even if the job is still going
5155
+ # Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
5156
+ # Shell fallback:
4927
5157
  node <detached-job.js> wait <id>
4928
5158
 
4929
5159
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -4936,7 +5166,7 @@ node <detached-job.js> status <id>
4936
5166
 
4937
5167
  Wait JSON:
4938
5168
 
4939
- - \`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.
5169
+ - \`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.
4940
5170
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
4941
5171
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
4942
5172
 
@@ -4966,7 +5196,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
4966
5196
 
4967
5197
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
4968
5198
  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.
4969
- 3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
5199
+ 3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
4970
5200
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
4971
5201
 
4972
5202
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -5031,7 +5261,7 @@ function parseFrontmatter(content) {
5031
5261
  }
5032
5262
  function readSkill(skillMd, source) {
5033
5263
  try {
5034
- const content = readFileSync10(skillMd, "utf8");
5264
+ const content = readFileSync11(skillMd, "utf8");
5035
5265
  const { name: fmName, description } = parseFrontmatter(content);
5036
5266
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
5037
5267
  const name = fmName || dirName;
@@ -5050,17 +5280,17 @@ function readSkill(skillMd, source) {
5050
5280
  }
5051
5281
  }
5052
5282
  function scanSkillsDir(dir, source, out) {
5053
- if (!existsSync13(dir)) return;
5283
+ if (!existsSync14(dir)) return;
5054
5284
  let entries;
5055
5285
  try {
5056
- entries = readdirSync4(dir);
5286
+ entries = readdirSync5(dir);
5057
5287
  } catch {
5058
5288
  return;
5059
5289
  }
5060
5290
  for (const entry of entries) {
5061
5291
  if (entry.startsWith(".")) continue;
5062
- const skillMd = join12(dir, entry, "SKILL.md");
5063
- if (!existsSync13(skillMd)) continue;
5292
+ const skillMd = join13(dir, entry, "SKILL.md");
5293
+ if (!existsSync14(skillMd)) continue;
5064
5294
  try {
5065
5295
  if (!statSync3(skillMd).isFile()) continue;
5066
5296
  } catch {
@@ -5071,22 +5301,22 @@ function scanSkillsDir(dir, source, out) {
5071
5301
  }
5072
5302
  }
5073
5303
  function scanClaudePluginSkills(pluginsRoot, out) {
5074
- if (!existsSync13(pluginsRoot)) return;
5304
+ if (!existsSync14(pluginsRoot)) return;
5075
5305
  const walk = (dir, depth, lookingForSkillsDir) => {
5076
5306
  if (depth > 7) return;
5077
5307
  let entries;
5078
5308
  try {
5079
- entries = readdirSync4(dir);
5309
+ entries = readdirSync5(dir);
5080
5310
  } catch {
5081
5311
  return;
5082
5312
  }
5083
5313
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
5084
- const skill = readSkill(join12(dir, "SKILL.md"), "cli");
5314
+ const skill = readSkill(join13(dir, "SKILL.md"), "cli");
5085
5315
  if (skill) out.push(skill);
5086
5316
  }
5087
5317
  for (const entry of entries) {
5088
5318
  if (entry === "node_modules" || entry === ".git") continue;
5089
- const full = join12(dir, entry);
5319
+ const full = join13(dir, entry);
5090
5320
  try {
5091
5321
  if (!statSync3(full).isDirectory()) continue;
5092
5322
  } catch {
@@ -5106,17 +5336,17 @@ function discoverSkills(worktreePath) {
5106
5336
  const home = homedir();
5107
5337
  const collected = [];
5108
5338
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
5109
- scanSkillsDir(join12(worktreePath, rel), "workspace", collected);
5339
+ scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
5110
5340
  }
5111
5341
  for (const abs of [
5112
- join12(home, ".claude/skills"),
5113
- join12(home, ".cursor/skills"),
5114
- join12(home, ".sideboard/skills"),
5115
- join12(home, ".brightsy/skills")
5342
+ join13(home, ".claude/skills"),
5343
+ join13(home, ".cursor/skills"),
5344
+ join13(home, ".sideboard/skills"),
5345
+ join13(home, ".brightsy/skills")
5116
5346
  ]) {
5117
5347
  scanSkillsDir(abs, "user", collected);
5118
5348
  }
5119
- scanClaudePluginSkills(join12(home, ".claude/plugins"), collected);
5349
+ scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
5120
5350
  collected.push(...bundledSkills());
5121
5351
  const rank = {
5122
5352
  workspace: 0,
@@ -5142,7 +5372,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
5142
5372
  \u2026(truncated)` : body;
5143
5373
  }
5144
5374
  }
5145
- const raw = readFileSync10(skillPath, "utf8");
5375
+ const raw = readFileSync11(skillPath, "utf8");
5146
5376
  if (raw.startsWith("---")) {
5147
5377
  const end = raw.indexOf("\n---", 3);
5148
5378
  if (end >= 0) {
@@ -5235,27 +5465,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
5235
5465
  }
5236
5466
 
5237
5467
  // src/agents/instructions.ts
5238
- import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5239
- import { join as join14 } from "path";
5468
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
5469
+ import { join as join15 } from "path";
5240
5470
 
5241
5471
  // src/skills/detached-job-path.ts
5242
- import { existsSync as existsSync14 } from "fs";
5243
- import { dirname as dirname4, join as join13 } from "path";
5472
+ import { existsSync as existsSync15 } from "fs";
5473
+ import { dirname as dirname5, join as join14 } from "path";
5244
5474
  import { fileURLToPath } from "url";
5245
5475
  function packagedDetachedJobPath() {
5246
5476
  const dir = packagedMcpDir();
5247
5477
  if (!dir) return null;
5248
- const script = join13(dir, "scripts", "detached-job.js");
5249
- return existsSync14(script) ? script : null;
5478
+ const script = join14(dir, "scripts", "detached-job.js");
5479
+ return existsSync15(script) ? script : null;
5250
5480
  }
5251
5481
  function resolveDetachedJobScript() {
5252
5482
  const packaged = packagedDetachedJobPath();
5253
5483
  if (packaged) return packaged;
5254
- let dir = dirname4(fileURLToPath(import.meta.url));
5484
+ let dir = dirname5(fileURLToPath(import.meta.url));
5255
5485
  for (let i = 0; i < 8; i++) {
5256
- const candidate = join13(dir, "scripts", "detached-job.js");
5257
- if (existsSync14(candidate)) return candidate;
5258
- const parent = dirname4(dir);
5486
+ const candidate = join14(dir, "scripts", "detached-job.js");
5487
+ if (existsSync15(candidate)) return candidate;
5488
+ const parent = dirname5(dir);
5259
5489
  if (parent === dir) break;
5260
5490
  dir = parent;
5261
5491
  }
@@ -5461,14 +5691,15 @@ function formatLongRunningDirective(opts) {
5461
5691
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
5462
5692
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5463
5693
  "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
5464
- `- 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.`,
5694
+ `- 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.`,
5695
+ "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
5465
5696
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
5466
5697
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5467
5698
  ].join("\n");
5468
5699
  }
5469
5700
  function formatLongRunningReminder(opts) {
5470
5701
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5471
- return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
5702
+ 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.`;
5472
5703
  }
5473
5704
  function formatArtifactDirective() {
5474
5705
  return [
@@ -5616,8 +5847,8 @@ async function syncThreadBranchFromGit(threadId) {
5616
5847
 
5617
5848
  // src/store/schedules.ts
5618
5849
  import { randomUUID as randomUUID4 } from "crypto";
5619
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
5620
- import { join as join15 } from "path";
5850
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
5851
+ import { join as join16 } from "path";
5621
5852
  import { Cron } from "croner";
5622
5853
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5623
5854
  var UNIT_MS = {
@@ -5627,7 +5858,7 @@ var UNIT_MS = {
5627
5858
  d: 864e5
5628
5859
  };
5629
5860
  function schedulesPath() {
5630
- return join15(appDataDir(), "schedules.json");
5861
+ return join16(appDataDir(), "schedules.json");
5631
5862
  }
5632
5863
  function parseDurationMs(every) {
5633
5864
  const m = every.trim().match(DURATION_RE);
@@ -5727,9 +5958,9 @@ function normalizeTask(raw) {
5727
5958
  }
5728
5959
  function readAll() {
5729
5960
  const path = schedulesPath();
5730
- if (!existsSync16(path)) return [];
5961
+ if (!existsSync17(path)) return [];
5731
5962
  try {
5732
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5963
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
5733
5964
  if (!Array.isArray(parsed)) return [];
5734
5965
  return parsed.map((row) => {
5735
5966
  try {
@@ -5852,7 +6083,7 @@ function formatScheduledPrompt(name, prompt) {
5852
6083
  ${prompt}`;
5853
6084
  }
5854
6085
  async function defaultDeps() {
5855
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-KQO4MXBI.js");
6086
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-NSVP7DJH.js");
5856
6087
  const orch = getOrchestrator2();
5857
6088
  return {
5858
6089
  findThread: (id) => findThreadByRef(id),
@@ -6009,6 +6240,9 @@ var Orchestrator = class {
6009
6240
  * finish or a user send so a later crash can recover again.
6010
6241
  */
6011
6242
  crashContinued = /* @__PURE__ */ new Set();
6243
+ /** Auto-continues after a worktree turn ended while a detached job still runs. */
6244
+ jobContinueCount = /* @__PURE__ */ new Map();
6245
+ jobContinueNudged = /* @__PURE__ */ new Set();
6012
6246
  maxConcurrent;
6013
6247
  runningCount = 0;
6014
6248
  constructor(opts) {
@@ -6092,7 +6326,7 @@ var Orchestrator = class {
6092
6326
  }
6093
6327
  continue;
6094
6328
  }
6095
- if (!existsSync17(thread.worktreePath)) {
6329
+ if (!existsSync18(thread.worktreePath)) {
6096
6330
  setStatus(thread.id, "broken", "Worktree missing on disk");
6097
6331
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
6098
6332
  continue;
@@ -6111,7 +6345,7 @@ var Orchestrator = class {
6111
6345
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
6112
6346
  });
6113
6347
  }
6114
- const { autoCleanupOrphansEnabled } = await import("./app-settings-VRQI3FIE.js");
6348
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-BJ5GFAE5.js");
6115
6349
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
6116
6350
  await cleanupOrphanWorktrees({ repoPaths });
6117
6351
  }
@@ -6307,6 +6541,37 @@ var Orchestrator = class {
6307
6541
  this.emit({ type: "queue_changed", threadId, queue });
6308
6542
  this.haltDrain.delete(threadId);
6309
6543
  }
6544
+ /**
6545
+ * Worktree agent ended the turn after “I’ll let you know” (or left a
6546
+ * detached test/pack job running). Queue a continue so the chat does not
6547
+ * go idle with nobody watching the log.
6548
+ */
6549
+ maybeEnqueueJobContinue(threadId, chatText, parts = []) {
6550
+ if (this.haltDrain.has(threadId)) return;
6551
+ const thread = readThread(threadId);
6552
+ if (!thread || thread.status === "archived") return;
6553
+ const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
6554
+ const decision = planJobContinue({
6555
+ runningJobIds,
6556
+ chatText,
6557
+ queueLength: thread.queue.length,
6558
+ continueCount: this.jobContinueCount.get(threadId) ?? 0,
6559
+ alreadyNudged: this.jobContinueNudged.has(threadId),
6560
+ isOrchestrator: isOrchestratorThread(thread),
6561
+ agent: thread.agent,
6562
+ watchedJob: turnWatchedDetachedJob(parts)
6563
+ });
6564
+ if (decision.action === "none") {
6565
+ if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
6566
+ return;
6567
+ }
6568
+ if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
6569
+ else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
6570
+ const queue = [decision.prompt, ...thread.queue];
6571
+ updateThread(threadId, { queue });
6572
+ this.emit({ type: "queue_changed", threadId, queue });
6573
+ this.haltDrain.delete(threadId);
6574
+ }
6310
6575
  getThreads(includeArchived = false) {
6311
6576
  return listThreads({ includeArchived });
6312
6577
  }
@@ -6351,7 +6616,7 @@ var Orchestrator = class {
6351
6616
  }
6352
6617
  await setup;
6353
6618
  if (skipSetup) return;
6354
- const { autoRunAfterSetupEnabled } = await import("./app-settings-VRQI3FIE.js");
6619
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-BJ5GFAE5.js");
6355
6620
  if (autoRunAfterSetupEnabled()) {
6356
6621
  try {
6357
6622
  await this.startDev(threadId);
@@ -6406,6 +6671,8 @@ var Orchestrator = class {
6406
6671
  }
6407
6672
  const queue = [...current.queue, prompt];
6408
6673
  this.crashContinued.delete(thread.id);
6674
+ this.jobContinueCount.delete(thread.id);
6675
+ this.jobContinueNudged.delete(thread.id);
6409
6676
  this.haltDrain.delete(thread.id);
6410
6677
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
6411
6678
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -6628,6 +6895,7 @@ var Orchestrator = class {
6628
6895
  ticketId: issueTicket?.id,
6629
6896
  ticketProvider: issueTicket?.provider
6630
6897
  }) : null;
6898
+ const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
6631
6899
  const slackReplyContext = formatSlackRepliesForTurn(
6632
6900
  pendingSlackExternalReplies(thread.messages)
6633
6901
  );
@@ -6637,6 +6905,7 @@ var Orchestrator = class {
6637
6905
  worktreeReminder,
6638
6906
  optionalServicesReminder,
6639
6907
  issueToolsReminder,
6908
+ viewerContextReminder,
6640
6909
  artifactReminder,
6641
6910
  longRunningReminder,
6642
6911
  slackReplyContext,
@@ -6658,7 +6927,7 @@ var Orchestrator = class {
6658
6927
  }
6659
6928
  const isBrightsy = fresh.agent === "brightsy";
6660
6929
  const isOrchestration = isOrchestratorThread(fresh);
6661
- const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-VRQI3FIE.js");
6930
+ const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-BJ5GFAE5.js");
6662
6931
  const gitAuthMode = getGithubGitAuthMode();
6663
6932
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
6664
6933
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -6677,6 +6946,18 @@ var Orchestrator = class {
6677
6946
  ticketId: freshIssueTicket?.id,
6678
6947
  ticketProvider: freshIssueTicket?.provider
6679
6948
  });
6949
+ let viewerContextDirective = null;
6950
+ if (!isBrightsy && !isOrchestration) {
6951
+ try {
6952
+ viewerContextDirective = formatViewerContextDirective(
6953
+ resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
6954
+ );
6955
+ } catch {
6956
+ viewerContextDirective = formatViewerContextDirective(
6957
+ resolveViewerProfileForRepo()
6958
+ );
6959
+ }
6960
+ }
6680
6961
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
6681
6962
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
6682
6963
  customPrompt: settings?.prompts?.renameBranch
@@ -6708,6 +6989,7 @@ var Orchestrator = class {
6708
6989
  worktreeDirective,
6709
6990
  optionalServicesDirective,
6710
6991
  issueToolsDirective,
6992
+ viewerContextDirective,
6711
6993
  artifactDirective,
6712
6994
  longRunningDirective,
6713
6995
  renameBranchDirective,
@@ -6818,6 +7100,7 @@ var Orchestrator = class {
6818
7100
  worktreeDirective,
6819
7101
  optionalServicesDirective,
6820
7102
  issueToolsDirective,
7103
+ viewerContextDirective,
6821
7104
  artifactDirective,
6822
7105
  longRunningDirective,
6823
7106
  renameBranchDirective,
@@ -6920,6 +7203,7 @@ var Orchestrator = class {
6920
7203
  this.emit({ type: "turn_finished", threadId, exitCode });
6921
7204
  if (exitCode === 0) {
6922
7205
  this.crashContinued.delete(threadId);
7206
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
6923
7207
  } else {
6924
7208
  const blob = [chatText, detail].filter(Boolean).join("\n");
6925
7209
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -7555,7 +7839,7 @@ var Orchestrator = class {
7555
7839
  updateThread(thread.id, { title: meta.title });
7556
7840
  }
7557
7841
  }
7558
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-VRQI3FIE.js");
7842
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-BJ5GFAE5.js");
7559
7843
  const latest = this.requireThread(thread.id);
7560
7844
  if (!shouldAutoArchiveOnPrMerge({
7561
7845
  previousPrState: prevState || null,
@@ -7872,7 +8156,7 @@ var Orchestrator = class {
7872
8156
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7873
8157
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7874
8158
  try {
7875
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-Q2VCHIAS.js");
8159
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OFSDJ7PZ.js");
7876
8160
  await ensureWorkspace2(thread.repoPath);
7877
8161
  } catch {
7878
8162
  }
@@ -7899,7 +8183,7 @@ var Orchestrator = class {
7899
8183
  await runArchiveScript(thread.repoPath, thread.worktreePath);
7900
8184
  } catch {
7901
8185
  }
7902
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-VRQI3FIE.js");
8186
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-BJ5GFAE5.js");
7903
8187
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
7904
8188
  await removeWorktree(thread.repoPath, thread.worktreePath, {
7905
8189
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -7920,13 +8204,13 @@ var Orchestrator = class {
7920
8204
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
7921
8205
  return restored2;
7922
8206
  }
7923
- if (!existsSync17(thread.worktreePath)) {
8207
+ if (!existsSync18(thread.worktreePath)) {
7924
8208
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
7925
8209
  throw new Error(
7926
8210
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
7927
8211
  );
7928
8212
  }
7929
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-PEWDAX5O.js");
8213
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-BQE5KLRY.js");
7930
8214
  const slug = thread.worktreePath.split("/").pop();
7931
8215
  const dest = thread.worktreePath;
7932
8216
  await withRepoGitLock(thread.repoPath, async () => {
@@ -8036,7 +8320,7 @@ async function startOrchestration(opts) {
8036
8320
  sourceRef: "default",
8037
8321
  ...createOpts
8038
8322
  }).catch(async () => {
8039
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-PEWDAX5O.js");
8323
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-BQE5KLRY.js");
8040
8324
  const repo = await resolveRepoRoot2(repoPath);
8041
8325
  const def = await resolveDefaultBranch2(repo);
8042
8326
  return orch.createThread({
@@ -8066,6 +8350,11 @@ export {
8066
8350
  recordSlackOutboundWatch,
8067
8351
  pollSlackOutboundWatches,
8068
8352
  AGENT_GIT_ACTIONS,
8353
+ mcpWaitForTurnTimeoutMs,
8354
+ mcpWaitStillRunningHint,
8355
+ mcpWaitFinishedHint,
8356
+ mcpWaitForJobTimeoutMs,
8357
+ waitForDetachedJob,
8069
8358
  BOARD_COLUMN_DEFS,
8070
8359
  HOME_BOARD_CACHE_TTL_MS,
8071
8360
  dedupeBoardIssues,