@sideboard-ai/core 0.1.156 → 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.
Files changed (32) hide show
  1. package/dist/{abletime-JSTJLXAW.js → abletime-BEIDJG7W.js} +5 -1
  2. package/dist/{abletime-H7LPGIP2.js → abletime-GWZE6OYM.js} +5 -1
  3. package/dist/{agents-IY4CSPWV.js → agents-E3ZPWZRS.js} +2 -2
  4. package/dist/{agents-2FTZAXO3.js → agents-RGE7PSUY.js} +4 -4
  5. package/dist/{chunk-TI6UMRWK.js → chunk-3SUZMPGL.js} +5 -4
  6. package/dist/{chunk-NVXVEGGM.js → chunk-5U4NZLBH.js} +4 -0
  7. package/dist/{chunk-CJQLPPEJ.js → chunk-A7QXLYBN.js} +1 -1
  8. package/dist/{chunk-XLU7DXHH.js → chunk-BLRHO7XQ.js} +54 -5
  9. package/dist/{chunk-MSFPM5VK.js → chunk-CVR4RJ6G.js} +3 -2
  10. package/dist/{chunk-JOK4XWBG.js → chunk-DVJBO3M7.js} +87 -3
  11. package/dist/{chunk-UA3YZKRX.js → chunk-EPQIOAAY.js} +25 -5
  12. package/dist/{chunk-OLE2BLIX.js → chunk-FBFIOVDH.js} +457 -106
  13. package/dist/{chunk-2SDMENAL.js → chunk-JV4VNFMS.js} +1 -1
  14. package/dist/{chunk-7EIPEPAH.js → chunk-KYKNFJK5.js} +484 -105
  15. package/dist/{chunk-FDCFXMDG.js → chunk-N27GVFZY.js} +87 -3
  16. package/dist/{coordinator-prompt-D2FHU5IZ.js → coordinator-prompt-26IJU2AV.js} +1 -1
  17. package/dist/{coordinator-prompt-UZDNIVP7.js → coordinator-prompt-GZESIQNH.js} +3 -3
  18. package/dist/{global-workspace-5WZVSWNT.js → global-workspace-5BIW26YR.js} +1 -1
  19. package/dist/{global-workspace-MR75BDBY.js → global-workspace-REI55FZ6.js} +3 -3
  20. package/dist/index.cjs +1405 -560
  21. package/dist/index.d.cts +138 -4
  22. package/dist/index.d.ts +138 -4
  23. package/dist/index.js +609 -274
  24. package/dist/mcp/run-stdio.cjs +1253 -500
  25. package/dist/mcp/run-stdio.js +525 -226
  26. package/dist/{orchestrator-LXO2CDOG.js → orchestrator-J727WIJY.js} +6 -6
  27. package/dist/{orchestrator-G4WKJG5Y.js → orchestrator-ZVW2AC6B.js} +3 -3
  28. package/dist/{plan-file-OZEBFSGQ.js → plan-file-I24VJVP2.js} +2 -2
  29. package/dist/{workspaces-RP32AW6X.js → workspaces-NYSM2EPX.js} +1 -1
  30. package/dist/{workspaces-HO4EQNNM.js → workspaces-OCEPZXFD.js} +3 -3
  31. package/dist/{worktree-PEWDAX5O.js → worktree-VTMBZKY6.js} +2 -2
  32. package/package.json +1 -1
@@ -24,7 +24,7 @@ import {
24
24
  stripBrightsyNdjsonNoise,
25
25
  sumUsageList,
26
26
  toolDescription
27
- } from "./chunk-XLU7DXHH.js";
27
+ } from "./chunk-BLRHO7XQ.js";
28
28
  import {
29
29
  SLACK_REPLY_FORMATTING,
30
30
  addWorkspace,
@@ -50,7 +50,7 @@ import {
50
50
  stageAbsolutePathsAsAttachments,
51
51
  stageBuffersAsAttachments,
52
52
  syncWorkspacesFromThreads
53
- } from "./chunk-MSFPM5VK.js";
53
+ } from "./chunk-CVR4RJ6G.js";
54
54
  import {
55
55
  addPrStackLayer,
56
56
  allocateTeamName,
@@ -101,7 +101,9 @@ import {
101
101
  } from "./chunk-LQMFCS54.js";
102
102
  import {
103
103
  ATTACHMENTS_DIR,
104
- LEGACY_ATTACHMENTS_DIR
104
+ DETACHED_JOBS_DIR,
105
+ LEGACY_ATTACHMENTS_DIR,
106
+ LEGACY_DETACHED_JOBS_DIR
105
107
  } from "./chunk-JRH62XC4.js";
106
108
  import {
107
109
  httpFetch
@@ -128,11 +130,13 @@ import {
128
130
  childEnvWithAppSettings,
129
131
  getIssueSource,
130
132
  isAbleTimeConnected,
133
+ isLinearConnected,
131
134
  isSecureFileEncrypted,
132
135
  loadAppSettings,
133
136
  orchestrationQuotaFallbackAgent,
134
137
  orchestrationQuotaOnLimit,
135
138
  readSecureJson,
139
+ resolveEffectiveIssueSource,
136
140
  resolveNewThreadOptions,
137
141
  resolveThreadDefaults,
138
142
  resolveVaultKey,
@@ -457,7 +461,7 @@ async function continueSourceThread(threadId, prompt) {
457
461
  await continueOnReply(threadId, prompt);
458
462
  return;
459
463
  }
460
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-G4WKJG5Y.js");
464
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-ZVW2AC6B.js");
461
465
  await getOrchestrator2().send(threadId, prompt);
462
466
  } catch {
463
467
  }
@@ -717,7 +721,7 @@ async function pollSlackOutboundWatches(opts) {
717
721
  }
718
722
 
719
723
  // src/orchestrator/orchestrator.ts
720
- import { existsSync as existsSync17 } from "fs";
724
+ import { existsSync as existsSync18 } from "fs";
721
725
 
722
726
  // src/agents/spawn.ts
723
727
  import { createInterface } from "readline";
@@ -737,9 +741,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
737
741
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
738
742
  );
739
743
  }
