@sideboard-ai/core 0.1.100 → 0.1.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/agents/cursor-runner.js +1 -1
  2. package/dist/{agents-CLP5BG4O.js → agents-UT5GCJH3.js} +8 -8
  3. package/dist/{agents-OXZDMPSE.js → agents-XZBAKBO2.js} +8 -8
  4. package/dist/{app-settings-BRF7VKTQ.js → app-settings-EKJM2344.js} +2 -2
  5. package/dist/{app-settings-ZXPAB3QN.js → app-settings-VDJK2BZ2.js} +2 -2
  6. package/dist/{caffeinate-hold-U3QJBV67.js → caffeinate-hold-NBRUWYXO.js} +2 -2
  7. package/dist/{caffeinate-hold-I7SG6UQR.js → caffeinate-hold-X3JNPR42.js} +2 -2
  8. package/dist/{chunk-MHJV4WS3.js → chunk-27MCESES.js} +3 -3
  9. package/dist/{chunk-QSLE4VEM.js → chunk-2GO3YKBA.js} +4 -4
  10. package/dist/{chunk-DKHGWYWR.js → chunk-7YKXHBIW.js} +1 -1
  11. package/dist/{chunk-JL4SJ6V7.js → chunk-BD2ICC4D.js} +4 -0
  12. package/dist/{chunk-NZPGMNFC.js → chunk-DCOZABNT.js} +1 -1
  13. package/dist/{chunk-CIRXAYWS.js → chunk-DHAGMJIK.js} +55 -28
  14. package/dist/{chunk-HYKZEP5A.js → chunk-ETMRBJKA.js} +11 -11
  15. package/dist/{chunk-OXC3MEFI.js → chunk-H6LP57EF.js} +6 -6
  16. package/dist/{chunk-GR4JKWR4.js → chunk-HHLZWO7U.js} +5 -5
  17. package/dist/{chunk-3WA37BRI.js → chunk-KGSVLZV6.js} +11 -4
  18. package/dist/{chunk-7KDBDMNC.js → chunk-KLBOWJ74.js} +1 -1
  19. package/dist/{chunk-EQPQTLW6.js → chunk-LANDJIAI.js} +6 -6
  20. package/dist/{chunk-MRBKGFTL.js → chunk-ND6AO7C5.js} +3 -3
  21. package/dist/{chunk-WQTTUC4N.js → chunk-OW34MVDN.js} +5 -5
  22. package/dist/{chunk-JOF3XIEM.js → chunk-P3BQ5DOL.js} +11 -4
  23. package/dist/{chunk-B4R2VB2A.js → chunk-QZQEXME2.js} +4 -0
  24. package/dist/{chunk-R7BQBSDT.js → chunk-WA3DVVWG.js} +55 -28
  25. package/dist/{chunk-6LFV4VFI.js → chunk-WSYCND2U.js} +1 -1
  26. package/dist/{chunk-FT2SQOL4.js → chunk-XHZJJWLI.js} +1 -1
  27. package/dist/{chunk-IFHKPZHA.js → chunk-YJADJH5U.js} +11 -11
  28. package/dist/{connected-teams-SWHJWK2A.js → connected-teams-J4I5AKMR.js} +2 -2
  29. package/dist/{connected-teams-TLVKZPJP.js → connected-teams-M5XNXRI5.js} +2 -2
  30. package/dist/{coordinator-prompt-OOBSQCWQ.js → coordinator-prompt-3DFZRMXE.js} +5 -5
  31. package/dist/{coordinator-prompt-HHRKQWCX.js → coordinator-prompt-UWHSD65V.js} +5 -5
  32. package/dist/{cursor-recover-S5ORPW34.js → cursor-recover-JBTIDARH.js} +1 -1
  33. package/dist/{cursor-recover-G34BYKL2.js → cursor-recover-ZUJG6XRQ.js} +1 -1
  34. package/dist/{global-workspace-YZWYHLQY.js → global-workspace-KJQS5S2C.js} +6 -6
  35. package/dist/{global-workspace-KYEBFWKB.js → global-workspace-UR4E3CCG.js} +6 -6
  36. package/dist/index.cjs +626 -153
  37. package/dist/index.d.cts +37 -5
  38. package/dist/index.d.ts +37 -5
  39. package/dist/index.js +545 -121
  40. package/dist/mcp/run-stdio.cjs +426 -177
  41. package/dist/mcp/run-stdio.js +330 -127
  42. package/dist/{paths-TZX4L5X7.js → paths-QCI3MTO5.js} +3 -1
  43. package/dist/{paths-JQM2XRAD.js → paths-UMEDHOS5.js} +3 -1
  44. package/dist/{thread-store-XAKWCV2E.js → thread-store-4OUEQ2DB.js} +2 -2
  45. package/dist/{thread-store-V7NOOXKE.js → thread-store-DQJGDKIJ.js} +2 -2
  46. package/dist/{workspaces-ENVUDK6C.js → workspaces-NTIVI6DI.js} +7 -7
  47. package/dist/{workspaces-NNPD7QVV.js → workspaces-TKJXPNV4.js} +7 -7
  48. package/dist/{worktree-EO5QAGJU.js → worktree-NKWN5ZTS.js} +4 -4
  49. package/dist/{worktree-DA4BOV7G.js → worktree-PEUPFAKL.js} +4 -4
  50. package/package.json +1 -1
