@sideboard-ai/core 0.1.157 → 0.1.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5491,12 +5491,14 @@ function isWorkspaceScratchPath(relativePath) {
5491
5491
  const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
5492
5492
  return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
5493
5493
  }
5494
- var ATTACHMENTS_DIR, LEGACY_ATTACHMENTS_DIR, ATTACHMENTS_GITIGNORE;
5494
+ var ATTACHMENTS_DIR, DETACHED_JOBS_DIR, LEGACY_ATTACHMENTS_DIR, LEGACY_DETACHED_JOBS_DIR, ATTACHMENTS_GITIGNORE;
5495
5495
  var init_workspace_scratch = __esm({
5496
5496
  "src/paths/workspace-scratch.ts"() {
5497
5497
  "use strict";
5498
5498
  ATTACHMENTS_DIR = ".context/attachments";
5499
+ DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
5499
5500
  LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
5501
+ LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
5500
5502
  ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
5501
5503
  *
5502
5504
  !.gitignore
@@ -8692,6 +8694,9 @@ function toolDescription(name, input) {
8692
8694
  if (/present_files$/i.test(name)) {
8693
8695
  return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
8694
8696
  }
8697
+ if (/wait_for_job$/i.test(name)) {
8698
+ return str2(input?.id) ? `Wait for ${str2(input?.id)}` : "Wait for job";
8699
+ }
8695
8700
  if (isSubagentToolName(name)) {
8696
8701
  const desc = str2(input?.description);
8697
8702
  const sub = asRecord(input?.subagentType);
@@ -9664,7 +9669,8 @@ var init_injected_mcp = __esm({
9664
9669
  "mcp__sideboard__present_schema",
9665
9670
  "mcp__sideboard__present_files",
9666
9671
  "mcp__sideboard__ask_user",
9667
- "mcp__sideboard__present_plan"
9672
+ "mcp__sideboard__present_plan",
9673
+ "mcp__sideboard__wait_for_job"
9668
9674
  ];
9669
9675
  SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
9670
9676
  SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
@@ -13686,6 +13692,242 @@ var init_child_halt = __esm({
13686
13692
  }
13687
13693
  });
13688
13694
 
13695
+ // src/mcp/wait-for-turn.ts
13696
+ function mcpWaitForTurnTimeoutMs(requested) {
13697
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
13698
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
13699
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
13700
+ }
13701
+ function mcpWaitStillRunningHint(status) {
13702
+ return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
13703
+ }
13704
+ function mcpWaitFinishedHint(status) {
13705
+ if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
13706
+ if (status === "broken") return MCP_WAIT_BROKEN_HINT;
13707
+ if (status === "error") return MCP_WAIT_ERROR_HINT;
13708
+ return void 0;
13709
+ }
13710
+ var MCP_WAIT_FOR_TURN_MAX_MS, MCP_WAIT_STILL_RUNNING_HINT, MCP_WAIT_QUEUED_HINT, MCP_WAIT_STOPPED_HINT, MCP_WAIT_BROKEN_HINT, MCP_WAIT_ERROR_HINT;
13711
+ var init_wait_for_turn = __esm({
13712
+ "src/mcp/wait-for-turn.ts"() {
13713
+ "use strict";
13714
+ MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
13715
+ 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.";
13716
+ 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.";
13717
+ 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.";
13718
+ MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
13719
+ 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.";
13720
+ }
13721
+ });
13722
+
13723
+ // src/mcp/wait-for-job.ts
13724
+ function mcpWaitForJobTimeoutMs(requested) {
13725
+ return mcpWaitForTurnTimeoutMs(requested);
13726
+ }
13727
+ function sanitizeDetachedJobId(id) {
13728
+ const s = id.trim();
13729
+ if (!JOB_ID_RE.test(s)) {
13730
+ throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
13731
+ }
13732
+ return s;
13733
+ }
13734
+ function jobAlive(pid) {
13735
+ if (!Number.isInteger(pid) || pid <= 0) return false;
13736
+ try {
13737
+ process.kill(pid, 0);
13738
+ return true;
13739
+ } catch {
13740
+ return false;
13741
+ }
13742
+ }
13743
+ function readIntFile(file) {
13744
+ if (!(0, import_node_fs38.existsSync)(file)) return null;
13745
+ const n = Number.parseInt((0, import_node_fs38.readFileSync)(file, "utf8").trim(), 10);
13746
+ return Number.isInteger(n) ? n : null;
13747
+ }
13748
+ function jobDir(root, id, legacy = false) {
13749
+ return (0, import_node_path38.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
13750
+ }
13751
+ function resolveJobDir(root, id) {
13752
+ const modern = jobDir(root, id, false);
13753
+ if ((0, import_node_fs38.existsSync)(modern)) return modern;
13754
+ const legacy = jobDir(root, id, true);
13755
+ if ((0, import_node_fs38.existsSync)(legacy)) return legacy;
13756
+ return modern;
13757
+ }
13758
+ function listJobIdsIn(root, rel) {
13759
+ const dir = (0, import_node_path38.join)(root, rel);
13760
+ if (!(0, import_node_fs38.existsSync)(dir)) return [];
13761
+ return (0, import_node_fs38.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
13762
+ }
13763
+ function listRunningDetachedJobs(worktreePath) {
13764
+ const root = worktreePath.trim();
13765
+ if (!root) return [];
13766
+ const ids = /* @__PURE__ */ new Set([
13767
+ ...listJobIdsIn(root, DETACHED_JOBS_DIR),
13768
+ ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
13769
+ ]);
13770
+ const running = [];
13771
+ for (const id of ids) {
13772
+ const dir = resolveJobDir(root, id);
13773
+ const pid = readIntFile((0, import_node_path38.join)(dir, "pid"));
13774
+ if (pid != null && jobAlive(pid)) running.push(id);
13775
+ }
13776
+ return running.sort();
13777
+ }
13778
+ function looksLikeDeferredDonePromise(text6) {
13779
+ const t = (text6 ?? "").trim();
13780
+ if (!t) return false;
13781
+ 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);
13782
+ }
13783
+ function formatJobStillRunningContinuePrompt(jobIds) {
13784
+ const ids = jobIds.join(", ");
13785
+ return [
13786
+ `Detached job still running: ${ids}.`,
13787
+ "Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
13788
+ "Then report the result. Do not tell the user you will let them know later."
13789
+ ].join(" ");
13790
+ }
13791
+ function formatDeferredDoneContinuePrompt() {
13792
+ return [
13793
+ "You ended the turn after promising to report later, but no detached job is running.",
13794
+ "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.",
13795
+ "Do not say you will let the user know later."
13796
+ ].join(" ");
13797
+ }
13798
+ function turnWatchedDetachedJob(parts) {
13799
+ return parts.some((p) => {
13800
+ if (p.type !== "tool") return false;
13801
+ if (/wait_for_job$/i.test(p.name ?? "")) return true;
13802
+ const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
13803
+ return /detached-job\.js\b/i.test(blob);
13804
+ });
13805
+ }
13806
+ function planJobContinue(opts) {
13807
+ if (opts.isOrchestrator) return { action: "none" };
13808
+ if (opts.agent === "brightsy") return { action: "none" };
13809
+ if (opts.queueLength > 0) return { action: "none" };
13810
+ if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
13811
+ const farewell = looksLikeDeferredDonePromise(opts.chatText);
13812
+ if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
13813
+ return {
13814
+ action: "wait",
13815
+ jobIds: opts.runningJobIds,
13816
+ prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
13817
+ };
13818
+ }
13819
+ if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
13820
+ return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
13821
+ }
13822
+ return { action: "none" };
13823
+ }
13824
+ function tailProgress(logFile, maxLines = 12) {
13825
+ if (!(0, import_node_fs38.existsSync)(logFile)) return "(no log yet)";
13826
+ const lines = (0, import_node_fs38.readFileSync)(logFile, "utf8").split("\n");
13827
+ return lines.slice(-maxLines).join("\n");
13828
+ }
13829
+ function readLogLines(file) {
13830
+ if (!(0, import_node_fs38.existsSync)(file)) return [];
13831
+ const lines = (0, import_node_fs38.readFileSync)(file, "utf8").split("\n");
13832
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
13833
+ return lines;
13834
+ }
13835
+ function takeDelta(logFile, cursorFile) {
13836
+ const lines = readLogLines(logFile);
13837
+ const cursor = readIntFile(cursorFile) ?? 0;
13838
+ const start = Math.min(Math.max(0, cursor), lines.length);
13839
+ return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
13840
+ }
13841
+ function snapshotJob(dir) {
13842
+ const pid = readIntFile((0, import_node_path38.join)(dir, "pid"));
13843
+ const running = pid != null && jobAlive(pid);
13844
+ return {
13845
+ pid,
13846
+ running,
13847
+ exitCode: readIntFile((0, import_node_path38.join)(dir, "exit")),
13848
+ log: (0, import_node_path38.join)(dir, "log"),
13849
+ cursor: (0, import_node_path38.join)(dir, "present.cursor"),
13850
+ progress: tailProgress((0, import_node_path38.join)(dir, "log"))
13851
+ };
13852
+ }
13853
+ function toResult(id, snap, extra) {
13854
+ const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
13855
+ const ok = !snap.running && snap.exitCode === 0;
13856
+ const stillRunning = snap.running && !ok;
13857
+ const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
13858
+ try {
13859
+ (0, import_node_fs38.mkdirSync)((0, import_node_path38.dirname)(snap.cursor), { recursive: true });
13860
+ (0, import_node_fs38.writeFileSync)(snap.cursor, `${nextCursor}
13861
+ `);
13862
+ } catch {
13863
+ }
13864
+ const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
13865
+ return {
13866
+ stillRunning,
13867
+ ok,
13868
+ failed: Boolean(failed && !stillRunning && !ok),
13869
+ status,
13870
+ id,
13871
+ pid: snap.pid,
13872
+ exitCode: snap.exitCode ?? void 0,
13873
+ delta,
13874
+ progress: extra?.progress ?? snap.progress,
13875
+ hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
13876
+ };
13877
+ }
13878
+ async function sleepMs(ms) {
13879
+ await new Promise((resolve) => setTimeout(resolve, ms));
13880
+ }
13881
+ async function waitForDetachedJob(cwd, id, opts) {
13882
+ const jobId = sanitizeDetachedJobId(id);
13883
+ const root = cwd.trim() || process.cwd();
13884
+ const dir = resolveJobDir(root, jobId);
13885
+ const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
13886
+ const sleep = opts?.sleep ?? sleepMs;
13887
+ if (!(0, import_node_fs38.existsSync)(dir)) {
13888
+ return {
13889
+ stillRunning: false,
13890
+ ok: false,
13891
+ failed: true,
13892
+ status: "failed",
13893
+ id: jobId,
13894
+ delta: "",
13895
+ progress: "No detached job. Start one first.",
13896
+ hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
13897
+ };
13898
+ }
13899
+ const deadline = Date.now() + timeoutMs;
13900
+ let snap = snapshotJob(dir);
13901
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
13902
+ if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
13903
+ return toResult(jobId, snap, {
13904
+ failed: true,
13905
+ progress: "No detached job. Start one first."
13906
+ });
13907
+ }
13908
+ while (Date.now() < deadline) {
13909
+ snap = snapshotJob(dir);
13910
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
13911
+ if (!snap.running) return toResult(jobId, snap, { failed: true });
13912
+ await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
13913
+ }
13914
+ snap = snapshotJob(dir);
13915
+ return toResult(jobId, snap);
13916
+ }
13917
+ var import_node_fs38, import_node_path38, MAX_JOB_CONTINUES, MCP_WAIT_JOB_STILL_RUNNING_HINT, JOB_ID_RE;
13918
+ var init_wait_for_job = __esm({
13919
+ "src/mcp/wait-for-job.ts"() {
13920
+ "use strict";
13921
+ import_node_fs38 = require("fs");
13922
+ import_node_path38 = require("path");
13923
+ init_workspace_scratch();
13924
+ init_wait_for_turn();
13925
+ MAX_JOB_CONTINUES = 8;
13926
+ 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.";
13927
+ JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
13928
+ }
13929
+ });
13930
+
13689
13931
  // src/detect/detect.ts
13690
13932
  async function requireAgent(agent, opts) {
13691
13933
  ensureAgentPath();
@@ -14770,7 +15012,7 @@ function reuseLiveThread(input, repoPath, match) {
14770
15012
  }
14771
15013
  async function createThread(input, _onSetupLine) {
14772
15014
  const repoPath = await resolveRepoRoot(input.repoPath);
14773
- if (!(0, import_node_fs38.existsSync)(repoPath)) {
15015
+ if (!(0, import_node_fs39.existsSync)(repoPath)) {
14774
15016
  throw new Error(`Repo not found: ${repoPath}`);
14775
15017
  }
14776
15018
  const reused = reuseLiveThread(input, repoPath, {
@@ -14941,11 +15183,11 @@ async function createThread(input, _onSetupLine) {
14941
15183
  await ensureWorkspace(repoPath);
14942
15184
  return readThread(thread.id) ?? thread;
14943
15185
  }
14944
- var import_node_fs38;
15186
+ var import_node_fs39;
14945
15187
  var init_create = __esm({
14946
15188
  "src/threads/create.ts"() {
14947
15189
  "use strict";
14948
- import_node_fs38 = require("fs");
15190
+ import_node_fs39 = require("fs");
14949
15191
  init_detect();
14950
15192
  init_worktree();
14951
15193
  init_home_board();
@@ -15048,20 +15290,20 @@ function writeTurnLive(threadId, progress) {
15048
15290
  const path = threadLivePath(threadId);
15049
15291
  const tmp = `${path}.${process.pid}.tmp`;
15050
15292
  try {
15051
- (0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
15052
- (0, import_node_fs39.renameSync)(tmp, path);
15293
+ (0, import_node_fs40.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
15294
+ (0, import_node_fs40.renameSync)(tmp, path);
15053
15295
  } catch {
15054
15296
  try {
15055
- (0, import_node_fs39.unlinkSync)(tmp);
15297
+ (0, import_node_fs40.unlinkSync)(tmp);
15056
15298
  } catch {
15057
15299
  }
15058
15300
  }
15059
15301
  }
15060
15302
  function readTurnLive(threadId) {
15061
15303
  const path = threadLivePath(threadId);
15062
- if (!(0, import_node_fs39.existsSync)(path)) return null;
15304
+ if (!(0, import_node_fs40.existsSync)(path)) return null;
15063
15305
  try {
15064
- const raw = JSON.parse((0, import_node_fs39.readFileSync)(path, "utf8"));
15306
+ const raw = JSON.parse((0, import_node_fs40.readFileSync)(path, "utf8"));
15065
15307
  if (!raw || typeof raw.summary !== "string") return null;
15066
15308
  return raw;
15067
15309
  } catch {
@@ -15073,17 +15315,17 @@ function clearTurnLive(threadId) {
15073
15315
  if (buf?.timer) clearTimeout(buf.timer);
15074
15316
  buffers.delete(threadId);
15075
15317
  const path = threadLivePath(threadId);
15076
- if (!(0, import_node_fs39.existsSync)(path)) return;
15318
+ if (!(0, import_node_fs40.existsSync)(path)) return;
15077
15319
  try {
15078
- (0, import_node_fs39.unlinkSync)(path);
15320
+ (0, import_node_fs40.unlinkSync)(path);
15079
15321
  } catch {
15080
15322
  }
15081
15323
  }
15082
- var import_node_fs39, buffers, FLUSH_MS, MAX_PARTS;
15324
+ var import_node_fs40, buffers, FLUSH_MS, MAX_PARTS;
15083
15325
  var init_turn_live = __esm({
15084
15326
  "src/store/turn-live.ts"() {
15085
15327
  "use strict";
15086
- import_node_fs39 = require("fs");
15328
+ import_node_fs40 = require("fs");
15087
15329
  init_message_parts();
15088
15330
  init_paths();
15089
15331
  buffers = /* @__PURE__ */ new Map();
@@ -15300,7 +15542,7 @@ var init_quota_failover = __esm({
15300
15542
  // src/threads/adopt.ts
15301
15543
  function thisModuleFile() {
15302
15544
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
15303
- return cjsFile || process.argv[1] || (0, import_node_path38.join)(process.cwd(), "package.json");
15545
+ return cjsFile || process.argv[1] || (0, import_node_path39.join)(process.cwd(), "package.json");
15304
15546
  }
15305
15547
  function openReadonlySqlite(file) {
15306
15548
  const req = (0, import_node_module4.createRequire)(thisModuleFile());
@@ -15318,21 +15560,21 @@ function mapAgentType(raw) {
15318
15560
  return null;
15319
15561
  }
15320
15562
  function resolveConductorCursorAgentId(workspacePath) {
15321
- if (!workspacePath || !(0, import_node_fs40.existsSync)(CURSOR_SDK_STORE)) return null;
15563
+ if (!workspacePath || !(0, import_node_fs41.existsSync)(CURSOR_SDK_STORE)) return null;
15322
15564
  const normalized = workspacePath.replace(/\/$/, "");
15323
15565
  let best = null;
15324
15566
  let hashes;
15325
15567
  try {
15326
- hashes = (0, import_node_fs40.readdirSync)(CURSOR_SDK_STORE);
15568
+ hashes = (0, import_node_fs41.readdirSync)(CURSOR_SDK_STORE);
15327
15569
  } catch {
15328
15570
  return null;
15329
15571
  }
15330
15572
  for (const hash of hashes) {
15331
- const agentsFile = (0, import_node_path38.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
15332
- if (!(0, import_node_fs40.existsSync)(agentsFile)) continue;
15573
+ const agentsFile = (0, import_node_path39.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
15574
+ if (!(0, import_node_fs41.existsSync)(agentsFile)) continue;
15333
15575
  let text6;
15334
15576
  try {
15335
- text6 = (0, import_node_fs40.readFileSync)(agentsFile, "utf8");
15577
+ text6 = (0, import_node_fs41.readFileSync)(agentsFile, "utf8");
15336
15578
  } catch {
15337
15579
  continue;
15338
15580
  }
@@ -15356,7 +15598,7 @@ function resolveConductorCursorAgentId(workspacePath) {
15356
15598
  return best?.agentId ?? null;
15357
15599
  }
15358
15600
  async function adoptThread(input) {
15359
- if (!(0, import_node_fs40.existsSync)(input.worktreePath)) {
15601
+ if (!(0, import_node_fs41.existsSync)(input.worktreePath)) {
15360
15602
  throw new Error(`Worktree not found: ${input.worktreePath}`);
15361
15603
  }
15362
15604
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -15382,18 +15624,18 @@ async function adoptThread(input) {
15382
15624
  return thread;
15383
15625
  }
15384
15626
  function listConductorWorkspaces() {
15385
- if (!(0, import_node_fs40.existsSync)(CONDUCTOR_DB)) {
15627
+ if (!(0, import_node_fs41.existsSync)(CONDUCTOR_DB)) {
15386
15628
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
15387
15629
  }
15388
- const tmp = (0, import_node_fs40.mkdtempSync)((0, import_node_path38.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
15389
- const snapshot = (0, import_node_path38.join)(tmp, "conductor.db");
15630
+ const tmp = (0, import_node_fs41.mkdtempSync)((0, import_node_path39.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
15631
+ const snapshot = (0, import_node_path39.join)(tmp, "conductor.db");
15390
15632
  try {
15391
- (0, import_node_fs40.copyFileSync)(CONDUCTOR_DB, snapshot);
15633
+ (0, import_node_fs41.copyFileSync)(CONDUCTOR_DB, snapshot);
15392
15634
  for (const suffix of ["-wal", "-shm"]) {
15393
15635
  const src = `${CONDUCTOR_DB}${suffix}`;
15394
- if ((0, import_node_fs40.existsSync)(src)) {
15636
+ if ((0, import_node_fs41.existsSync)(src)) {
15395
15637
  try {
15396
- (0, import_node_fs40.copyFileSync)(src, `${snapshot}${suffix}`);
15638
+ (0, import_node_fs41.copyFileSync)(src, `${snapshot}${suffix}`);
15397
15639
  } catch {
15398
15640
  }
15399
15641
  }
@@ -15469,22 +15711,22 @@ function listConductorWorkspaces() {
15469
15711
  db.close();
15470
15712
  }
15471
15713
  } finally {
15472
- (0, import_node_fs40.rmSync)(tmp, { recursive: true, force: true });
15714
+ (0, import_node_fs41.rmSync)(tmp, { recursive: true, force: true });
15473
15715
  }
15474
15716
  }
15475
15717
  function importConductorWorkspace(workspaceId) {
15476
- if (!(0, import_node_fs40.existsSync)(CONDUCTOR_DB)) {
15718
+ if (!(0, import_node_fs41.existsSync)(CONDUCTOR_DB)) {
15477
15719
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
15478
15720
  }
15479
- const tmp = (0, import_node_fs40.mkdtempSync)((0, import_node_path38.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
15480
- const snapshot = (0, import_node_path38.join)(tmp, "conductor.db");
15721
+ const tmp = (0, import_node_fs41.mkdtempSync)((0, import_node_path39.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
15722
+ const snapshot = (0, import_node_path39.join)(tmp, "conductor.db");
15481
15723
  try {
15482
- (0, import_node_fs40.copyFileSync)(CONDUCTOR_DB, snapshot);
15724
+ (0, import_node_fs41.copyFileSync)(CONDUCTOR_DB, snapshot);
15483
15725
  for (const suffix of ["-wal", "-shm"]) {
15484
15726
  const src = `${CONDUCTOR_DB}${suffix}`;
15485
- if ((0, import_node_fs40.existsSync)(src)) {
15727
+ if ((0, import_node_fs41.existsSync)(src)) {
15486
15728
  try {
15487
- (0, import_node_fs40.copyFileSync)(src, `${snapshot}${suffix}`);
15729
+ (0, import_node_fs41.copyFileSync)(src, `${snapshot}${suffix}`);
15488
15730
  } catch {
15489
15731
  }
15490
15732
  }
@@ -15502,7 +15744,7 @@ function importConductorWorkspace(workspaceId) {
15502
15744
  ).get(workspaceId);
15503
15745
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
15504
15746
  const worktreePath = String(row.workspacePath);
15505
- if (!(0, import_node_fs40.existsSync)(worktreePath)) {
15747
+ if (!(0, import_node_fs41.existsSync)(worktreePath)) {
15506
15748
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
15507
15749
  }
15508
15750
  let sessionId = null;
@@ -15565,31 +15807,31 @@ function importConductorWorkspace(workspaceId) {
15565
15807
  db.close();
15566
15808
  }
15567
15809
  } finally {
15568
- (0, import_node_fs40.rmSync)(tmp, { recursive: true, force: true });
15810
+ (0, import_node_fs41.rmSync)(tmp, { recursive: true, force: true });
15569
15811
  }
15570
15812
  }
15571
15813
  async function importConductorWorkspaceAsync(workspaceId) {
15572
15814
  return importConductorWorkspace(workspaceId);
15573
15815
  }
15574
- var import_node_child_process3, import_node_fs40, import_node_os13, import_node_path38, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
15816
+ var import_node_child_process3, import_node_fs41, import_node_os13, import_node_path39, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
15575
15817
  var init_adopt = __esm({
15576
15818
  "src/threads/adopt.ts"() {
15577
15819
  "use strict";
15578
15820
  import_node_child_process3 = require("child_process");
15579
- import_node_fs40 = require("fs");
15821
+ import_node_fs41 = require("fs");
15580
15822
  import_node_os13 = require("os");
15581
- import_node_path38 = require("path");
15823
+ import_node_path39 = require("path");
15582
15824
  import_node_module4 = require("module");
15583
15825
  init_worktree();
15584
15826
  init_thread_store();
15585
- CONDUCTOR_APP_SUPPORT = (0, import_node_path38.join)(
15827
+ CONDUCTOR_APP_SUPPORT = (0, import_node_path39.join)(
15586
15828
  process.env.HOME ?? "",
15587
15829
  "Library",
15588
15830
  "Application Support",
15589
15831
  "com.conductor.app"
15590
15832
  );
15591
- CONDUCTOR_DB = (0, import_node_path38.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
15592
- CURSOR_SDK_STORE = (0, import_node_path38.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
15833
+ CONDUCTOR_DB = (0, import_node_path39.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
15834
+ CURSOR_SDK_STORE = (0, import_node_path39.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
15593
15835
  }
15594
15836
  });
15595
15837
 
@@ -15656,7 +15898,7 @@ async function openStackLayer(input, _onSetupLine) {
15656
15898
  let createdWorktree = false;
15657
15899
  const trees = await listWorktrees(repoPath);
15658
15900
  const checkedOut = trees.find((w) => w.branch === branchName);
15659
- if (checkedOut?.path && (0, import_node_fs41.existsSync)(checkedOut.path)) {
15901
+ if (checkedOut?.path && (0, import_node_fs42.existsSync)(checkedOut.path)) {
15660
15902
  if (input.reuseExistingWorktree !== false) {
15661
15903
  worktreePath = checkedOut.path;
15662
15904
  } else {
@@ -15798,7 +16040,7 @@ async function initStackFromThread(input, onSetupLine) {
15798
16040
  async function createPrStack(input, onSetupLine) {
15799
16041
  await requireAgent(input.agent);
15800
16042
  const repoPath = await resolveRepoRoot(input.repoPath);
15801
- if (!(0, import_node_fs41.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
16043
+ if (!(0, import_node_fs42.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
15802
16044
  if (!input.branches.length) throw new Error("At least one branch name required");
15803
16045
  const status = await detectGhStack(repoPath);
15804
16046
  if (!status.available) throw new Error(status.reason);
@@ -15865,7 +16107,7 @@ async function createPrStack(input, onSetupLine) {
15865
16107
  }
15866
16108
  }
15867
16109
  const claimed = new Set(threads.map((t) => t.worktreePath));
15868
- if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs41.existsSync)(bootstrap.worktreePath)) {
16110
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs42.existsSync)(bootstrap.worktreePath)) {
15869
16111
  try {
15870
16112
  await removeWorktree(repoPath, bootstrap.worktreePath, {
15871
16113
  deleteBranch: bootstrap.branchName
@@ -15875,11 +16117,11 @@ async function createPrStack(input, onSetupLine) {
15875
16117
  }
15876
16118
  return { stack, threads, createdThreadIds };
15877
16119
  }
15878
- var import_node_fs41;
16120
+ var import_node_fs42;
15879
16121
  var init_stack_layers = __esm({
15880
16122
  "src/threads/stack-layers.ts"() {
15881
16123
  "use strict";
15882
- import_node_fs41 = require("fs");
16124
+ import_node_fs42 = require("fs");
15883
16125
  init_detect();
15884
16126
  init_run();
15885
16127
  init_stack();
@@ -15892,7 +16134,7 @@ var init_stack_layers = __esm({
15892
16134
 
15893
16135
  // src/diff/diff.ts
15894
16136
  async function inspectGitWorktree(worktreePath) {
15895
- if (!worktreePath || !(0, import_node_fs42.existsSync)(worktreePath)) return "missing_worktree";
16137
+ if (!worktreePath || !(0, import_node_fs43.existsSync)(worktreePath)) return "missing_worktree";
15896
16138
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
15897
16139
  reject: false
15898
16140
  });
@@ -15900,7 +16142,7 @@ async function inspectGitWorktree(worktreePath) {
15900
16142
  return "ok";
15901
16143
  }
15902
16144
  async function initializeGitRepository(worktreePath) {
15903
- if (!worktreePath || !(0, import_node_fs42.existsSync)(worktreePath)) {
16145
+ if (!worktreePath || !(0, import_node_fs43.existsSync)(worktreePath)) {
15904
16146
  throw new Error("Worktree not found");
15905
16147
  }
15906
16148
  const status = await inspectGitWorktree(worktreePath);
@@ -16034,11 +16276,11 @@ new file mode 100644
16034
16276
  };
16035
16277
  }
16036
16278
  async function untrackedPatch(worktreePath, path, maxHunk) {
16037
- const abs = (0, import_node_path39.join)(worktreePath, path);
16279
+ const abs = (0, import_node_path40.join)(worktreePath, path);
16038
16280
  try {
16039
- const st = (0, import_node_fs42.statSync)(abs);
16281
+ const st = (0, import_node_fs43.statSync)(abs);
16040
16282
  if (st.isFile() && st.size > maxHunk) {
16041
- const buf = (0, import_node_fs42.readFileSync)(abs).subarray(0, maxHunk);
16283
+ const buf = (0, import_node_fs43.readFileSync)(abs).subarray(0, maxHunk);
16042
16284
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
16043
16285
  }
16044
16286
  } catch {
@@ -16527,8 +16769,8 @@ function isImageRelativePath(relativePath) {
16527
16769
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
16528
16770
  assertSafeRelativePath(relativePath);
16529
16771
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
16530
- const abs = (0, import_node_path39.join)(worktreePath, relativePath);
16531
- const st = (0, import_node_fs42.statSync)(abs);
16772
+ const abs = (0, import_node_path40.join)(worktreePath, relativePath);
16773
+ const st = (0, import_node_fs43.statSync)(abs);
16532
16774
  if (!st.isFile()) {
16533
16775
  throw new Error(`Not a file: ${relativePath}`);
16534
16776
  }
@@ -16537,7 +16779,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
16537
16779
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
16538
16780
  );
16539
16781
  }
16540
- const buf = (0, import_node_fs42.readFileSync)(abs);
16782
+ const buf = (0, import_node_fs43.readFileSync)(abs);
16541
16783
  return {
16542
16784
  path: relativePath,
16543
16785
  contentBase64: buf.toString("base64"),
@@ -16547,12 +16789,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
16547
16789
  function readWorktreeFile(worktreePath, relativePath, opts) {
16548
16790
  assertSafeRelativePath(relativePath);
16549
16791
  const maxBytes = opts?.maxBytes ?? 2e5;
16550
- const abs = (0, import_node_path39.join)(worktreePath, relativePath);
16551
- const st = (0, import_node_fs42.statSync)(abs);
16792
+ const abs = (0, import_node_path40.join)(worktreePath, relativePath);
16793
+ const st = (0, import_node_fs43.statSync)(abs);
16552
16794
  if (!st.isFile()) {
16553
16795
  throw new Error(`Not a file: ${relativePath}`);
16554
16796
  }
16555
- const buf = (0, import_node_fs42.readFileSync)(abs);
16797
+ const buf = (0, import_node_fs43.readFileSync)(abs);
16556
16798
  if (isImageRelativePath(relativePath)) {
16557
16799
  const maxImageBytes = Math.max(maxBytes, 15e6);
16558
16800
  const truncated2 = buf.length > maxImageBytes;
@@ -16595,9 +16837,9 @@ function assertSafeRelativePath(relativePath) {
16595
16837
  }
16596
16838
  function writeWorktreeFile(worktreePath, relativePath, content) {
16597
16839
  assertSafeRelativePath(relativePath);
16598
- const abs = (0, import_node_path39.join)(worktreePath, relativePath);
16599
- (0, import_node_fs42.mkdirSync)((0, import_node_path39.dirname)(abs), { recursive: true });
16600
- (0, import_node_fs42.writeFileSync)(abs, content, "utf8");
16840
+ const abs = (0, import_node_path40.join)(worktreePath, relativePath);
16841
+ (0, import_node_fs43.mkdirSync)((0, import_node_path40.dirname)(abs), { recursive: true });
16842
+ (0, import_node_fs43.writeFileSync)(abs, content, "utf8");
16601
16843
  return { path: relativePath };
16602
16844
  }
16603
16845
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -16614,12 +16856,12 @@ async function getDiffSummary(worktreePath, repoPath, opts) {
16614
16856
  truncated: full.files.length > maxFiles
16615
16857
  };
16616
16858
  }
16617
- var import_node_fs42, import_node_path39, mergeBaseCache, MERGE_BASE_TTL_MS, SHA_RE, IMAGE_EXTENSIONS2, DEFAULT_UPLOAD_MAX_BYTES;
16859
+ var import_node_fs43, import_node_path40, mergeBaseCache, MERGE_BASE_TTL_MS, SHA_RE, IMAGE_EXTENSIONS2, DEFAULT_UPLOAD_MAX_BYTES;
16618
16860
  var init_diff = __esm({
16619
16861
  "src/diff/diff.ts"() {
16620
16862
  "use strict";
16621
- import_node_fs42 = require("fs");
16622
- import_node_path39 = require("path");
16863
+ import_node_fs43 = require("fs");
16864
+ import_node_path40 = require("path");
16623
16865
  init_run();
16624
16866
  init_worktree();
16625
16867
  mergeBaseCache = /* @__PURE__ */ new Map();
@@ -16768,7 +17010,7 @@ var init_long_running = __esm({
16768
17010
 
16769
17011
  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.
16770
17012
 
16771
- 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.
17013
+ 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
16772
17014
 
16773
17015
  ## Tool
16774
17016
 
@@ -16778,7 +17020,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
16778
17020
  # Start (exits in ~1s; job survives this turn)
16779
17021
  node <detached-job.js> start <id> -- <command> [args...]
16780
17022
 
16781
- # Wait \u2014 returns within ~45s even if the job is still going
17023
+ # Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
17024
+ # Shell fallback:
16782
17025
  node <detached-job.js> wait <id>
16783
17026
 
16784
17027
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -16791,7 +17034,7 @@ node <detached-job.js> status <id>
16791
17034
 
16792
17035
  Wait JSON:
16793
17036
 
16794
- - \`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.
17037
+ - \`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.
16795
17038
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
16796
17039
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
16797
17040
 
@@ -16821,7 +17064,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
16821
17064
 
16822
17065
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
16823
17066
  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.
16824
- 3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
17067
+ 3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
16825
17068
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
16826
17069
 
16827
17070
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -16887,7 +17130,7 @@ function parseFrontmatter(content) {
16887
17130
  }
16888
17131
  function readSkill(skillMd, source) {
16889
17132
  try {
16890
- const content = (0, import_node_fs43.readFileSync)(skillMd, "utf8");
17133
+ const content = (0, import_node_fs44.readFileSync)(skillMd, "utf8");
16891
17134
  const { name: fmName, description } = parseFrontmatter(content);
16892
17135
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
16893
17136
  const name = fmName || dirName;
@@ -16906,19 +17149,19 @@ function readSkill(skillMd, source) {
16906
17149
  }
16907
17150
  }
16908
17151
  function scanSkillsDir(dir, source, out) {
16909
- if (!(0, import_node_fs43.existsSync)(dir)) return;
17152
+ if (!(0, import_node_fs44.existsSync)(dir)) return;
16910
17153
  let entries;
16911
17154
  try {
16912
- entries = (0, import_node_fs43.readdirSync)(dir);
17155
+ entries = (0, import_node_fs44.readdirSync)(dir);
16913
17156
  } catch {
16914
17157
  return;
16915
17158
  }
16916
17159
  for (const entry of entries) {
16917
17160
  if (entry.startsWith(".")) continue;
16918
- const skillMd = (0, import_node_path40.join)(dir, entry, "SKILL.md");
16919
- if (!(0, import_node_fs43.existsSync)(skillMd)) continue;
17161
+ const skillMd = (0, import_node_path41.join)(dir, entry, "SKILL.md");
17162
+ if (!(0, import_node_fs44.existsSync)(skillMd)) continue;
16920
17163
  try {
16921
- if (!(0, import_node_fs43.statSync)(skillMd).isFile()) continue;
17164
+ if (!(0, import_node_fs44.statSync)(skillMd).isFile()) continue;
16922
17165
  } catch {
16923
17166
  continue;
16924
17167
  }
@@ -16927,24 +17170,24 @@ function scanSkillsDir(dir, source, out) {
16927
17170
  }
16928
17171
  }
16929
17172
  function scanClaudePluginSkills(pluginsRoot, out) {
16930
- if (!(0, import_node_fs43.existsSync)(pluginsRoot)) return;
17173
+ if (!(0, import_node_fs44.existsSync)(pluginsRoot)) return;
16931
17174
  const walk = (dir, depth, lookingForSkillsDir) => {
16932
17175
  if (depth > 7) return;
16933
17176
  let entries;
16934
17177
  try {
16935
- entries = (0, import_node_fs43.readdirSync)(dir);
17178
+ entries = (0, import_node_fs44.readdirSync)(dir);
16936
17179
  } catch {
16937
17180
  return;
16938
17181
  }
16939
17182
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
16940
- const skill = readSkill((0, import_node_path40.join)(dir, "SKILL.md"), "cli");
17183
+ const skill = readSkill((0, import_node_path41.join)(dir, "SKILL.md"), "cli");
16941
17184
  if (skill) out.push(skill);
16942
17185
  }
16943
17186
  for (const entry of entries) {
16944
17187
  if (entry === "node_modules" || entry === ".git") continue;
16945
- const full = (0, import_node_path40.join)(dir, entry);
17188
+ const full = (0, import_node_path41.join)(dir, entry);
16946
17189
  try {
16947
- if (!(0, import_node_fs43.statSync)(full).isDirectory()) continue;
17190
+ if (!(0, import_node_fs44.statSync)(full).isDirectory()) continue;
16948
17191
  } catch {
16949
17192
  continue;
16950
17193
  }
@@ -16962,17 +17205,17 @@ function discoverSkills(worktreePath) {
16962
17205
  const home = (0, import_node_os14.homedir)();
16963
17206
  const collected = [];
16964
17207
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
16965
- scanSkillsDir((0, import_node_path40.join)(worktreePath, rel), "workspace", collected);
17208
+ scanSkillsDir((0, import_node_path41.join)(worktreePath, rel), "workspace", collected);
16966
17209
  }
16967
17210
  for (const abs of [
16968
- (0, import_node_path40.join)(home, ".claude/skills"),
16969
- (0, import_node_path40.join)(home, ".cursor/skills"),
16970
- (0, import_node_path40.join)(home, ".sideboard/skills"),
16971
- (0, import_node_path40.join)(home, ".brightsy/skills")
17211
+ (0, import_node_path41.join)(home, ".claude/skills"),
17212
+ (0, import_node_path41.join)(home, ".cursor/skills"),
17213
+ (0, import_node_path41.join)(home, ".sideboard/skills"),
17214
+ (0, import_node_path41.join)(home, ".brightsy/skills")
16972
17215
  ]) {
16973
17216
  scanSkillsDir(abs, "user", collected);
16974
17217
  }
16975
- scanClaudePluginSkills((0, import_node_path40.join)(home, ".claude/plugins"), collected);
17218
+ scanClaudePluginSkills((0, import_node_path41.join)(home, ".claude/plugins"), collected);
16976
17219
  collected.push(...bundledSkills());
16977
17220
  const rank = {
16978
17221
  workspace: 0,
@@ -16998,7 +17241,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
16998
17241
  \u2026(truncated)` : body;
16999
17242
  }
17000
17243
  }
17001
- const raw = (0, import_node_fs43.readFileSync)(skillPath, "utf8");
17244
+ const raw = (0, import_node_fs44.readFileSync)(skillPath, "utf8");
17002
17245
  if (raw.startsWith("---")) {
17003
17246
  const end = raw.indexOf("\n---", 3);
17004
17247
  if (end >= 0) {
@@ -17012,13 +17255,13 @@ function readSkillBody(skillPath, maxChars = 12e3) {
17012
17255
 
17013
17256
  \u2026(truncated)` : raw;
17014
17257
  }
17015
- var import_node_fs43, import_node_os14, import_node_path40, BUNDLED_SKILL_PREFIX;
17258
+ var import_node_fs44, import_node_os14, import_node_path41, BUNDLED_SKILL_PREFIX;
17016
17259
  var init_discover = __esm({
17017
17260
  "src/skills/discover.ts"() {
17018
17261
  "use strict";
17019
- import_node_fs43 = require("fs");
17262
+ import_node_fs44 = require("fs");
17020
17263
  import_node_os14 = require("os");
17021
- import_node_path40 = require("path");
17264
+ import_node_path41 = require("path");
17022
17265
  init_long_running();
17023
17266
  BUNDLED_SKILL_PREFIX = "bundled:";
17024
17267
  }
@@ -17113,17 +17356,17 @@ var init_expand = __esm({
17113
17356
  function packagedDetachedJobPath() {
17114
17357
  const dir = packagedMcpDir();
17115
17358
  if (!dir) return null;
17116
- const script = (0, import_node_path41.join)(dir, "scripts", "detached-job.js");
17117
- return (0, import_node_fs44.existsSync)(script) ? script : null;
17359
+ const script = (0, import_node_path42.join)(dir, "scripts", "detached-job.js");
17360
+ return (0, import_node_fs45.existsSync)(script) ? script : null;
17118
17361
  }
17119
17362
  function resolveDetachedJobScript() {
17120
17363
  const packaged = packagedDetachedJobPath();
17121
17364
  if (packaged) return packaged;
17122
- let dir = (0, import_node_path41.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
17365
+ let dir = (0, import_node_path42.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
17123
17366
  for (let i = 0; i < 8; i++) {
17124
- const candidate = (0, import_node_path41.join)(dir, "scripts", "detached-job.js");
17125
- if ((0, import_node_fs44.existsSync)(candidate)) return candidate;
17126
- const parent = (0, import_node_path41.dirname)(dir);
17367
+ const candidate = (0, import_node_path42.join)(dir, "scripts", "detached-job.js");
17368
+ if ((0, import_node_fs45.existsSync)(candidate)) return candidate;
17369
+ const parent = (0, import_node_path42.dirname)(dir);
17127
17370
  if (parent === dir) break;
17128
17371
  dir = parent;
17129
17372
  }
@@ -17134,12 +17377,12 @@ function formatDetachedJobInvoke(scriptPath) {
17134
17377
  if (resolved) return `node ${JSON.stringify(resolved)}`;
17135
17378
  return "node scripts/detached-job.js";
17136
17379
  }
17137
- var import_node_fs44, import_node_path41, import_node_url3, import_meta3;
17380
+ var import_node_fs45, import_node_path42, import_node_url3, import_meta3;
17138
17381
  var init_detached_job_path = __esm({
17139
17382
  "src/skills/detached-job-path.ts"() {
17140
17383
  "use strict";
17141
- import_node_fs44 = require("fs");
17142
- import_node_path41 = require("path");
17384
+ import_node_fs45 = require("fs");
17385
+ import_node_path42 = require("path");
17143
17386
  import_node_url3 = require("url");
17144
17387
  init_packaged_runtime();
17145
17388
  import_meta3 = {};
@@ -17338,14 +17581,15 @@ function formatLongRunningDirective(opts) {
17338
17581
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
17339
17582
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
17340
17583
  "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
17341
- `- 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.`,
17584
+ `- 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.`,
17585
+ "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
17342
17586
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
17343
17587
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
17344
17588
  ].join("\n");
17345
17589
  }
17346
17590
  function formatLongRunningReminder(opts) {
17347
17591
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
17348
- return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
17592
+ 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.`;
17349
17593
  }
17350
17594
  function formatArtifactDirective() {
17351
17595
  return [
@@ -17375,12 +17619,12 @@ function formatArtifactDirective() {
17375
17619
  function formatUiReminder() {
17376
17620
  return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos or \u201Cwhat next?\u201D) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable.";
17377
17621
  }
17378
- var import_node_fs45, import_node_path42, GITHUB_TICKET_REF, KEYED_TICKET_REF;
17622
+ var import_node_fs46, import_node_path43, GITHUB_TICKET_REF, KEYED_TICKET_REF;
17379
17623
  var init_instructions = __esm({
17380
17624
  "src/agents/instructions.ts"() {
17381
17625
  "use strict";
17382
- import_node_fs45 = require("fs");
17383
- import_node_path42 = require("path");
17626
+ import_node_fs46 = require("fs");
17627
+ import_node_path43 = require("path");
17384
17628
  init_git_auth_mode();
17385
17629
  init_worktree_labels();
17386
17630
  init_detached_job_path();
@@ -17552,40 +17796,40 @@ __export(plan_file_exports, {
17552
17796
  writePlanFile: () => writePlanFile
17553
17797
  });
17554
17798
  function ensureAttachmentsGitignore(worktreePath) {
17555
- const gitignoreAbs = (0, import_node_path43.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
17556
- if ((0, import_node_fs46.existsSync)(gitignoreAbs)) return;
17557
- (0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(gitignoreAbs), { recursive: true });
17558
- (0, import_node_fs46.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
17799
+ const gitignoreAbs = (0, import_node_path44.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
17800
+ if ((0, import_node_fs47.existsSync)(gitignoreAbs)) return;
17801
+ (0, import_node_fs47.mkdirSync)((0, import_node_path44.dirname)(gitignoreAbs), { recursive: true });
17802
+ (0, import_node_fs47.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
17559
17803
  }
17560
17804
  function planFileAbs(worktreePath) {
17561
- return (0, import_node_path43.join)(worktreePath, PLAN_FILE_REL);
17805
+ return (0, import_node_path44.join)(worktreePath, PLAN_FILE_REL);
17562
17806
  }
17563
17807
  function readTextIfPresent2(abs) {
17564
- if (!(0, import_node_fs46.existsSync)(abs)) return null;
17808
+ if (!(0, import_node_fs47.existsSync)(abs)) return null;
17565
17809
  try {
17566
- const content = (0, import_node_fs46.readFileSync)(abs, "utf8");
17810
+ const content = (0, import_node_fs47.readFileSync)(abs, "utf8");
17567
17811
  return content.trim() ? content : null;
17568
17812
  } catch {
17569
17813
  return null;
17570
17814
  }
17571
17815
  }
17572
17816
  function readPlanFile(worktreePath) {
17573
- return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path43.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path43.join)(worktreePath, LEGACY_PLAN_FILE_REL));
17817
+ return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path44.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path44.join)(worktreePath, LEGACY_PLAN_FILE_REL));
17574
17818
  }
17575
17819
  function writePlanFile(worktreePath, content) {
17576
17820
  ensureAttachmentsGitignore(worktreePath);
17577
17821
  const abs = planFileAbs(worktreePath);
17578
- (0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(abs), { recursive: true });
17822
+ (0, import_node_fs47.mkdirSync)((0, import_node_path44.dirname)(abs), { recursive: true });
17579
17823
  const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
17580
- (0, import_node_fs46.writeFileSync)(abs, body, "utf8");
17824
+ (0, import_node_fs47.writeFileSync)(abs, body, "utf8");
17581
17825
  return PLAN_FILE_REL;
17582
17826
  }
17583
- var import_node_fs46, import_node_path43;
17827
+ var import_node_fs47, import_node_path44;
17584
17828
  var init_plan_file = __esm({
17585
17829
  "src/plan/plan-file.ts"() {
17586
17830
  "use strict";
17587
- import_node_fs46 = require("fs");
17588
- import_node_path43 = require("path");
17831
+ import_node_fs47 = require("fs");
17832
+ import_node_path44 = require("path");
17589
17833
  init_workspace_scratch();
17590
17834
  init_plan_present();
17591
17835
  init_plan_present();
@@ -17645,7 +17889,7 @@ function setCaffeinateHoldHooks(next) {
17645
17889
  hooks = next;
17646
17890
  }
17647
17891
  function caffeinateHoldPath() {
17648
- return (0, import_node_path44.join)(appDataDir(), "caffeinate-hold.json");
17892
+ return (0, import_node_path45.join)(appDataDir(), "caffeinate-hold.json");
17649
17893
  }
17650
17894
  function processAlive(pid) {
17651
17895
  if (hooks.processAlive) return hooks.processAlive(pid);
@@ -17679,9 +17923,9 @@ function uniqueIds(ids) {
17679
17923
  }
17680
17924
  function readHold() {
17681
17925
  const path = caffeinateHoldPath();
17682
- if (!(0, import_node_fs47.existsSync)(path)) return null;
17926
+ if (!(0, import_node_fs48.existsSync)(path)) return null;
17683
17927
  try {
17684
- const parsed = JSON.parse((0, import_node_fs47.readFileSync)(path, "utf8"));
17928
+ const parsed = JSON.parse((0, import_node_fs48.readFileSync)(path, "utf8"));
17685
17929
  if (typeof parsed?.pid === "number" && parsed.pid > 0) {
17686
17930
  return {
17687
17931
  pid: parsed.pid,
@@ -17703,7 +17947,7 @@ function writeHold(pid, threadIds) {
17703
17947
  }
17704
17948
  function clearHold() {
17705
17949
  try {
17706
- (0, import_node_fs47.unlinkSync)(caffeinateHoldPath());
17950
+ (0, import_node_fs48.unlinkSync)(caffeinateHoldPath());
17707
17951
  } catch {
17708
17952
  }
17709
17953
  }
@@ -17788,13 +18032,13 @@ function releaseCaffeinateHoldForThread(threadId) {
17788
18032
  }
17789
18033
  return setCaffeinateHold(false, { threadId: id });
17790
18034
  }
17791
- var import_node_child_process4, import_node_fs47, import_node_path44, hooks;
18035
+ var import_node_child_process4, import_node_fs48, import_node_path45, hooks;
17792
18036
  var init_caffeinate_hold = __esm({
17793
18037
  "src/store/caffeinate-hold.ts"() {
17794
18038
  "use strict";
17795
18039
  import_node_child_process4 = require("child_process");
17796
- import_node_fs47 = require("fs");
17797
- import_node_path44 = require("path");
18040
+ import_node_fs48 = require("fs");
18041
+ import_node_path45 = require("path");
17798
18042
  init_paths();
17799
18043
  init_private_file();
17800
18044
  hooks = {};
@@ -17803,7 +18047,7 @@ var init_caffeinate_hold = __esm({
17803
18047
 
17804
18048
  // src/store/schedules.ts
17805
18049
  function schedulesPath() {
17806
- return (0, import_node_path45.join)(appDataDir(), "schedules.json");
18050
+ return (0, import_node_path46.join)(appDataDir(), "schedules.json");
17807
18051
  }
17808
18052
  function parseDurationMs(every) {
17809
18053
  const m = every.trim().match(DURATION_RE);
@@ -17903,9 +18147,9 @@ function normalizeTask(raw) {
17903
18147
  }
17904
18148
  function readAll2() {
17905
18149
  const path = schedulesPath();
17906
- if (!(0, import_node_fs48.existsSync)(path)) return [];
18150
+ if (!(0, import_node_fs49.existsSync)(path)) return [];
17907
18151
  try {
17908
- const parsed = JSON.parse((0, import_node_fs48.readFileSync)(path, "utf8"));
18152
+ const parsed = JSON.parse((0, import_node_fs49.readFileSync)(path, "utf8"));
17909
18153
  if (!Array.isArray(parsed)) return [];
17910
18154
  return parsed.map((row) => {
17911
18155
  try {
@@ -18017,13 +18261,13 @@ function recordScheduleRun(id, result) {
18017
18261
  writeAll2(rows);
18018
18262
  return next;
18019
18263
  }
18020
- var import_node_crypto9, import_node_fs48, import_node_path45, import_croner, DURATION_RE, UNIT_MS;
18264
+ var import_node_crypto9, import_node_fs49, import_node_path46, import_croner, DURATION_RE, UNIT_MS;
18021
18265
  var init_schedules = __esm({
18022
18266
  "src/store/schedules.ts"() {
18023
18267
  "use strict";
18024
18268
  import_node_crypto9 = require("crypto");
18025
- import_node_fs48 = require("fs");
18026
- import_node_path45 = require("path");
18269
+ import_node_fs49 = require("fs");
18270
+ import_node_path46 = require("path");
18027
18271
  import_croner = require("croner");
18028
18272
  init_orchestrator_capable();
18029
18273
  init_paths();
@@ -18165,13 +18409,13 @@ var init_schedule_runner = __esm({
18165
18409
 
18166
18410
  // src/agents/cursor-store.ts
18167
18411
  function cursorSdkStoreDir(threadId) {
18168
- const root = (0, import_node_path46.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
18412
+ const root = (0, import_node_path47.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
18169
18413
  const id = sanitizeCursorStoreSegment(threadId);
18170
18414
  if (!id) return root;
18171
- return (0, import_node_path46.join)(root, "threads", id);
18415
+ return (0, import_node_path47.join)(root, "threads", id);
18172
18416
  }
18173
18417
  function cursorSdkRunsNdjsonPath(threadId) {
18174
- return (0, import_node_path46.join)(cursorSdkStoreDir(threadId), "runs.ndjson");
18418
+ return (0, import_node_path47.join)(cursorSdkStoreDir(threadId), "runs.ndjson");
18175
18419
  }
18176
18420
  function cursorSdkRunsNdjsonSearchPaths(threadId) {
18177
18421
  const scoped = cursorSdkRunsNdjsonPath(threadId);
@@ -18184,11 +18428,11 @@ function cursorSdkRunsNdjsonSearchPaths(threadId) {
18184
18428
  function sanitizeCursorStoreSegment(threadId) {
18185
18429
  return (threadId ?? "").trim().replace(/[^a-zA-Z0-9._-]/g, "_");
18186
18430
  }
18187
- var import_node_path46, CURSOR_SDK_STORE_DIR;
18431
+ var import_node_path47, CURSOR_SDK_STORE_DIR;
18188
18432
  var init_cursor_store = __esm({
18189
18433
  "src/agents/cursor-store.ts"() {
18190
18434
  "use strict";
18191
- import_node_path46 = require("path");
18435
+ import_node_path47 = require("path");
18192
18436
  init_paths();
18193
18437
  CURSOR_SDK_STORE_DIR = "cursor-sdk-store";
18194
18438
  }
@@ -18210,9 +18454,9 @@ function recoverFinishedCursorRun(opts) {
18210
18454
  return best;
18211
18455
  }
18212
18456
  function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
18213
- if (!(0, import_node_fs49.existsSync)(runsPath)) return null;
18457
+ if (!(0, import_node_fs50.existsSync)(runsPath)) return null;
18214
18458
  try {
18215
- const lines = (0, import_node_fs49.readFileSync)(runsPath, "utf8").split("\n");
18459
+ const lines = (0, import_node_fs50.readFileSync)(runsPath, "utf8").split("\n");
18216
18460
  let best = null;
18217
18461
  for (const line of lines) {
18218
18462
  const trimmed = line.trim();
@@ -18238,11 +18482,11 @@ function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
18238
18482
  return null;
18239
18483
  }
18240
18484
  }
18241
- var import_node_fs49;
18485
+ var import_node_fs50;
18242
18486
  var init_cursor_recover = __esm({
18243
18487
  "src/agents/cursor-recover.ts"() {
18244
18488
  "use strict";
18245
- import_node_fs49 = require("fs");
18489
+ import_node_fs50 = require("fs");
18246
18490
  init_cursor_store();
18247
18491
  }
18248
18492
  });
@@ -18374,13 +18618,13 @@ async function startOrchestration(opts) {
18374
18618
  }
18375
18619
  return updated;
18376
18620
  }
18377
- var import_node_events, import_node_fs50, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, Orchestrator, singleton;
18621
+ var import_node_events, import_node_fs51, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, Orchestrator, singleton;
18378
18622
  var init_orchestrator = __esm({
18379
18623
  "src/orchestrator/orchestrator.ts"() {
18380
18624
  "use strict";
18381
18625
  import_node_events = require("events");
18382
18626
  init_outbound_watch();
18383
- import_node_fs50 = require("fs");
18627
+ import_node_fs51 = require("fs");
18384
18628
  init_error_detail();
18385
18629
  init_run();
18386
18630
  init_stale_lock();
@@ -18401,6 +18645,7 @@ var init_orchestrator = __esm({
18401
18645
  init_thread_store();
18402
18646
  init_desktop_host();
18403
18647
  init_child_halt();
18648
+ init_wait_for_job();
18404
18649
  init_create();
18405
18650
  init_cowboy();
18406
18651
  init_orchestrator_capable();
@@ -18465,6 +18710,9 @@ var init_orchestrator = __esm({
18465
18710
  * finish or a user send so a later crash can recover again.
18466
18711
  */
18467
18712
  crashContinued = /* @__PURE__ */ new Set();
18713
+ /** Auto-continues after a worktree turn ended while a detached job still runs. */
18714
+ jobContinueCount = /* @__PURE__ */ new Map();
18715
+ jobContinueNudged = /* @__PURE__ */ new Set();
18468
18716
  maxConcurrent;
18469
18717
  runningCount = 0;
18470
18718
  constructor(opts) {
@@ -18548,7 +18796,7 @@ var init_orchestrator = __esm({
18548
18796
  }
18549
18797
  continue;
18550
18798
  }
18551
- if (!(0, import_node_fs50.existsSync)(thread.worktreePath)) {
18799
+ if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
18552
18800
  setStatus(thread.id, "broken", "Worktree missing on disk");
18553
18801
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
18554
18802
  continue;
@@ -18763,6 +19011,37 @@ var init_orchestrator = __esm({
18763
19011
  this.emit({ type: "queue_changed", threadId, queue });
18764
19012
  this.haltDrain.delete(threadId);
18765
19013
  }
19014
+ /**
19015
+ * Worktree agent ended the turn after “I’ll let you know” (or left a
19016
+ * detached test/pack job running). Queue a continue so the chat does not
19017
+ * go idle with nobody watching the log.
19018
+ */
19019
+ maybeEnqueueJobContinue(threadId, chatText, parts = []) {
19020
+ if (this.haltDrain.has(threadId)) return;
19021
+ const thread = readThread(threadId);
19022
+ if (!thread || thread.status === "archived") return;
19023
+ const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
19024
+ const decision = planJobContinue({
19025
+ runningJobIds,
19026
+ chatText,
19027
+ queueLength: thread.queue.length,
19028
+ continueCount: this.jobContinueCount.get(threadId) ?? 0,
19029
+ alreadyNudged: this.jobContinueNudged.has(threadId),
19030
+ isOrchestrator: isOrchestratorThread(thread),
19031
+ agent: thread.agent,
19032
+ watchedJob: turnWatchedDetachedJob(parts)
19033
+ });
19034
+ if (decision.action === "none") {
19035
+ if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
19036
+ return;
19037
+ }
19038
+ if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
19039
+ else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
19040
+ const queue = [decision.prompt, ...thread.queue];
19041
+ updateThread(threadId, { queue });
19042
+ this.emit({ type: "queue_changed", threadId, queue });
19043
+ this.haltDrain.delete(threadId);
19044
+ }
18766
19045
  getThreads(includeArchived = false) {
18767
19046
  return listThreads({ includeArchived });
18768
19047
  }
@@ -18862,6 +19141,8 @@ var init_orchestrator = __esm({
18862
19141
  }
18863
19142
  const queue = [...current.queue, prompt];
18864
19143
  this.crashContinued.delete(thread.id);
19144
+ this.jobContinueCount.delete(thread.id);
19145
+ this.jobContinueNudged.delete(thread.id);
18865
19146
  this.haltDrain.delete(thread.id);
18866
19147
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
18867
19148
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -19376,6 +19657,7 @@ var init_orchestrator = __esm({
19376
19657
  this.emit({ type: "turn_finished", threadId, exitCode });
19377
19658
  if (exitCode === 0) {
19378
19659
  this.crashContinued.delete(threadId);
19660
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
19379
19661
  } else {
19380
19662
  const blob = [chatText, detail].filter(Boolean).join("\n");
19381
19663
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -20376,7 +20658,7 @@ var init_orchestrator = __esm({
20376
20658
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
20377
20659
  return restored2;
20378
20660
  }
20379
- if (!(0, import_node_fs50.existsSync)(thread.worktreePath)) {
20661
+ if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
20380
20662
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
20381
20663
  throw new Error(
20382
20664
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
@@ -20459,7 +20741,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
20459
20741
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
20460
20742
  var import_zod6 = require("zod");
20461
20743
  var import_node_crypto12 = require("crypto");
20462
- var import_node_path49 = require("path");
20744
+ var import_node_path50 = require("path");
20463
20745
  init_orchestrator();
20464
20746
  init_worktree();
20465
20747
 
@@ -21308,30 +21590,8 @@ function mcpArchiveBlockedReason(thread) {
21308
21590
 
21309
21591
  // src/mcp/server.ts
21310
21592
  init_profile();
21311
-
21312
- // src/mcp/wait-for-turn.ts
21313
- var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
21314
- function mcpWaitForTurnTimeoutMs(requested) {
21315
- const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
21316
- if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
21317
- return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
21318
- }
21319
- 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.";
21320
- 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.";
21321
- function mcpWaitStillRunningHint(status) {
21322
- return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
21323
- }
21324
- 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.";
21325
- var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
21326
- 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.";
21327
- function mcpWaitFinishedHint(status) {
21328
- if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
21329
- if (status === "broken") return MCP_WAIT_BROKEN_HINT;
21330
- if (status === "error") return MCP_WAIT_ERROR_HINT;
21331
- return void 0;
21332
- }
21333
-
21334
- // src/mcp/server.ts
21593
+ init_wait_for_turn();
21594
+ init_wait_for_job();
21335
21595
  init_message_parts();
21336
21596
  init_turn_live();
21337
21597
 
@@ -22658,27 +22918,27 @@ init_gh_errors();
22658
22918
  init_git_auth_mode();
22659
22919
 
22660
22920
  // src/board/load-home-board.ts
22661
- var import_node_fs52 = require("fs");
22662
- var import_node_path48 = require("path");
22921
+ var import_node_fs53 = require("fs");
22922
+ var import_node_path49 = require("path");
22663
22923
  init_worktree();
22664
22924
  init_paths();
22665
22925
 
22666
22926
  // src/board/board-pins.ts
22667
22927
  var import_node_crypto11 = require("crypto");
22668
- var import_node_fs51 = require("fs");
22669
- var import_node_path47 = require("path");
22928
+ var import_node_fs52 = require("fs");
22929
+ var import_node_path48 = require("path");
22670
22930
  init_paths();
22671
22931
  init_home_board();
22672
22932
  var FILE = "home-board-pins.json";
22673
22933
  var VERSION = 1;
22674
22934
  function pinsFile() {
22675
- return (0, import_node_path47.join)(appDataDir(), FILE);
22935
+ return (0, import_node_path48.join)(appDataDir(), FILE);
22676
22936
  }
22677
22937
  function readDisk() {
22678
22938
  const path = pinsFile();
22679
- if (!(0, import_node_fs51.existsSync)(path)) return [];
22939
+ if (!(0, import_node_fs52.existsSync)(path)) return [];
22680
22940
  try {
22681
- const raw = JSON.parse((0, import_node_fs51.readFileSync)(path, "utf8"));
22941
+ const raw = JSON.parse((0, import_node_fs52.readFileSync)(path, "utf8"));
22682
22942
  if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
22683
22943
  return raw.items.filter((item) => item?.id && item.kind && item.ref);
22684
22944
  } catch {
@@ -22686,8 +22946,8 @@ function readDisk() {
22686
22946
  }
22687
22947
  }
22688
22948
  function writeDisk(items) {
22689
- (0, import_node_fs51.mkdirSync)(appDataDir(), { recursive: true });
22690
- (0, import_node_fs51.writeFileSync)(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
22949
+ (0, import_node_fs52.mkdirSync)(appDataDir(), { recursive: true });
22950
+ (0, import_node_fs52.writeFileSync)(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
22691
22951
  }
22692
22952
  function listBoardPins() {
22693
22953
  return readDisk();
@@ -22708,7 +22968,7 @@ function homeBoardWorkspaceKey(workspaces) {
22708
22968
  return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
22709
22969
  }
22710
22970
  function cacheFile() {
22711
- return (0, import_node_path48.join)(appDataDir(), "home-board-cache.json");
22971
+ return (0, import_node_path49.join)(appDataDir(), "home-board-cache.json");
22712
22972
  }
22713
22973
  function emptyInputs() {
22714
22974
  return {
@@ -22734,9 +22994,9 @@ function shouldCacheHomeBoardInputs(inputs) {
22734
22994
  }
22735
22995
  function readDiskCache() {
22736
22996
  const path = cacheFile();
22737
- if (!(0, import_node_fs52.existsSync)(path)) return null;
22997
+ if (!(0, import_node_fs53.existsSync)(path)) return null;
22738
22998
  try {
22739
- const raw = JSON.parse((0, import_node_fs52.readFileSync)(path, "utf8"));
22999
+ const raw = JSON.parse((0, import_node_fs53.readFileSync)(path, "utf8"));
22740
23000
  if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
22741
23001
  return null;
22742
23002
  }
@@ -22750,8 +23010,8 @@ function readDiskCache() {
22750
23010
  }
22751
23011
  function writeDiskCache(entry) {
22752
23012
  try {
22753
- (0, import_node_fs52.mkdirSync)(appDataDir(), { recursive: true });
22754
- (0, import_node_fs52.writeFileSync)(cacheFile(), JSON.stringify(entry), "utf8");
23013
+ (0, import_node_fs53.mkdirSync)(appDataDir(), { recursive: true });
23014
+ (0, import_node_fs53.writeFileSync)(cacheFile(), JSON.stringify(entry), "utf8");
22755
23015
  } catch {
22756
23016
  }
22757
23017
  }
@@ -23050,7 +23310,7 @@ async function startMcpServer() {
23050
23310
  async () => {
23051
23311
  const threads = orch.getThreads(true);
23052
23312
  const lines = threads.map((t) => {
23053
- const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path49.basename)(t.repoPath) || t.repoPath;
23313
+ const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path50.basename)(t.repoPath) || t.repoPath;
23054
23314
  const live = orch.threadLooksLive(t) ? readTurnLive(t.id) : null;
23055
23315
  const parent = t.parentThreadId ? ` parent:${t.parentThreadId.slice(0, 8)}` : "";
23056
23316
  const preview = lastMessagePreview(t.messages, 80);
@@ -23284,6 +23544,20 @@ async function startMcpServer() {
23284
23544
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
23285
23545
  }
23286
23546
  );
23547
+ server.tool(
23548
+ "wait_for_job",
23549
+ "Wait on a detached long job (tests, pack, deploy) started with detached-job.js. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
23550
+ {
23551
+ id: import_zod6.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
23552
+ timeoutMs: import_zod6.z.number().optional()
23553
+ },
23554
+ async ({ id, timeoutMs }) => {
23555
+ const result = await waitForDetachedJob(process.cwd(), id, {
23556
+ timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
23557
+ });
23558
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
23559
+ }
23560
+ );
23287
23561
  if (!worktreeProfile) {
23288
23562
  registerSlackTools(server);
23289
23563
  registerConnectedIssueVendorTools(server);