@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.
package/dist/index.cjs CHANGED
@@ -9763,6 +9763,9 @@ function toolDescription(name, input) {
9763
9763
  if (/present_files$/i.test(name)) {
9764
9764
  return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
9765
9765
  }
9766
+ if (/wait_for_job$/i.test(name)) {
9767
+ return str2(input?.id) ? `Wait for ${str2(input?.id)}` : "Wait for job";
9768
+ }
9766
9769
  if (isSubagentToolName(name)) {
9767
9770
  const desc = str2(input?.description);
9768
9771
  const sub = asRecord(input?.subagentType);
@@ -10250,7 +10253,8 @@ var init_profile = __esm({
10250
10253
  "ask_user",
10251
10254
  "present_plan",
10252
10255
  "present_schema",
10253
- "present_files"
10256
+ "present_files",
10257
+ "wait_for_job"
10254
10258
  ];
10255
10259
  WORKTREE_GITHUB_MCP_TOOLS = [
10256
10260
  "github_get_issue",
@@ -10842,7 +10846,8 @@ var init_injected_mcp = __esm({
10842
10846
  "mcp__sideboard__present_schema",
10843
10847
  "mcp__sideboard__present_files",
10844
10848
  "mcp__sideboard__ask_user",
10845
- "mcp__sideboard__present_plan"
10849
+ "mcp__sideboard__present_plan",
10850
+ "mcp__sideboard__wait_for_job"
10846
10851
  ];
10847
10852
  SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
10848
10853
  SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
@@ -15321,6 +15326,242 @@ var init_child_halt = __esm({
15321
15326
  }
15322
15327
  });
15323
15328
 
15329
+ // src/mcp/wait-for-turn.ts
15330
+ function mcpWaitForTurnTimeoutMs(requested) {
15331
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
15332
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
15333
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
15334
+ }
15335
+ function mcpWaitStillRunningHint(status) {
15336
+ return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
15337
+ }
15338
+ function mcpWaitFinishedHint(status) {
15339
+ if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
15340
+ if (status === "broken") return MCP_WAIT_BROKEN_HINT;
15341
+ if (status === "error") return MCP_WAIT_ERROR_HINT;
15342
+ return void 0;
15343
+ }
15344
+ 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;
15345
+ var init_wait_for_turn = __esm({
15346
+ "src/mcp/wait-for-turn.ts"() {
15347
+ "use strict";
15348
+ MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
15349
+ 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.";
15350
+ 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.";
15351
+ 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.";
15352
+ MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
15353
+ 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.";
15354
+ }
15355
+ });
15356
+
15357
+ // src/mcp/wait-for-job.ts
15358
+ function mcpWaitForJobTimeoutMs(requested) {
15359
+ return mcpWaitForTurnTimeoutMs(requested);
15360
+ }
15361
+ function sanitizeDetachedJobId(id) {
15362
+ const s = id.trim();
15363
+ if (!JOB_ID_RE.test(s)) {
15364
+ throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
15365
+ }
15366
+ return s;
15367
+ }
15368
+ function jobAlive(pid) {
15369
+ if (!Number.isInteger(pid) || pid <= 0) return false;
15370
+ try {
15371
+ process.kill(pid, 0);
15372
+ return true;
15373
+ } catch {
15374
+ return false;
15375
+ }
15376
+ }
15377
+ function readIntFile(file) {
15378
+ if (!(0, import_node_fs40.existsSync)(file)) return null;
15379
+ const n = Number.parseInt((0, import_node_fs40.readFileSync)(file, "utf8").trim(), 10);
15380
+ return Number.isInteger(n) ? n : null;
15381
+ }
15382
+ function jobDir(root, id, legacy = false) {
15383
+ return (0, import_node_path40.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
15384
+ }
15385
+ function resolveJobDir(root, id) {
15386
+ const modern = jobDir(root, id, false);
15387
+ if ((0, import_node_fs40.existsSync)(modern)) return modern;
15388
+ const legacy = jobDir(root, id, true);
15389
+ if ((0, import_node_fs40.existsSync)(legacy)) return legacy;
15390
+ return modern;
15391
+ }
15392
+ function listJobIdsIn(root, rel) {
15393
+ const dir = (0, import_node_path40.join)(root, rel);
15394
+ if (!(0, import_node_fs40.existsSync)(dir)) return [];
15395
+ return (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
15396
+ }
15397
+ function listRunningDetachedJobs(worktreePath) {
15398
+ const root = worktreePath.trim();
15399
+ if (!root) return [];
15400
+ const ids = /* @__PURE__ */ new Set([
15401
+ ...listJobIdsIn(root, DETACHED_JOBS_DIR),
15402
+ ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
15403
+ ]);
15404
+ const running = [];
15405
+ for (const id of ids) {
15406
+ const dir = resolveJobDir(root, id);
15407
+ const pid = readIntFile((0, import_node_path40.join)(dir, "pid"));
15408
+ if (pid != null && jobAlive(pid)) running.push(id);
15409
+ }
15410
+ return running.sort();
15411
+ }
15412
+ function looksLikeDeferredDonePromise(text6) {
15413
+ const t = (text6 ?? "").trim();
15414
+ if (!t) return false;
15415
+ 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);
15416
+ }
15417
+ function formatJobStillRunningContinuePrompt(jobIds) {
15418
+ const ids = jobIds.join(", ");
15419
+ return [
15420
+ `Detached job still running: ${ids}.`,
15421
+ "Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
15422
+ "Then report the result. Do not tell the user you will let them know later."
15423
+ ].join(" ");
15424
+ }
15425
+ function formatDeferredDoneContinuePrompt() {
15426
+ return [
15427
+ "You ended the turn after promising to report later, but no detached job is running.",
15428
+ "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.",
15429
+ "Do not say you will let the user know later."
15430
+ ].join(" ");
15431
+ }
15432
+ function turnWatchedDetachedJob(parts) {
15433
+ return parts.some((p) => {
15434
+ if (p.type !== "tool") return false;
15435
+ if (/wait_for_job$/i.test(p.name ?? "")) return true;
15436
+ const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
15437
+ return /detached-job\.js\b/i.test(blob);
15438
+ });
15439
+ }
15440
+ function planJobContinue(opts) {
15441
+ if (opts.isOrchestrator) return { action: "none" };
15442
+ if (opts.agent === "brightsy") return { action: "none" };
15443
+ if (opts.queueLength > 0) return { action: "none" };
15444
+ if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
15445
+ const farewell = looksLikeDeferredDonePromise(opts.chatText);
15446
+ if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
15447
+ return {
15448
+ action: "wait",
15449
+ jobIds: opts.runningJobIds,
15450
+ prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
15451
+ };
15452
+ }
15453
+ if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
15454
+ return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
15455
+ }
15456
+ return { action: "none" };
15457
+ }
15458
+ function tailProgress(logFile, maxLines = 12) {
15459
+ if (!(0, import_node_fs40.existsSync)(logFile)) return "(no log yet)";
15460
+ const lines = (0, import_node_fs40.readFileSync)(logFile, "utf8").split("\n");
15461
+ return lines.slice(-maxLines).join("\n");
15462
+ }
15463
+ function readLogLines(file) {
15464
+ if (!(0, import_node_fs40.existsSync)(file)) return [];
15465
+ const lines = (0, import_node_fs40.readFileSync)(file, "utf8").split("\n");
15466
+ if (lines.length && lines[lines.length - 1] === "") lines.pop();
15467
+ return lines;
15468
+ }
15469
+ function takeDelta(logFile, cursorFile) {
15470
+ const lines = readLogLines(logFile);
15471
+ const cursor = readIntFile(cursorFile) ?? 0;
15472
+ const start = Math.min(Math.max(0, cursor), lines.length);
15473
+ return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
15474
+ }
15475
+ function snapshotJob(dir) {
15476
+ const pid = readIntFile((0, import_node_path40.join)(dir, "pid"));
15477
+ const running = pid != null && jobAlive(pid);
15478
+ return {
15479
+ pid,
15480
+ running,
15481
+ exitCode: readIntFile((0, import_node_path40.join)(dir, "exit")),
15482
+ log: (0, import_node_path40.join)(dir, "log"),
15483
+ cursor: (0, import_node_path40.join)(dir, "present.cursor"),
15484
+ progress: tailProgress((0, import_node_path40.join)(dir, "log"))
15485
+ };
15486
+ }
15487
+ function toResult(id, snap, extra) {
15488
+ const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
15489
+ const ok = !snap.running && snap.exitCode === 0;
15490
+ const stillRunning = snap.running && !ok;
15491
+ const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
15492
+ try {
15493
+ (0, import_node_fs40.mkdirSync)((0, import_node_path40.dirname)(snap.cursor), { recursive: true });
15494
+ (0, import_node_fs40.writeFileSync)(snap.cursor, `${nextCursor}
15495
+ `);
15496
+ } catch {
15497
+ }
15498
+ const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
15499
+ return {
15500
+ stillRunning,
15501
+ ok,
15502
+ failed: Boolean(failed && !stillRunning && !ok),
15503
+ status,
15504
+ id,
15505
+ pid: snap.pid,
15506
+ exitCode: snap.exitCode ?? void 0,
15507
+ delta,
15508
+ progress: extra?.progress ?? snap.progress,
15509
+ hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
15510
+ };
15511
+ }
15512
+ async function sleepMs(ms) {
15513
+ await new Promise((resolve) => setTimeout(resolve, ms));
15514
+ }
15515
+ async function waitForDetachedJob(cwd, id, opts) {
15516
+ const jobId = sanitizeDetachedJobId(id);
15517
+ const root = cwd.trim() || process.cwd();
15518
+ const dir = resolveJobDir(root, jobId);
15519
+ const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
15520
+ const sleep2 = opts?.sleep ?? sleepMs;
15521
+ if (!(0, import_node_fs40.existsSync)(dir)) {
15522
+ return {
15523
+ stillRunning: false,
15524
+ ok: false,
15525
+ failed: true,
15526
+ status: "failed",
15527
+ id: jobId,
15528
+ delta: "",
15529
+ progress: "No detached job. Start one first.",
15530
+ hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
15531
+ };
15532
+ }
15533
+ const deadline = Date.now() + timeoutMs;
15534
+ let snap = snapshotJob(dir);
15535
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
15536
+ if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
15537
+ return toResult(jobId, snap, {
15538
+ failed: true,
15539
+ progress: "No detached job. Start one first."
15540
+ });
15541
+ }
15542
+ while (Date.now() < deadline) {
15543
+ snap = snapshotJob(dir);
15544
+ if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
15545
+ if (!snap.running) return toResult(jobId, snap, { failed: true });
15546
+ await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
15547
+ }
15548
+ snap = snapshotJob(dir);
15549
+ return toResult(jobId, snap);
15550
+ }
15551
+ var import_node_fs40, import_node_path40, MAX_JOB_CONTINUES, MCP_WAIT_JOB_STILL_RUNNING_HINT, JOB_ID_RE;
15552
+ var init_wait_for_job = __esm({
15553
+ "src/mcp/wait-for-job.ts"() {
15554
+ "use strict";
15555
+ import_node_fs40 = require("fs");
15556
+ import_node_path40 = require("path");
15557
+ init_workspace_scratch();
15558
+ init_wait_for_turn();
15559
+ MAX_JOB_CONTINUES = 8;
15560
+ 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.";
15561
+ JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
15562
+ }
15563
+ });
15564
+
15324
15565
  // src/detect/detect.ts
15325
15566
  async function detectAgents() {
15326
15567
  ensureAgentPath();
@@ -16447,7 +16688,7 @@ function reuseLiveThread(input, repoPath, match) {
16447
16688
  }
16448
16689
  async function createThread(input, _onSetupLine) {
16449
16690
  const repoPath = await resolveRepoRoot(input.repoPath);
16450
- if (!(0, import_node_fs40.existsSync)(repoPath)) {
16691
+ if (!(0, import_node_fs41.existsSync)(repoPath)) {
16451
16692
  throw new Error(`Repo not found: ${repoPath}`);
16452
16693
  }
16453
16694
  const reused = reuseLiveThread(input, repoPath, {
@@ -16627,11 +16868,11 @@ async function listLinearIssues(agent, repoPath) {
16627
16868
  }
16628
16869
  return adapter.listLinearIssues(repoPath);
16629
16870
  }
16630
- var import_node_fs40;
16871
+ var import_node_fs41;
16631
16872
  var init_create = __esm({
16632
16873
  "src/threads/create.ts"() {
16633
16874
  "use strict";
16634
- import_node_fs40 = require("fs");
16875
+ import_node_fs41 = require("fs");
16635
16876
  init_detect();
16636
16877
  init_worktree();
16637
16878
  init_home_board();
@@ -16734,20 +16975,20 @@ function writeTurnLive(threadId, progress) {
16734
16975
  const path2 = threadLivePath(threadId);
16735
16976
  const tmp = `${path2}.${process.pid}.tmp`;
16736
16977
  try {
16737
- (0, import_node_fs41.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
16738
- (0, import_node_fs41.renameSync)(tmp, path2);
16978
+ (0, import_node_fs42.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
16979
+ (0, import_node_fs42.renameSync)(tmp, path2);
16739
16980
  } catch {
16740
16981
  try {
16741
- (0, import_node_fs41.unlinkSync)(tmp);
16982
+ (0, import_node_fs42.unlinkSync)(tmp);
16742
16983
  } catch {
16743
16984
  }
16744
16985
  }
16745
16986
  }
16746
16987
  function readTurnLive(threadId) {
16747
16988
  const path2 = threadLivePath(threadId);
16748
- if (!(0, import_node_fs41.existsSync)(path2)) return null;
16989
+ if (!(0, import_node_fs42.existsSync)(path2)) return null;
16749
16990
  try {
16750
- const raw = JSON.parse((0, import_node_fs41.readFileSync)(path2, "utf8"));
16991
+ const raw = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
16751
16992
  if (!raw || typeof raw.summary !== "string") return null;
16752
16993
  return raw;
16753
16994
  } catch {
@@ -16759,17 +17000,17 @@ function clearTurnLive(threadId) {
16759
17000
  if (buf?.timer) clearTimeout(buf.timer);
16760
17001
  buffers.delete(threadId);
16761
17002
  const path2 = threadLivePath(threadId);
16762
- if (!(0, import_node_fs41.existsSync)(path2)) return;
17003
+ if (!(0, import_node_fs42.existsSync)(path2)) return;
16763
17004
  try {
16764
- (0, import_node_fs41.unlinkSync)(path2);
17005
+ (0, import_node_fs42.unlinkSync)(path2);
16765
17006
  } catch {
16766
17007
  }
16767
17008
  }
16768
- var import_node_fs41, buffers, FLUSH_MS, MAX_PARTS;
17009
+ var import_node_fs42, buffers, FLUSH_MS, MAX_PARTS;
16769
17010
  var init_turn_live = __esm({
16770
17011
  "src/store/turn-live.ts"() {
16771
17012
  "use strict";
16772
- import_node_fs41 = require("fs");
17013
+ import_node_fs42 = require("fs");
16773
17014
  init_message_parts();
16774
17015
  init_paths();
16775
17016
  buffers = /* @__PURE__ */ new Map();
@@ -16989,7 +17230,7 @@ var init_quota_failover = __esm({
16989
17230
  // src/threads/adopt.ts
16990
17231
  function thisModuleFile() {
16991
17232
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
16992
- return cjsFile || process.argv[1] || (0, import_node_path40.join)(process.cwd(), "package.json");
17233
+ return cjsFile || process.argv[1] || (0, import_node_path41.join)(process.cwd(), "package.json");
16993
17234
  }
16994
17235
  function openReadonlySqlite(file) {
16995
17236
  const req = (0, import_node_module4.createRequire)(thisModuleFile());
@@ -17007,21 +17248,21 @@ function mapAgentType(raw) {
17007
17248
  return null;
17008
17249
  }
17009
17250
  function resolveConductorCursorAgentId(workspacePath) {
17010
- if (!workspacePath || !(0, import_node_fs42.existsSync)(CURSOR_SDK_STORE)) return null;
17251
+ if (!workspacePath || !(0, import_node_fs43.existsSync)(CURSOR_SDK_STORE)) return null;
17011
17252
  const normalized = workspacePath.replace(/\/$/, "");
17012
17253
  let best = null;
17013
17254
  let hashes;
17014
17255
  try {
17015
- hashes = (0, import_node_fs42.readdirSync)(CURSOR_SDK_STORE);
17256
+ hashes = (0, import_node_fs43.readdirSync)(CURSOR_SDK_STORE);
17016
17257
  } catch {
17017
17258
  return null;
17018
17259
  }
17019
17260
  for (const hash of hashes) {
17020
- const agentsFile = (0, import_node_path40.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
17021
- if (!(0, import_node_fs42.existsSync)(agentsFile)) continue;
17261
+ const agentsFile = (0, import_node_path41.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
17262
+ if (!(0, import_node_fs43.existsSync)(agentsFile)) continue;
17022
17263
  let text6;
17023
17264
  try {
17024
- text6 = (0, import_node_fs42.readFileSync)(agentsFile, "utf8");
17265
+ text6 = (0, import_node_fs43.readFileSync)(agentsFile, "utf8");
17025
17266
  } catch {
17026
17267
  continue;
17027
17268
  }
@@ -17045,7 +17286,7 @@ function resolveConductorCursorAgentId(workspacePath) {
17045
17286
  return best?.agentId ?? null;
17046
17287
  }
17047
17288
  async function adoptThread(input) {
17048
- if (!(0, import_node_fs42.existsSync)(input.worktreePath)) {
17289
+ if (!(0, import_node_fs43.existsSync)(input.worktreePath)) {
17049
17290
  throw new Error(`Worktree not found: ${input.worktreePath}`);
17050
17291
  }
17051
17292
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -17074,18 +17315,18 @@ function conductorDbPath() {
17074
17315
  return CONDUCTOR_DB;
17075
17316
  }
17076
17317
  function listConductorWorkspaces() {
17077
- if (!(0, import_node_fs42.existsSync)(CONDUCTOR_DB)) {
17318
+ if (!(0, import_node_fs43.existsSync)(CONDUCTOR_DB)) {
17078
17319
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
17079
17320
  }
17080
- const tmp = (0, import_node_fs42.mkdtempSync)((0, import_node_path40.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
17081
- const snapshot = (0, import_node_path40.join)(tmp, "conductor.db");
17321
+ const tmp = (0, import_node_fs43.mkdtempSync)((0, import_node_path41.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
17322
+ const snapshot = (0, import_node_path41.join)(tmp, "conductor.db");
17082
17323
  try {
17083
- (0, import_node_fs42.copyFileSync)(CONDUCTOR_DB, snapshot);
17324
+ (0, import_node_fs43.copyFileSync)(CONDUCTOR_DB, snapshot);
17084
17325
  for (const suffix of ["-wal", "-shm"]) {
17085
17326
  const src = `${CONDUCTOR_DB}${suffix}`;
17086
- if ((0, import_node_fs42.existsSync)(src)) {
17327
+ if ((0, import_node_fs43.existsSync)(src)) {
17087
17328
  try {
17088
- (0, import_node_fs42.copyFileSync)(src, `${snapshot}${suffix}`);
17329
+ (0, import_node_fs43.copyFileSync)(src, `${snapshot}${suffix}`);
17089
17330
  } catch {
17090
17331
  }
17091
17332
  }
@@ -17161,22 +17402,22 @@ function listConductorWorkspaces() {
17161
17402
  db.close();
17162
17403
  }
17163
17404
  } finally {
17164
- (0, import_node_fs42.rmSync)(tmp, { recursive: true, force: true });
17405
+ (0, import_node_fs43.rmSync)(tmp, { recursive: true, force: true });
17165
17406
  }
17166
17407
  }
17167
17408
  function importConductorWorkspace(workspaceId) {
17168
- if (!(0, import_node_fs42.existsSync)(CONDUCTOR_DB)) {
17409
+ if (!(0, import_node_fs43.existsSync)(CONDUCTOR_DB)) {
17169
17410
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
17170
17411
  }
17171
- const tmp = (0, import_node_fs42.mkdtempSync)((0, import_node_path40.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
17172
- const snapshot = (0, import_node_path40.join)(tmp, "conductor.db");
17412
+ const tmp = (0, import_node_fs43.mkdtempSync)((0, import_node_path41.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
17413
+ const snapshot = (0, import_node_path41.join)(tmp, "conductor.db");
17173
17414
  try {
17174
- (0, import_node_fs42.copyFileSync)(CONDUCTOR_DB, snapshot);
17415
+ (0, import_node_fs43.copyFileSync)(CONDUCTOR_DB, snapshot);
17175
17416
  for (const suffix of ["-wal", "-shm"]) {
17176
17417
  const src = `${CONDUCTOR_DB}${suffix}`;
17177
- if ((0, import_node_fs42.existsSync)(src)) {
17418
+ if ((0, import_node_fs43.existsSync)(src)) {
17178
17419
  try {
17179
- (0, import_node_fs42.copyFileSync)(src, `${snapshot}${suffix}`);
17420
+ (0, import_node_fs43.copyFileSync)(src, `${snapshot}${suffix}`);
17180
17421
  } catch {
17181
17422
  }
17182
17423
  }
@@ -17194,7 +17435,7 @@ function importConductorWorkspace(workspaceId) {
17194
17435
  ).get(workspaceId);
17195
17436
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
17196
17437
  const worktreePath = String(row.workspacePath);
17197
- if (!(0, import_node_fs42.existsSync)(worktreePath)) {
17438
+ if (!(0, import_node_fs43.existsSync)(worktreePath)) {
17198
17439
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
17199
17440
  }
17200
17441
  let sessionId = null;
@@ -17257,31 +17498,31 @@ function importConductorWorkspace(workspaceId) {
17257
17498
  db.close();
17258
17499
  }
17259
17500
  } finally {
17260
- (0, import_node_fs42.rmSync)(tmp, { recursive: true, force: true });
17501
+ (0, import_node_fs43.rmSync)(tmp, { recursive: true, force: true });
17261
17502
  }
17262
17503
  }
17263
17504
  async function importConductorWorkspaceAsync(workspaceId) {
17264
17505
  return importConductorWorkspace(workspaceId);
17265
17506
  }
17266
- var import_node_child_process4, import_node_fs42, import_node_os13, import_node_path40, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
17507
+ var import_node_child_process4, import_node_fs43, import_node_os13, import_node_path41, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
17267
17508
  var init_adopt = __esm({
17268
17509
  "src/threads/adopt.ts"() {
17269
17510
  "use strict";
17270
17511
  import_node_child_process4 = require("child_process");
17271
- import_node_fs42 = require("fs");
17512
+ import_node_fs43 = require("fs");
17272
17513
  import_node_os13 = require("os");
17273
- import_node_path40 = require("path");
17514
+ import_node_path41 = require("path");
17274
17515
  import_node_module4 = require("module");
17275
17516
  init_worktree();
17276
17517
  init_thread_store();
17277
- CONDUCTOR_APP_SUPPORT = (0, import_node_path40.join)(
17518
+ CONDUCTOR_APP_SUPPORT = (0, import_node_path41.join)(
17278
17519
  process.env.HOME ?? "",
17279
17520
  "Library",
17280
17521
  "Application Support",
17281
17522
  "com.conductor.app"
17282
17523
  );
17283
- CONDUCTOR_DB = (0, import_node_path40.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
17284
- CURSOR_SDK_STORE = (0, import_node_path40.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
17524
+ CONDUCTOR_DB = (0, import_node_path41.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
17525
+ CURSOR_SDK_STORE = (0, import_node_path41.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
17285
17526
  }
17286
17527
  });
17287
17528
 
@@ -17348,7 +17589,7 @@ async function openStackLayer(input, _onSetupLine) {
17348
17589
  let createdWorktree = false;
17349
17590
  const trees = await listWorktrees(repoPath);
17350
17591
  const checkedOut = trees.find((w) => w.branch === branchName);
17351
- if (checkedOut?.path && (0, import_node_fs43.existsSync)(checkedOut.path)) {
17592
+ if (checkedOut?.path && (0, import_node_fs44.existsSync)(checkedOut.path)) {
17352
17593
  if (input.reuseExistingWorktree !== false) {
17353
17594
  worktreePath = checkedOut.path;
17354
17595
  } else {
@@ -17490,7 +17731,7 @@ async function initStackFromThread(input, onSetupLine) {
17490
17731
  async function createPrStack(input, onSetupLine) {
17491
17732
  await requireAgent(input.agent);
17492
17733
  const repoPath = await resolveRepoRoot(input.repoPath);
17493
- if (!(0, import_node_fs43.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
17734
+ if (!(0, import_node_fs44.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
17494
17735
  if (!input.branches.length) throw new Error("At least one branch name required");
17495
17736
  const status = await detectGhStack(repoPath);
17496
17737
  if (!status.available) throw new Error(status.reason);
@@ -17557,7 +17798,7 @@ async function createPrStack(input, onSetupLine) {
17557
17798
  }
17558
17799
  }
17559
17800
  const claimed = new Set(threads.map((t) => t.worktreePath));
17560
- if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs43.existsSync)(bootstrap.worktreePath)) {
17801
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs44.existsSync)(bootstrap.worktreePath)) {
17561
17802
  try {
17562
17803
  await removeWorktree(repoPath, bootstrap.worktreePath, {
17563
17804
  deleteBranch: bootstrap.branchName
@@ -17577,11 +17818,11 @@ function stackAgentDefaultsFrom(input) {
17577
17818
  planMode: input.planMode
17578
17819
  };
17579
17820
  }
17580
- var import_node_fs43;
17821
+ var import_node_fs44;
17581
17822
  var init_stack_layers = __esm({
17582
17823
  "src/threads/stack-layers.ts"() {
17583
17824
  "use strict";
17584
- import_node_fs43 = require("fs");
17825
+ import_node_fs44 = require("fs");
17585
17826
  init_detect();
17586
17827
  init_run();
17587
17828
  init_stack();
@@ -17594,7 +17835,7 @@ var init_stack_layers = __esm({
17594
17835
 
17595
17836
  // src/diff/diff.ts
17596
17837
  async function inspectGitWorktree(worktreePath) {
17597
- if (!worktreePath || !(0, import_node_fs44.existsSync)(worktreePath)) return "missing_worktree";
17838
+ if (!worktreePath || !(0, import_node_fs45.existsSync)(worktreePath)) return "missing_worktree";
17598
17839
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
17599
17840
  reject: false
17600
17841
  });
@@ -17602,7 +17843,7 @@ async function inspectGitWorktree(worktreePath) {
17602
17843
  return "ok";
17603
17844
  }
17604
17845
  async function initializeGitRepository(worktreePath) {
17605
- if (!worktreePath || !(0, import_node_fs44.existsSync)(worktreePath)) {
17846
+ if (!worktreePath || !(0, import_node_fs45.existsSync)(worktreePath)) {
17606
17847
  throw new Error("Worktree not found");
17607
17848
  }
17608
17849
  const status = await inspectGitWorktree(worktreePath);
@@ -17736,11 +17977,11 @@ new file mode 100644
17736
17977
  };
17737
17978
  }
17738
17979
  async function untrackedPatch(worktreePath, path2, maxHunk) {
17739
- const abs = (0, import_node_path41.join)(worktreePath, path2);
17980
+ const abs = (0, import_node_path42.join)(worktreePath, path2);
17740
17981
  try {
17741
- const st = (0, import_node_fs44.statSync)(abs);
17982
+ const st = (0, import_node_fs45.statSync)(abs);
17742
17983
  if (st.isFile() && st.size > maxHunk) {
17743
- const buf = (0, import_node_fs44.readFileSync)(abs).subarray(0, maxHunk);
17984
+ const buf = (0, import_node_fs45.readFileSync)(abs).subarray(0, maxHunk);
17744
17985
  return syntheticAddPatch(path2, buf.toString("utf8"), maxHunk);
17745
17986
  }
17746
17987
  } catch {
@@ -18229,8 +18470,8 @@ function isImageRelativePath(relativePath) {
18229
18470
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
18230
18471
  assertSafeRelativePath(relativePath);
18231
18472
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
18232
- const abs = (0, import_node_path41.join)(worktreePath, relativePath);
18233
- const st = (0, import_node_fs44.statSync)(abs);
18473
+ const abs = (0, import_node_path42.join)(worktreePath, relativePath);
18474
+ const st = (0, import_node_fs45.statSync)(abs);
18234
18475
  if (!st.isFile()) {
18235
18476
  throw new Error(`Not a file: ${relativePath}`);
18236
18477
  }
@@ -18239,7 +18480,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
18239
18480
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
18240
18481
  );
18241
18482
  }
18242
- const buf = (0, import_node_fs44.readFileSync)(abs);
18483
+ const buf = (0, import_node_fs45.readFileSync)(abs);
18243
18484
  return {
18244
18485
  path: relativePath,
18245
18486
  contentBase64: buf.toString("base64"),
@@ -18249,12 +18490,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
18249
18490
  function readWorktreeFile(worktreePath, relativePath, opts) {
18250
18491
  assertSafeRelativePath(relativePath);
18251
18492
  const maxBytes = opts?.maxBytes ?? 2e5;
18252
- const abs = (0, import_node_path41.join)(worktreePath, relativePath);
18253
- const st = (0, import_node_fs44.statSync)(abs);
18493
+ const abs = (0, import_node_path42.join)(worktreePath, relativePath);
18494
+ const st = (0, import_node_fs45.statSync)(abs);
18254
18495
  if (!st.isFile()) {
18255
18496
  throw new Error(`Not a file: ${relativePath}`);
18256
18497
  }
18257
- const buf = (0, import_node_fs44.readFileSync)(abs);
18498
+ const buf = (0, import_node_fs45.readFileSync)(abs);
18258
18499
  if (isImageRelativePath(relativePath)) {
18259
18500
  const maxImageBytes = Math.max(maxBytes, 15e6);
18260
18501
  const truncated2 = buf.length > maxImageBytes;
@@ -18297,9 +18538,9 @@ function assertSafeRelativePath(relativePath) {
18297
18538
  }
18298
18539
  function writeWorktreeFile(worktreePath, relativePath, content) {
18299
18540
  assertSafeRelativePath(relativePath);
18300
- const abs = (0, import_node_path41.join)(worktreePath, relativePath);
18301
- (0, import_node_fs44.mkdirSync)((0, import_node_path41.dirname)(abs), { recursive: true });
18302
- (0, import_node_fs44.writeFileSync)(abs, content, "utf8");
18541
+ const abs = (0, import_node_path42.join)(worktreePath, relativePath);
18542
+ (0, import_node_fs45.mkdirSync)((0, import_node_path42.dirname)(abs), { recursive: true });
18543
+ (0, import_node_fs45.writeFileSync)(abs, content, "utf8");
18303
18544
  return { path: relativePath };
18304
18545
  }
18305
18546
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -18316,12 +18557,12 @@ async function getDiffSummary(worktreePath, repoPath, opts) {
18316
18557
  truncated: full.files.length > maxFiles
18317
18558
  };
18318
18559
  }
18319
- var import_node_fs44, import_node_path41, mergeBaseCache, MERGE_BASE_TTL_MS, SHA_RE, IMAGE_EXTENSIONS2, DEFAULT_UPLOAD_MAX_BYTES;
18560
+ var import_node_fs45, import_node_path42, mergeBaseCache, MERGE_BASE_TTL_MS, SHA_RE, IMAGE_EXTENSIONS2, DEFAULT_UPLOAD_MAX_BYTES;
18320
18561
  var init_diff = __esm({
18321
18562
  "src/diff/diff.ts"() {
18322
18563
  "use strict";
18323
- import_node_fs44 = require("fs");
18324
- import_node_path41 = require("path");
18564
+ import_node_fs45 = require("fs");
18565
+ import_node_path42 = require("path");
18325
18566
  init_run();
18326
18567
  init_worktree();
18327
18568
  mergeBaseCache = /* @__PURE__ */ new Map();
@@ -18470,7 +18711,7 @@ var init_long_running = __esm({
18470
18711
 
18471
18712
  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.
18472
18713
 
18473
- 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.
18714
+ 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
18474
18715
 
18475
18716
  ## Tool
18476
18717
 
@@ -18480,7 +18721,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
18480
18721
  # Start (exits in ~1s; job survives this turn)
18481
18722
  node <detached-job.js> start <id> -- <command> [args...]
18482
18723
 
18483
- # Wait \u2014 returns within ~45s even if the job is still going
18724
+ # Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
18725
+ # Shell fallback:
18484
18726
  node <detached-job.js> wait <id>
18485
18727
 
18486
18728
  # Block until the process exits (humans / a turn that will not be interrupted)
@@ -18493,7 +18735,7 @@ node <detached-job.js> status <id>
18493
18735
 
18494
18736
  Wait JSON:
18495
18737
 
18496
- - \`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.
18738
+ - \`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.
18497
18739
  - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
18498
18740
  - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
18499
18741
 
@@ -18523,7 +18765,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
18523
18765
 
18524
18766
  1. \`start\` once. If JSON says \`already-running\`, do not start again.
18525
18767
  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.
18526
- 3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
18768
+ 3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
18527
18769
  4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
18528
18770
 
18529
18771
  Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
@@ -18589,7 +18831,7 @@ function parseFrontmatter(content) {
18589
18831
  }
18590
18832
  function readSkill(skillMd, source) {
18591
18833
  try {
18592
- const content = (0, import_node_fs45.readFileSync)(skillMd, "utf8");
18834
+ const content = (0, import_node_fs46.readFileSync)(skillMd, "utf8");
18593
18835
  const { name: fmName, description } = parseFrontmatter(content);
18594
18836
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
18595
18837
  const name = fmName || dirName;
@@ -18608,19 +18850,19 @@ function readSkill(skillMd, source) {
18608
18850
  }
18609
18851
  }
18610
18852
  function scanSkillsDir(dir, source, out) {
18611
- if (!(0, import_node_fs45.existsSync)(dir)) return;
18853
+ if (!(0, import_node_fs46.existsSync)(dir)) return;
18612
18854
  let entries;
18613
18855
  try {
18614
- entries = (0, import_node_fs45.readdirSync)(dir);
18856
+ entries = (0, import_node_fs46.readdirSync)(dir);
18615
18857
  } catch {
18616
18858
  return;
18617
18859
  }
18618
18860
  for (const entry of entries) {
18619
18861
  if (entry.startsWith(".")) continue;
18620
- const skillMd = (0, import_node_path42.join)(dir, entry, "SKILL.md");
18621
- if (!(0, import_node_fs45.existsSync)(skillMd)) continue;
18862
+ const skillMd = (0, import_node_path43.join)(dir, entry, "SKILL.md");
18863
+ if (!(0, import_node_fs46.existsSync)(skillMd)) continue;
18622
18864
  try {
18623
- if (!(0, import_node_fs45.statSync)(skillMd).isFile()) continue;
18865
+ if (!(0, import_node_fs46.statSync)(skillMd).isFile()) continue;
18624
18866
  } catch {
18625
18867
  continue;
18626
18868
  }
@@ -18629,24 +18871,24 @@ function scanSkillsDir(dir, source, out) {
18629
18871
  }
18630
18872
  }
18631
18873
  function scanClaudePluginSkills(pluginsRoot, out) {
18632
- if (!(0, import_node_fs45.existsSync)(pluginsRoot)) return;
18874
+ if (!(0, import_node_fs46.existsSync)(pluginsRoot)) return;
18633
18875
  const walk = (dir, depth, lookingForSkillsDir) => {
18634
18876
  if (depth > 7) return;
18635
18877
  let entries;
18636
18878
  try {
18637
- entries = (0, import_node_fs45.readdirSync)(dir);
18879
+ entries = (0, import_node_fs46.readdirSync)(dir);
18638
18880
  } catch {
18639
18881
  return;
18640
18882
  }
18641
18883
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
18642
- const skill = readSkill((0, import_node_path42.join)(dir, "SKILL.md"), "cli");
18884
+ const skill = readSkill((0, import_node_path43.join)(dir, "SKILL.md"), "cli");
18643
18885
  if (skill) out.push(skill);
18644
18886
  }
18645
18887
  for (const entry of entries) {
18646
18888
  if (entry === "node_modules" || entry === ".git") continue;
18647
- const full = (0, import_node_path42.join)(dir, entry);
18889
+ const full = (0, import_node_path43.join)(dir, entry);
18648
18890
  try {
18649
- if (!(0, import_node_fs45.statSync)(full).isDirectory()) continue;
18891
+ if (!(0, import_node_fs46.statSync)(full).isDirectory()) continue;
18650
18892
  } catch {
18651
18893
  continue;
18652
18894
  }
@@ -18664,17 +18906,17 @@ function discoverSkills(worktreePath) {
18664
18906
  const home = (0, import_node_os14.homedir)();
18665
18907
  const collected = [];
18666
18908
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
18667
- scanSkillsDir((0, import_node_path42.join)(worktreePath, rel), "workspace", collected);
18909
+ scanSkillsDir((0, import_node_path43.join)(worktreePath, rel), "workspace", collected);
18668
18910
  }
18669
18911
  for (const abs of [
18670
- (0, import_node_path42.join)(home, ".claude/skills"),
18671
- (0, import_node_path42.join)(home, ".cursor/skills"),
18672
- (0, import_node_path42.join)(home, ".sideboard/skills"),
18673
- (0, import_node_path42.join)(home, ".brightsy/skills")
18912
+ (0, import_node_path43.join)(home, ".claude/skills"),
18913
+ (0, import_node_path43.join)(home, ".cursor/skills"),
18914
+ (0, import_node_path43.join)(home, ".sideboard/skills"),
18915
+ (0, import_node_path43.join)(home, ".brightsy/skills")
18674
18916
  ]) {
18675
18917
  scanSkillsDir(abs, "user", collected);
18676
18918
  }
18677
- scanClaudePluginSkills((0, import_node_path42.join)(home, ".claude/plugins"), collected);
18919
+ scanClaudePluginSkills((0, import_node_path43.join)(home, ".claude/plugins"), collected);
18678
18920
  collected.push(...bundledSkills());
18679
18921
  const rank = {
18680
18922
  workspace: 0,
@@ -18700,7 +18942,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
18700
18942
  \u2026(truncated)` : body;
18701
18943
  }
18702
18944
  }
18703
- const raw = (0, import_node_fs45.readFileSync)(skillPath, "utf8");
18945
+ const raw = (0, import_node_fs46.readFileSync)(skillPath, "utf8");
18704
18946
  if (raw.startsWith("---")) {
18705
18947
  const end = raw.indexOf("\n---", 3);
18706
18948
  if (end >= 0) {
@@ -18714,13 +18956,13 @@ function readSkillBody(skillPath, maxChars = 12e3) {
18714
18956
 
18715
18957
  \u2026(truncated)` : raw;
18716
18958
  }
18717
- var import_node_fs45, import_node_os14, import_node_path42, BUNDLED_SKILL_PREFIX;
18959
+ var import_node_fs46, import_node_os14, import_node_path43, BUNDLED_SKILL_PREFIX;
18718
18960
  var init_discover = __esm({
18719
18961
  "src/skills/discover.ts"() {
18720
18962
  "use strict";
18721
- import_node_fs45 = require("fs");
18963
+ import_node_fs46 = require("fs");
18722
18964
  import_node_os14 = require("os");
18723
- import_node_path42 = require("path");
18965
+ import_node_path43 = require("path");
18724
18966
  init_long_running();
18725
18967
  BUNDLED_SKILL_PREFIX = "bundled:";
18726
18968
  }
@@ -18815,17 +19057,17 @@ var init_expand = __esm({
18815
19057
  function packagedDetachedJobPath() {
18816
19058
  const dir = packagedMcpDir();
18817
19059
  if (!dir) return null;
18818
- const script = (0, import_node_path43.join)(dir, "scripts", "detached-job.js");
18819
- return (0, import_node_fs46.existsSync)(script) ? script : null;
19060
+ const script = (0, import_node_path44.join)(dir, "scripts", "detached-job.js");
19061
+ return (0, import_node_fs47.existsSync)(script) ? script : null;
18820
19062
  }
18821
19063
  function resolveDetachedJobScript() {
18822
19064
  const packaged = packagedDetachedJobPath();
18823
19065
  if (packaged) return packaged;
18824
- let dir = (0, import_node_path43.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
19066
+ let dir = (0, import_node_path44.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
18825
19067
  for (let i = 0; i < 8; i++) {
18826
- const candidate = (0, import_node_path43.join)(dir, "scripts", "detached-job.js");
18827
- if ((0, import_node_fs46.existsSync)(candidate)) return candidate;
18828
- const parent = (0, import_node_path43.dirname)(dir);
19068
+ const candidate = (0, import_node_path44.join)(dir, "scripts", "detached-job.js");
19069
+ if ((0, import_node_fs47.existsSync)(candidate)) return candidate;
19070
+ const parent = (0, import_node_path44.dirname)(dir);
18829
19071
  if (parent === dir) break;
18830
19072
  dir = parent;
18831
19073
  }
@@ -18836,12 +19078,12 @@ function formatDetachedJobInvoke(scriptPath) {
18836
19078
  if (resolved) return `node ${JSON.stringify(resolved)}`;
18837
19079
  return "node scripts/detached-job.js";
18838
19080
  }
18839
- var import_node_fs46, import_node_path43, import_node_url3, import_meta3;
19081
+ var import_node_fs47, import_node_path44, import_node_url3, import_meta3;
18840
19082
  var init_detached_job_path = __esm({
18841
19083
  "src/skills/detached-job-path.ts"() {
18842
19084
  "use strict";
18843
- import_node_fs46 = require("fs");
18844
- import_node_path43 = require("path");
19085
+ import_node_fs47 = require("fs");
19086
+ import_node_path44 = require("path");
18845
19087
  import_node_url3 = require("url");
18846
19088
  init_packaged_runtime();
18847
19089
  import_meta3 = {};
@@ -19062,14 +19304,15 @@ function formatLongRunningDirective(opts) {
19062
19304
  `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
19063
19305
  `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
19064
19306
  "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
19065
- `- 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.`,
19307
+ `- 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.`,
19308
+ "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
19066
19309
  "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
19067
19310
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
19068
19311
  ].join("\n");
19069
19312
  }
19070
19313
  function formatLongRunningReminder(opts) {
19071
19314
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
19072
- return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
19315
+ 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.`;
19073
19316
  }
19074
19317
  function formatArtifactDirective() {
19075
19318
  return [
@@ -19106,11 +19349,11 @@ function loadAgentInstructions(worktreePath, agent) {
19106
19349
  const out = [];
19107
19350
  for (const rel of candidates) {
19108
19351
  if (seenPaths.has(rel)) continue;
19109
- const abs = (0, import_node_path44.join)(worktreePath, rel);
19110
- if (!(0, import_node_fs47.existsSync)(abs)) continue;
19352
+ const abs = (0, import_node_path45.join)(worktreePath, rel);
19353
+ if (!(0, import_node_fs48.existsSync)(abs)) continue;
19111
19354
  try {
19112
- if (!(0, import_node_fs47.statSync)(abs).isFile()) continue;
19113
- let content = (0, import_node_fs47.readFileSync)(abs, "utf8");
19355
+ if (!(0, import_node_fs48.statSync)(abs).isFile()) continue;
19356
+ let content = (0, import_node_fs48.readFileSync)(abs, "utf8");
19114
19357
  if (!content.trim()) continue;
19115
19358
  if (content.length > MAX_CHARS_PER_FILE) {
19116
19359
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -19150,12 +19393,12 @@ function withAgentInstructions(prompt, files) {
19150
19393
 
19151
19394
  ${prompt}`;
19152
19395
  }
19153
- var import_node_fs47, import_node_path44, GITHUB_TICKET_REF, KEYED_TICKET_REF, FILES_BY_AGENT, MAX_CHARS_PER_FILE;
19396
+ var import_node_fs48, import_node_path45, GITHUB_TICKET_REF, KEYED_TICKET_REF, FILES_BY_AGENT, MAX_CHARS_PER_FILE;
19154
19397
  var init_instructions = __esm({
19155
19398
  "src/agents/instructions.ts"() {
19156
19399
  "use strict";
19157
- import_node_fs47 = require("fs");
19158
- import_node_path44 = require("path");
19400
+ import_node_fs48 = require("fs");
19401
+ import_node_path45 = require("path");
19159
19402
  init_git_auth_mode();
19160
19403
  init_worktree_labels();
19161
19404
  init_detached_job_path();
@@ -19503,40 +19746,40 @@ __export(plan_file_exports, {
19503
19746
  writePlanFile: () => writePlanFile
19504
19747
  });
19505
19748
  function ensureAttachmentsGitignore(worktreePath) {
19506
- const gitignoreAbs = (0, import_node_path45.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
19507
- if ((0, import_node_fs48.existsSync)(gitignoreAbs)) return;
19508
- (0, import_node_fs48.mkdirSync)((0, import_node_path45.dirname)(gitignoreAbs), { recursive: true });
19509
- (0, import_node_fs48.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
19749
+ const gitignoreAbs = (0, import_node_path46.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
19750
+ if ((0, import_node_fs49.existsSync)(gitignoreAbs)) return;
19751
+ (0, import_node_fs49.mkdirSync)((0, import_node_path46.dirname)(gitignoreAbs), { recursive: true });
19752
+ (0, import_node_fs49.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
19510
19753
  }
19511
19754
  function planFileAbs(worktreePath) {
19512
- return (0, import_node_path45.join)(worktreePath, PLAN_FILE_REL);
19755
+ return (0, import_node_path46.join)(worktreePath, PLAN_FILE_REL);
19513
19756
  }
19514
19757
  function readTextIfPresent2(abs) {
19515
- if (!(0, import_node_fs48.existsSync)(abs)) return null;
19758
+ if (!(0, import_node_fs49.existsSync)(abs)) return null;
19516
19759
  try {
19517
- const content = (0, import_node_fs48.readFileSync)(abs, "utf8");
19760
+ const content = (0, import_node_fs49.readFileSync)(abs, "utf8");
19518
19761
  return content.trim() ? content : null;
19519
19762
  } catch {
19520
19763
  return null;
19521
19764
  }
19522
19765
  }
19523
19766
  function readPlanFile(worktreePath) {
19524
- return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path45.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path45.join)(worktreePath, LEGACY_PLAN_FILE_REL));
19767
+ return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path46.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path46.join)(worktreePath, LEGACY_PLAN_FILE_REL));
19525
19768
  }
19526
19769
  function writePlanFile(worktreePath, content) {
19527
19770
  ensureAttachmentsGitignore(worktreePath);
19528
19771
  const abs = planFileAbs(worktreePath);
19529
- (0, import_node_fs48.mkdirSync)((0, import_node_path45.dirname)(abs), { recursive: true });
19772
+ (0, import_node_fs49.mkdirSync)((0, import_node_path46.dirname)(abs), { recursive: true });
19530
19773
  const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
19531
- (0, import_node_fs48.writeFileSync)(abs, body, "utf8");
19774
+ (0, import_node_fs49.writeFileSync)(abs, body, "utf8");
19532
19775
  return PLAN_FILE_REL;
19533
19776
  }
19534
- var import_node_fs48, import_node_path45;
19777
+ var import_node_fs49, import_node_path46;
19535
19778
  var init_plan_file = __esm({
19536
19779
  "src/plan/plan-file.ts"() {
19537
19780
  "use strict";
19538
- import_node_fs48 = require("fs");
19539
- import_node_path45 = require("path");
19781
+ import_node_fs49 = require("fs");
19782
+ import_node_path46 = require("path");
19540
19783
  init_workspace_scratch();
19541
19784
  init_plan_present();
19542
19785
  init_plan_present();
@@ -19584,13 +19827,13 @@ var init_sync_branch = __esm({
19584
19827
 
19585
19828
  // src/agents/cursor-store.ts
19586
19829
  function cursorSdkStoreDir(threadId) {
19587
- const root = (0, import_node_path46.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
19830
+ const root = (0, import_node_path47.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
19588
19831
  const id = sanitizeCursorStoreSegment(threadId);
19589
19832
  if (!id) return root;
19590
- return (0, import_node_path46.join)(root, "threads", id);
19833
+ return (0, import_node_path47.join)(root, "threads", id);
19591
19834
  }
19592
19835
  function cursorSdkRunsNdjsonPath(threadId) {
19593
- return (0, import_node_path46.join)(cursorSdkStoreDir(threadId), "runs.ndjson");
19836
+ return (0, import_node_path47.join)(cursorSdkStoreDir(threadId), "runs.ndjson");
19594
19837
  }
19595
19838
  function cursorSdkRunsNdjsonSearchPaths(threadId) {
19596
19839
  const scoped = cursorSdkRunsNdjsonPath(threadId);
@@ -19603,11 +19846,11 @@ function cursorSdkRunsNdjsonSearchPaths(threadId) {
19603
19846
  function sanitizeCursorStoreSegment(threadId) {
19604
19847
  return (threadId ?? "").trim().replace(/[^a-zA-Z0-9._-]/g, "_");
19605
19848
  }
19606
- var import_node_path46, CURSOR_SDK_STORE_DIR;
19849
+ var import_node_path47, CURSOR_SDK_STORE_DIR;
19607
19850
  var init_cursor_store = __esm({
19608
19851
  "src/agents/cursor-store.ts"() {
19609
19852
  "use strict";
19610
- import_node_path46 = require("path");
19853
+ import_node_path47 = require("path");
19611
19854
  init_paths();
19612
19855
  CURSOR_SDK_STORE_DIR = "cursor-sdk-store";
19613
19856
  }
@@ -19629,9 +19872,9 @@ function recoverFinishedCursorRun(opts) {
19629
19872
  return best;
19630
19873
  }
19631
19874
  function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
19632
- if (!(0, import_node_fs49.existsSync)(runsPath)) return null;
19875
+ if (!(0, import_node_fs50.existsSync)(runsPath)) return null;
19633
19876
  try {
19634
- const lines = (0, import_node_fs49.readFileSync)(runsPath, "utf8").split("\n");
19877
+ const lines = (0, import_node_fs50.readFileSync)(runsPath, "utf8").split("\n");
19635
19878
  let best = null;
19636
19879
  for (const line of lines) {
19637
19880
  const trimmed = line.trim();
@@ -19657,11 +19900,11 @@ function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
19657
19900
  return null;
19658
19901
  }
19659
19902
  }
19660
- var import_node_fs49;
19903
+ var import_node_fs50;
19661
19904
  var init_cursor_recover = __esm({
19662
19905
  "src/agents/cursor-recover.ts"() {
19663
19906
  "use strict";
19664
- import_node_fs49 = require("fs");
19907
+ import_node_fs50 = require("fs");
19665
19908
  init_cursor_store();
19666
19909
  }
19667
19910
  });
@@ -19793,13 +20036,13 @@ async function startOrchestration(opts) {
19793
20036
  }
19794
20037
  return updated;
19795
20038
  }
19796
- var import_node_events, import_node_fs50, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, Orchestrator, singleton;
20039
+ var import_node_events, import_node_fs51, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, Orchestrator, singleton;
19797
20040
  var init_orchestrator = __esm({
19798
20041
  "src/orchestrator/orchestrator.ts"() {
19799
20042
  "use strict";
19800
20043
  import_node_events = require("events");
19801
20044
  init_outbound_watch();
19802
- import_node_fs50 = require("fs");
20045
+ import_node_fs51 = require("fs");
19803
20046
  init_error_detail();
19804
20047
  init_run();
19805
20048
  init_stale_lock();
@@ -19820,6 +20063,7 @@ var init_orchestrator = __esm({
19820
20063
  init_thread_store();
19821
20064
  init_desktop_host();
19822
20065
  init_child_halt();
20066
+ init_wait_for_job();
19823
20067
  init_create();
19824
20068
  init_cowboy();
19825
20069
  init_orchestrator_capable();
@@ -19884,6 +20128,9 @@ var init_orchestrator = __esm({
19884
20128
  * finish or a user send so a later crash can recover again.
19885
20129
  */
19886
20130
  crashContinued = /* @__PURE__ */ new Set();
20131
+ /** Auto-continues after a worktree turn ended while a detached job still runs. */
20132
+ jobContinueCount = /* @__PURE__ */ new Map();
20133
+ jobContinueNudged = /* @__PURE__ */ new Set();
19887
20134
  maxConcurrent;
19888
20135
  runningCount = 0;
19889
20136
  constructor(opts) {
@@ -19967,7 +20214,7 @@ var init_orchestrator = __esm({
19967
20214
  }
19968
20215
  continue;
19969
20216
  }
19970
- if (!(0, import_node_fs50.existsSync)(thread.worktreePath)) {
20217
+ if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
19971
20218
  setStatus(thread.id, "broken", "Worktree missing on disk");
19972
20219
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
19973
20220
  continue;
@@ -20182,6 +20429,37 @@ var init_orchestrator = __esm({
20182
20429
  this.emit({ type: "queue_changed", threadId, queue });
20183
20430
  this.haltDrain.delete(threadId);
20184
20431
  }
20432
+ /**
20433
+ * Worktree agent ended the turn after “I’ll let you know” (or left a
20434
+ * detached test/pack job running). Queue a continue so the chat does not
20435
+ * go idle with nobody watching the log.
20436
+ */
20437
+ maybeEnqueueJobContinue(threadId, chatText, parts = []) {
20438
+ if (this.haltDrain.has(threadId)) return;
20439
+ const thread = readThread(threadId);
20440
+ if (!thread || thread.status === "archived") return;
20441
+ const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
20442
+ const decision = planJobContinue({
20443
+ runningJobIds,
20444
+ chatText,
20445
+ queueLength: thread.queue.length,
20446
+ continueCount: this.jobContinueCount.get(threadId) ?? 0,
20447
+ alreadyNudged: this.jobContinueNudged.has(threadId),
20448
+ isOrchestrator: isOrchestratorThread(thread),
20449
+ agent: thread.agent,
20450
+ watchedJob: turnWatchedDetachedJob(parts)
20451
+ });
20452
+ if (decision.action === "none") {
20453
+ if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
20454
+ return;
20455
+ }
20456
+ if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
20457
+ else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
20458
+ const queue = [decision.prompt, ...thread.queue];
20459
+ updateThread(threadId, { queue });
20460
+ this.emit({ type: "queue_changed", threadId, queue });
20461
+ this.haltDrain.delete(threadId);
20462
+ }
20185
20463
  getThreads(includeArchived = false) {
20186
20464
  return listThreads({ includeArchived });
20187
20465
  }
@@ -20281,6 +20559,8 @@ var init_orchestrator = __esm({
20281
20559
  }
20282
20560
  const queue = [...current.queue, prompt];
20283
20561
  this.crashContinued.delete(thread.id);
20562
+ this.jobContinueCount.delete(thread.id);
20563
+ this.jobContinueNudged.delete(thread.id);
20284
20564
  this.haltDrain.delete(thread.id);
20285
20565
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
20286
20566
  shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
@@ -20795,6 +21075,7 @@ var init_orchestrator = __esm({
20795
21075
  this.emit({ type: "turn_finished", threadId, exitCode });
20796
21076
  if (exitCode === 0) {
20797
21077
  this.crashContinued.delete(threadId);
21078
+ this.maybeEnqueueJobContinue(threadId, chatText, parts);
20798
21079
  } else {
20799
21080
  const blob = [chatText, detail].filter(Boolean).join("\n");
20800
21081
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
@@ -21795,7 +22076,7 @@ var init_orchestrator = __esm({
21795
22076
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
21796
22077
  return restored2;
21797
22078
  }
21798
- if (!(0, import_node_fs50.existsSync)(thread.worktreePath)) {
22079
+ if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
21799
22080
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
21800
22081
  throw new Error(
21801
22082
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
@@ -24415,7 +24696,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
24415
24696
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
24416
24697
  var import_zod6 = require("zod");
24417
24698
  var import_node_crypto13 = require("crypto");
24418
- var import_node_path49 = require("path");
24699
+ var import_node_path50 = require("path");
24419
24700
  init_orchestrator();
24420
24701
  init_worktree();
24421
24702
  init_global_workspace();
@@ -24432,30 +24713,8 @@ function mcpArchiveBlockedReason(thread) {
24432
24713
 
24433
24714
  // src/mcp/server.ts
24434
24715
  init_profile();
24435
-
24436
- // src/mcp/wait-for-turn.ts
24437
- var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
24438
- function mcpWaitForTurnTimeoutMs(requested) {
24439
- const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
24440
- if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
24441
- return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
24442
- }
24443
- 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.";
24444
- 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.";
24445
- function mcpWaitStillRunningHint(status) {
24446
- return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
24447
- }
24448
- 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.";
24449
- var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
24450
- 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.";
24451
- function mcpWaitFinishedHint(status) {
24452
- if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
24453
- if (status === "broken") return MCP_WAIT_BROKEN_HINT;
24454
- if (status === "error") return MCP_WAIT_ERROR_HINT;
24455
- return void 0;
24456
- }
24457
-
24458
- // src/mcp/server.ts
24716
+ init_wait_for_turn();
24717
+ init_wait_for_job();
24459
24718
  init_message_parts();
24460
24719
  init_turn_live();
24461
24720
 
@@ -25606,27 +25865,27 @@ init_gh_errors();
25606
25865
  init_git_auth_mode();
25607
25866
 
25608
25867
  // src/board/load-home-board.ts
25609
- var import_node_fs52 = require("fs");
25610
- var import_node_path48 = require("path");
25868
+ var import_node_fs53 = require("fs");
25869
+ var import_node_path49 = require("path");
25611
25870
  init_worktree();
25612
25871
  init_paths();
25613
25872
 
25614
25873
  // src/board/board-pins.ts
25615
25874
  var import_node_crypto12 = require("crypto");
25616
- var import_node_fs51 = require("fs");
25617
- var import_node_path47 = require("path");
25875
+ var import_node_fs52 = require("fs");
25876
+ var import_node_path48 = require("path");
25618
25877
  init_paths();
25619
25878
  init_home_board();
25620
25879
  var FILE = "home-board-pins.json";
25621
25880
  var VERSION = 1;
25622
25881
  function pinsFile() {
25623
- return (0, import_node_path47.join)(appDataDir(), FILE);
25882
+ return (0, import_node_path48.join)(appDataDir(), FILE);
25624
25883
  }
25625
25884
  function readDisk() {
25626
25885
  const path2 = pinsFile();
25627
- if (!(0, import_node_fs51.existsSync)(path2)) return [];
25886
+ if (!(0, import_node_fs52.existsSync)(path2)) return [];
25628
25887
  try {
25629
- const raw = JSON.parse((0, import_node_fs51.readFileSync)(path2, "utf8"));
25888
+ const raw = JSON.parse((0, import_node_fs52.readFileSync)(path2, "utf8"));
25630
25889
  if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
25631
25890
  return raw.items.filter((item) => item?.id && item.kind && item.ref);
25632
25891
  } catch {
@@ -25634,8 +25893,8 @@ function readDisk() {
25634
25893
  }
25635
25894
  }
25636
25895
  function writeDisk(items) {
25637
- (0, import_node_fs51.mkdirSync)(appDataDir(), { recursive: true });
25638
- (0, import_node_fs51.writeFileSync)(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
25896
+ (0, import_node_fs52.mkdirSync)(appDataDir(), { recursive: true });
25897
+ (0, import_node_fs52.writeFileSync)(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
25639
25898
  }
25640
25899
  function listBoardPins() {
25641
25900
  return readDisk();
@@ -25687,7 +25946,7 @@ function replaceBoardPins(items) {
25687
25946
  }
25688
25947
  function clearBoardPins() {
25689
25948
  try {
25690
- (0, import_node_fs51.unlinkSync)(pinsFile());
25949
+ (0, import_node_fs52.unlinkSync)(pinsFile());
25691
25950
  } catch {
25692
25951
  }
25693
25952
  }
@@ -25704,7 +25963,7 @@ function homeBoardWorkspaceKey(workspaces) {
25704
25963
  return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
25705
25964
  }
25706
25965
  function cacheFile() {
25707
- return (0, import_node_path48.join)(appDataDir(), "home-board-cache.json");
25966
+ return (0, import_node_path49.join)(appDataDir(), "home-board-cache.json");
25708
25967
  }
25709
25968
  function emptyInputs() {
25710
25969
  return {
@@ -25730,9 +25989,9 @@ function shouldCacheHomeBoardInputs(inputs) {
25730
25989
  }
25731
25990
  function readDiskCache() {
25732
25991
  const path2 = cacheFile();
25733
- if (!(0, import_node_fs52.existsSync)(path2)) return null;
25992
+ if (!(0, import_node_fs53.existsSync)(path2)) return null;
25734
25993
  try {
25735
- const raw = JSON.parse((0, import_node_fs52.readFileSync)(path2, "utf8"));
25994
+ const raw = JSON.parse((0, import_node_fs53.readFileSync)(path2, "utf8"));
25736
25995
  if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
25737
25996
  return null;
25738
25997
  }
@@ -25746,14 +26005,14 @@ function readDiskCache() {
25746
26005
  }
25747
26006
  function writeDiskCache(entry) {
25748
26007
  try {
25749
- (0, import_node_fs52.mkdirSync)(appDataDir(), { recursive: true });
25750
- (0, import_node_fs52.writeFileSync)(cacheFile(), JSON.stringify(entry), "utf8");
26008
+ (0, import_node_fs53.mkdirSync)(appDataDir(), { recursive: true });
26009
+ (0, import_node_fs53.writeFileSync)(cacheFile(), JSON.stringify(entry), "utf8");
25751
26010
  } catch {
25752
26011
  }
25753
26012
  }
25754
26013
  function deleteDiskCache() {
25755
26014
  try {
25756
- (0, import_node_fs52.unlinkSync)(cacheFile());
26015
+ (0, import_node_fs53.unlinkSync)(cacheFile());
25757
26016
  } catch {
25758
26017
  }
25759
26018
  }
@@ -26060,7 +26319,7 @@ async function startMcpServer() {
26060
26319
  async () => {
26061
26320
  const threads = orch.getThreads(true);
26062
26321
  const lines = threads.map((t) => {
26063
- const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path49.basename)(t.repoPath) || t.repoPath;
26322
+ const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path50.basename)(t.repoPath) || t.repoPath;
26064
26323
  const live = orch.threadLooksLive(t) ? readTurnLive(t.id) : null;
26065
26324
  const parent = t.parentThreadId ? ` parent:${t.parentThreadId.slice(0, 8)}` : "";
26066
26325
  const preview = lastMessagePreview(t.messages, 80);
@@ -26294,6 +26553,20 @@ async function startMcpServer() {
26294
26553
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
26295
26554
  }
26296
26555
  );
26556
+ server.tool(
26557
+ "wait_for_job",
26558
+ "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.",
26559
+ {
26560
+ id: import_zod6.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
26561
+ timeoutMs: import_zod6.z.number().optional()
26562
+ },
26563
+ async ({ id, timeoutMs }) => {
26564
+ const result = await waitForDetachedJob(process.cwd(), id, {
26565
+ timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
26566
+ });
26567
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
26568
+ }
26569
+ );
26297
26570
  if (!worktreeProfile) {
26298
26571
  registerSlackTools(server);
26299
26572
  registerConnectedIssueVendorTools(server);
@@ -29156,9 +29429,9 @@ var import_node_http3 = require("http");
29156
29429
  var import_ws3 = require("ws");
29157
29430
 
29158
29431
  // src/slack/relay-static.ts
29159
- var import_node_fs53 = require("fs");
29432
+ var import_node_fs54 = require("fs");
29160
29433
  var import_promises = require("fs/promises");
29161
- var import_node_path50 = __toESM(require("path"), 1);
29434
+ var import_node_path51 = __toESM(require("path"), 1);
29162
29435
  var TYPES = {
29163
29436
  ".css": "text/css; charset=utf-8",
29164
29437
  ".html": "text/html; charset=utf-8",
@@ -29189,9 +29462,9 @@ function resolveStaticPath(root, requestUrl) {
29189
29462
  return null;
29190
29463
  }
29191
29464
  if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
29192
- const rootResolved = import_node_path50.default.resolve(root);
29193
- const candidate = import_node_path50.default.resolve(rootResolved, `.${pathname}`);
29194
- if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path50.default.sep)) {
29465
+ const rootResolved = import_node_path51.default.resolve(root);
29466
+ const candidate = import_node_path51.default.resolve(rootResolved, `.${pathname}`);
29467
+ if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path51.default.sep)) {
29195
29468
  return null;
29196
29469
  }
29197
29470
  return candidate;
@@ -29205,7 +29478,7 @@ async function fileSize(file) {
29205
29478
  }
29206
29479
  }
29207
29480
  function sendFile(req, res, file, size) {
29208
- const ext = import_node_path50.default.extname(file).toLowerCase();
29481
+ const ext = import_node_path51.default.extname(file).toLowerCase();
29209
29482
  res.writeHead(200, {
29210
29483
  "Content-Type": TYPES[ext] ?? "application/octet-stream",
29211
29484
  "Content-Length": size,
@@ -29215,7 +29488,7 @@ function sendFile(req, res, file, size) {
29215
29488
  res.end();
29216
29489
  return true;
29217
29490
  }
29218
- (0, import_node_fs53.createReadStream)(file).pipe(res);
29491
+ (0, import_node_fs54.createReadStream)(file).pipe(res);
29219
29492
  return true;
29220
29493
  }
29221
29494
  async function tryServeStatic(req, res, root) {
@@ -29224,7 +29497,7 @@ async function tryServeStatic(req, res, root) {
29224
29497
  if (!candidate) return false;
29225
29498
  const direct = await fileSize(candidate);
29226
29499
  if (direct != null) return sendFile(req, res, candidate, direct);
29227
- const asIndex = import_node_path50.default.join(candidate, "index.html");
29500
+ const asIndex = import_node_path51.default.join(candidate, "index.html");
29228
29501
  const indexSize = await fileSize(asIndex);
29229
29502
  if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
29230
29503
  return false;