@@ -18,14 +18,14 @@ import {
18
18
  sideboardMcpProfile,
19
19
  summarizeTurnStderr,
20
20
  turnFailChatText
21
- } from "../chunk-EQPQTLW6.js";
22
- import "../chunk-DKHGWYWR.js";
21
+ } from "../chunk-LANDJIAI.js";
22
+ import "../chunk-7YKXHBIW.js";
23
23
  import {
24
24
  addWorkspace,
25
25
  ensureWorkspace,
26
26
  removeWorkspace,
27
27
  syncWorkspacesFromThreads
28
- } from "../chunk-MRBKGFTL.js";
28
+ } from "../chunk-ND6AO7C5.js";
29
29
  import {
30
30
  extractPresentedPlan,
31
31
  readPlanFile,
@@ -33,7 +33,7 @@ import {
33
33
  } from "../chunk-N5PM7HGQ.js";
34
34
  import {
35
35
  releaseCaffeinateHoldForThread
36
- } from "../chunk-7KDBDMNC.js";
36
+ } from "../chunk-KLBOWJ74.js";
37
37
  import {
38
38
  GLOBAL_WORKSPACE_ID,
39
39
  assertOrchestratorCapableAgent,
@@ -44,14 +44,14 @@ import {
44
44
  isOrchestratorThread,
45
45
  isSlackCoordinatorThread,
46
46
  orchestratorSessionPoisonedByBuiltins
47
- } from "../chunk-WQTTUC4N.js";
47
+ } from "../chunk-OW34MVDN.js";
48
48
  import {
49
49
  SLACK_REPLY_FORMATTING,
50
50
  coordinatorSystemPrompt,
51
51
  coordinatorTurnReminder,
52
52
  enrichWorkspacesWithGithub,
53
53
  ensureGlobalCoordinatorCwd
54
- } from "../chunk-IFHKPZHA.js";
54
+ } from "../chunk-YJADJH5U.js";
55
55
  import {
56
56
  addPrStackLayer,
57
57
  allocateTeamName,
@@ -62,6 +62,7 @@ import {
62
62
  createThreadWorktree,
63
63
  currentBranch,
64
64
  detectGhStack,
65
+ enqueueByKey,
65
66
  fetchPrHead,
66
67
  formatGhLandError,
67
68
  formatGitAuthModeDirective,
@@ -94,8 +95,9 @@ import {
94
95
  takenSlugsFromThread,
95
96
  threadDisplayLabel,
96
97
  warmGithubAgentAuth,
98
+ withRepoGitLock,
97
99
  worktreeNameFromPath
98
- } from "../chunk-R7BQBSDT.js";
100
+ } from "../chunk-WA3DVVWG.js";
99
101
  import {
100
102
  ATTACHMENTS_DIR,
101
103
  LEGACY_ATTACHMENTS_DIR,
@@ -112,7 +114,7 @@ import {
112
114
  updateThread,
113
115
  withThreadLock,
114
116
  writeThread
115
- } from "../chunk-JOF3XIEM.js";
117
+ } from "../chunk-P3BQ5DOL.js";
116
118
  import {
117
119
  childEnvWithAppSettings,
118
120
  disconnectLinearConnection,
@@ -130,7 +132,7 @@ import {
130
132
  stripNestedElectronEnv,
131
133
  updateAdvancedSettings,
132
134
  writeSecureJson
133
- } from "../chunk-6LFV4VFI.js";
135
+ } from "../chunk-WSYCND2U.js";
134
136
  import {
135
137
  writePrivateFile
136
138
  } from "../chunk-NSTQ6QKD.js";
@@ -143,9 +145,10 @@ import {
143
145
  runCursorWorktreeSetup,
144
146
  sideboardReposDir,
145
147
  sideboardWorkspacesDir,
148
+ threadLivePath,
146
149
  workspaceSettingsSourceLabel,
147
150
  worktreesRoot
148
- } from "../chunk-JL4SJ6V7.js";
151
+ } from "../chunk-BD2ICC4D.js";
149
152
  import {
150
153
  enrichPathWithNpmGlobalBin,
151
154
  ensureAgentPath,
@@ -660,7 +663,7 @@ async function refreshSlackReplyBadges(opts) {
660
663
  }
661
664
 
662
665
  // src/orchestrator/orchestrator.ts
663
- import { existsSync as existsSync14 } from "fs";
666
+ import { existsSync as existsSync15 } from "fs";
664
667
 
665
668
  // src/agents/spawn.ts
666
669
  import { createInterface } from "readline";
@@ -902,7 +905,7 @@ function createAgentStreamCoalescer(emit, opts) {
902
905
  const intervalMs = opts?.intervalMs ?? 32;
903
906
  let pending = null;
904
907
  let timer = null;
905
- const flush = () => {
908
+ const flush2 = () => {
906
909
  if (timer) {
907
910
  clearTimeout(timer);
908
911
  timer = null;
@@ -914,13 +917,13 @@ function createAgentStreamCoalescer(emit, opts) {
914
917
  };
915
918
  const schedule = () => {
916
919
  if (timer) return;
917
- timer = setTimeout(flush, intervalMs);
920
+ timer = setTimeout(flush2, intervalMs);
918
921
  timer.unref?.();
919
922
  };
920
923
  return {
921
924
  push(event) {
922
925
  if (!isTextEvent(event)) {
923
- flush();
926
+ flush2();
924
927
  emit(event);
925
928
  return;
926
929
  }
@@ -928,12 +931,12 @@ function createAgentStreamCoalescer(emit, opts) {
928
931
  if (pending && pending.type === event.type) {
929
932
  pending.data += event.data;
930
933
  } else {
931
- flush();
934
+ flush2();
932
935
  pending = { type: event.type, data: event.data };
933
936
  }
934
937
  schedule();
935
938
  },
936
- flush
939
+ flush: flush2
937
940
  };
938
941
  }
939
942
 
@@ -945,9 +948,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
945
948
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
946
949
  );
947
950
  }
948
- const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-YZWYHLQY.js");
951
+ const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-KJQS5S2C.js");
949
952
  if (isGlobalThread2(thread)) {
950
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-OOBSQCWQ.js");
953
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-3DFZRMXE.js");
951
954
  ensureGlobalCoordinatorCwd2(
952
955
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
953
956
  );
@@ -1888,7 +1891,7 @@ async function createThread(input, _onSetupLine) {
1888
1891
  return readThread(thread.id) ?? thread;
1889
1892
  }
1890
1893
  async function listLinearIssues(agent, repoPath) {
1891
- const { getAdapter: getAdapter2 } = await import("../agents-CLP5BG4O.js");
1894
+ const { getAdapter: getAdapter2 } = await import("../agents-UT5GCJH3.js");
1892
1895
  await requireAgent(agent, { requireLinear: true });
1893
1896
  const adapter = getAdapter2(agent);
1894
1897
  if (!adapter.listLinearIssues) {
@@ -2213,8 +2216,11 @@ function worktreeBindingFrom(from) {
2213
2216
  };
2214
2217
  }
2215
2218
  function threadsSharingWorktree(worktreePath) {
2219
+ if (!worktreePath) return [];
2216
2220
  const key = normalizeWorktreePath(worktreePath);
2217
- return listThreads({ includeArchived: true }).filter((t) => sameWorktreePath(t.worktreePath, key) && t.status !== "archived").sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2221
+ return listThreads({ includeArchived: true }).filter(
2222
+ (t) => Boolean(t.worktreePath) && sameWorktreePath(t.worktreePath, key) && t.status !== "archived"
2223
+ ).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2218
2224
  }
2219
2225
  function formatTranscriptMarkdown(title, messages) {
2220
2226
  const body = formatMessagesAsTranscript(messages, { tools: "full" });
@@ -2285,9 +2291,132 @@ function forkChatTab(input) {
2285
2291
  return tab;
2286
2292
  }
2287
2293
 
2294
+ // src/store/turn-live.ts
2295
+ import {
2296
+ existsSync as existsSync7,
2297
+ renameSync,
2298
+ unlinkSync as unlinkSync2,
2299
+ writeFileSync as writeFileSync2,
2300
+ readFileSync as readFileSync5
2301
+ } from "fs";
2302
+ var buffers = /* @__PURE__ */ new Map();
2303
+ var FLUSH_MS = 800;
2304
+ var MAX_PARTS = 80;
2305
+ function trimParts(parts) {
2306
+ if (parts.length <= MAX_PARTS) return parts;
2307
+ return parts.slice(-Math.floor(MAX_PARTS / 2));
2308
+ }
2309
+ function lastText(parts, type) {
2310
+ for (let i = parts.length - 1; i >= 0; i--) {
2311
+ const p = parts[i];
2312
+ if (p.type === type && p.text.trim()) return p.text.trim();
2313
+ }
2314
+ return "";
2315
+ }
2316
+ function summarizeTurnLive(parts) {
2317
+ const tools = parts.filter(
2318
+ (p) => p.type === "tool"
2319
+ );
2320
+ const lastTool = tools[tools.length - 1];
2321
+ const lastToolLabel = lastTool ? lastTool.description || toolDescription(lastTool.name, lastTool.input) || lastTool.name : void 0;
2322
+ const running = lastTool?.status === "running";
2323
+ const thinking = lastText(parts, "thinking");
2324
+ const text3 = lastText(parts, "text");
2325
+ const excerptRaw = thinking || text3;
2326
+ const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
2327
+ let summary = "Working\u2026";
2328
+ if (lastToolLabel) {
2329
+ const verb = running ? lastToolLabel : `Finished ${lastToolLabel}`;
2330
+ summary = tools.length > 1 ? `${verb} (${tools.length} tools)` : verb;
2331
+ } else if (thinking) {
2332
+ summary = "Thinking\u2026";
2333
+ } else if (text3) {
2334
+ summary = "Writing reply\u2026";
2335
+ }
2336
+ return {
2337
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2338
+ summary,
2339
+ lastTool: lastToolLabel,
2340
+ toolCount: tools.length,
2341
+ excerpt
2342
+ };
2343
+ }
2344
+ function flush(threadId) {
2345
+ const buf = buffers.get(threadId);
2346
+ if (!buf) return;
2347
+ if (buf.timer) {
2348
+ clearTimeout(buf.timer);
2349
+ buf.timer = null;
2350
+ }
2351
+ writeTurnLive(threadId, summarizeTurnLive(buf.parts));
2352
+ }
2353
+ function scheduleFlush(threadId) {
2354
+ const buf = buffers.get(threadId);
2355
+ if (!buf) return;
2356
+ if (buf.timer) return;
2357
+ buf.timer = setTimeout(() => {
2358
+ const live = buffers.get(threadId);
2359
+ if (live) live.timer = null;
2360
+ flush(threadId);
2361
+ }, FLUSH_MS);
2362
+ }
2363
+ function noteTurnLiveEvent(threadId, event) {
2364
+ if (event.type === "session_id" || event.type === "usage" || event.type === "stderr") {
2365
+ return;
2366
+ }
2367
+ const prev = buffers.get(threadId) ?? { parts: [], timer: null };
2368
+ prev.parts = trimParts(applyAgentEvent(prev.parts, event));
2369
+ buffers.set(threadId, prev);
2370
+ if (event.type === "exit") {
2371
+ flush(threadId);
2372
+ const cur = buffers.get(threadId);
2373
+ if (cur?.timer) clearTimeout(cur.timer);
2374
+ buffers.delete(threadId);
2375
+ return;
2376
+ }
2377
+ const immediate = event.type === "tool_use" || event.type === "tool_result";
2378
+ if (immediate) flush(threadId);
2379
+ else scheduleFlush(threadId);
2380
+ }
2381
+ function writeTurnLive(threadId, progress) {
2382
+ const path = threadLivePath(threadId);
2383
+ const tmp = `${path}.${process.pid}.tmp`;
2384
+ try {
2385
+ writeFileSync2(tmp, JSON.stringify(progress), "utf8");
2386
+ renameSync(tmp, path);
2387
+ } catch {
2388
+ try {
2389
+ unlinkSync2(tmp);
2390
+ } catch {
2391
+ }
2392
+ }
2393
+ }
2394
+ function readTurnLive(threadId) {
2395
+ const path = threadLivePath(threadId);
2396
+ if (!existsSync7(path)) return null;
2397
+ try {
2398
+ const raw = JSON.parse(readFileSync5(path, "utf8"));
2399
+ if (!raw || typeof raw.summary !== "string") return null;
2400
+ return raw;
2401
+ } catch {
2402
+ return null;
2403
+ }
2404
+ }
2405
+ function clearTurnLive(threadId) {
2406
+ const buf = buffers.get(threadId);
2407
+ if (buf?.timer) clearTimeout(buf.timer);
2408
+ buffers.delete(threadId);
2409
+ const path = threadLivePath(threadId);
2410
+ if (!existsSync7(path)) return;
2411
+ try {
2412
+ unlinkSync2(path);
2413
+ } catch {
2414
+ }
2415
+ }
2416
+
2288
2417
  // src/review/request-review.ts
2289
2418
  import { randomUUID as randomUUID2 } from "crypto";
2290
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
2419
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
2291
2420
  import { dirname as dirname2, join as join8 } from "path";
2292
2421
 
2293
2422
  // src/review/review-request-template.ts
@@ -2433,9 +2562,9 @@ function shouldRefreshReviewRequestTemplate(content) {
2433
2562
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
2434
2563
  }
2435
2564
  function readTextIfPresent(abs) {
2436
- if (!existsSync7(abs)) return null;
2565
+ if (!existsSync8(abs)) return null;
2437
2566
  try {
2438
- const content = readFileSync5(abs, "utf8");
2567
+ const content = readFileSync6(abs, "utf8");
2439
2568
  return content.trim() ? content : null;
2440
2569
  } catch {
2441
2570
  return null;
@@ -2443,9 +2572,9 @@ function readTextIfPresent(abs) {
2443
2572
  }
2444
2573
  function ensureAttachmentsGitignore(worktreePath) {
2445
2574
  const gitignoreAbs = join8(worktreePath, ATTACHMENTS_DIR, ".gitignore");
2446
- if (existsSync7(gitignoreAbs)) return;
2575
+ if (existsSync8(gitignoreAbs)) return;
2447
2576
  mkdirSync2(dirname2(gitignoreAbs), { recursive: true });
2448
- writeFileSync2(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
2577
+ writeFileSync3(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
2449
2578
  }
2450
2579
  function resolveReviewGuidelines(worktreePath) {
2451
2580
  const repoAbs = join8(worktreePath, REPO_REVIEW_PATH);
@@ -2480,7 +2609,7 @@ function resolveReviewGuidelines(worktreePath) {
2480
2609
  }
2481
2610
  ensureAttachmentsGitignore(worktreePath);
2482
2611
  mkdirSync2(dirname2(localAbs), { recursive: true });
2483
- writeFileSync2(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
2612
+ writeFileSync3(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
2484
2613
  return {
2485
2614
  path: REVIEW_REQUEST_PATH,
2486
2615
  name: REVIEW_REQUEST_NAME,
@@ -2676,10 +2805,10 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
2676
2805
  import { execFileSync } from "child_process";
2677
2806
  import {
2678
2807
  copyFileSync as copyFileSync2,
2679
- existsSync as existsSync8,
2808
+ existsSync as existsSync9,
2680
2809
  mkdtempSync,
2681
2810
  readdirSync as readdirSync3,
2682
- readFileSync as readFileSync6,
2811
+ readFileSync as readFileSync7,
2683
2812
  rmSync
2684
2813
  } from "fs";
2685
2814
  import { tmpdir } from "os";
@@ -2709,7 +2838,7 @@ function mapAgentType(raw) {
2709
2838
  return null;
2710
2839
  }
2711
2840
  function resolveConductorCursorAgentId(workspacePath) {
2712
- if (!workspacePath || !existsSync8(CURSOR_SDK_STORE)) return null;
2841
+ if (!workspacePath || !existsSync9(CURSOR_SDK_STORE)) return null;
2713
2842
  const normalized = workspacePath.replace(/\/$/, "");
2714
2843
  let best = null;
2715
2844
  let hashes;
@@ -2720,10 +2849,10 @@ function resolveConductorCursorAgentId(workspacePath) {
2720
2849
  }
2721
2850
  for (const hash of hashes) {
2722
2851
  const agentsFile = join9(CURSOR_SDK_STORE, hash, "agents.ndjson");
2723
- if (!existsSync8(agentsFile)) continue;
2852
+ if (!existsSync9(agentsFile)) continue;
2724
2853
  let text3;
2725
2854
  try {
2726
- text3 = readFileSync6(agentsFile, "utf8");
2855
+ text3 = readFileSync7(agentsFile, "utf8");
2727
2856
  } catch {
2728
2857
  continue;
2729
2858
  }
@@ -2747,7 +2876,7 @@ function resolveConductorCursorAgentId(workspacePath) {
2747
2876
  return best?.agentId ?? null;
2748
2877
  }
2749
2878
  async function adoptThread(input) {
2750
- if (!existsSync8(input.worktreePath)) {
2879
+ if (!existsSync9(input.worktreePath)) {
2751
2880
  throw new Error(`Worktree not found: ${input.worktreePath}`);
2752
2881
  }
2753
2882
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -2766,12 +2895,12 @@ async function adoptThread(input) {
2766
2895
  messages: input.messages ?? []
2767
2896
  });
2768
2897
  writeThread(thread);
2769
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-ENVUDK6C.js");
2898
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-NTIVI6DI.js");
2770
2899
  await ensureWorkspace2(repoPath);
2771
2900
  return thread;
2772
2901
  }
2773
2902
  function listConductorWorkspaces() {
2774
- if (!existsSync8(CONDUCTOR_DB)) {
2903
+ if (!existsSync9(CONDUCTOR_DB)) {
2775
2904
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
2776
2905
  }
2777
2906
  const tmp = mkdtempSync(join9(tmpdir(), "sideboard-conductor-"));
@@ -2780,7 +2909,7 @@ function listConductorWorkspaces() {
2780
2909
  copyFileSync2(CONDUCTOR_DB, snapshot);
2781
2910
  for (const suffix of ["-wal", "-shm"]) {
2782
2911
  const src = `${CONDUCTOR_DB}${suffix}`;
2783
- if (existsSync8(src)) {
2912
+ if (existsSync9(src)) {
2784
2913
  try {
2785
2914
  copyFileSync2(src, `${snapshot}${suffix}`);
2786
2915
  } catch {
@@ -2862,7 +2991,7 @@ function listConductorWorkspaces() {
2862
2991
  }
2863
2992
  }
2864
2993
  function importConductorWorkspace(workspaceId) {
2865
- if (!existsSync8(CONDUCTOR_DB)) {
2994
+ if (!existsSync9(CONDUCTOR_DB)) {
2866
2995
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
2867
2996
  }
2868
2997
  const tmp = mkdtempSync(join9(tmpdir(), "sideboard-conductor-"));
@@ -2871,7 +3000,7 @@ function importConductorWorkspace(workspaceId) {
2871
3000
  copyFileSync2(CONDUCTOR_DB, snapshot);
2872
3001
  for (const suffix of ["-wal", "-shm"]) {
2873
3002
  const src = `${CONDUCTOR_DB}${suffix}`;
2874
- if (existsSync8(src)) {
3003
+ if (existsSync9(src)) {
2875
3004
  try {
2876
3005
  copyFileSync2(src, `${snapshot}${suffix}`);
2877
3006
  } catch {
@@ -2891,7 +3020,7 @@ function importConductorWorkspace(workspaceId) {
2891
3020
  ).get(workspaceId);
2892
3021
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
2893
3022
  const worktreePath = String(row.workspacePath);
2894
- if (!existsSync8(worktreePath)) {
3023
+ if (!existsSync9(worktreePath)) {
2895
3024
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
2896
3025
  }
2897
3026
  let sessionId = null;
@@ -2962,7 +3091,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
2962
3091
  }
2963
3092
 
2964
3093
  // src/threads/stack-layers.ts
2965
- import { existsSync as existsSync9 } from "fs";
3094
+ import { existsSync as existsSync10 } from "fs";
2966
3095
  function stackIdFrom(stack) {
2967
3096
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
2968
3097
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -3025,7 +3154,7 @@ async function openStackLayer(input, _onSetupLine) {
3025
3154
  let createdWorktree = false;
3026
3155
  const trees = await listWorktrees(repoPath);
3027
3156
  const checkedOut = trees.find((w) => w.branch === branchName);
3028
- if (checkedOut?.path && existsSync9(checkedOut.path)) {
3157
+ if (checkedOut?.path && existsSync10(checkedOut.path)) {
3029
3158
  if (input.reuseExistingWorktree !== false) {
3030
3159
  worktreePath = checkedOut.path;
3031
3160
  } else {
@@ -3167,7 +3296,7 @@ async function initStackFromThread(input, onSetupLine) {
3167
3296
  async function createPrStack(input, onSetupLine) {
3168
3297
  await requireAgent(input.agent);
3169
3298
  const repoPath = await resolveRepoRoot(input.repoPath);
3170
- if (!existsSync9(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3299
+ if (!existsSync10(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3171
3300
  if (!input.branches.length) throw new Error("At least one branch name required");
3172
3301
  const status = await detectGhStack(repoPath);
3173
3302
  if (!status.available) throw new Error(status.reason);
@@ -3234,7 +3363,7 @@ async function createPrStack(input, onSetupLine) {
3234
3363
  }
3235
3364
  }
3236
3365
  const claimed = new Set(threads.map((t) => t.worktreePath));
3237
- if (!claimed.has(bootstrap.worktreePath) && existsSync9(bootstrap.worktreePath)) {
3366
+ if (!claimed.has(bootstrap.worktreePath) && existsSync10(bootstrap.worktreePath)) {
3238
3367
  try {
3239
3368
  await removeWorktree(repoPath, bootstrap.worktreePath, {
3240
3369
  deleteBranch: bootstrap.branchName
@@ -3246,10 +3375,10 @@ async function createPrStack(input, onSetupLine) {
3246
3375
  }
3247
3376
 
3248
3377
  // src/diff/diff.ts
3249
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync7, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
3378
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
3250
3379
  import { dirname as dirname3, join as join10 } from "path";
3251
3380
  async function inspectGitWorktree(worktreePath) {
3252
- if (!worktreePath || !existsSync10(worktreePath)) return "missing_worktree";
3381
+ if (!worktreePath || !existsSync11(worktreePath)) return "missing_worktree";
3253
3382
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
3254
3383
  reject: false
3255
3384
  });
@@ -3257,7 +3386,7 @@ async function inspectGitWorktree(worktreePath) {
3257
3386
  return "ok";
3258
3387
  }
3259
3388
  async function initializeGitRepository(worktreePath) {
3260
- if (!worktreePath || !existsSync10(worktreePath)) {
3389
+ if (!worktreePath || !existsSync11(worktreePath)) {
3261
3390
  throw new Error("Worktree not found");
3262
3391
  }
3263
3392
  const status = await inspectGitWorktree(worktreePath);
@@ -3397,7 +3526,7 @@ async function untrackedPatch(worktreePath, path, maxHunk) {
3397
3526
  try {
3398
3527
  const st = statSync2(abs);
3399
3528
  if (st.isFile() && st.size > maxHunk) {
3400
- const buf = readFileSync7(abs).subarray(0, maxHunk);
3529
+ const buf = readFileSync8(abs).subarray(0, maxHunk);
3401
3530
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
3402
3531
  }
3403
3532
  } catch {
@@ -3908,7 +4037,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
3908
4037
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
3909
4038
  );
3910
4039
  }
3911
- const buf = readFileSync7(abs);
4040
+ const buf = readFileSync8(abs);
3912
4041
  return {
3913
4042
  path: relativePath,
3914
4043
  contentBase64: buf.toString("base64"),
@@ -3923,7 +4052,7 @@ function readWorktreeFile(worktreePath, relativePath, opts) {
3923
4052
  if (!st.isFile()) {
3924
4053
  throw new Error(`Not a file: ${relativePath}`);
3925
4054
  }
3926
- const buf = readFileSync7(abs);
4055
+ const buf = readFileSync8(abs);
3927
4056
  if (isImageRelativePath(relativePath)) {
3928
4057
  const maxImageBytes = Math.max(maxBytes, 15e6);
3929
4058
  const truncated2 = buf.length > maxImageBytes;
@@ -3968,7 +4097,7 @@ function writeWorktreeFile(worktreePath, relativePath, content) {
3968
4097
  assertSafeRelativePath(relativePath);
3969
4098
  const abs = join10(worktreePath, relativePath);
3970
4099
  mkdirSync3(dirname3(abs), { recursive: true });
3971
- writeFileSync3(abs, content, "utf8");
4100
+ writeFileSync4(abs, content, "utf8");
3972
4101
  return { path: relativePath };
3973
4102
  }
3974
4103
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -4071,7 +4200,7 @@ async function confirmLand(thread, opts) {
4071
4200
  }
4072
4201
 
4073
4202
  // src/skills/discover.ts
4074
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
4203
+ import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
4075
4204
  import { homedir } from "os";
4076
4205
  import { join as join11 } from "path";
4077
4206
  function toCommand(name) {
@@ -4105,7 +4234,7 @@ function parseFrontmatter(content) {
4105
4234
  }
4106
4235
  function readSkill(skillMd, source) {
4107
4236
  try {
4108
- const content = readFileSync8(skillMd, "utf8");
4237
+ const content = readFileSync9(skillMd, "utf8");
4109
4238
  const { name: fmName, description } = parseFrontmatter(content);
4110
4239
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
4111
4240
  const name = fmName || dirName;
@@ -4124,7 +4253,7 @@ function readSkill(skillMd, source) {
4124
4253
  }
4125
4254
  }
4126
4255
  function scanSkillsDir(dir, source, out) {
4127
- if (!existsSync11(dir)) return;
4256
+ if (!existsSync12(dir)) return;
4128
4257
  let entries;
4129
4258
  try {
4130
4259
  entries = readdirSync4(dir);
@@ -4134,7 +4263,7 @@ function scanSkillsDir(dir, source, out) {
4134
4263
  for (const entry of entries) {
4135
4264
  if (entry.startsWith(".")) continue;
4136
4265
  const skillMd = join11(dir, entry, "SKILL.md");
4137
- if (!existsSync11(skillMd)) continue;
4266
+ if (!existsSync12(skillMd)) continue;
4138
4267
  try {
4139
4268
  if (!statSync3(skillMd).isFile()) continue;
4140
4269
  } catch {
@@ -4145,7 +4274,7 @@ function scanSkillsDir(dir, source, out) {
4145
4274
  }
4146
4275
  }
4147
4276
  function scanClaudePluginSkills(pluginsRoot, out) {
4148
- if (!existsSync11(pluginsRoot)) return;
4277
+ if (!existsSync12(pluginsRoot)) return;
4149
4278
  const walk = (dir, depth, lookingForSkillsDir) => {
4150
4279
  if (depth > 7) return;
4151
4280
  let entries;
@@ -4202,7 +4331,7 @@ function discoverSkills(worktreePath) {
4202
4331
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
4203
4332
  }
4204
4333
  function readSkillBody(skillPath, maxChars = 12e3) {
4205
- const raw = readFileSync8(skillPath, "utf8");
4334
+ const raw = readFileSync9(skillPath, "utf8");
4206
4335
  if (raw.startsWith("---")) {
4207
4336
  const end = raw.indexOf("\n---", 3);
4208
4337
  if (end >= 0) {
@@ -4295,7 +4424,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
4295
4424
  }
4296
4425
 
4297
4426
  // src/composer/stage-files.ts
4298
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
4427
+ import { copyFileSync as copyFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
4299
4428
  import { basename as basename3, extname, join as join12 } from "path";
4300
4429
  import { randomUUID as randomUUID4 } from "crypto";
4301
4430
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
@@ -4334,19 +4463,19 @@ function ensureAttachmentsDir(worktreePath) {
4334
4463
  const dir = join12(worktreePath, ATTACHMENTS_DIR);
4335
4464
  mkdirSync4(dir, { recursive: true });
4336
4465
  const gi = join12(dir, ".gitignore");
4337
- if (!existsSync12(gi)) {
4338
- writeFileSync4(gi, attachmentsGitignoreBody(), "utf8");
4466
+ if (!existsSync13(gi)) {
4467
+ writeFileSync5(gi, attachmentsGitignoreBody(), "utf8");
4339
4468
  }
4340
4469
  return dir;
4341
4470
  }
4342
4471
  function uniqueAttachmentName(dir, originalName) {
4343
4472
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
4344
- if (!existsSync12(join12(dir, safe))) return safe;
4473
+ if (!existsSync13(join12(dir, safe))) return safe;
4345
4474
  const ext = extname(safe);
4346
4475
  const stem = ext ? safe.slice(0, -ext.length) : safe;
4347
4476
  for (let i = 1; i < 1e4; i++) {
4348
4477
  const candidate = `${stem}-${i}${ext}`;
4349
- if (!existsSync12(join12(dir, candidate))) return candidate;
4478
+ if (!existsSync13(join12(dir, candidate))) return candidate;
4350
4479
  }
4351
4480
  return `${stem}-${randomUUID4()}${ext}`;
4352
4481
  }
@@ -4410,7 +4539,7 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
4410
4539
  const destAbs = join12(dir, name);
4411
4540
  copyFileSync3(abs, destAbs);
4412
4541
  const rel = `${ATTACHMENTS_DIR}/${name}`;
4413
- const buf = readFileSync9(destAbs);
4542
+ const buf = readFileSync10(destAbs);
4414
4543
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
4415
4544
  } catch (err) {
4416
4545
  out.push({
@@ -4423,17 +4552,17 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
4423
4552
  }
4424
4553
  return out;
4425
4554
  }
4426
- function stageBuffersAsAttachments(worktreePath, buffers) {
4427
- if (buffers.length === 0) return [];
4555
+ function stageBuffersAsAttachments(worktreePath, buffers2) {
4556
+ if (buffers2.length === 0) return [];
4428
4557
  const dir = ensureAttachmentsDir(worktreePath);
4429
4558
  const out = [];
4430
- for (const item of buffers) {
4559
+ for (const item of buffers2) {
4431
4560
  const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
4432
4561
  try {
4433
4562
  const buf = Buffer.from(item.dataBase64, "base64");
4434
4563
  const name = uniqueAttachmentName(dir, originalName);
4435
4564
  const destAbs = join12(dir, name);
4436
- writeFileSync4(destAbs, buf);
4565
+ writeFileSync5(destAbs, buf);
4437
4566
  const rel = `${ATTACHMENTS_DIR}/${name}`;
4438
4567
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
4439
4568
  } catch (err) {
@@ -4464,7 +4593,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
4464
4593
  const abs = join12(worktreePath, rel);
4465
4594
  const st = statSync4(abs);
4466
4595
  if (!st.isFile()) continue;
4467
- const buf = readFileSync9(abs);
4596
+ const buf = readFileSync10(abs);
4468
4597
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
4469
4598
  } catch (err) {
4470
4599
  out.push({
@@ -4479,7 +4608,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
4479
4608
  }
4480
4609
 
4481
4610
  // src/agents/instructions.ts
4482
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
4611
+ import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
4483
4612
  import { join as join13 } from "path";
4484
4613
  function normPath(p) {
4485
4614
  return p.replace(/\/+$/, "");
@@ -4669,6 +4798,15 @@ function isPidAlive(pid) {
4669
4798
  return false;
4670
4799
  }
4671
4800
  }
4801
+ async function waitForPidExit(pid, timeoutMs) {
4802
+ const start = Date.now();
4803
+ while (isPidAlive(pid)) {
4804
+ if (Date.now() - start >= timeoutMs) return false;
4805
+ await new Promise((r) => setTimeout(r, 80));
4806
+ }
4807
+ return true;
4808
+ }
4809
+ var STALE_AGENT_PID_WAIT_MS = 2500;
4672
4810
  function writeLiveStatus(threadId, status, lastError) {
4673
4811
  const latest = readThread(threadId);
4674
4812
  if (!latest || latest.status === "archived") return latest;
@@ -4746,7 +4884,7 @@ var Orchestrator = class {
4746
4884
  }
4747
4885
  continue;
4748
4886
  }
4749
- if (!existsSync14(thread.worktreePath)) {
4887
+ if (!existsSync15(thread.worktreePath)) {
4750
4888
  setStatus(thread.id, "broken", "Worktree missing on disk");
4751
4889
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
4752
4890
  continue;
@@ -4765,7 +4903,7 @@ var Orchestrator = class {
4765
4903
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
4766
4904
  });
4767
4905
  }
4768
- const { autoCleanupOrphansEnabled } = await import("../app-settings-ZXPAB3QN.js");
4906
+ const { autoCleanupOrphansEnabled } = await import("../app-settings-VDJK2BZ2.js");
4769
4907
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
4770
4908
  await cleanupOrphanWorktrees({ repoPaths });
4771
4909
  }
@@ -4933,7 +5071,7 @@ var Orchestrator = class {
4933
5071
  }
4934
5072
  }
4935
5073
  await setup;
4936
- const { autoRunAfterSetupEnabled } = await import("../app-settings-ZXPAB3QN.js");
5074
+ const { autoRunAfterSetupEnabled } = await import("../app-settings-VDJK2BZ2.js");
4937
5075
  if (autoRunAfterSetupEnabled()) {
4938
5076
  try {
4939
5077
  await this.startDev(threadId);
@@ -5067,14 +5205,10 @@ var Orchestrator = class {
5067
5205
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
5068
5206
  const livePid = current.agentPid;
5069
5207
  const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
5070
- if (inFlight) {
5208
+ if (inFlight || foreignLive) {
5071
5209
  this.stop(thread.id, { clearQueue: false, continueQueue: true });
5072
- } else {
5073
- if (foreignLive) {
5074
- this.stop(thread.id, { clearQueue: false, continueQueue: true });
5075
- }
5076
- void this.drainQueue(thread.id);
5077
5210
  }
5211
+ void this.drainQueue(thread.id);
5078
5212
  return this.requireThread(thread.id);
5079
5213
  }
5080
5214
  async drainQueue(threadId) {
@@ -5103,7 +5237,18 @@ var Orchestrator = class {
5103
5237
  }
5104
5238
  const livePid = thread.agentPid;
5105
5239
  if (typeof livePid === "number" && livePid > 0 && isPidAlive(livePid)) {
5106
- await new Promise((r) => setTimeout(r, 200));
5240
+ const exited = await waitForPidExit(livePid, STALE_AGENT_PID_WAIT_MS);
5241
+ if (!exited && isPidAlive(livePid)) {
5242
+ try {
5243
+ process.kill(livePid, "SIGKILL");
5244
+ } catch {
5245
+ }
5246
+ await waitForPidExit(livePid, 400);
5247
+ try {
5248
+ updateThread(threadId, { agentPid: null });
5249
+ } catch {
5250
+ }
5251
+ }
5107
5252
  continue;
5108
5253
  }
5109
5254
  const prompt = thread.queue[0];
@@ -5211,7 +5356,7 @@ var Orchestrator = class {
5211
5356
  }
5212
5357
  const isBrightsy = fresh.agent === "brightsy";
5213
5358
  const isOrchestration = isOrchestratorThread(fresh);
5214
- const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("../app-settings-ZXPAB3QN.js");
5359
+ const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("../app-settings-VDJK2BZ2.js");
5215
5360
  const gitAuthMode = getGithubGitAuthMode();
5216
5361
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
5217
5362
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -5260,6 +5405,7 @@ var Orchestrator = class {
5260
5405
  { cachedPrefix, prompt: agentPrompt },
5261
5406
  (event) => {
5262
5407
  this.emit({ type: "turn_output", threadId, event });
5408
+ noteTurnLiveEvent(threadId, event);
5263
5409
  if (event.type === "session_id") {
5264
5410
  updateThread(threadId, { sessionId: event.data });
5265
5411
  }
@@ -5306,7 +5452,7 @@ var Orchestrator = class {
5306
5452
  if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
5307
5453
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
5308
5454
  if (sessionId) {
5309
- const { recoverFinishedCursorRun } = await import("../cursor-recover-S5ORPW34.js");
5455
+ const { recoverFinishedCursorRun } = await import("../cursor-recover-JBTIDARH.js");
5310
5456
  for (let i = 0; i < 8; i++) {
5311
5457
  const recovered = recoverFinishedCursorRun({
5312
5458
  agentId: sessionId,
@@ -5464,6 +5610,7 @@ var Orchestrator = class {
5464
5610
  this.processes.delete(`${threadId}:agent`);
5465
5611
  this.stoppedTurns.delete(threadId);
5466
5612
  this.runningCount = Math.max(0, this.runningCount - 1);
5613
+ clearTurnLive(threadId);
5467
5614
  try {
5468
5615
  updateThread(threadId, { agentPid: null });
5469
5616
  } catch {
@@ -5737,51 +5884,56 @@ var Orchestrator = class {
5737
5884
  }
5738
5885
  return created;
5739
5886
  }
5740
- async waitForTurn(threadRef, timeoutMs = 6e5) {
5887
+ async waitForTurn(threadRef, timeoutMs = 6e5, opts) {
5741
5888
  const thread = this.requireThread(threadRef);
5889
+ if (!["running", "queued"].includes(thread.status)) {
5890
+ return thread;
5891
+ }
5742
5892
  const start = Date.now();
5743
5893
  return new Promise((resolve, reject) => {
5744
- if (!["running", "queued"].includes(thread.status)) {
5745
- resolve(thread);
5746
- return;
5747
- }
5894
+ let settled = false;
5895
+ let timer;
5896
+ const finish = (fn) => {
5897
+ if (settled) return;
5898
+ settled = true;
5899
+ if (timer) clearInterval(timer);
5900
+ off();
5901
+ fn();
5902
+ };
5748
5903
  const off = this.on((event) => {
5749
5904
  if (event.type === "turn_finished" && event.threadId === thread.id) {
5750
- off();
5751
5905
  const latest = readThread(thread.id);
5752
5906
  if (!latest) {
5753
- reject(new Error(`Thread not found: ${thread.id}`));
5907
+ finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
5754
5908
  return;
5755
5909
  }
5756
- resolve(latest);
5910
+ finish(() => resolve(latest));
5757
5911
  }
5758
5912
  if (event.type === "error" && event.threadId === thread.id) {
5759
- off();
5760
5913
  const latest = readThread(thread.id);
5761
5914
  if (!latest) {
5762
- reject(new Error(`Thread not found: ${thread.id}`));
5915
+ finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
5763
5916
  return;
5764
5917
  }
5765
- resolve(latest);
5918
+ finish(() => resolve(latest));
5766
5919
  }
5767
5920
  });
5768
- const timer = setInterval(() => {
5769
- if (Date.now() - start > timeoutMs) {
5770
- clearInterval(timer);
5771
- off();
5772
- reject(new Error("wait_for_turn timed out"));
5773
- }
5921
+ timer = setInterval(() => {
5774
5922
  const current = readThread(thread.id);
5775
5923
  if (!current) {
5776
- clearInterval(timer);
5777
- off();
5778
- reject(new Error(`Thread not found: ${thread.id}`));
5924
+ finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
5779
5925
  return;
5780
5926
  }
5781
5927
  if (!["running", "queued"].includes(current.status)) {
5782
- clearInterval(timer);
5783
- off();
5784
- resolve(current);
5928
+ finish(() => resolve(current));
5929
+ return;
5930
+ }
5931
+ if (Date.now() - start > timeoutMs) {
5932
+ if (opts?.resolveIfStillRunning) {
5933
+ finish(() => resolve(current));
5934
+ } else {
5935
+ finish(() => reject(new Error("wait_for_turn timed out")));
5936
+ }
5785
5937
  }
5786
5938
  }, 200);
5787
5939
  });
@@ -5791,11 +5943,16 @@ var Orchestrator = class {
5791
5943
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
5792
5944
  const lastError = thread.lastError ?? null;
5793
5945
  const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
5946
+ const stillRunning = thread.status === "running" || thread.status === "queued";
5947
+ const live = stillRunning ? readTurnLive(thread.id) : null;
5794
5948
  return {
5795
5949
  text: text3,
5796
5950
  status: thread.status,
5797
5951
  sessionId: thread.sessionId,
5798
- lastError
5952
+ lastError,
5953
+ stillRunning,
5954
+ progress: live?.summary ?? null,
5955
+ lastActivityAt: live?.updatedAt ?? null
5799
5956
  };
5800
5957
  }
5801
5958
  assertNotGlobal(thread, action) {
@@ -5958,7 +6115,7 @@ var Orchestrator = class {
5958
6115
  updateThread(thread.id, { title: meta.title });
5959
6116
  }
5960
6117
  }
5961
- const { autoArchiveOnMergeEnabled } = await import("../app-settings-ZXPAB3QN.js");
6118
+ const { autoArchiveOnMergeEnabled } = await import("../app-settings-VDJK2BZ2.js");
5962
6119
  const latest = this.requireThread(thread.id);
5963
6120
  if (!shouldAutoArchiveOnPrMerge({
5964
6121
  previousPrState: prevState || null,
@@ -6221,7 +6378,19 @@ var Orchestrator = class {
6221
6378
  const thread = this.requireThread(threadRef);
6222
6379
  return threadsSharingWorktree(thread.worktreePath);
6223
6380
  }
6381
+ /**
6382
+ * Last-tab teardown must see siblings already archived. Serialize per
6383
+ * worktree so parallel archive/purge of sibling tabs still removes once.
6384
+ */
6385
+ enqueueWorktreeTeardown(thread, fn) {
6386
+ const key = isGlobalThread(thread) ? `archive:global:${thread.id}` : `archive:wt:${normalizeWorktreePath(thread.worktreePath)}`;
6387
+ return enqueueByKey(key, fn);
6388
+ }
6224
6389
  async archive(threadRef) {
6390
+ const thread = this.requireThread(threadRef);
6391
+ return this.enqueueWorktreeTeardown(thread, () => this.archiveUnlocked(threadRef));
6392
+ }
6393
+ async archiveUnlocked(threadRef) {
6225
6394
  const thread = this.requireThread(threadRef);
6226
6395
  this.stop(thread.id);
6227
6396
  this.releaseOrchestratorCaffeinate(thread);
@@ -6249,7 +6418,7 @@ var Orchestrator = class {
6249
6418
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
6250
6419
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
6251
6420
  try {
6252
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-ENVUDK6C.js");
6421
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-NTIVI6DI.js");
6253
6422
  await ensureWorkspace2(thread.repoPath);
6254
6423
  } catch {
6255
6424
  }
@@ -6257,6 +6426,10 @@ var Orchestrator = class {
6257
6426
  return archived;
6258
6427
  }
6259
6428
  async purge(threadRef, opts) {
6429
+ const thread = this.requireThread(threadRef);
6430
+ await this.enqueueWorktreeTeardown(thread, () => this.purgeUnlocked(threadRef, opts));
6431
+ }
6432
+ async purgeUnlocked(threadRef, opts) {
6260
6433
  const thread = this.requireThread(threadRef);
6261
6434
  this.stop(thread.id);
6262
6435
  this.releaseOrchestratorCaffeinate(thread);
@@ -6271,7 +6444,7 @@ var Orchestrator = class {
6271
6444
  await runArchiveScript(thread.repoPath, thread.worktreePath);
6272
6445
  } catch {
6273
6446
  }
6274
- const { deleteBranchOnPurgeEnabled } = await import("../app-settings-ZXPAB3QN.js");
6447
+ const { deleteBranchOnPurgeEnabled } = await import("../app-settings-VDJK2BZ2.js");
6275
6448
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
6276
6449
  await removeWorktree(thread.repoPath, thread.worktreePath, {
6277
6450
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -6285,19 +6458,18 @@ var Orchestrator = class {
6285
6458
  throw new Error("Thread is not archived");
6286
6459
  }
6287
6460
  if (isGlobalThread(thread)) {
6288
- const { globalAgentCwd } = await import("../paths-TZX4L5X7.js");
6461
+ const { globalAgentCwd } = await import("../paths-QCI3MTO5.js");
6289
6462
  updateThread(thread.id, { worktreePath: globalAgentCwd() });
6290
6463
  const restored2 = setStatus(thread.id, "idle");
6291
6464
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
6292
6465
  return restored2;
6293
6466
  }
6294
- if (!existsSync14(thread.worktreePath)) {
6295
- const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-EO5QAGJU.js");
6296
- const { execa: execa5 } = await import("execa");
6467
+ if (!existsSync15(thread.worktreePath)) {
6468
+ const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-NKWN5ZTS.js");
6297
6469
  const slug = thread.worktreePath.split("/").pop();
6298
6470
  const dest = thread.worktreePath;
6299
- await execa5("git", ["worktree", "add", dest, thread.branchName], {
6300
- cwd: thread.repoPath
6471
+ await withRepoGitLock(thread.repoPath, async () => {
6472
+ await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
6301
6473
  });
6302
6474
  void createThreadWorktree2;
6303
6475
  void slug;
@@ -6844,6 +7016,15 @@ function mcpArchiveBlockedReason(thread) {
6844
7016
  return null;
6845
7017
  }
6846
7018
 
7019
+ // src/mcp/wait-for-turn.ts
7020
+ var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
7021
+ function mcpWaitForTurnTimeoutMs(requested) {
7022
+ const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
7023
+ if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
7024
+ return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
7025
+ }
7026
+ 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.";
7027
+
6847
7028
  // src/mcp/slack-tools.ts
6848
7029
  import { z } from "zod";
6849
7030
 
@@ -7471,7 +7652,7 @@ async function startMcpServer() {
7471
7652
  );
7472
7653
  }
7473
7654
  try {
7474
- const { maxConcurrentAgents } = await import("../app-settings-ZXPAB3QN.js");
7655
+ const { maxConcurrentAgents } = await import("../app-settings-VDJK2BZ2.js");
7475
7656
  orch.setMaxConcurrent(maxConcurrentAgents());
7476
7657
  } catch {
7477
7658
  }
@@ -7514,7 +7695,9 @@ async function startMcpServer() {
7514
7695
  const threads = orch.getThreads(true);
7515
7696
  const lines = threads.map((t) => {
7516
7697
  const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : basename4(t.repoPath) || t.repoPath;
7517
- return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}`;
7698
+ const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
7699
+ const progress = live?.summary ? ` ${live.summary}` : "";
7700
+ return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
7518
7701
  });
7519
7702
  return {
7520
7703
  content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
@@ -7523,13 +7706,14 @@ async function startMcpServer() {
7523
7706
  );
7524
7707
  server.tool(
7525
7708
  "get_thread",
7526
- "Get a compact thread summary by id/ref",
7709
+ "Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt.",
7527
7710
  { ref: z3.string() },
7528
7711
  async ({ ref }) => {
7529
7712
  const t = orch.getThread(ref);
7530
7713
  if (!t) {
7531
7714
  return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
7532
7715
  }
7716
+ const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
7533
7717
  const summary = {
7534
7718
  id: t.id,
7535
7719
  title: t.title,
@@ -7544,7 +7728,10 @@ async function startMcpServer() {
7544
7728
  messageCount: t.messages.length,
7545
7729
  devPort: t.devPort,
7546
7730
  prUrl: t.prUrl,
7547
- lastError: t.lastError ?? null
7731
+ lastError: t.lastError ?? null,
7732
+ stillRunning: t.status === "running" || t.status === "queued",
7733
+ progress: live?.summary ?? null,
7734
+ lastActivityAt: live?.updatedAt ?? null
7548
7735
  };
7549
7736
  return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
7550
7737
  }
@@ -7680,8 +7867,8 @@ async function startMcpServer() {
7680
7867
  registerSlackTools(server);
7681
7868
  registerLinearTools(server);
7682
7869
  if (sideboardMcpProfile() !== "worktree") {
7683
- const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-U3QJBV67.js");
7684
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults } = await import("../app-settings-ZXPAB3QN.js");
7870
+ const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-NBRUWYXO.js");
7871
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults } = await import("../app-settings-VDJK2BZ2.js");
7685
7872
  const accountDefaults = resolveThreadDefaults();
7686
7873
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
7687
7874
  server.tool(
@@ -7870,13 +8057,15 @@ async function startMcpServer() {
7870
8057
  );
7871
8058
  server.tool(
7872
8059
  "wait_for_turn",
7873
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git. Returns status, text, and lastError. If status is error, lastError/text is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success.",
8060
+ "Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. If stillRunning is true, progress is tools/thinking \u2014 call wait_for_turn again. Do not send a check-in prompt or assume a hang. On status error, lastError/text is the failure.",
7874
8061
  {
7875
8062
  ref: z3.string(),
7876
8063
  timeoutMs: z3.number().optional()
7877
8064
  },
7878
8065
  async ({ ref, timeoutMs }) => {
7879
- const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
8066
+ const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
8067
+ resolveIfStillRunning: true
8068
+ });
7880
8069
  const result = orch.getTurnResult(thread.id);
7881
8070
  return {
7882
8071
  content: [
@@ -7886,7 +8075,11 @@ async function startMcpServer() {
7886
8075
  id: thread.id,
7887
8076
  status: result.status,
7888
8077
  text: result.text,
7889
- lastError: result.lastError
8078
+ lastError: result.lastError,
8079
+ stillRunning: result.stillRunning,
8080
+ progress: result.progress,
8081
+ lastActivityAt: result.lastActivityAt,
8082
+ hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
7890
8083
  })
7891
8084
  }
7892
8085
  ]
@@ -7895,11 +8088,21 @@ async function startMcpServer() {
7895
8088
  );
7896
8089
  server.tool(
7897
8090
  "get_turn_result",
7898
- "Final assistant message (and lastError when the turn failed). Not the full transcript.",
8091
+ "Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript.",
7899
8092
  { ref: z3.string() },
7900
8093
  async ({ ref }) => {
7901
8094
  const result = orch.getTurnResult(ref);
7902
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
8095
+ return {
8096
+ content: [
8097
+ {
8098
+ type: "text",
8099
+ text: JSON.stringify({
8100
+ ...result,
8101
+ hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
8102
+ })
8103
+ }
8104
+ ]
8105
+ };
7903
8106
  }
7904
8107
  );
7905
8108
  server.tool(
@@ -8016,7 +8219,7 @@ async function startMcpServer() {
8016
8219
  );
8017
8220
  server.tool(
8018
8221
  "request_review",
8019
- 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
8222
+ 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
8020
8223
  { ref: z3.string().describe("Worktree thread id/ref to review") },
8021
8224
  async ({ ref }) => {
8022
8225
  try {
@@ -8044,7 +8247,7 @@ async function startMcpServer() {
8044
8247
  );
8045
8248
  server.tool(
8046
8249
  "ask_git",
8047
- "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
8250
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn (loop while stillRunning). Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
8048
8251
  {
8049
8252
  ref: z3.string().describe("Worktree thread id/ref"),
8050
8253
  action: z3.enum(AGENT_GIT_ACTIONS).describe(
@@ -8095,7 +8298,7 @@ async function startMcpServer() {
8095
8298
  );
8096
8299
  server.tool(
8097
8300
  "fork_worktree",
8098
- "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
8301
+ "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id.",
8099
8302
  {
8100
8303
  ref: z3.string().describe("Worktree thread id/ref to fork"),
8101
8304
  through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
@@ -8140,7 +8343,7 @@ async function startMcpServer() {
8140
8343
  );
8141
8344
  server.tool(
8142
8345
  "fork_chat",
8143
- "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
8346
+ "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
8144
8347
  {
8145
8348
  ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
8146
8349
  through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),