@sideboard-ai/core 0.1.157 → 0.1.158

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.
@@ -33,12 +33,12 @@ import {
33
33
  summarizeTurnStderr,
34
34
  toolDescription,
35
35
  turnFailChatText
36
- } from "./chunk-KQ4JOGUH.js";
36
+ } from "./chunk-EPQIOAAY.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-3SUZMPGL.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-A7QXLYBN.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,
@@ -394,7 +396,7 @@ async function continueSourceThread(threadId, prompt) {
394
396
  await continueOnReply(threadId, prompt);
395
397
  return;
396
398
  }
397
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-KQO4MXBI.js");
399
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-J727WIJY.js");
398
400
  await getOrchestrator2().send(threadId, prompt);
399
401
  } catch {
400
402
  }
@@ -654,7 +656,7 @@ async function pollSlackOutboundWatches(opts) {
654
656
  }
655
657
 
656
658
  // src/orchestrator/orchestrator.ts
657
- import { existsSync as existsSync17 } from "fs";
659
+ import { existsSync as existsSync18 } from "fs";
658
660
 
659
661
  // src/agents/spawn.ts
660
662
  import { createInterface } from "readline";
@@ -735,9 +737,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
735
737
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
736
738
  );
737
739
  }
738
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-QV7CC7SK.js");
740
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-REI55FZ6.js");
739
741
  if (isGlobalThread2(thread)) {
740
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-AM47SATF.js");
742
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-GZESIQNH.js");
741
743
  ensureGlobalCoordinatorCwd2(
742
744
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
743
745
  );
@@ -2479,8 +2481,232 @@ function notifyParentOfChildHalt(child, status, send) {
2479
2481
  return true;
2480
2482
  }
2481
2483
 
2484
+ // src/mcp/wait-for-job.ts
2485
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2486
+ import { dirname as dirname3, join as join10 } from "path";
2487
+
2488
+ // src/mcp/wait-for-turn.ts
2489
+ var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
2490
+ function mcpWaitForTurnTimeoutMs(requested) {
2491
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
2492
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
2493
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
2494
+ }
2495
+ 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.";
2496
+ 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.";
2497
+ function mcpWaitStillRunningHint(status) {
2498
+ return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
2499
+ }
2500
+ 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.";
2501
+ var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
2502
+ 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.";
2503
+ function mcpWaitFinishedHint(status) {
2504
+ if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
2505
+ if (status === "broken") return MCP_WAIT_BROKEN_HINT;
2506
+ if (status === "error") return MCP_WAIT_ERROR_HINT;
2507
+ return void 0;
2508
+ }
2509
+
2510
+ // src/mcp/wait-for-job.ts
2511
+ var MAX_JOB_CONTINUES = 8;
2512
+ 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.";
2513
+ var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
2514
+ function mcpWaitForJobTimeoutMs(requested) {
2515
+ return mcpWaitForTurnTimeoutMs(requested);
2516
+ }
2517
+ function sanitizeDetachedJobId(id) {
2518
+ const s = id.trim();
2519
+ if (!JOB_ID_RE.test(s)) {
2520
+ throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
2521
+ }
2522
+ return s;
2523
+ }
2524
+ function jobAlive(pid) {
2525
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2526
+ try {
2527
+ process.kill(pid, 0);
2528
+ return true;
2529
+ } catch {
2530
+ return false;
2531
+ }
2532
+ }
2533
+ function readIntFile(file) {
2534
+ if (!existsSync8(file)) return null;
2535
+ const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
2536
+ return Number.isInteger(n) ? n : null;
2537
+ }
2538
+ function jobDir(root, id, legacy = false) {
2539
+ return join10(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
2540
+ }
2541
+ function resolveJobDir(root, id) {
2542
+ const modern = jobDir(root, id, false);
2543
+ if (existsSync8(modern)) return modern;
2544
+ const legacy = jobDir(root, id, true);
2545
+ if (existsSync8(legacy)) return legacy;
2546
+ return modern;
2547
+ }
2548
+ function listJobIdsIn(root, rel) {
2549
+ const dir = join10(root, rel);
2550
+ if (!existsSync8(dir)) return [];
2551
+ return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
2552
+ }
2553
+ function listRunningDetachedJobs(worktreePath) {
2554
+ const root = worktreePath.trim();
2555
+ if (!root) return [];
2556
+ const ids = /* @__PURE__ */ new Set([
2557
+ ...listJobIdsIn(root, DETACHED_JOBS_DIR),
2558
+ ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
2559
+ ]);
2560
+ const running = [];
2561
+ for (const id of ids) {
2562
+ const dir = resolveJobDir(root, id);
2563
+ const pid = readIntFile(join10(dir, "pid"));
2564
+ if (pid != null && jobAlive(pid)) running.push(id);
2565
+ }
2566
+ return running.sort();
2567
+ }
2568
+ function looksLikeDeferredDonePromise(text) {
2569
+ const t = (text ?? "").trim();
2570
+ if (!t) return false;
2571
+ 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);
2572
+ }
2573
+ function formatJobStillRunningContinuePrompt(jobIds) {
2574
+ const ids = jobIds.join(", ");
2575
+ return [
2576
+ `Detached job still running: ${ids}.`,
2577
+ "Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
2578
+ "Then report the result. Do not tell the user you will let them know later."
2579
+ ].join(" ");
2580
+ }
2581
+ function formatDeferredDoneContinuePrompt() {
2582
+ return [
2583
+ "You ended the turn after promising to report later, but no detached job is running.",
2584
+ "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.",
2585
+ "Do not say you will let the user know later."
2586
+ ].join(" ");
2587
+ }
2588
+ function turnWatchedDetachedJob(parts) {
2589
+ return parts.some((p) => {
2590
+ if (p.type !== "tool") return false;
2591
+ if (/wait_for_job$/i.test(p.name ?? "")) return true;
2592
+ const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
2593
+ return /detached-job\.js\b/i.test(blob);
2594
+ });
2595
+ }
2596
+ function planJobContinue(opts) {
2597
+ if (opts.isOrchestrator) return { action: "none" };
2598
+ if (opts.agent === "brightsy") return { action: "none" };
2599
+ if (opts.queueLength > 0) return { action: "none" };
2600
+ if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
2601
+ const farewell = looksLikeDeferredDonePromise(opts.chatText);
2602
+ if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
2603
+ return {
2604
+ action: "wait",
2605
+ jobIds: opts.runningJobIds,
2606
+ prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
2607
+ };
2608
+ }
2609
+ if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
2610
+ return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
2611
+ }
2612
+ return { action: "none" };
2613
+ }
2614
+ function tailProgress(logFile, maxLines = 12) {
2615
+ if (!existsSync8(logFile)) return "(no log yet)";
2616
+ const lines = readFileSync7(logFile, "utf8").split("\n");
2617
+ return lines.slice(-maxLines).join("\n");
2618
+ }
2619
+ function readLogLines(file) {
2620
+ if (!existsSync8(file)) return [];
2621
+ const lines = readFileSync7(file, "utf8").split("\n");
2622
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
2623
+ return lines;
2624
+ }
2625
+ function takeDelta(logFile, cursorFile) {
2626
+ const lines = readLogLines(logFile);
2627
+ const cursor = readIntFile(cursorFile) ?? 0;
2628
+ const start = Math.min(Math.max(0, cursor), lines.length);
2629
+ return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
2630
+ }
2631
+ function snapshotJob(dir) {
2632
+ const pid = readIntFile(join10(dir, "pid"));
2633
+ const running = pid != null && jobAlive(pid);
2634
+ return {
2635
+ pid,
2636
+ running,
2637
+ exitCode: readIntFile(join10(dir, "exit")),
2638
+ log: join10(dir, "log"),
2639
+ cursor: join10(dir, "present.cursor"),
2640
+ progress: tailProgress(join10(dir, "log"))
2641
+ };
2642
+ }
2643
+ function toResult(id, snap, extra) {
2644
+ const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
2645
+ const ok = !snap.running && snap.exitCode === 0;
2646
+ const stillRunning = snap.running && !ok;
2647
+ const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
2648
+ try {
2649
+ mkdirSync3(dirname3(snap.cursor), { recursive: true });
2650
+ writeFileSync4(snap.cursor, `${nextCursor}
2651
+ `);
2652
+ } catch {
2653
+ }
2654
+ const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
2655
+ return {
2656
+ stillRunning,
2657
+ ok,
2658
+ failed: Boolean(failed && !stillRunning && !ok),
2659
+ status,
2660
+ id,
2661
+ pid: snap.pid,
2662
+ exitCode: snap.exitCode ?? void 0,
2663
+ delta,
2664
+ progress: extra?.progress ?? snap.progress,
2665
+ hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
2666
+ };
2667
+ }
2668
+ async function sleepMs(ms) {
2669
+ await new Promise((resolve) => setTimeout(resolve, ms));
2670
+ }
2671
+ async function waitForDetachedJob(cwd, id, opts) {
2672
+ const jobId = sanitizeDetachedJobId(id);
2673
+ const root = cwd.trim() || process.cwd();
2674
+ const dir = resolveJobDir(root, jobId);
2675
+ const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
2676
+ const sleep = opts?.sleep ?? sleepMs;
2677
+ if (!existsSync8(dir)) {
2678
+ return {
2679
+ stillRunning: false,
2680
+ ok: false,
2681
+ failed: true,
2682
+ status: "failed",
2683
+ id: jobId,
2684
+ delta: "",
2685
+ progress: "No detached job. Start one first.",
2686
+ hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
2687
+ };
2688
+ }
2689
+ const deadline = Date.now() + timeoutMs;
2690
+ let snap = snapshotJob(dir);
2691
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2692
+ if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
2693
+ return toResult(jobId, snap, {
2694
+ failed: true,
2695
+ progress: "No detached job. Start one first."
2696
+ });
2697
+ }
2698
+ while (Date.now() < deadline) {
2699
+ snap = snapshotJob(dir);
2700
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2701
+ if (!snap.running) return toResult(jobId, snap, { failed: true });
2702
+ await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
2703
+ }
2704
+ snap = snapshotJob(dir);
2705
+ return toResult(jobId, snap);
2706
+ }
2707
+
2482
2708
  // src/threads/create.ts
