@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
@@ -33,12 +33,12 @@ import {
33
33
  summarizeTurnStderr,
34
34
  toolDescription,
35
35
  turnFailChatText
36
- } from "./chunk-UA3YZKRX.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-TI6UMRWK.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,
@@ -147,11 +149,13 @@ import {
147
149
  childEnvWithAppSettings,
148
150
  getIssueSource,
149
151
  isAbleTimeConnected,
152
+ isLinearConnected,
150
153
  isSecureFileEncrypted,
151
154
  loadAppSettings,
152
155
  orchestrationQuotaFallbackAgent,
153
156
  orchestrationQuotaOnLimit,
154
157
  readSecureJson,
158
+ resolveEffectiveIssueSource,
155
159
  resolveNewThreadOptions,
156
160
  resolveThreadDefaults,
157
161
  resolveVaultKey,
@@ -392,7 +396,7 @@ async function continueSourceThread(threadId, prompt) {
392
396
  await continueOnReply(threadId, prompt);
393
397
  return;
394
398
  }
395
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-LXO2CDOG.js");
399
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-J727WIJY.js");
396
400
  await getOrchestrator2().send(threadId, prompt);
397
401
  } catch {
398
402
  }
@@ -652,7 +656,7 @@ async function pollSlackOutboundWatches(opts) {
652
656
  }
653
657
 
654
658
  // src/orchestrator/orchestrator.ts
655
- import { existsSync as existsSync17 } from "fs";
659
+ import { existsSync as existsSync18 } from "fs";
656
660
 
657
661
  // src/agents/spawn.ts
658
662
  import { createInterface } from "readline";
@@ -733,9 +737,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
733
737
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
734
738
  );
735
739
  }