740
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-5WZVSWNT.js");
744
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-5BIW26YR.js");
741
745
  if (isGlobalThread2(thread)) {
742
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-D2FHU5IZ.js");
746
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-26IJU2AV.js");
743
747
  ensureGlobalCoordinatorCwd2(
744
748
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
745
749
  );
@@ -2562,8 +2566,232 @@ function notifyParentOfChildHalt(child, status, send) {
2562
2566
  return true;
2563
2567
  }
2564
2568
 
2569
+ // src/mcp/wait-for-job.ts
2570
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2571
+ import { dirname as dirname3, join as join10 } from "path";
2572
+
2573
+ // src/mcp/wait-for-turn.ts
2574
+ var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
2575
+ function mcpWaitForTurnTimeoutMs(requested) {
2576
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
2577
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
2578
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
2579
+ }
2580
+ 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.";
2581
+ 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.";
2582
+ function mcpWaitStillRunningHint(status) {
2583
+ return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
2584
+ }
2585
+ 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.";
2586
+ var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
2587
+ 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.";
2588
+ function mcpWaitFinishedHint(status) {
2589
+ if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
2590
+ if (status === "broken") return MCP_WAIT_BROKEN_HINT;
2591
+ if (status === "error") return MCP_WAIT_ERROR_HINT;
2592
+ return void 0;
2593
+ }
2594
+
2595
+ // src/mcp/wait-for-job.ts
2596
+ var MAX_JOB_CONTINUES = 8;
2597
+ 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.";
2598
+ var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
2599
+ function mcpWaitForJobTimeoutMs(requested) {
2600
+ return mcpWaitForTurnTimeoutMs(requested);
2601
+ }
2602
+ function sanitizeDetachedJobId(id) {
2603
+ const s = id.trim();
2604
+ if (!JOB_ID_RE.test(s)) {
2605
+ throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
2606
+ }
2607
+ return s;
2608
+ }
2609
+ function jobAlive(pid) {
2610
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2611
+ try {
2612
+ process.kill(pid, 0);
2613
+ return true;
2614
+ } catch {
2615
+ return false;
2616
+ }
2617
+ }
2618
+ function readIntFile(file) {
2619
+ if (!existsSync8(file)) return null;
2620
+ const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
2621
+ return Number.isInteger(n) ? n : null;
2622
+ }
2623
+ function jobDir(root, id, legacy = false) {
2624
+ return join10(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
2625
+ }
2626
+ function resolveJobDir(root, id) {
2627
+ const modern = jobDir(root, id, false);
2628
+ if (existsSync8(modern)) return modern;
2629
+ const legacy = jobDir(root, id, true);
2630
+ if (existsSync8(legacy)) return legacy;
2631
+ return modern;
2632
+ }
2633
+ function listJobIdsIn(root, rel) {
2634
+ const dir = join10(root, rel);
2635
+ if (!existsSync8(dir)) return [];
2636
+ return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
2637
+ }
2638
+ function listRunningDetachedJobs(worktreePath) {
2639
+ const root = worktreePath.trim();
2640
+ if (!root) return [];
2641
+ const ids = /* @__PURE__ */ new Set([
2642
+ ...listJobIdsIn(root, DETACHED_JOBS_DIR),
2643
+ ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
2644
+ ]);
2645
+ const running = [];
2646
+ for (const id of ids) {
2647
+ const dir = resolveJobDir(root, id);
2648
+ const pid = readIntFile(join10(dir, "pid"));
2649
+ if (pid != null && jobAlive(pid)) running.push(id);
2650
+ }
2651
+ return running.sort();
2652
+ }
2653
+ function looksLikeDeferredDonePromise(text) {
2654
+ const t = (text ?? "").trim();
2655
+ if (!t) return false;
2656
+ 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);
2657
+ }
2658
+ function formatJobStillRunningContinuePrompt(jobIds) {
2659
+ const ids = jobIds.join(", ");
2660
+ return [
2661
+ `Detached job still running: ${ids}.`,
2662
+ "Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
2663
+ "Then report the result. Do not tell the user you will let them know later."
2664
+ ].join(" ");
2665
+ }
2666
+ function formatDeferredDoneContinuePrompt() {
2667
+ return [
2668
+ "You ended the turn after promising to report later, but no detached job is running.",
2669
+ "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.",
2670
+ "Do not say you will let the user know later."
2671
+ ].join(" ");
2672
+ }
2673
+ function turnWatchedDetachedJob(parts) {
2674
+ return parts.some((p) => {
2675
+ if (p.type !== "tool") return false;
2676
+ if (/wait_for_job$/i.test(p.name ?? "")) return true;
2677
+ const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
2678
+ return /detached-job\.js\b/i.test(blob);
2679
+ });
2680
+ }
2681
+ function planJobContinue(opts) {
2682
+ if (opts.isOrchestrator) return { action: "none" };
2683
+ if (opts.agent === "brightsy") return { action: "none" };
2684
+ if (opts.queueLength > 0) return { action: "none" };
2685
+ if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
2686
+ const farewell = looksLikeDeferredDonePromise(opts.chatText);
2687
+ if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
2688
+ return {
2689
+ action: "wait",
2690
+ jobIds: opts.runningJobIds,
2691
+ prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
2692
+ };
2693
+ }
2694
+ if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
2695
+ return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
2696
+ }
2697
+ return { action: "none" };
2698
+ }
2699
+ function tailProgress(logFile, maxLines = 12) {
2700
+ if (!existsSync8(logFile)) return "(no log yet)";
2701
+ const lines = readFileSync7(logFile, "utf8").split("\n");
2702
+ return lines.slice(-maxLines).join("\n");
2703
+ }
2704
+ function readLogLines(file) {
2705
+ if (!existsSync8(file)) return [];
2706
+ const lines = readFileSync7(file, "utf8").split("\n");
2707
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
2708
+ return lines;
2709
+ }
2710
+ function takeDelta(logFile, cursorFile) {
2711
+ const lines = readLogLines(logFile);
2712
+ const cursor = readIntFile(cursorFile) ?? 0;
2713
+ const start = Math.min(Math.max(0, cursor), lines.length);
2714
+ return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
2715
+ }
2716
+ function snapshotJob(dir) {
2717
+ const pid = readIntFile(join10(dir, "pid"));
2718
+ const running = pid != null && jobAlive(pid);
2719
+ return {
2720
+ pid,
2721
+ running,
2722
+ exitCode: readIntFile(join10(dir, "exit")),
2723
+ log: join10(dir, "log"),
2724
+ cursor: join10(dir, "present.cursor"),
2725
+ progress: tailProgress(join10(dir, "log"))
2726
+ };
2727
+ }
2728
+ function toResult(id, snap, extra) {
2729
+ const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
2730
+ const ok = !snap.running && snap.exitCode === 0;
2731
+ const stillRunning = snap.running && !ok;
2732
+ const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
2733
+ try {
2734
+ mkdirSync3(dirname3(snap.cursor), { recursive: true });
2735
+ writeFileSync4(snap.cursor, `${nextCursor}
2736
+ `);
2737
+ } catch {
2738
+ }
2739
+ const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
2740
+ return {
2741
+ stillRunning,
2742
+ ok,
2743
+ failed: Boolean(failed && !stillRunning && !ok),
2744
+ status,
2745
+ id,
2746
+ pid: snap.pid,
2747
+ exitCode: snap.exitCode ?? void 0,
2748
+ delta,
2749
+ progress: extra?.progress ?? snap.progress,
2750
+ hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
2751
+ };
2752
+ }
2753
+ async function sleepMs(ms) {
2754
+ await new Promise((resolve) => setTimeout(resolve, ms));
2755
+ }
2756
+ async function waitForDetachedJob(cwd, id, opts) {
2757
+ const jobId = sanitizeDetachedJobId(id);
2758
+ const root = cwd.trim() || process.cwd();
2759
+ const dir = resolveJobDir(root, jobId);
2760
+ const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
2761
+ const sleep = opts?.sleep ?? sleepMs;
2762
+ if (!existsSync8(dir)) {
2763
+ return {
2764
+ stillRunning: false,
2765
+ ok: false,
2766
+ failed: true,
2767
+ status: "failed",
2768
+ id: jobId,
2769
+ delta: "",
2770
+ progress: "No detached job. Start one first.",
2771
+ hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
2772
+ };
2773
+ }
2774
+ const deadline = Date.now() + timeoutMs;
2775
+ let snap = snapshotJob(dir);
2776
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2777
+ if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
2778
+ return toResult(jobId, snap, {
2779
+ failed: true,
2780
+ progress: "No detached job. Start one first."
2781
+ });
2782
+ }
2783
+ while (Date.now() < deadline) {
2784
+ snap = snapshotJob(dir);
2785
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2786
+ if (!snap.running) return toResult(jobId, snap, { failed: true });
2787
+ await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
2788
+ }
2789
+ snap = snapshotJob(dir);
2790
+ return toResult(jobId, snap);
2791
+ }
2792
+
2565
2793
  // src/threads/create.ts