2483
- import { existsSync as existsSync8 } from "fs";
2709
+ import { existsSync as existsSync9 } from "fs";
2484
2710
 
2485
2711
  // src/detect/detect.ts
2486
2712
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
@@ -3000,7 +3226,7 @@ function reuseLiveThread(input, repoPath, match) {
3000
3226
  }
3001
3227
  async function createThread(input, _onSetupLine) {
3002
3228
  const repoPath = await resolveRepoRoot(input.repoPath);
3003
- if (!existsSync8(repoPath)) {
3229
+ if (!existsSync9(repoPath)) {
3004
3230
  throw new Error(`Repo not found: ${repoPath}`);
3005
3231
  }
3006
3232
  const reused = reuseLiveThread(input, repoPath, {
@@ -3186,11 +3412,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
3186
3412
 
3187
3413
  // src/store/turn-live.ts
3188
3414
  import {
3189
- existsSync as existsSync9,
3415
+ existsSync as existsSync10,
3190
3416
  renameSync,
3191
3417
  unlinkSync as unlinkSync2,
3192
- writeFileSync as writeFileSync4,
3193
- readFileSync as readFileSync7
3418
+ writeFileSync as writeFileSync5,
3419
+ readFileSync as readFileSync8
3194
3420
  } from "fs";
3195
3421
  var buffers = /* @__PURE__ */ new Map();
3196
3422
  var FLUSH_MS = 800;
@@ -3267,7 +3493,7 @@ function writeTurnLive(threadId, progress) {
3267
3493
  const path = threadLivePath(threadId);
3268
3494
  const tmp = `${path}.${process.pid}.tmp`;
3269
3495
  try {
3270
- writeFileSync4(tmp, JSON.stringify(progress), "utf8");
3496
+ writeFileSync5(tmp, JSON.stringify(progress), "utf8");
3271
3497
  renameSync(tmp, path);
3272
3498
  } catch {
3273
3499
  try {
@@ -3278,9 +3504,9 @@ function writeTurnLive(threadId, progress) {
3278
3504
  }
3279
3505
  function readTurnLive(threadId) {
3280
3506
  const path = threadLivePath(threadId);
3281
- if (!existsSync9(path)) return null;
3507
+ if (!existsSync10(path)) return null;
3282
3508
  try {
3283
- const raw = JSON.parse(readFileSync7(path, "utf8"));
3509
+ const raw = JSON.parse(readFileSync8(path, "utf8"));
3284
3510
  if (!raw || typeof raw.summary !== "string") return null;
3285
3511
  return raw;
3286
3512
  } catch {
@@ -3292,7 +3518,7 @@ function clearTurnLive(threadId) {
3292
3518
  if (buf?.timer) clearTimeout(buf.timer);
3293
3519
  buffers.delete(threadId);
3294
3520
  const path = threadLivePath(threadId);
3295
- if (!existsSync9(path)) return;
3521
+ if (!existsSync10(path)) return;
3296
3522
  try {
3297
3523
  unlinkSync2(path);
3298
3524
  } catch {
@@ -3477,26 +3703,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
3477
3703
  import { execFileSync } from "child_process";
3478
3704
  import {
3479
3705
  copyFileSync as copyFileSync2,
3480
- existsSync as existsSync10,
3706
+ existsSync as existsSync11,
3481
3707
  mkdtempSync,
3482
- readdirSync as readdirSync3,
3483
- readFileSync as readFileSync8,
3708
+ readdirSync as readdirSync4,
3709
+ readFileSync as readFileSync9,
3484
3710
  rmSync
3485
3711
  } from "fs";
3486
3712
  import { tmpdir } from "os";
3487
- import { join as join10 } from "path";
3713
+ import { join as join11 } from "path";
3488
3714
  import { createRequire } from "module";
3489
- var CONDUCTOR_APP_SUPPORT = join10(
3715
+ var CONDUCTOR_APP_SUPPORT = join11(
3490
3716
  process.env.HOME ?? "",
3491
3717
  "Library",
3492
3718
  "Application Support",
3493
3719
  "com.conductor.app"
3494
3720
  );
3495
- var CONDUCTOR_DB = join10(CONDUCTOR_APP_SUPPORT, "conductor.db");
3496
- var CURSOR_SDK_STORE = join10(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3721
+ var CONDUCTOR_DB = join11(CONDUCTOR_APP_SUPPORT, "conductor.db");
3722
+ var CURSOR_SDK_STORE = join11(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3497
3723
  function thisModuleFile() {
3498
3724
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
3499
- return cjsFile || process.argv[1] || join10(process.cwd(), "package.json");
3725
+ return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
3500
3726
  }
3501
3727
  function openReadonlySqlite(file) {
3502
3728
  const req = createRequire(thisModuleFile());
@@ -3514,21 +3740,21 @@ function mapAgentType(raw) {
3514
3740
  return null;
3515
3741
  }
3516
3742
  function resolveConductorCursorAgentId(workspacePath) {
3517
- if (!workspacePath || !existsSync10(CURSOR_SDK_STORE)) return null;
3743
+ if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
3518
3744
  const normalized = workspacePath.replace(/\/$/, "");
3519
3745
  let best = null;
3520
3746
  let hashes;
3521
3747
  try {
3522
- hashes = readdirSync3(CURSOR_SDK_STORE);
3748
+ hashes = readdirSync4(CURSOR_SDK_STORE);
3523
3749
  } catch {
3524
3750
  return null;
3525
3751
  }
3526
3752
  for (const hash of hashes) {
3527
- const agentsFile = join10(CURSOR_SDK_STORE, hash, "agents.ndjson");
3528
- if (!existsSync10(agentsFile)) continue;
3753
+ const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
3754
+ if (!existsSync11(agentsFile)) continue;
3529
3755
  let text;
3530
3756
  try {
3531
- text = readFileSync8(agentsFile, "utf8");
3757
+ text = readFileSync9(agentsFile, "utf8");
3532
3758
  } catch {
3533
3759
  continue;
3534
3760
  }
@@ -3552,7 +3778,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3552
3778
  return best?.agentId ?? null;
3553
3779
  }
3554
3780
  async function adoptThread(input) {
3555
- if (!existsSync10(input.worktreePath)) {
3781
+ if (!existsSync11(input.worktreePath)) {
3556
3782
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3557
3783
  }
3558
3784
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -3571,23 +3797,23 @@ async function adoptThread(input) {
3571
3797
  messages: input.messages ?? []
3572
3798
  });
3573
3799
  writeThread(thread);
3574
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-Q2VCHIAS.js");
3800
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OCEPZXFD.js");
3575
3801
  await ensureWorkspace2(repoPath);
3576
3802
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-N5X5I422.js");
3577
3803
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
3578
3804
  return thread;
3579
3805
  }
3580
3806
  function listConductorWorkspaces() {
3581
- if (!existsSync10(CONDUCTOR_DB)) {
3807
+ if (!existsSync11(CONDUCTOR_DB)) {
3582
3808
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3583
3809
  }
3584
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3585
- const snapshot = join10(tmp, "conductor.db");
3810
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3811
+ const snapshot = join11(tmp, "conductor.db");
3586
3812
  try {
3587
3813
  copyFileSync2(CONDUCTOR_DB, snapshot);
3588
3814
  for (const suffix of ["-wal", "-shm"]) {
3589
3815
  const src = `${CONDUCTOR_DB}${suffix}`;
3590
- if (existsSync10(src)) {
3816
+ if (existsSync11(src)) {
3591
3817
  try {
3592
3818
  copyFileSync2(src, `${snapshot}${suffix}`);
3593
3819
  } catch {
@@ -3669,16 +3895,16 @@ function listConductorWorkspaces() {
3669
3895
  }
3670
3896
  }
3671
3897
  function importConductorWorkspace(workspaceId) {
3672
- if (!existsSync10(CONDUCTOR_DB)) {
3898
+ if (!existsSync11(CONDUCTOR_DB)) {
3673
3899
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3674
3900
  }
3675
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3676
- const snapshot = join10(tmp, "conductor.db");
3901
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3902
+ const snapshot = join11(tmp, "conductor.db");
3677
3903
  try {
3678
3904
  copyFileSync2(CONDUCTOR_DB, snapshot);
3679
3905
  for (const suffix of ["-wal", "-shm"]) {
3680
3906
  const src = `${CONDUCTOR_DB}${suffix}`;
3681
- if (existsSync10(src)) {
3907
+ if (existsSync11(src)) {
3682
3908
  try {
3683
3909
  copyFileSync2(src, `${snapshot}${suffix}`);
3684
3910
  } catch {
@@ -3698,7 +3924,7 @@ function importConductorWorkspace(workspaceId) {
3698
3924
  ).get(workspaceId);
3699
3925
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3700
3926
  const worktreePath = String(row.workspacePath);
3701
- if (!existsSync10(worktreePath)) {
3927
+ if (!existsSync11(worktreePath)) {
3702
3928
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3703
3929
  }
3704
3930
  let sessionId = null;
@@ -3769,7 +3995,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
3769
3995
  }
3770
3996
 
3771
3997
  // src/threads/stack-layers.ts
3772
- import { existsSync as existsSync11 } from "fs";
3998
+ import { existsSync as existsSync12 } from "fs";
3773
3999
  function stackIdFrom(stack) {
3774
4000
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3775
4001
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -3832,7 +4058,7 @@ async function openStackLayer(input, _onSetupLine) {
3832
4058
  let createdWorktree = false;
3833
4059
  const trees = await listWorktrees(repoPath);
3834
4060
  const checkedOut = trees.find((w) => w.branch === branchName);
3835
- if (checkedOut?.path && existsSync11(checkedOut.path)) {
4061
+ if (checkedOut?.path && existsSync12(checkedOut.path)) {
3836
4062
  if (input.reuseExistingWorktree !== false) {
3837
4063
  worktreePath = checkedOut.path;
3838
4064
  } else {
@@ -3974,7 +4200,7 @@ async function initStackFromThread(input, onSetupLine) {
3974
4200
  async function createPrStack(input, onSetupLine) {
3975
4201
  await requireAgent(input.agent);
3976
4202
  const repoPath = await resolveRepoRoot(input.repoPath);
3977
- if (!existsSync11(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4203
+ if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3978
4204
  if (!input.branches.length) throw new Error("At least one branch name required");
3979
4205
  const status = await detectGhStack(repoPath);
3980
4206
  if (!status.available) throw new Error(status.reason);
@@ -4041,7 +4267,7 @@ async function createPrStack(input, onSetupLine) {
4041
4267
  }
4042
4268
  }
4043
4269
  const claimed = new Set(threads.map((t) => t.worktreePath));
4044
- if (!claimed.has(bootstrap.worktreePath) && existsSync11(bootstrap.worktreePath)) {
4270
+ if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
4045
4271
  try {
4046
4272
  await removeWorktree(repoPath, bootstrap.worktreePath, {
4047
4273
  deleteBranch: bootstrap.branchName
@@ -4053,10 +4279,10 @@ async function createPrStack(input, onSetupLine) {
4053
4279
  }
4054
4280
 
4055
4281
  // 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";
4282
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
4283
+ import { dirname as dirname4, join as join12 } from "path";
4058
4284
  async function inspectGitWorktree(worktreePath) {
4059
- if (!worktreePath || !existsSync12(worktreePath)) return "missing_worktree";
4285
+ if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
4060
4286
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
4061
4287
  reject: false
4062
4288
  });
@@ -4064,7 +4290,7 @@ async function inspectGitWorktree(worktreePath) {
4064
4290
  return "ok";
4065
4291
  }
4066
4292
  async function initializeGitRepository(worktreePath) {
4067
- if (!worktreePath || !existsSync12(worktreePath)) {
4293
+ if (!worktreePath || !existsSync13(worktreePath)) {
4068
4294
  throw new Error("Worktree not found");
4069
4295
  }
4070
4296
  const status = await inspectGitWorktree(worktreePath);
@@ -4200,11 +4426,11 @@ new file mode 100644
4200
4426
  };
4201
4427
  }
4202
4428
  async function untrackedPatch(worktreePath, path, maxHunk) {
4203
- const abs = join11(worktreePath, path);
4429
+ const abs = join12(worktreePath, path);
4204
4430
  try {
4205
4431
  const st = statSync2(abs);
4206
4432
  if (st.isFile() && st.size > maxHunk) {
4207
- const buf = readFileSync9(abs).subarray(0, maxHunk);
4433
+ const buf = readFileSync10(abs).subarray(0, maxHunk);
4208
4434
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
4209
4435
  }
4210
4436
  } catch {
@@ -4705,7 +4931,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
4705
4931
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4706
4932
  assertSafeRelativePath(relativePath);
4707
4933
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
4708
- const abs = join11(worktreePath, relativePath);
4934
+ const abs = join12(worktreePath, relativePath);
4709
4935
  const st = statSync2(abs);
4710
4936
  if (!st.isFile()) {
4711
4937
  throw new Error(`Not a file: ${relativePath}`);
@@ -4715,7 +4941,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4715
4941
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
4716
4942
  );
4717
4943
  }
4718
- const buf = readFileSync9(abs);
4944
+ const buf = readFileSync10(abs);
4719
4945
  return {
4720
4946
  path: relativePath,
4721
4947
  contentBase64: buf.toString("base64"),
@@ -4725,12 +4951,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4725
4951
  function readWorktreeFile(worktreePath, relativePath, opts) {
4726
4952
  assertSafeRelativePath(relativePath);
4727
4953
  const maxBytes = opts?.maxBytes ?? 2e5;
4728
- const abs = join11(worktreePath, relativePath);
4954
+ const abs = join12(worktreePath, relativePath);
4729
4955
  const st = statSync2(abs);
4730
4956
  if (!st.isFile()) {
4731
4957
  throw new Error(`Not a file: ${relativePath}`);
4732
4958
  }
4733
- const buf = readFileSync9(abs);
4959
+ const buf = readFileSync10(abs);
4734
4960
  if (isImageRelativePath(relativePath)) {
4735
4961
  const maxImageBytes = Math.max(maxBytes, 15e6);
4736
4962
  const truncated2 = buf.length > maxImageBytes;
@@ -4773,9 +4999,9 @@ function assertSafeRelativePath(relativePath) {
4773
4999
  }
4774
5000
  function writeWorktreeFile(worktreePath, relativePath, content) {
4775
5001
  assertSafeRelativePath(relativePath);
4776
- const abs = join11(worktreePath, relativePath);
4777
- mkdirSync3(dirname3(abs), { recursive: true });
4778
- writeFileSync5(abs, content, "utf8");
5002
+ const abs = join12(worktreePath, relativePath);
5003
+ mkdirSync4(dirname4(abs), { recursive: true });
5004
+ writeFileSync6(abs, content, "utf8");
4779
5005
  return { path: relativePath };
4780
5006
  }
4781
5007
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -4900,9 +5126,9 @@ async function confirmLand(thread, opts) {
4900
5126
  }
4901
5127
 
4902
5128
  // src/skills/discover.ts
4903
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5129
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
4904
5130
  import { homedir } from "os";
4905
- import { join as join12 } from "path";
5131
+ import { join as join13 } from "path";
4906
5132
 
4907
5133
  // src/skills/bundled/long-running.ts
4908
5134
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -4913,7 +5139,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
4913
5139
 
4914
5140
  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
5141
 
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.
5142
+ 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
5143
 
4918
5144
  ## Tool
4919
5145
 
@@ -4923,7 +5149,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
4923
5149
  # Start (exits in ~1s; job survives this turn)
4924
5150
  node <detached-job.js> start <id> -- <command> [args...]
4925
5151
 
4926
- # Wait \u2014 returns within ~45s even if the job is still going
5152
+ # Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
5153
+ # Shell fallback:
4927
5154
  node <detached-job.js> wait <id>
4928
5155
 
4929
5156
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -4936,7 +5163,7 @@ node <detached-job.js> status <id>
4936
5163
 
4937
5164
  Wait JSON:
4938
5165
 
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.
5166
+ - \`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
5167
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
4941
5168
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
4942
5169
 
@@ -4966,7 +5193,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
4966
5193
 
4967
5194
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
4968
5195
  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.
5196
+ 3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
4970
5197
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
4971
5198
 
4972
5199
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -5031,7 +5258,7 @@ function parseFrontmatter(content) {
5031
5258
  }
5032
5259
  function readSkill(skillMd, source) {
5033
5260
  try {
5034
- const content = readFileSync10(skillMd, "utf8");
5261
+ const content = readFileSync11(skillMd, "utf8");
5035
5262
  const { name: fmName, description } = parseFrontmatter(content);
5036
5263
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
5037
5264
  const name = fmName || dirName;
@@ -5050,17 +5277,17 @@ function readSkill(skillMd, source) {
5050
5277
  }
5051
5278
  }
5052
5279
  function scanSkillsDir(dir, source, out) {
5053
- if (!existsSync13(dir)) return;
5280
+ if (!existsSync14(dir)) return;
5054
5281
  let entries;
5055
5282
  try {
5056
- entries = readdirSync4(dir);
5283
+ entries = readdirSync5(dir);
5057
5284
  } catch {
5058
5285
  return;
5059
5286
  }
5060
5287
  for (const entry of entries) {
5061
5288
  if (entry.startsWith(".")) continue;
5062
- const skillMd = join12(dir, entry, "SKILL.md");
5063
- if (!existsSync13(skillMd)) continue;
5289
+ const skillMd = join13(dir, entry, "SKILL.md");
5290
+ if (!existsSync14(skillMd)) continue;
5064
5291
  try {
5065
5292
  if (!statSync3(skillMd).isFile()) continue;
5066
5293
  } catch {
@@ -5071,22 +5298,22 @@ function scanSkillsDir(dir, source, out) {
5071
5298
  }
5072
5299
  }
5073
5300
  function scanClaudePluginSkills(pluginsRoot, out) {
5074
- if (!existsSync13(pluginsRoot)) return;
5301
+ if (!existsSync14(pluginsRoot)) return;
5075
5302
  const walk = (dir, depth, lookingForSkillsDir) => {
5076
5303
  if (depth > 7) return;
5077
5304
  let entries;
5078
5305
  try {
5079
- entries = readdirSync4(dir);
5306
+ entries = readdirSync5(dir);
5080
5307
  } catch {
5081
5308
  return;
5082
5309
  }
5083
5310
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
5084
- const skill = readSkill(join12(dir, "SKILL.md"), "cli");
5311
+ const skill = readSkill(join13(dir, "SKILL.md"), "cli");
5085
5312
  if (skill) out.push(skill);
5086
5313
  }
5087
5314
  for (const entry of entries) {
5088
5315
  if (entry === "node_modules" || entry === ".git") continue;
5089
- const full = join12(dir, entry);
5316
+ const full = join13(dir, entry);
5090
5317
  try {
5091
5318
  if (!statSync3(full).isDirectory()) continue;
5092
5319
  } catch {
@@ -5106,17 +5333,17 @@ function discoverSkills(worktreePath) {
5106
5333
  const home = homedir();
5107
5334
  const collected = [];
5108
5335
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
5109
- scanSkillsDir(join12(worktreePath, rel), "workspace", collected);
5336
+ scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
5110
5337
  }
5111
5338
  for (const abs of [
5112
- join12(home, ".claude/skills"),
5113
- join12(home, ".cursor/skills"),
5114
- join12(home, ".sideboard/skills"),
5115
- join12(home, ".brightsy/skills")
5339
+ join13(home, ".claude/skills"),
5340
+ join13(home, ".cursor/skills"),
5341
+ join13(home, ".sideboard/skills"),
5342
+ join13(home, ".brightsy/skills")
5116
5343
  ]) {
5117
5344
  scanSkillsDir(abs, "user", collected);
5118
5345
  }
5119
- scanClaudePluginSkills(join12(home, ".claude/plugins"), collected);
5346
+ scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
5120
5347
  collected.push(...bundledSkills());
5121
5348
  const rank = {
5122
5349
  workspace: 0,
@@ -5142,7 +5369,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
5142
5369
  \u2026(truncated)` : body;
5143
5370
  }
5144
5371
  }
5145
- const raw = readFileSync10(skillPath, "utf8");
5372
+ const raw = readFileSync11(skillPath, "utf8");
5146
5373
  if (raw.startsWith("---")) {
5147
5374
  const end = raw.indexOf("\n---", 3);
5148
5375
  if (end >= 0) {
@@ -5235,27 +5462,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
5235
5462
  }
5236
5463
 
5237
5464
  // src/agents/instructions.ts
5238
- import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5239
- import { join as join14 } from "path";
5465
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
5466
+ import { join as join15 } from "path";
5240
5467
 
5241
5468
  // src/skills/detached-job-path.ts
5242
- import { existsSync as existsSync14 } from "fs";
5243
- import { dirname as dirname4, join as join13 } from "path";
5469
+ import { existsSync as existsSync15 } from "fs";
5470
+ import { dirname as dirname5, join as join14 } from "path";
5244
5471
  import { fileURLToPath } from "url";
5245
5472
  function packagedDetachedJobPath() {
5246
5473
  const dir = packagedMcpDir();
5247
5474
  if (!dir) return null;
5248
- const script = join13(dir, "scripts", "detached-job.js");
5249
- return existsSync14(script) ? script : null;
5475
+ const script = join14(dir, "scripts", "detached-job.js");
5476
+ return existsSync15(script) ? script : null;
5250
5477
  }
5251
5478
  function resolveDetachedJobScript() {
5252
5479
  const packaged = packagedDetachedJobPath();
5253
5480
  if (packaged) return packaged;
5254
- let dir = dirname4(fileURLToPath(import.meta.url));
5481
+ let dir = dirname5(fileURLToPath(import.meta.url));
5255
5482
  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);
5483
+ const candidate = join14(dir, "scripts", "detached-job.js");
5484
+ if (existsSync15(candidate)) return candidate;
5485
+ const parent = dirname5(dir);
5259
5486
  if (parent === dir) break;
5260
5487
  dir = parent;
5261
5488
  }
@@ -5461,14 +5688,15 @@ function formatLongRunningDirective(opts) {
5461
5688
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
5462
5689
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5463
5690
  "- 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.`,
5691
+ `- 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.`,
5692
+ "- 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
5693
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
5466
5694
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5467
5695
  ].join("\n");
5468
5696
  }
5469
5697
  function formatLongRunningReminder(opts) {
5470
5698
  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.`;
5699
+ 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
5700
  }
5473
5701
  function formatArtifactDirective() {
5474
5702
  return [
@@ -5616,8 +5844,8 @@ async function syncThreadBranchFromGit(threadId) {
5616
5844
 
5617
5845
  // src/store/schedules.ts
5618
5846
  import { randomUUID as randomUUID4 } from "crypto";
5619
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
5620
- import { join as join15 } from "path";
5847
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
5848
+ import { join as join16 } from "path";
5621
5849
  import { Cron } from "croner";
5622
5850
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5623
5851
  var UNIT_MS = {
@@ -5627,7 +5855,7 @@ var UNIT_MS = {
5627
5855
  d: 864e5
5628
5856
  };
5629
5857
  function schedulesPath() {
5630
- return join15(appDataDir(), "schedules.json");
5858
+ return join16(appDataDir(), "schedules.json");
5631
5859
  }
5632
5860
  function parseDurationMs(every) {
5633
5861
  const m = every.trim().match(DURATION_RE);
@@ -5727,9 +5955,9 @@ function normalizeTask(raw) {
5727
5955
  }
5728
5956
  function readAll() {
5729
5957
  const path = schedulesPath();
5730
- if (!existsSync16(path)) return [];
5958
+ if (!existsSync17(path)) return [];
5731
5959
  try {
5732
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5960
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
5733
5961
  if (!Array.isArray(parsed)) return [];
5734
5962
  return parsed.map((row) => {
5735
5963
  try {
@@ -5852,7 +6080,7 @@ function formatScheduledPrompt(name, prompt) {
5852
6080
  ${prompt}`;
5853
6081
  }
5854
6082
  async function defaultDeps() {
5855
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-KQO4MXBI.js");
6083
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-J727WIJY.js");
5856
6084
  const orch = getOrchestrator2();
5857
6085
  return {
5858
6086
  findThread: (id) => findThreadByRef(id),
@@ -6009,6 +6237,9 @@ var Orchestrator = class {
6009
6237
  * finish or a user send so a later crash can recover again.
6010
6238
  */
6011
6239
  crashContinued = /* @__PURE__ */ new Set();
6240
+ /** Auto-continues after a worktree turn ended while a detached job still runs. */
6241
+ jobContinueCount = /* @__PURE__ */ new Map();
6242
+ jobContinueNudged = /* @__PURE__ */ new Set();
6012
6243
  maxConcurrent;
6013
6244
  runningCount = 0;
6014
6245
  constructor(opts) {
@@ -6092,7 +6323,7 @@ var Orchestrator = class {
6092
6323
  }
6093
6324
  continue;
6094
6325
  }
6095
- if (!existsSync17(thread.worktreePath)) {
6326
+ if (!existsSync18(thread.worktreePath)) {
6096
6327
  setStatus(thread.id, "broken", "Worktree missing on disk");
6097
6328
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
6098
6329
  continue;
@@ -6307,6 +6538,37 @@ var Orchestrator = class {
6307
6538
  this.emit({ type: "queue_changed", threadId, queue });
6308
6539
  this.haltDrain.delete(threadId);
6309
6540
  }
6541
+ /**
6542
+ * Worktree agent ended the turn after “I’ll let you know” (or left a
6543
+ * detached test/pack job running). Queue a continue so the chat does not
6544
+ * go idle with nobody watching the log.
6545
+ */
6546
+ maybeEnqueueJobContinue(threadId, chatText, parts = []) {
6547
+ if (this.haltDrain.has(threadId)) return;
6548
+ const thread = readThread(threadId);
6549
+ if (!thread || thread.status === "archived") return;
6550
+ const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
6551
+ const decision = planJobContinue({
6552
+ runningJobIds,
6553
+ chatText,
6554
+ queueLength: thread.queue.length,
6555
+ continueCount: this.jobContinueCount.get(threadId) ?? 0,
6556
+ alreadyNudged: this.jobContinueNudged.has(threadId),
6557
+ isOrchestrator: isOrchestratorThread(thread),
6558
+ agent: thread.agent,
6559
+ watchedJob: turnWatchedDetachedJob(parts)
6560
+ });
6561
+ if (decision.action === "none") {
6562
+ if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
6563
+ return;
6564
+ }
6565
+ if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
6566
+ else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
6567
+ const queue = [decision.prompt, ...thread.queue];
6568
+ updateThread(threadId, { queue });
6569
+ this.emit({ type: "queue_changed", threadId, queue });
6570
+ this.haltDrain.delete(threadId);
6571
+ }
6310
6572
  getThreads(includeArchived = false) {
6311
6573
  return listThreads({ includeArchived });
6312
6574
  }
@@ -6406,6 +6668,8 @@ var Orchestrator = class {
6406
6668
  }
6407
6669
  const queue = [...current.queue, prompt];
6408
6670
  this.crashContinued.delete(thread.id);
6671
+ this.jobContinueCount.delete(thread.id);
6672
+ this.jobContinueNudged.delete(thread.id);
6409
6673
  this.haltDrain.delete(thread.id);
6410
6674
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
6411
6675
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -6920,6 +7184,7 @@ var Orchestrator = class {
6920
7184
  this.emit({ type: "turn_finished", threadId, exitCode });
6921
7185
  if (exitCode === 0) {
6922
7186
  this.crashContinued.delete(threadId);
7187
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
6923
7188
  } else {
6924
7189
  const blob = [chatText, detail].filter(Boolean).join("\n");
6925
7190
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -7872,7 +8137,7 @@ var Orchestrator = class {
7872
8137
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7873
8138
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7874
8139
  try {
7875
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-Q2VCHIAS.js");
8140
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OCEPZXFD.js");
7876
8141
  await ensureWorkspace2(thread.repoPath);
7877
8142
  } catch {
7878
8143
  }
@@ -7920,13 +8185,13 @@ var Orchestrator = class {
7920
8185
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
7921
8186
  return restored2;
7922
8187
  }
7923
- if (!existsSync17(thread.worktreePath)) {
8188
+ if (!existsSync18(thread.worktreePath)) {
7924
8189
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
7925
8190
  throw new Error(
7926
8191
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
7927
8192
  );
7928
8193
  }
7929
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-PEWDAX5O.js");
8194
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-VTMBZKY6.js");
7930
8195
  const slug = thread.worktreePath.split("/").pop();
7931
8196
  const dest = thread.worktreePath;
7932
8197
  await withRepoGitLock(thread.repoPath, async () => {
@@ -8036,7 +8301,7 @@ async function startOrchestration(opts) {
8036
8301
  sourceRef: "default",
8037
8302
  ...createOpts
8038
8303
  }).catch(async () => {
8039
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-PEWDAX5O.js");
8304
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-VTMBZKY6.js");
8040
8305
  const repo = await resolveRepoRoot2(repoPath);
8041
8306
  const def = await resolveDefaultBranch2(repo);
8042
8307
  return orch.createThread({
@@ -8066,6 +8331,11 @@ export {
8066
8331
  recordSlackOutboundWatch,
8067
8332
  pollSlackOutboundWatches,
8068
8333
  AGENT_GIT_ACTIONS,
8334
+ mcpWaitForTurnTimeoutMs,
8335
+ mcpWaitStillRunningHint,
8336
+ mcpWaitFinishedHint,
8337
+ mcpWaitForJobTimeoutMs,
8338
+ waitForDetachedJob,
8069
8339
  BOARD_COLUMN_DEFS,
8070
8340
  HOME_BOARD_CACHE_TTL_MS,
8071
8341
  dedupeBoardIssues,