736
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-MR75BDBY.js");
740
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-REI55FZ6.js");
737
741
  if (isGlobalThread2(thread)) {
738
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-UZDNIVP7.js");
742
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-GZESIQNH.js");
739
743
  ensureGlobalCoordinatorCwd2(
740
744
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
741
745
  );
@@ -2477,8 +2481,232 @@ function notifyParentOfChildHalt(child, status, send) {
2477
2481
  return true;
2478
2482
  }
2479
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
+
2480
2708
  // src/threads/create.ts
2481
- import { existsSync as existsSync8 } from "fs";
2709
+ import { existsSync as existsSync9 } from "fs";
2482
2710
 
2483
2711
  // src/detect/detect.ts
2484
2712
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
@@ -2998,7 +3226,7 @@ function reuseLiveThread(input, repoPath, match) {
2998
3226
  }
2999
3227
  async function createThread(input, _onSetupLine) {
3000
3228
  const repoPath = await resolveRepoRoot(input.repoPath);
3001
- if (!existsSync8(repoPath)) {
3229
+ if (!existsSync9(repoPath)) {
3002
3230
  throw new Error(`Repo not found: ${repoPath}`);
3003
3231
  }
3004
3232
  const reused = reuseLiveThread(input, repoPath, {
@@ -3073,7 +3301,7 @@ async function createThread(input, _onSetupLine) {
3073
3301
  let sourceIsFork = false;
3074
3302
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
3075
3303
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
3076
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-JSTJLXAW.js");
3304
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-BEIDJG7W.js");
3077
3305
  const task = await ensureAbleTimeTask({
3078
3306
  title: title?.trim() || "Untitled work",
3079
3307
  description: input.prompt?.trim() || void 0
@@ -3184,11 +3412,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
3184
3412
 
3185
3413
  // src/store/turn-live.ts
3186
3414
  import {
3187
- existsSync as existsSync9,
3415
+ existsSync as existsSync10,
3188
3416
  renameSync,
3189
3417
  unlinkSync as unlinkSync2,
3190
- writeFileSync as writeFileSync4,
3191
- readFileSync as readFileSync7
3418
+ writeFileSync as writeFileSync5,
3419
+ readFileSync as readFileSync8
3192
3420
  } from "fs";
3193
3421
  var buffers = /* @__PURE__ */ new Map();
3194
3422
  var FLUSH_MS = 800;
@@ -3265,7 +3493,7 @@ function writeTurnLive(threadId, progress) {
3265
3493
  const path = threadLivePath(threadId);
3266
3494
  const tmp = `${path}.${process.pid}.tmp`;
3267
3495
  try {
3268
- writeFileSync4(tmp, JSON.stringify(progress), "utf8");
3496
+ writeFileSync5(tmp, JSON.stringify(progress), "utf8");
3269
3497
  renameSync(tmp, path);
3270
3498
  } catch {
3271
3499
  try {
@@ -3276,9 +3504,9 @@ function writeTurnLive(threadId, progress) {
3276
3504
  }
3277
3505
  function readTurnLive(threadId) {
3278
3506
  const path = threadLivePath(threadId);
3279
- if (!existsSync9(path)) return null;
3507
+ if (!existsSync10(path)) return null;
3280
3508
  try {
3281
- const raw = JSON.parse(readFileSync7(path, "utf8"));
3509
+ const raw = JSON.parse(readFileSync8(path, "utf8"));
3282
3510
  if (!raw || typeof raw.summary !== "string") return null;
3283
3511
  return raw;
3284
3512
  } catch {
@@ -3290,7 +3518,7 @@ function clearTurnLive(threadId) {
3290
3518
  if (buf?.timer) clearTimeout(buf.timer);
3291
3519
  buffers.delete(threadId);
3292
3520
  const path = threadLivePath(threadId);
3293
- if (!existsSync9(path)) return;
3521
+ if (!existsSync10(path)) return;
3294
3522
  try {
3295
3523
  unlinkSync2(path);
3296
3524
  } catch {
@@ -3475,26 +3703,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
3475
3703
  import { execFileSync } from "child_process";
3476
3704
  import {
3477
3705
  copyFileSync as copyFileSync2,
3478
- existsSync as existsSync10,
3706
+ existsSync as existsSync11,
3479
3707
  mkdtempSync,
3480
- readdirSync as readdirSync3,
3481
- readFileSync as readFileSync8,
3708
+ readdirSync as readdirSync4,
3709
+ readFileSync as readFileSync9,
3482
3710
  rmSync
3483
3711
  } from "fs";
3484
3712
  import { tmpdir } from "os";
3485
- import { join as join10 } from "path";
3713
+ import { join as join11 } from "path";
3486
3714
  import { createRequire } from "module";
3487
- var CONDUCTOR_APP_SUPPORT = join10(
3715
+ var CONDUCTOR_APP_SUPPORT = join11(
3488
3716
  process.env.HOME ?? "",
3489
3717
  "Library",
3490
3718
  "Application Support",
3491
3719
  "com.conductor.app"
3492
3720
  );
3493
- var CONDUCTOR_DB = join10(CONDUCTOR_APP_SUPPORT, "conductor.db");
3494
- 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");
3495
3723
  function thisModuleFile() {
3496
3724
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
3497
- return cjsFile || process.argv[1] || join10(process.cwd(), "package.json");
3725
+ return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
3498
3726
  }
3499
3727
  function openReadonlySqlite(file) {
3500
3728
  const req = createRequire(thisModuleFile());
@@ -3512,21 +3740,21 @@ function mapAgentType(raw) {
3512
3740
  return null;
3513
3741
  }
3514
3742
  function resolveConductorCursorAgentId(workspacePath) {
3515
- if (!workspacePath || !existsSync10(CURSOR_SDK_STORE)) return null;
3743
+ if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
3516
3744
  const normalized = workspacePath.replace(/\/$/, "");
3517
3745
  let best = null;
3518
3746
  let hashes;
3519
3747
  try {
3520
- hashes = readdirSync3(CURSOR_SDK_STORE);
3748
+ hashes = readdirSync4(CURSOR_SDK_STORE);
3521
3749
  } catch {
3522
3750
  return null;
3523
3751
  }
3524
3752
  for (const hash of hashes) {
3525
- const agentsFile = join10(CURSOR_SDK_STORE, hash, "agents.ndjson");
3526
- if (!existsSync10(agentsFile)) continue;
3753
+ const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
3754
+ if (!existsSync11(agentsFile)) continue;
3527
3755
  let text;
3528
3756
  try {
3529
- text = readFileSync8(agentsFile, "utf8");
3757
+ text = readFileSync9(agentsFile, "utf8");
3530
3758
  } catch {
3531
3759
  continue;
3532
3760
  }
@@ -3550,7 +3778,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3550
3778
  return best?.agentId ?? null;
3551
3779
  }
3552
3780
  async function adoptThread(input) {
3553
- if (!existsSync10(input.worktreePath)) {
3781
+ if (!existsSync11(input.worktreePath)) {
3554
3782
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3555
3783
  }
3556
3784
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -3569,23 +3797,23 @@ async function adoptThread(input) {
3569
3797
  messages: input.messages ?? []
3570
3798
  });
3571
3799
  writeThread(thread);
3572
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-HO4EQNNM.js");
3800
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OCEPZXFD.js");
3573
3801
  await ensureWorkspace2(repoPath);
3574
3802
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-N5X5I422.js");
3575
3803
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
3576
3804
  return thread;
3577
3805
  }
3578
3806
  function listConductorWorkspaces() {
3579
- if (!existsSync10(CONDUCTOR_DB)) {
3807
+ if (!existsSync11(CONDUCTOR_DB)) {
3580
3808
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3581
3809
  }
3582
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3583
- const snapshot = join10(tmp, "conductor.db");
3810
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3811
+ const snapshot = join11(tmp, "conductor.db");
3584
3812
  try {
3585
3813
  copyFileSync2(CONDUCTOR_DB, snapshot);
3586
3814
  for (const suffix of ["-wal", "-shm"]) {
3587
3815
  const src = `${CONDUCTOR_DB}${suffix}`;
3588
- if (existsSync10(src)) {
3816
+ if (existsSync11(src)) {
3589
3817
  try {
3590
3818
  copyFileSync2(src, `${snapshot}${suffix}`);
3591
3819
  } catch {
@@ -3667,16 +3895,16 @@ function listConductorWorkspaces() {
3667
3895
  }
3668
3896
  }
3669
3897
  function importConductorWorkspace(workspaceId) {
3670
- if (!existsSync10(CONDUCTOR_DB)) {
3898
+ if (!existsSync11(CONDUCTOR_DB)) {
3671
3899
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3672
3900
  }
3673
- const tmp = mkdtempSync(join10(tmpdir(), "sideboard-conductor-"));
3674
- const snapshot = join10(tmp, "conductor.db");
3901
+ const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
3902
+ const snapshot = join11(tmp, "conductor.db");
3675
3903
  try {
3676
3904
  copyFileSync2(CONDUCTOR_DB, snapshot);
3677
3905
  for (const suffix of ["-wal", "-shm"]) {
3678
3906
  const src = `${CONDUCTOR_DB}${suffix}`;
3679
- if (existsSync10(src)) {
3907
+ if (existsSync11(src)) {
3680
3908
  try {
3681
3909
  copyFileSync2(src, `${snapshot}${suffix}`);
3682
3910
  } catch {
@@ -3696,7 +3924,7 @@ function importConductorWorkspace(workspaceId) {
3696
3924
  ).get(workspaceId);
3697
3925
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3698
3926
  const worktreePath = String(row.workspacePath);
3699
- if (!existsSync10(worktreePath)) {
3927
+ if (!existsSync11(worktreePath)) {
3700
3928
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3701
3929
  }
3702
3930
  let sessionId = null;
@@ -3767,7 +3995,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
3767
3995
  }
3768
3996
 
3769
3997
  // src/threads/stack-layers.ts
3770
- import { existsSync as existsSync11 } from "fs";
3998
+ import { existsSync as existsSync12 } from "fs";
3771
3999
  function stackIdFrom(stack) {
3772
4000
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3773
4001
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -3830,7 +4058,7 @@ async function openStackLayer(input, _onSetupLine) {
3830
4058
  let createdWorktree = false;
3831
4059
  const trees = await listWorktrees(repoPath);
3832
4060
  const checkedOut = trees.find((w) => w.branch === branchName);
3833
- if (checkedOut?.path && existsSync11(checkedOut.path)) {
4061
+ if (checkedOut?.path && existsSync12(checkedOut.path)) {
3834
4062
  if (input.reuseExistingWorktree !== false) {
3835
4063
  worktreePath = checkedOut.path;
3836
4064
  } else {
@@ -3972,7 +4200,7 @@ async function initStackFromThread(input, onSetupLine) {
3972
4200
  async function createPrStack(input, onSetupLine) {
3973
4201
  await requireAgent(input.agent);
3974
4202
  const repoPath = await resolveRepoRoot(input.repoPath);
3975
- if (!existsSync11(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
4203
+ if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3976
4204
  if (!input.branches.length) throw new Error("At least one branch name required");
3977
4205
  const status = await detectGhStack(repoPath);
3978
4206
  if (!status.available) throw new Error(status.reason);
@@ -4039,7 +4267,7 @@ async function createPrStack(input, onSetupLine) {
4039
4267
  }
4040
4268
  }
4041
4269
  const claimed = new Set(threads.map((t) => t.worktreePath));
4042
- if (!claimed.has(bootstrap.worktreePath) && existsSync11(bootstrap.worktreePath)) {
4270
+ if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
4043
4271
  try {
4044
4272
  await removeWorktree(repoPath, bootstrap.worktreePath, {
4045
4273
  deleteBranch: bootstrap.branchName
@@ -4051,10 +4279,10 @@ async function createPrStack(input, onSetupLine) {
4051
4279
  }
4052
4280
 
4053
4281
  // src/diff/diff.ts
4054
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
4055
- 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";
4056
4284
  async function inspectGitWorktree(worktreePath) {
4057
- if (!worktreePath || !existsSync12(worktreePath)) return "missing_worktree";
4285
+ if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
4058
4286
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
4059
4287
  reject: false
4060
4288
  });
@@ -4062,7 +4290,7 @@ async function inspectGitWorktree(worktreePath) {
4062
4290
  return "ok";
4063
4291
  }
4064
4292
  async function initializeGitRepository(worktreePath) {
4065
- if (!worktreePath || !existsSync12(worktreePath)) {
4293
+ if (!worktreePath || !existsSync13(worktreePath)) {
4066
4294
  throw new Error("Worktree not found");
4067
4295
  }
4068
4296
  const status = await inspectGitWorktree(worktreePath);
@@ -4198,11 +4426,11 @@ new file mode 100644
4198
4426
  };
4199
4427
  }
4200
4428
  async function untrackedPatch(worktreePath, path, maxHunk) {
4201
- const abs = join11(worktreePath, path);
4429
+ const abs = join12(worktreePath, path);
4202
4430
  try {
4203
4431
  const st = statSync2(abs);
4204
4432
  if (st.isFile() && st.size > maxHunk) {
4205
- const buf = readFileSync9(abs).subarray(0, maxHunk);
4433
+ const buf = readFileSync10(abs).subarray(0, maxHunk);
4206
4434
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
4207
4435
  }
4208
4436
  } catch {
@@ -4703,7 +4931,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
4703
4931
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4704
4932
  assertSafeRelativePath(relativePath);
4705
4933
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
4706
- const abs = join11(worktreePath, relativePath);
4934
+ const abs = join12(worktreePath, relativePath);
4707
4935
  const st = statSync2(abs);
4708
4936
  if (!st.isFile()) {
4709
4937
  throw new Error(`Not a file: ${relativePath}`);
@@ -4713,7 +4941,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4713
4941
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
4714
4942
  );
4715
4943
  }
4716
- const buf = readFileSync9(abs);
4944
+ const buf = readFileSync10(abs);
4717
4945
  return {
4718
4946
  path: relativePath,
4719
4947
  contentBase64: buf.toString("base64"),
@@ -4723,12 +4951,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
4723
4951
  function readWorktreeFile(worktreePath, relativePath, opts) {
4724
4952
  assertSafeRelativePath(relativePath);
4725
4953
  const maxBytes = opts?.maxBytes ?? 2e5;
4726
- const abs = join11(worktreePath, relativePath);
4954
+ const abs = join12(worktreePath, relativePath);
4727
4955
  const st = statSync2(abs);
4728
4956
  if (!st.isFile()) {
4729
4957
  throw new Error(`Not a file: ${relativePath}`);
4730
4958
  }
4731
- const buf = readFileSync9(abs);
4959
+ const buf = readFileSync10(abs);
4732
4960
  if (isImageRelativePath(relativePath)) {
4733
4961
  const maxImageBytes = Math.max(maxBytes, 15e6);
4734
4962
  const truncated2 = buf.length > maxImageBytes;
@@ -4771,9 +4999,9 @@ function assertSafeRelativePath(relativePath) {
4771
4999
  }
4772
5000
  function writeWorktreeFile(worktreePath, relativePath, content) {
4773
5001
  assertSafeRelativePath(relativePath);
4774
- const abs = join11(worktreePath, relativePath);
4775
- mkdirSync3(dirname3(abs), { recursive: true });
4776
- writeFileSync5(abs, content, "utf8");
5002
+ const abs = join12(worktreePath, relativePath);
5003
+ mkdirSync4(dirname4(abs), { recursive: true });
5004
+ writeFileSync6(abs, content, "utf8");
4777
5005
  return { path: relativePath };
4778
5006
  }
4779
5007
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -4898,9 +5126,9 @@ async function confirmLand(thread, opts) {
4898
5126
  }
4899
5127
 
4900
5128
  // src/skills/discover.ts
4901
- 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";
4902
5130
  import { homedir } from "os";
4903
- import { join as join12 } from "path";
5131
+ import { join as join13 } from "path";
4904
5132
 
4905
5133
  // src/skills/bundled/long-running.ts
4906
5134
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -4911,7 +5139,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
4911
5139
 
4912
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.
4913
5141
 
4914
- 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
4915
5143
 
4916
5144
  ## Tool
4917
5145
 
@@ -4921,7 +5149,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
4921
5149
  # Start (exits in ~1s; job survives this turn)
4922
5150
  node <detached-job.js> start <id> -- <command> [args...]
4923
5151
 
4924
- # 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:
4925
5154
  node <detached-job.js> wait <id>
4926
5155
 
4927
5156
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -4934,7 +5163,7 @@ node <detached-job.js> status <id>
4934
5163
 
4935
5164
  Wait JSON:
4936
5165
 
4937
- - \`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.
4938
5167
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
4939
5168
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
4940
5169
 
@@ -4964,7 +5193,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
4964
5193
 
4965
5194
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
4966
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.
4967
- 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.
4968
5197
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
4969
5198
 
4970
5199
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -5029,7 +5258,7 @@ function parseFrontmatter(content) {
5029
5258
  }
5030
5259
  function readSkill(skillMd, source) {
5031
5260
  try {
5032
- const content = readFileSync10(skillMd, "utf8");
5261
+ const content = readFileSync11(skillMd, "utf8");
5033
5262
  const { name: fmName, description } = parseFrontmatter(content);
5034
5263
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
5035
5264
  const name = fmName || dirName;
@@ -5048,17 +5277,17 @@ function readSkill(skillMd, source) {
5048
5277
  }
5049
5278
  }
5050
5279
  function scanSkillsDir(dir, source, out) {
5051
- if (!existsSync13(dir)) return;
5280
+ if (!existsSync14(dir)) return;
5052
5281
  let entries;
5053
5282
  try {
5054
- entries = readdirSync4(dir);
5283
+ entries = readdirSync5(dir);
5055
5284
  } catch {
5056
5285
  return;
5057
5286
  }
5058
5287
  for (const entry of entries) {
5059
5288
  if (entry.startsWith(".")) continue;
5060
- const skillMd = join12(dir, entry, "SKILL.md");
5061
- if (!existsSync13(skillMd)) continue;
5289
+ const skillMd = join13(dir, entry, "SKILL.md");
5290
+ if (!existsSync14(skillMd)) continue;
5062
5291
  try {
5063
5292
  if (!statSync3(skillMd).isFile()) continue;
5064
5293
  } catch {
@@ -5069,22 +5298,22 @@ function scanSkillsDir(dir, source, out) {
5069
5298
  }
5070
5299
  }
5071
5300
  function scanClaudePluginSkills(pluginsRoot, out) {
5072
- if (!existsSync13(pluginsRoot)) return;
5301
+ if (!existsSync14(pluginsRoot)) return;
5073
5302
  const walk = (dir, depth, lookingForSkillsDir) => {
5074
5303
  if (depth > 7) return;
5075
5304
  let entries;
5076
5305
  try {
5077
- entries = readdirSync4(dir);
5306
+ entries = readdirSync5(dir);
5078
5307
  } catch {
5079
5308
  return;
5080
5309
  }
5081
5310
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
5082
- const skill = readSkill(join12(dir, "SKILL.md"), "cli");
5311
+ const skill = readSkill(join13(dir, "SKILL.md"), "cli");
5083
5312
  if (skill) out.push(skill);
5084
5313
  }
5085
5314
  for (const entry of entries) {
5086
5315
  if (entry === "node_modules" || entry === ".git") continue;
5087
- const full = join12(dir, entry);
5316
+ const full = join13(dir, entry);
5088
5317
  try {
5089
5318
  if (!statSync3(full).isDirectory()) continue;
5090
5319
  } catch {
@@ -5104,17 +5333,17 @@ function discoverSkills(worktreePath) {
5104
5333
  const home = homedir();
5105
5334
  const collected = [];
5106
5335
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
5107
- scanSkillsDir(join12(worktreePath, rel), "workspace", collected);
5336
+ scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
5108
5337
  }
5109
5338
  for (const abs of [
5110
- join12(home, ".claude/skills"),
5111
- join12(home, ".cursor/skills"),
5112
- join12(home, ".sideboard/skills"),
5113
- join12(home, ".brightsy/skills")
5339
+ join13(home, ".claude/skills"),
5340
+ join13(home, ".cursor/skills"),
5341
+ join13(home, ".sideboard/skills"),
5342
+ join13(home, ".brightsy/skills")
5114
5343
  ]) {
5115
5344
  scanSkillsDir(abs, "user", collected);
5116
5345
  }
5117
- scanClaudePluginSkills(join12(home, ".claude/plugins"), collected);
5346
+ scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
5118
5347
  collected.push(...bundledSkills());
5119
5348
  const rank = {
5120
5349
  workspace: 0,
@@ -5140,7 +5369,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
5140
5369
  \u2026(truncated)` : body;
5141
5370
  }
5142
5371
  }
5143
- const raw = readFileSync10(skillPath, "utf8");
5372
+ const raw = readFileSync11(skillPath, "utf8");
5144
5373
  if (raw.startsWith("---")) {
5145
5374
  const end = raw.indexOf("\n---", 3);
5146
5375
  if (end >= 0) {
@@ -5233,27 +5462,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
5233
5462
  }
5234
5463
 
5235
5464
  // src/agents/instructions.ts
5236
- import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5237
- 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";
5238
5467
 
5239
5468
  // src/skills/detached-job-path.ts
5240
- import { existsSync as existsSync14 } from "fs";
5241
- 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";
5242
5471
  import { fileURLToPath } from "url";
5243
5472
  function packagedDetachedJobPath() {
5244
5473
  const dir = packagedMcpDir();
5245
5474
  if (!dir) return null;
5246
- const script = join13(dir, "scripts", "detached-job.js");
5247
- return existsSync14(script) ? script : null;
5475
+ const script = join14(dir, "scripts", "detached-job.js");
5476
+ return existsSync15(script) ? script : null;
5248
5477
  }
5249
5478
  function resolveDetachedJobScript() {
5250
5479
  const packaged = packagedDetachedJobPath();
5251
5480
  if (packaged) return packaged;
5252
- let dir = dirname4(fileURLToPath(import.meta.url));
5481
+ let dir = dirname5(fileURLToPath(import.meta.url));
5253
5482
  for (let i = 0; i < 8; i++) {
5254
- const candidate = join13(dir, "scripts", "detached-job.js");
5255
- if (existsSync14(candidate)) return candidate;
5256
- const parent = dirname4(dir);
5483
+ const candidate = join14(dir, "scripts", "detached-job.js");
5484
+ if (existsSync15(candidate)) return candidate;
5485
+ const parent = dirname5(dir);
5257
5486
  if (parent === dir) break;
5258
5487
  dir = parent;
5259
5488
  }
@@ -5372,6 +5601,66 @@ function formatWorktreeDirective(thread, opts) {
5372
5601
  function formatWorktreeReminder() {
5373
5602
  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.";
5374
5603
  }
5604
+ var GITHUB_TICKET_REF = /^(?:#?\d+|gh-\d+)$/i;
5605
+ 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;
5606
+ function issueTicketFromThread(thread, preferredSource = "github") {
5607
+ if (thread?.sourceType !== "ticket") return null;
5608
+ const ref = thread.sourceRef?.trim() ?? "";
5609
+ if (!ref) return null;
5610
+ if (/github\.com\/[^/]+\/[^/]+\/issues\/\d+/i.test(ref) || GITHUB_TICKET_REF.test(ref)) {
5611
+ return { id: ref, provider: "github" };
5612
+ }
5613
+ if (KEYED_TICKET_REF.test(ref)) {
5614
+ return { id: ref, provider: preferredSource === "github" ? "linear" : preferredSource };
5615
+ }
5616
+ return { id: ref, provider: preferredSource };
5617
+ }
5618
+ function formatIssueToolsDirective(opts) {
5619
+ const github = opts.github !== false;
5620
+ if (!opts.linear && !opts.abletime && !github) return null;
5621
+ const lines = [
5622
+ "Issue tracking (Settings \u2192 Issues / Git \u2014 already signed in):",
5623
+ "- 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."
5624
+ ];
5625
+ if (opts.linear) {
5626
+ lines.push(
5627
+ "- 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."
5628
+ );
5629
+ }
5630
+ if (github) {
5631
+ lines.push(
5632
+ "- GitHub: `github_get_issue` (comments), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
5633
+ );
5634
+ }
5635
+ if (opts.abletime) {
5636
+ lines.push(
5637
+ "- AbleTime: `abletime_get_task` (comments), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
5638
+ );
5639
+ }
5640
+ lines.push(
5641
+ "- Do not ask the user to `claude mcp login` for tickets. Reconnect the Account source in Settings \u2192 Issues (or Git for `gh`)."
5642
+ );
5643
+ const ticket = opts.ticketId?.trim();
5644
+ if (ticket) {
5645
+ const provider = opts.ticketProvider ?? "linear";
5646
+ lines.push(
5647
+ `- This thread's ticket is \`${ticket}\` (${provider}). Use that id for get/comment/update; pass it as \`parent\` on spin-offs.`
5648
+ );
5649
+ }
5650
+ return lines.join("\n");
5651
+ }
5652
+ function formatIssueToolsReminder(opts) {
5653
+ const github = opts.github !== false;
5654
+ if (!opts.linear && !opts.abletime && !github) return null;
5655
+ const names = [
5656
+ opts.linear ? "linear_*" : null,
5657
+ github ? "github_*" : null,
5658
+ opts.abletime ? "abletime_*" : null
5659
+ ].filter(Boolean);
5660
+ const ticket = opts.ticketId?.trim();
5661
+ 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).";
5662
+ return `Issues: Sideboard ${names.join(" / ")} (Account). Ignore vendor issue MCP auth.${ticketBit}`;
5663
+ }
5375
5664
  function formatPrGateDirective() {
5376
5665
  return [
5377
5666
  "If a goal is given (not after every push):",
@@ -5399,14 +5688,15 @@ function formatLongRunningDirective(opts) {
5399
5688
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
5400
5689
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5401
5690
  "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
5402
- `- 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.",
5403
5693
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
5404
5694
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5405
5695
  ].join("\n");
5406
5696
  }
5407
5697
  function formatLongRunningReminder(opts) {
5408
5698
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5409
- 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.`;
5410
5700
  }
5411
5701
  function formatArtifactDirective() {
5412
5702
  return [
@@ -5554,8 +5844,8 @@ async function syncThreadBranchFromGit(threadId) {
5554
5844
 
5555
5845
  // src/store/schedules.ts
5556
5846
  import { randomUUID as randomUUID4 } from "crypto";
5557
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
5558
- import { join as join15 } from "path";
5847
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
5848
+ import { join as join16 } from "path";
5559
5849
  import { Cron } from "croner";
5560
5850
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5561
5851
  var UNIT_MS = {
@@ -5565,7 +5855,7 @@ var UNIT_MS = {
5565
5855
  d: 864e5
5566
5856
  };
5567
5857
  function schedulesPath() {
5568
- return join15(appDataDir(), "schedules.json");
5858
+ return join16(appDataDir(), "schedules.json");
5569
5859
  }
5570
5860
  function parseDurationMs(every) {
5571
5861
  const m = every.trim().match(DURATION_RE);
@@ -5665,9 +5955,9 @@ function normalizeTask(raw) {
5665
5955
  }
5666
5956
  function readAll() {
5667
5957
  const path = schedulesPath();
5668
- if (!existsSync16(path)) return [];
5958
+ if (!existsSync17(path)) return [];
5669
5959
  try {
5670
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5960
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
5671
5961
  if (!Array.isArray(parsed)) return [];
5672
5962
  return parsed.map((row) => {
5673
5963
  try {
@@ -5790,7 +6080,7 @@ function formatScheduledPrompt(name, prompt) {
5790
6080
  ${prompt}`;
5791
6081
  }
5792
6082
  async function defaultDeps() {
5793
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-LXO2CDOG.js");
6083
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-J727WIJY.js");
5794
6084
  const orch = getOrchestrator2();
5795
6085
  return {
5796
6086
  findThread: (id) => findThreadByRef(id),
@@ -5947,6 +6237,9 @@ var Orchestrator = class {
5947
6237
  * finish or a user send so a later crash can recover again.
5948
6238
  */
5949
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();
5950
6243
  maxConcurrent;
5951
6244
  runningCount = 0;
5952
6245
  constructor(opts) {
@@ -6030,7 +6323,7 @@ var Orchestrator = class {
6030
6323
  }
6031
6324
  continue;
6032
6325
  }
6033
- if (!existsSync17(thread.worktreePath)) {
6326
+ if (!existsSync18(thread.worktreePath)) {
6034
6327
  setStatus(thread.id, "broken", "Worktree missing on disk");
6035
6328
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
6036
6329
  continue;
@@ -6245,6 +6538,37 @@ var Orchestrator = class {
6245
6538
  this.emit({ type: "queue_changed", threadId, queue });
6246
6539
  this.haltDrain.delete(threadId);
6247
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
+ }
6248
6572
  getThreads(includeArchived = false) {
6249
6573
  return listThreads({ includeArchived });
6250
6574
  }
@@ -6344,6 +6668,8 @@ var Orchestrator = class {
6344
6668
  }
6345
6669
  const queue = [...current.queue, prompt];
6346
6670
  this.crashContinued.delete(thread.id);
6671
+ this.jobContinueCount.delete(thread.id);
6672
+ this.jobContinueNudged.delete(thread.id);
6347
6673
  this.haltDrain.delete(thread.id);
6348
6674
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
6349
6675
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -6558,6 +6884,14 @@ var Orchestrator = class {
6558
6884
  const longRunningReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatLongRunningReminder() : null;
6559
6885
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
6560
6886
  const optionalServicesReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatOptionalServicesReminder(loadAppSettings().integrations) : null;
6887
+ const issueTicket = issueTicketFromThread(thread, resolveEffectiveIssueSource());
6888
+ const issueToolsReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatIssueToolsReminder({
6889
+ linear: isLinearConnected(),
6890
+ abletime: isAbleTimeConnected(),
6891
+ github: true,
6892
+ ticketId: issueTicket?.id,
6893
+ ticketProvider: issueTicket?.provider
6894
+ }) : null;
6561
6895
  const slackReplyContext = formatSlackRepliesForTurn(
6562
6896
  pendingSlackExternalReplies(thread.messages)
6563
6897
  );
@@ -6566,6 +6900,7 @@ var Orchestrator = class {
6566
6900
  orchestrationReminder,
6567
6901
  worktreeReminder,
6568
6902
  optionalServicesReminder,
6903
+ issueToolsReminder,
6569
6904
  artifactReminder,
6570
6905
  longRunningReminder,
6571
6906
  slackReplyContext,
@@ -6598,6 +6933,14 @@ var Orchestrator = class {
6598
6933
  const artifactDirective = isBrightsy ? null : formatArtifactDirective();
6599
6934
  const longRunningDirective = isBrightsy || isOrchestration ? null : formatLongRunningDirective();
6600
6935
  const optionalServicesDirective = isBrightsy || isOrchestration ? null : formatOptionalServicesDirective(loadAppSettings().integrations);
6936
+ const freshIssueTicket = issueTicketFromThread(fresh, resolveEffectiveIssueSource());
6937
+ const issueToolsDirective = isBrightsy || isOrchestration ? null : formatIssueToolsDirective({
6938
+ linear: isLinearConnected(),
6939
+ abletime: isAbleTimeConnected(),
6940
+ github: true,
6941
+ ticketId: freshIssueTicket?.id,
6942
+ ticketProvider: freshIssueTicket?.provider
6943
+ });
6601
6944
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
6602
6945
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
6603
6946
  customPrompt: settings?.prompts?.renameBranch
@@ -6628,6 +6971,7 @@ var Orchestrator = class {
6628
6971
  coordinatorDirective,
6629
6972
  worktreeDirective,
6630
6973
  optionalServicesDirective,
6974
+ issueToolsDirective,
6631
6975
  artifactDirective,
6632
6976
  longRunningDirective,
6633
6977
  renameBranchDirective,
@@ -6737,6 +7081,7 @@ var Orchestrator = class {
6737
7081
  coordinatorDirective,
6738
7082
  worktreeDirective,
6739
7083
  optionalServicesDirective,
7084
+ issueToolsDirective,
6740
7085
  artifactDirective,
6741
7086
  longRunningDirective,
6742
7087
  renameBranchDirective,
@@ -6839,6 +7184,7 @@ var Orchestrator = class {
6839
7184
  this.emit({ type: "turn_finished", threadId, exitCode });
6840
7185
  if (exitCode === 0) {
6841
7186
  this.crashContinued.delete(threadId);
7187
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
6842
7188
  } else {
6843
7189
  const blob = [chatText, detail].filter(Boolean).join("\n");
6844
7190
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -7791,7 +8137,7 @@ var Orchestrator = class {
7791
8137
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7792
8138
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7793
8139
  try {
7794
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-HO4EQNNM.js");
8140
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OCEPZXFD.js");
7795
8141
  await ensureWorkspace2(thread.repoPath);
7796
8142
  } catch {
7797
8143
  }
@@ -7839,13 +8185,13 @@ var Orchestrator = class {
7839
8185
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
7840
8186
  return restored2;
7841
8187
  }
7842
- if (!existsSync17(thread.worktreePath)) {
8188
+ if (!existsSync18(thread.worktreePath)) {
7843
8189
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
7844
8190
  throw new Error(
7845
8191
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
7846
8192
  );
7847
8193
  }
7848
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-PEWDAX5O.js");
8194
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-VTMBZKY6.js");
7849
8195
  const slug = thread.worktreePath.split("/").pop();
7850
8196
  const dest = thread.worktreePath;
7851
8197
  await withRepoGitLock(thread.repoPath, async () => {
@@ -7955,7 +8301,7 @@ async function startOrchestration(opts) {
7955
8301
  sourceRef: "default",
7956
8302
  ...createOpts
7957
8303
  }).catch(async () => {
7958
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-PEWDAX5O.js");
8304
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-VTMBZKY6.js");
7959
8305
  const repo = await resolveRepoRoot2(repoPath);
7960
8306
  const def = await resolveDefaultBranch2(repo);
7961
8307
  return orch.createThread({
@@ -7985,6 +8331,11 @@ export {
7985
8331
  recordSlackOutboundWatch,
7986
8332
  pollSlackOutboundWatches,
7987
8333
  AGENT_GIT_ACTIONS,
8334
+ mcpWaitForTurnTimeoutMs,
8335
+ mcpWaitStillRunningHint,
8336
+ mcpWaitFinishedHint,
8337
+ mcpWaitForJobTimeoutMs,
8338
+ waitForDetachedJob,
7988
8339
  BOARD_COLUMN_DEFS,
7989
8340
  HOME_BOARD_CACHE_TTL_MS,
7990
8341
  dedupeBoardIssues,