2566
- import { existsSync as existsSync8 } from "fs";
2794
+ import { existsSync as existsSync9 } from "fs";
2567
2795
 
2568
2796
  // src/detect/detect.ts
2569
2797
  async function detectAgents() {
@@ -3125,7 +3353,7 @@ function reuseLiveThread(input, repoPath, match) {
3125
3353
  }
3126
3354
  async function createThread(input, _onSetupLine) {
3127
3355
  const repoPath = await resolveRepoRoot(input.repoPath);
3128
- if (!existsSync8(repoPath)) {
3356
+ if (!existsSync9(repoPath)) {
3129
3357
  throw new Error(`Repo not found: ${repoPath}`);
3130
3358
  }
3131
3359
  const reused = reuseLiveThread(input, repoPath, {
@@ -3200,7 +3428,7 @@ async function createThread(input, _onSetupLine) {
3200
3428
  let sourceIsFork = false;
3201
3429
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
3202
3430
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
3203
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-H7LPGIP2.js");
3431
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-GWZE6OYM.js");
3204
3432
  const task = await ensureAbleTimeTask({
3205
3433
  title: title?.trim() || "Untitled work",
3206
3434
  description: input.prompt?.trim() || void 0
@@ -3297,7 +3525,7 @@ async function createThread(input, _onSetupLine) {
3297
3525
  return readThread(thread.id) ?? thread;
3298
3526
  }
3299
3527
  async function listLinearIssues(agent, repoPath) {
3300
- const { getAdapter: getAdapter2 } = await import("./agents-IY4CSPWV.js");
3528
+ const { getAdapter: getAdapter2 } = await import("./agents-E3ZPWZRS.js");
3301
3529
  await requireAgent(agent, { requireLinear: true });
3302
3530
  const adapter = getAdapter2(agent);
3303
3531
  if (!adapter.listLinearIssues) {
@@ -3320,11 +3548,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
3320
3548
 
3321
3549
  // src/store/turn-live.ts
3322
3550
  import {
3323
- existsSync as existsSync9,
3551
+ existsSync as existsSync10,
3324
3552
  renameSync,
3325
3553
  unlinkSync as unlinkSync2,
3326
- writeFileSync as writeFileSync4,
3327
- readFileSync as readFileSync7
3554
+ writeFileSync as writeFileSync5,
3555
+ readFileSync as readFileSync8
3328
3556
  } from "fs";
3329
3557
  var buffers = /* @__PURE__ */ new Map();
3330
3558
  var FLUSH_MS = 800;
@@ -3401,7 +3629,7 @@ function writeTurnLive(threadId, progress) {
3401
3629
  const path = threadLivePath(threadId);
3402
3630
  const tmp = `${path}.${process.pid}.tmp`;
3403
3631
  try {
3404
- writeFileSync4(tmp, JSON.stringify(progress), "utf8");
3632
+ writeFileSync5(tmp, JSON.stringify(progress), "utf8");
3405
3633
  renameSync(tmp, path);
3406
3634
  } catch {
3407
3635
  try {
@@ -3412,9 +3640,9 @@ function writeTurnLive(threadId, progress) {
3412
3640
  }
3413
3641
  function readTurnLive(threadId) {
3414
3642
  const path = threadLivePath(threadId);
3415
- if (!existsSync9(path)) return null;
3643
+ if (!existsSync10(path)) return null;
3416
3644
  try {
3417
- const raw = JSON.parse(readFileSync7(path, "utf8"));
3645
+ const raw = JSON.parse(readFileSync8(path, "utf8"));
3418
3646
  if (!raw || typeof raw.summary !== "string") return null;
3419
3647
  return raw;
3420
3648
  } catch {
@@ -3426,7 +3654,7 @@ function clearTurnLive(threadId) {
3426
3654
  if (buf?.timer) clearTimeout(buf.timer);
3427
3655
  buffers.delete(threadId);
3428
3656
  const path = threadLivePath(threadId);
3429
- if (!existsSync9(path)) return;
3657
+ if (!existsSync10(path)) return;
3430
3658
  try {
3431
3659
  unlinkSync2(path);
3432
3660
  } catch {
@@ -3614,26 +3842,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
3614
3842
  import { execFileSync } from "child_process";
3615
3843
  import {
3616
3844
  copyFileSync as copyFileSync2,
3617
- existsSync as existsSync10,
3845
+ existsSync as existsSync11,
3618
3846
  mkdtempSync,
3619
- readdirSync as readdirSync3,
3620
- readFileSync as readFileSync8,
3847
+ readdirSync as readdirSync4,
3848
+ readFileSync as readFileSync9,
3621
3849
  rmSync
3622
3850
  } from "fs";
3623
3851
  import { tmpdir } from "os";
3624
- import { join as join10 } from "path";
3852
+ import { join as join11 } from "path";
3625
3853
  import { createRequire } from "module";
3626
- var CONDUCTOR_APP_SUPPORT = join10(
3854
+ var CONDUCTOR_APP_SUPPORT = join11(
3627
3855
  process.env.HOME ?? "",
3628
3856
  "Library",
3629
3857
  "Application Support",
3630
3858
  "com.conductor.app"
3631
3859
  );
3632
- var CONDUCTOR_DB = join10(CONDUCTOR_APP_SUPPORT, "conductor.db");
3633
- var CURSOR_SDK_STORE = join10(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3860
+ var CONDUCTOR_DB = join11(CONDUCTOR_APP_SUPPORT, "conductor.db");
3861
+ var CURSOR_SDK_STORE = join11(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
3634
3862
  function thisModuleFile() {
3635
3863
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
3636
- return cjsFile || process.argv[1] || join10(process.cwd(), "package.json");
3864
+ return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
3637
3865
  }
3638
3866
  function openReadonlySqlite(file) {
3639
3867
  const req = createRequire(thisModuleFile());
@@ -3651,21 +3879,21 @@ function mapAgentType(raw) {
3651
3879
  return null;
3652
3880
  }
3653
3881
  function resolveConductorCursorAgentId(workspacePath) {
3654
- if (!workspacePath || !existsSync10(CURSOR_SDK_STORE)) return null;
3882
+ if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
3655
3883
  const normalized = workspacePath.replace(/\/$/, "");
3656
3884
  let best = null;
3657
3885
  let hashes;
3658
3886
  try {
3659
- hashes = readdirSync3(CURSOR_SDK_STORE);
3887
+ hashes = readdirSync4(CURSOR_SDK_STORE);
3660
3888
  } catch {
3661
3889
  return null;
3662
3890
  }
3663
3891
  for (const hash of hashes) {
3664
- const agentsFile = join10(CURSOR_SDK_STORE, hash, "agents.ndjson");
3665
- if (!existsSync10(agentsFile)) continue;
3892
+ const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
3893
+ if (!existsSync11(agentsFile)) continue;
3666
3894
  let text;
3667
3895
  try {
3668
- text = readFileSync8(agentsFile, "utf8");
3896
+ text = readFileSync9(agentsFile, "utf8");
3669
3897
  } catch {
3670
3898
  continue;
3671
3899
  }
@@ -3689,7 +3917,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3689
3917
  return best?.agentId ?? null;
3690
3918
  }
3691
3919
  async function adoptThread(input) {
3692
- if (!existsSync10(input.worktreePath)) {
3920
+ if (!existsSync11(input.worktreePath)) {
3693
3921
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3694
3922
  }
3695
3923
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -3708,7 +3936,7 @@ async function adoptThread(input) {
3708
3936
  messages: input.messages ?? []
3709
3937
  });
3710
3938
  writeThread(thread);
3711
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-RP32AW6X.js");
3939
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NYSM2EPX.js");
3712
3940
  await ensureWorkspace2(repoPath);
3713
3941
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-X54WSOXM.js");
3714
3942
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
@@ -3718,16 +3946,16 @@ function conductorDbPath() {
3718
3946
  return CONDUCTOR_DB;
3719
3947
  }
3720
3948
  function listConductorWorkspaces() {
3721
- if (!existsSync10(CONDUCTOR_DB)) {
3949
+ if (!existsSync11(CONDUCTOR_DB)) {
3722
3950
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3723
3951
  }
3724
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3725
- const snapshot = join10(tmp, "conductor.db");
3952
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3953
+ const snapshot = join11(tmp, "conductor.db");
3726
3954
  try {
3727
3955
  copyFileSync2(CONDUCTOR_DB, snapshot);
3728
3956
  for (const suffix of ["-wal", "-shm"]) {
3729
3957
  const src = `${CONDUCTOR_DB}${suffix}`;
3730
- if (existsSync10(src)) {
3958
+ if (existsSync11(src)) {
3731
3959
  try {
3732
3960
  copyFileSync2(src, `${snapshot}${suffix}`);
3733
3961
  } catch {
@@ -3809,16 +4037,16 @@ function listConductorWorkspaces() {
3809
4037
  }
3810
4038
  }
3811
4039
  function importConductorWorkspace(workspaceId) {
3812
- if (!existsSync10(CONDUCTOR_DB)) {
4040
+ if (!existsSync11(CONDUCTOR_DB)) {
3813
4041
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3814
4042
  }
3815
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3816
- const snapshot = join10(tmp, "conductor.db");
4043
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
4044
+ const snapshot = join11(tmp, "conductor.db");
3817
4045
  try {
3818
4046
  copyFileSync2(CONDUCTOR_DB, snapshot);
3819
4047
  for (const suffix of ["-wal", "-shm"]) {
3820
4048
  const src = `${CONDUCTOR_DB}${suffix}`;
3821
- if (existsSync10(src)) {
4049
+ if (existsSync11(src)) {
3822
4050
  try {
3823
4051
  copyFileSync2(src, `${snapshot}${suffix}`);
3824
4052
  } catch {
@@ -3838,7 +4066,7 @@ function importConductorWorkspace(workspaceId) {
3838
4066
  ).get(workspaceId);
3839
4067
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3840
4068
  const worktreePath = String(row.workspacePath);
3841
- if (!existsSync10(worktreePath)) {
4069
+ if (!existsSync11(worktreePath)) {
3842
4070
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3843
4071
  }
3844
4072
  let sessionId = null;
@@ -3909,7 +4137,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
3909
4137
  }
3910
4138
 
3911
4139
  // src/threads/stack-layers.ts
3912
- import { existsSync as existsSync11 } from "fs";
4140
+ import { existsSync as existsSync12 } from "fs";
3913
4141
  function stackIdFrom(stack) {
3914
4142
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3915
4143
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -3972,7 +4200,7 @@ async function openStackLayer(input, _onSetupLine) {
3972
4200
  let createdWorktree = false;
3973
4201
  const trees = await listWorktrees(repoPath);
3974
4202
  const checkedOut = trees.find((w) => w.branch === branchName);
3975
- if (checkedOut?.path && existsSync11(checkedOut.path)) {
4203
+ if (checkedOut?.path && existsSync12(checkedOut.path)) {
3976
4204
  if (input.reuseExistingWorktree !== false) {
3977
4205
  worktreePath = checkedOut.path;
3978
4206
  } else {
@@ -4114,7 +4342,7 @@ async function initStackFromThread(input, onSetupLine) {
4114
4342
  async function createPrStack(input, onSetupLine) {
4115
4343
  await requireAgent(input.agent);
4116
4344
  const repoPath = await resolveRepoRoot(input.repoPath);
4117
- if (!existsSync11(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4345
+ if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4118
4346
  if (!input.branches.length) throw new Error("At least one branch name required");
4119
4347
  const status = await detectGhStack(repoPath);
4120
4348
  if (!status.available) throw new Error(status.reason);
@@ -4181,7 +4409,7 @@ async function createPrStack(input, onSetupLine) {
4181
4409
  }
4182
4410
  }
4183
4411
  const claimed = new Set(threads.map((t) => t.worktreePath));
4184
- if (!claimed.has(bootstrap.worktreePath) && existsSync11(bootstrap.worktreePath)) {
4412
+ if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
4185
4413
  try {
4186
4414
  await removeWorktree(repoPath, bootstrap.worktreePath, {
4187
4415
  deleteBranch: bootstrap.branchName
@@ -4203,10 +4431,10 @@ function stackAgentDefaultsFrom(input) {
4203
4431
  }
4204
4432
 
4205
4433
  // src/diff/diff.ts
4206
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4207
- import { dirname as dirname3, join as join11 } from "path";
4434
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
4435
+ import { dirname as dirname4, join as join12 } from "path";
4208
4436
  async function inspectGitWorktree(worktreePath) {
4209
- if (!worktreePath || !existsSync12(worktreePath)) return "missing_worktree";
4437
+ if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
4210
4438
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
4211
4439
  reject: false
4212
4440
  });
@@ -4214,7 +4442,7 @@ async function inspectGitWorktree(worktreePath) {
4214
4442
  return "ok";
4215
4443
  }
4216
4444
  async function initializeGitRepository(worktreePath) {
4217
- if (!worktreePath || !existsSync12(worktreePath)) {
4445
+ if (!worktreePath || !existsSync13(worktreePath)) {
4218
4446
  throw new Error("Worktree not found");
4219
4447
  }
4220
4448
  const status = await inspectGitWorktree(worktreePath);
@@ -4350,11 +4578,11 @@ new file mode 100644
4350
4578
  };
4351
4579
  }
4352
4580
  async function untrackedPatch(worktreePath, path, maxHunk) {
4353
- const abs = join11(worktreePath, path);
4581
+ const abs = join12(worktreePath, path);
4354
4582
  try {
4355
4583
  const st = statSync2(abs);
4356
4584
  if (st.isFile() && st.size > maxHunk) {
4357
- const buf = readFileSync9(abs).subarray(0, maxHunk);
4585
+ const buf = readFileSync10(abs).subarray(0, maxHunk);
4358
4586
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
4359
4587
  }
4360
4588
  } catch {
@@ -4855,7 +5083,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
4855
5083
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4856
5084
  assertSafeRelativePath(relativePath);
4857
5085
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
4858
- const abs = join11(worktreePath, relativePath);
5086
+ const abs = join12(worktreePath, relativePath);
4859
5087
  const st = statSync2(abs);
4860
5088
  if (!st.isFile()) {
4861
5089
  throw new Error(`Not a file: ${relativePath}`);
@@ -4865,7 +5093,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4865
5093
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
4866
5094
  );
4867
5095
  }
4868
- const buf = readFileSync9(abs);
5096
+ const buf = readFileSync10(abs);
4869
5097
  return {
4870
5098
  path: relativePath,
4871
5099
  contentBase64: buf.toString("base64"),
@@ -4875,12 +5103,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4875
5103
  function readWorktreeFile(worktreePath, relativePath, opts) {
4876
5104
  assertSafeRelativePath(relativePath);
4877
5105
  const maxBytes = opts?.maxBytes ?? 2e5;
4878
- const abs = join11(worktreePath, relativePath);
5106
+ const abs = join12(worktreePath, relativePath);
4879
5107
  const st = statSync2(abs);
4880
5108
  if (!st.isFile()) {
4881
5109
  throw new Error(`Not a file: ${relativePath}`);
4882
5110
  }
4883
- const buf = readFileSync9(abs);
5111
+ const buf = readFileSync10(abs);
4884
5112
  if (isImageRelativePath(relativePath)) {
4885
5113
  const maxImageBytes = Math.max(maxBytes, 15e6);
4886
5114
  const truncated2 = buf.length > maxImageBytes;
@@ -4923,9 +5151,9 @@ function assertSafeRelativePath(relativePath) {
4923
5151
  }
4924
5152
  function writeWorktreeFile(worktreePath, relativePath, content) {
4925
5153
  assertSafeRelativePath(relativePath);
4926
- const abs = join11(worktreePath, relativePath);
4927
- mkdirSync3(dirname3(abs), { recursive: true });
4928
- writeFileSync5(abs, content, "utf8");
5154
+ const abs = join12(worktreePath, relativePath);
5155
+ mkdirSync4(dirname4(abs), { recursive: true });
5156
+ writeFileSync6(abs, content, "utf8");
4929
5157
  return { path: relativePath };
4930
5158
  }
4931
5159
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -5050,9 +5278,9 @@ async function confirmLand(thread, opts) {
5050
5278
  }
5051
5279
 
5052
5280
  // src/skills/discover.ts
5053
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5281
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
5054
5282
  import { homedir } from "os";
5055
- import { join as join12 } from "path";
5283
+ import { join as join13 } from "path";
5056
5284
 
5057
5285
  // src/skills/bundled/long-running.ts
5058
5286
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -5063,7 +5291,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
5063
5291
 
5064
5292
  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.
5065
5293
 
5066
- 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.
5294
+ 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
5067
5295
 
5068
5296
  ## Tool
5069
5297
 
@@ -5073,7 +5301,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
5073
5301
  # Start (exits in ~1s; job survives this turn)
5074
5302
  node <detached-job.js> start <id> -- <command> [args...]
5075
5303
 
5076
- # Wait \u2014 returns within ~45s even if the job is still going
5304
+ # Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
5305
+ # Shell fallback:
5077
5306
  node <detached-job.js> wait <id>
5078
5307
 
5079
5308
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -5086,7 +5315,7 @@ node <detached-job.js> status <id>
5086
5315
 
5087
5316
  Wait JSON:
5088
5317
 
5089
- - \`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.
5318
+ - \`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.
5090
5319
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
5091
5320
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
5092
5321
 
@@ -5116,7 +5345,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
5116
5345
 
5117
5346
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
5118
5347
  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.
5119
- 3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
5348
+ 3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
5120
5349
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
5121
5350
 
5122
5351
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -5181,7 +5410,7 @@ function parseFrontmatter(content) {
5181
5410
  }
5182
5411
  function readSkill(skillMd, source) {
5183
5412
  try {
5184
- const content = readFileSync10(skillMd, "utf8");
5413
+ const content = readFileSync11(skillMd, "utf8");
5185
5414
  const { name: fmName, description } = parseFrontmatter(content);
5186
5415
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
5187
5416
  const name = fmName || dirName;
@@ -5200,17 +5429,17 @@ function readSkill(skillMd, source) {
5200
5429
  }
5201
5430
  }
5202
5431
  function scanSkillsDir(dir, source, out) {
5203
- if (!existsSync13(dir)) return;
5432
+ if (!existsSync14(dir)) return;
5204
5433
  let entries;
5205
5434
  try {
5206
- entries = readdirSync4(dir);
5435
+ entries = readdirSync5(dir);
5207
5436
  } catch {
5208
5437
  return;
5209
5438
  }
5210
5439
  for (const entry of entries) {
5211
5440
  if (entry.startsWith(".")) continue;
5212
- const skillMd = join12(dir, entry, "SKILL.md");
5213
- if (!existsSync13(skillMd)) continue;
5441
+ const skillMd = join13(dir, entry, "SKILL.md");
5442
+ if (!existsSync14(skillMd)) continue;
5214
5443
  try {
5215
5444
  if (!statSync3(skillMd).isFile()) continue;
5216
5445
  } catch {
@@ -5221,22 +5450,22 @@ function scanSkillsDir(dir, source, out) {
5221
5450
  }
5222
5451
  }
5223
5452
  function scanClaudePluginSkills(pluginsRoot, out) {
5224
- if (!existsSync13(pluginsRoot)) return;
5453
+ if (!existsSync14(pluginsRoot)) return;
5225
5454
  const walk = (dir, depth, lookingForSkillsDir) => {
5226
5455
  if (depth > 7) return;
5227
5456
  let entries;
5228
5457
  try {
5229
- entries = readdirSync4(dir);
5458
+ entries = readdirSync5(dir);
5230
5459
  } catch {
5231
5460
  return;
5232
5461
  }
5233
5462
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
5234
- const skill = readSkill(join12(dir, "SKILL.md"), "cli");
5463
+ const skill = readSkill(join13(dir, "SKILL.md"), "cli");
5235
5464
  if (skill) out.push(skill);
5236
5465
  }
5237
5466
  for (const entry of entries) {
5238
5467
  if (entry === "node_modules" || entry === ".git") continue;
5239
- const full = join12(dir, entry);
5468
+ const full = join13(dir, entry);
5240
5469
  try {
5241
5470
  if (!statSync3(full).isDirectory()) continue;
5242
5471
  } catch {
@@ -5256,17 +5485,17 @@ function discoverSkills(worktreePath) {
5256
5485
  const home = homedir();
5257
5486
  const collected = [];
5258
5487
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
5259
- scanSkillsDir(join12(worktreePath, rel), "workspace", collected);
5488
+ scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
5260
5489
  }
5261
5490
  for (const abs of [
5262
- join12(home, ".claude/skills"),
5263
- join12(home, ".cursor/skills"),
5264
- join12(home, ".sideboard/skills"),
5265
- join12(home, ".brightsy/skills")
5491
+ join13(home, ".claude/skills"),
5492
+ join13(home, ".cursor/skills"),
5493
+ join13(home, ".sideboard/skills"),
5494
+ join13(home, ".brightsy/skills")
5266
5495
  ]) {
5267
5496
  scanSkillsDir(abs, "user", collected);
5268
5497
  }
5269
- scanClaudePluginSkills(join12(home, ".claude/plugins"), collected);
5498
+ scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
5270
5499
  collected.push(...bundledSkills());
5271
5500
  const rank = {
5272
5501
  workspace: 0,
@@ -5292,7 +5521,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
5292
5521
  \u2026(truncated)` : body;
5293
5522
  }
5294
5523
  }
5295
- const raw = readFileSync10(skillPath, "utf8");
5524
+ const raw = readFileSync11(skillPath, "utf8");
5296
5525
  if (raw.startsWith("---")) {
5297
5526
  const end = raw.indexOf("\n---", 3);
5298
5527
  if (end >= 0) {
@@ -5385,27 +5614,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
5385
5614
  }
5386
5615
 
5387
5616
  // src/agents/instructions.ts
5388
- import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5389
- import { join as join14 } from "path";
5617
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
5618
+ import { join as join15 } from "path";
5390
5619
 
5391
5620
  // src/skills/detached-job-path.ts
5392
- import { existsSync as existsSync14 } from "fs";
5393
- import { dirname as dirname4, join as join13 } from "path";
5621
+ import { existsSync as existsSync15 } from "fs";
5622
+ import { dirname as dirname5, join as join14 } from "path";
5394
5623
  import { fileURLToPath } from "url";
5395
5624
  function packagedDetachedJobPath() {
5396
5625
  const dir = packagedMcpDir();
5397
5626
  if (!dir) return null;
5398
- const script = join13(dir, "scripts", "detached-job.js");
5399
- return existsSync14(script) ? script : null;
5627
+ const script = join14(dir, "scripts", "detached-job.js");
5628
+ return existsSync15(script) ? script : null;
5400
5629
  }
5401
5630
  function resolveDetachedJobScript() {
5402
5631
  const packaged = packagedDetachedJobPath();
5403
5632
  if (packaged) return packaged;
5404
- let dir = dirname4(fileURLToPath(import.meta.url));
5633
+ let dir = dirname5(fileURLToPath(import.meta.url));
5405
5634
  for (let i = 0; i < 8; i++) {
5406
- const candidate = join13(dir, "scripts", "detached-job.js");
5407
- if (existsSync14(candidate)) return candidate;
5408
- const parent = dirname4(dir);
5635
+ const candidate = join14(dir, "scripts", "detached-job.js");
5636
+ if (existsSync15(candidate)) return candidate;
5637
+ const parent = dirname5(dir);
5409
5638
  if (parent === dir) break;
5410
5639
  dir = parent;
5411
5640
  }
@@ -5524,6 +5753,88 @@ function formatWorktreeDirective(thread, opts) {
5524
5753
  function formatWorktreeReminder() {
5525
5754
  return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push.";
5526
5755
  }
5756
+ var GITHUB_TICKET_REF = /^(?:#?\d+|gh-\d+)$/i;
5757
+ var KEYED_TICKET_REF = /^(?:[A-Z][A-Z0-9]{0,9}-\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
5758
+ function issueTicketFromThread(thread, preferredSource = "github") {
5759
+ if (thread?.sourceType !== "ticket") return null;
5760
+ const ref = thread.sourceRef?.trim() ?? "";
5761
+ if (!ref) return null;
5762
+ if (/github\.com\/[^/]+\/[^/]+\/issues\/\d+/i.test(ref) || GITHUB_TICKET_REF.test(ref)) {
5763
+ return { id: ref, provider: "github" };
5764
+ }
5765
+ if (KEYED_TICKET_REF.test(ref)) {
5766
+ return { id: ref, provider: preferredSource === "github" ? "linear" : preferredSource };
5767
+ }
5768
+ return { id: ref, provider: preferredSource };
5769
+ }
5770
+ function linearTicketIdFromThread(thread) {
5771
+ const ticket = issueTicketFromThread(thread, "linear");
5772
+ return ticket?.provider === "linear" ? ticket.id : null;
5773
+ }
5774
+ function formatIssueToolsDirective(opts) {
5775
+ const github = opts.github !== false;
5776
+ if (!opts.linear && !opts.abletime && !github) return null;
5777
+ const lines = [
5778
+ "Issue tracking (Settings \u2192 Issues / Git \u2014 already signed in):",
5779
+ "- Use Sideboard `linear_*` / `github_*` / `abletime_*` tools. Do not call Claude Linear MCP, vendor GitHub MCP, or any other vendor issue MCP \u2014 those need a separate login and hang. If a vendor namespace shows needsAuth, ignore it and keep going with Sideboard tools."
5780
+ ];
5781
+ if (opts.linear) {
5782
+ lines.push(
5783
+ "- Linear: `linear_get_issue` (comments), `linear_comment`, `linear_update_issue` (state), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
5784
+ );
5785
+ }
5786
+ if (github) {
5787
+ lines.push(
5788
+ "- GitHub: `github_get_issue` (comments), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
5789
+ );
5790
+ }
5791
+ if (opts.abletime) {
5792
+ lines.push(
5793
+ "- AbleTime: `abletime_get_task` (comments), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
5794
+ );
5795
+ }
5796
+ lines.push(
5797
+ "- Do not ask the user to `claude mcp login` for tickets. Reconnect the Account source in Settings \u2192 Issues (or Git for `gh`)."
5798
+ );
5799
+ const ticket = opts.ticketId?.trim();
5800
+ if (ticket) {
5801
+ const provider = opts.ticketProvider ?? "linear";
5802
+ lines.push(
5803
+ `- This thread's ticket is \`${ticket}\` (${provider}). Use that id for get/comment/update; pass it as \`parent\` on spin-offs.`
5804
+ );
5805
+ }
5806
+ return lines.join("\n");
5807
+ }
5808
+ function formatLinearDirective(opts) {
5809
+ return formatIssueToolsDirective({
5810
+ linear: opts.connected,
5811
+ abletime: false,
5812
+ github: false,
5813
+ ticketId: opts.ticketId,
5814
+ ticketProvider: "linear"
5815
+ });
5816
+ }
5817
+ function formatIssueToolsReminder(opts) {
5818
+ const github = opts.github !== false;
5819
+ if (!opts.linear && !opts.abletime && !github) return null;
5820
+ const names = [
5821
+ opts.linear ? "linear_*" : null,
5822
+ github ? "github_*" : null,
5823
+ opts.abletime ? "abletime_*" : null
5824
+ ].filter(Boolean);
5825
+ const ticket = opts.ticketId?.trim();
5826
+ const ticketBit = ticket ? ` This ticket: ${ticket}${opts.ticketProvider ? ` (${opts.ticketProvider})` : ""} \u2014 get/comment/update; create with parent for spin-offs.` : " get/comment/update/create (parent= for spin-offs).";
5827
+ return `Issues: Sideboard ${names.join(" / ")} (Account). Ignore vendor issue MCP auth.${ticketBit}`;
5828
+ }
5829
+ function formatLinearReminder(opts) {
5830
+ return formatIssueToolsReminder({
5831
+ linear: opts.connected,
5832
+ abletime: false,
5833
+ github: false,
5834
+ ticketId: opts.ticketId,
5835
+ ticketProvider: "linear"
5836
+ });
5837
+ }
5527
5838
  function formatPrGateDirective() {
5528
5839
  return [
5529
5840
  "If a goal is given (not after every push):",
@@ -5551,14 +5862,15 @@ function formatLongRunningDirective(opts) {
5551
5862
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
5552
5863
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5553
5864
  "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
5554
- `- 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.`,
5865
+ `- 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.`,
5866
+ "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
5555
5867
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
5556
5868
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5557
5869
  ].join("\n");
5558
5870
  }
5559
5871
  function formatLongRunningReminder(opts) {
5560
5872
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5561
- return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
5873
+ 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.`;
5562
5874
  }
5563
5875
  function formatArtifactDirective() {
5564
5876
  return [
@@ -5609,11 +5921,11 @@ function loadAgentInstructions(worktreePath, agent) {
5609
5921
  const out = [];
5610
5922
  for (const rel of candidates) {
5611
5923
  if (seenPaths.has(rel)) continue;
5612
- const abs = join14(worktreePath, rel);
5613
- if (!existsSync15(abs)) continue;
5924
+ const abs = join15(worktreePath, rel);
5925
+ if (!existsSync16(abs)) continue;
5614
5926
  try {
5615
5927
  if (!statSync4(abs).isFile()) continue;
5616
- let content = readFileSync11(abs, "utf8");
5928
+ let content = readFileSync12(abs, "utf8");
5617
5929
  if (!content.trim()) continue;
5618
5930
  if (content.length > MAX_CHARS_PER_FILE) {
5619
5931
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -5933,8 +6245,8 @@ async function syncThreadBranchFromGit(threadId) {
5933
6245
 
5934
6246
  // src/store/schedules.ts
5935
6247
  import { randomUUID as randomUUID4 } from "crypto";
5936
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
5937
- import { join as join15 } from "path";
6248
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
6249
+ import { join as join16 } from "path";
5938
6250
  import { Cron } from "croner";
5939
6251
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5940
6252
  var UNIT_MS = {
@@ -5944,7 +6256,7 @@ var UNIT_MS = {
5944
6256
  d: 864e5
5945
6257
  };
5946
6258
  function schedulesPath() {
5947
- return join15(appDataDir(), "schedules.json");
6259
+ return join16(appDataDir(), "schedules.json");
5948
6260
  }
5949
6261
  function parseDurationMs(every) {
5950
6262
  const m = every.trim().match(DURATION_RE);
@@ -6050,9 +6362,9 @@ function normalizeTask(raw) {
6050
6362
  }
6051
6363
  function readAll() {
6052
6364
  const path = schedulesPath();
6053
- if (!existsSync16(path)) return [];
6365
+ if (!existsSync17(path)) return [];
6054
6366
  try {
6055
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
6367
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
6056
6368
  if (!Array.isArray(parsed)) return [];
6057
6369
  return parsed.map((row) => {
6058
6370
  try {
@@ -6178,7 +6490,7 @@ function formatScheduledPrompt(name, prompt) {
6178
6490
  ${prompt}`;
6179
6491
  }
6180
6492
  async function defaultDeps() {
6181
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-G4WKJG5Y.js");
6493
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-ZVW2AC6B.js");
6182
6494
  const orch = getOrchestrator2();
6183
6495
  return {
6184
6496
  findThread: (id) => findThreadByRef(id),
@@ -6335,6 +6647,9 @@ var Orchestrator = class {
6335
6647
  * finish or a user send so a later crash can recover again.
6336
6648
  */
6337
6649
  crashContinued = /* @__PURE__ */ new Set();
6650
+ /** Auto-continues after a worktree turn ended while a detached job still runs. */
6651
+ jobContinueCount = /* @__PURE__ */ new Map();
6652
+ jobContinueNudged = /* @__PURE__ */ new Set();
6338
6653
  maxConcurrent;
6339
6654
  runningCount = 0;
6340
6655
  constructor(opts) {
@@ -6418,7 +6733,7 @@ var Orchestrator = class {
6418
6733
  }
6419
6734
  continue;
6420
6735
  }
6421
- if (!existsSync17(thread.worktreePath)) {
6736
+ if (!existsSync18(thread.worktreePath)) {
6422
6737
  setStatus(thread.id, "broken", "Worktree missing on disk");
6423
6738
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
6424
6739
  continue;
@@ -6633,6 +6948,37 @@ var Orchestrator = class {
6633
6948
  this.emit({ type: "queue_changed", threadId, queue });
6634
6949
  this.haltDrain.delete(threadId);
6635
6950
  }
6951
+ /**
6952
+ * Worktree agent ended the turn after “I’ll let you know” (or left a
6953
+ * detached test/pack job running). Queue a continue so the chat does not
6954
+ * go idle with nobody watching the log.
6955
+ */
6956
+ maybeEnqueueJobContinue(threadId, chatText, parts = []) {
6957
+ if (this.haltDrain.has(threadId)) return;
6958
+ const thread = readThread(threadId);
6959
+ if (!thread || thread.status === "archived") return;
6960
+ const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
6961
+ const decision = planJobContinue({
6962
+ runningJobIds,
6963
+ chatText,
6964
+ queueLength: thread.queue.length,
6965
+ continueCount: this.jobContinueCount.get(threadId) ?? 0,
6966
+ alreadyNudged: this.jobContinueNudged.has(threadId),
6967
+ isOrchestrator: isOrchestratorThread(thread),
6968
+ agent: thread.agent,
6969
+ watchedJob: turnWatchedDetachedJob(parts)
6970
+ });
6971
+ if (decision.action === "none") {
6972
+ if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
6973
+ return;
6974
+ }
6975
+ if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
6976
+ else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
6977
+ const queue = [decision.prompt, ...thread.queue];
6978
+ updateThread(threadId, { queue });
6979
+ this.emit({ type: "queue_changed", threadId, queue });
6980
+ this.haltDrain.delete(threadId);
6981
+ }
6636
6982
  getThreads(includeArchived = false) {
6637
6983
  return listThreads({ includeArchived });
6638
6984
  }
@@ -6732,6 +7078,8 @@ var Orchestrator = class {
6732
7078
  }
6733
7079
  const queue = [...current.queue, prompt];
6734
7080
  this.crashContinued.delete(thread.id);
7081
+ this.jobContinueCount.delete(thread.id);
7082
+ this.jobContinueNudged.delete(thread.id);
6735
7083
  this.haltDrain.delete(thread.id);
6736
7084
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
6737
7085
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -6946,6 +7294,14 @@ var Orchestrator = class {
6946
7294
  const longRunningReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatLongRunningReminder() : null;
6947
7295
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
6948
7296
  const optionalServicesReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatOptionalServicesReminder(loadAppSettings().integrations) : null;
7297
+ const issueTicket = issueTicketFromThread(thread, resolveEffectiveIssueSource());
7298
+ const issueToolsReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatIssueToolsReminder({
7299
+ linear: isLinearConnected(),
7300
+ abletime: isAbleTimeConnected(),
7301
+ github: true,
7302
+ ticketId: issueTicket?.id,
7303
+ ticketProvider: issueTicket?.provider
7304
+ }) : null;
6949
7305
  const slackReplyContext = formatSlackRepliesForTurn(
6950
7306
  pendingSlackExternalReplies(thread.messages)
6951
7307
  );
@@ -6954,6 +7310,7 @@ var Orchestrator = class {
6954
7310
  orchestrationReminder,
6955
7311
  worktreeReminder,
6956
7312
  optionalServicesReminder,
7313
+ issueToolsReminder,
6957
7314
  artifactReminder,
6958
7315
  longRunningReminder,
6959
7316
  slackReplyContext,
@@ -6986,6 +7343,14 @@ var Orchestrator = class {
6986
7343
  const artifactDirective = isBrightsy ? null : formatArtifactDirective();
6987
7344
  const longRunningDirective = isBrightsy || isOrchestration ? null : formatLongRunningDirective();
6988
7345
  const optionalServicesDirective = isBrightsy || isOrchestration ? null : formatOptionalServicesDirective(loadAppSettings().integrations);
7346
+ const freshIssueTicket = issueTicketFromThread(fresh, resolveEffectiveIssueSource());
7347
+ const issueToolsDirective = isBrightsy || isOrchestration ? null : formatIssueToolsDirective({
7348
+ linear: isLinearConnected(),
7349
+ abletime: isAbleTimeConnected(),
7350
+ github: true,
7351
+ ticketId: freshIssueTicket?.id,
7352
+ ticketProvider: freshIssueTicket?.provider
7353
+ });
6989
7354
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
6990
7355
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
6991
7356
  customPrompt: settings?.prompts?.renameBranch
@@ -7016,6 +7381,7 @@ var Orchestrator = class {
7016
7381
  coordinatorDirective,
7017
7382
  worktreeDirective,
7018
7383
  optionalServicesDirective,
7384
+ issueToolsDirective,
7019
7385
  artifactDirective,
7020
7386
  longRunningDirective,
7021
7387
  renameBranchDirective,
@@ -7125,6 +7491,7 @@ var Orchestrator = class {
7125
7491
  coordinatorDirective,
7126
7492
  worktreeDirective,
7127
7493
  optionalServicesDirective,
7494
+ issueToolsDirective,
7128
7495
  artifactDirective,
7129
7496
  longRunningDirective,
7130
7497
  renameBranchDirective,
@@ -7227,6 +7594,7 @@ var Orchestrator = class {
7227
7594
  this.emit({ type: "turn_finished", threadId, exitCode });
7228
7595
  if (exitCode === 0) {
7229
7596
  this.crashContinued.delete(threadId);
7597
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
7230
7598
  } else {
7231
7599
  const blob = [chatText, detail].filter(Boolean).join("\n");
7232
7600
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -8179,7 +8547,7 @@ var Orchestrator = class {
8179
8547
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
8180
8548
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
8181
8549
  try {
8182
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-RP32AW6X.js");
8550
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NYSM2EPX.js");
8183
8551
  await ensureWorkspace2(thread.repoPath);
8184
8552
  } catch {
8185
8553
  }
@@ -8227,7 +8595,7 @@ var Orchestrator = class {
8227
8595
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
8228
8596
  return restored2;
8229
8597
  }
8230
- if (!existsSync17(thread.worktreePath)) {
8598
+ if (!existsSync18(thread.worktreePath)) {
8231
8599
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
8232
8600
  throw new Error(
8233
8601
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
@@ -8489,6 +8857,11 @@ export {
8489
8857
  worktreeCleanupSettings,
8490
8858
  applyThreadIntoMain,
8491
8859
  cloneRepoIntoSideboard,
8860
+ mcpWaitForTurnTimeoutMs,
8861
+ mcpWaitStillRunningHint,
8862
+ mcpWaitFinishedHint,
8863
+ mcpWaitForJobTimeoutMs,
8864
+ waitForDetachedJob,
8492
8865
  detectAgents,
8493
8866
  requireAgent,
8494
8867
  isHomeBoardThread,
@@ -8559,6 +8932,12 @@ export {
8559
8932
  formatRenameBranchDirective,
8560
8933
  formatWorktreeDirective,
8561
8934
  formatWorktreeReminder,
8935
+ issueTicketFromThread,
8936
+ linearTicketIdFromThread,
8937
+ formatIssueToolsDirective,
8938
+ formatLinearDirective,
8939
+ formatIssueToolsReminder,
8940
+ formatLinearReminder,
8562
8941
  formatPrGateDirective,
8563
8942
  formatProcessGuideDirective,
8564
8943
  formatLongRunningDirective,