@sideboard-ai/core 0.1.97 → 0.1.99

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.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-QKYO6BHB.js";
7
+ } from "./chunk-UYQYK2RY.js";
8
8
  import {
9
9
  BRIGHTSY_MCP_ALLOWED_TOOLS,
10
10
  CLAUDE_MODEL_CATALOG,
@@ -65,7 +65,7 @@ import {
65
65
  threadRequestsBrightsyMcp,
66
66
  totalTokens,
67
67
  writeInjectedMcpConfig
68
- } from "./chunk-KWNUZ4LR.js";
68
+ } from "./chunk-MBP3XG57.js";
69
69
  import {
70
70
  CLOUD_COORDINATOR_BUSY_REPLY,
71
71
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -79,6 +79,7 @@ import {
79
79
  createGlobalChat,
80
80
  ensureCloudCoordinator,
81
81
  ensureSlackCoordinator,
82
+ findSlackCoordinator,
82
83
  healOrchestrationSoccerTitles,
83
84
  isCloudCoordinatorThread,
84
85
  isGlobalRepoPath,
@@ -92,7 +93,7 @@ import {
92
93
  parseForceStopMessage,
93
94
  slackCoordinatorSourceRef,
94
95
  takenTeamSlugsForOrchestration
95
- } from "./chunk-CLGO7TLO.js";
96
+ } from "./chunk-FO67IJTY.js";
96
97
  import {
97
98
  COORDINATOR_TOOL_PLAYBOOK,
98
99
  SLACK_REPLY_FORMATTING,
@@ -366,6 +367,52 @@ import {
366
367
  withExportedPath
367
368
  } from "./chunk-LGXBYZZA.js";
368
369
 
370
+ // src/store/desktop-host.ts
371
+ import { readFileSync, unlinkSync, writeFileSync } from "fs";
372
+ import { join } from "path";
373
+ function desktopHostPidPath() {
374
+ return join(appDataDir(), "desktop-host.pid");
375
+ }
376
+ function claimDesktopHost(pid = process.pid) {
377
+ writeFileSync(desktopHostPidPath(), `${pid}
378
+ `, "utf8");
379
+ }
380
+ function releaseDesktopHost(pid = process.pid) {
381
+ if (readDesktopHostPid() !== pid) return;
382
+ try {
383
+ unlinkSync(desktopHostPidPath());
384
+ } catch {
385
+ }
386
+ }
387
+ function readDesktopHostPid() {
388
+ try {
389
+ const pid = Number.parseInt(readFileSync(desktopHostPidPath(), "utf8").trim(), 10);
390
+ if (!Number.isFinite(pid) || pid <= 0) return null;
391
+ return pid;
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ function pidAlive(pid) {
397
+ try {
398
+ process.kill(pid, 0);
399
+ return true;
400
+ } catch {
401
+ return false;
402
+ }
403
+ }
404
+ function isDesktopHostAlive() {
405
+ const pid = readDesktopHostPid();
406
+ return pid != null && pidAlive(pid);
407
+ }
408
+ function isThisProcessDesktopHost() {
409
+ return readDesktopHostPid() === process.pid && pidAlive(process.pid);
410
+ }
411
+ function thisProcessShouldDrainAgentQueues() {
412
+ if (isThisProcessDesktopHost()) return true;
413
+ return !isDesktopHostAlive();
414
+ }
415
+
369
416
  // src/git/agent-git-actions.ts
370
417
  var AGENT_GIT_ACTIONS = [
371
418
  "commit-push",
@@ -1338,7 +1385,7 @@ async function spawnAgentTurn(thread, input, onEvent) {
1338
1385
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
1339
1386
  );
1340
1387
  }
1341
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-M3OMVDDH.js");
1388
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-RSQXRLT7.js");
1342
1389
  if (isGlobalThread2(thread)) {
1343
1390
  const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-AKEY4WSO.js");
1344
1391
  ensureGlobalCoordinatorCwd2(
@@ -1451,8 +1498,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
1451
1498
  }
1452
1499
 
1453
1500
  // src/agents/instructions.ts
1454
- import { existsSync, readFileSync, statSync } from "fs";
1455
- import { join } from "path";
1501
+ import { existsSync, readFileSync as readFileSync2, statSync } from "fs";
1502
+ import { join as join2 } from "path";
1456
1503
  function normPath(p) {
1457
1504
  return p.replace(/\/+$/, "");
1458
1505
  }
@@ -1621,11 +1668,11 @@ function loadAgentInstructions(worktreePath, agent) {
1621
1668
  const out = [];
1622
1669
  for (const rel of candidates) {
1623
1670
  if (seenPaths.has(rel)) continue;
1624
- const abs = join(worktreePath, rel);
1671
+ const abs = join2(worktreePath, rel);
1625
1672
  if (!existsSync(abs)) continue;
1626
1673
  try {
1627
1674
  if (!statSync(abs).isFile()) continue;
1628
- let content = readFileSync(abs, "utf8");
1675
+ let content = readFileSync2(abs, "utf8");
1629
1676
  if (!content.trim()) continue;
1630
1677
  if (content.length > MAX_CHARS_PER_FILE) {
1631
1678
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -1705,10 +1752,10 @@ import {
1705
1752
  existsSync as existsSync2,
1706
1753
  mkdirSync,
1707
1754
  readdirSync,
1708
- readFileSync as readFileSync2
1755
+ readFileSync as readFileSync3
1709
1756
  } from "fs";
1710
1757
  import { createServer as createServer2 } from "net";
1711
- import { basename, dirname, join as join2 } from "path";
1758
+ import { basename, dirname, join as join3 } from "path";
1712
1759
  import { execa as execa2 } from "execa";
1713
1760
  import { createInterface as createInterface2 } from "readline";
1714
1761
  var PORT_RANGE_SIZE = 10;
@@ -1717,9 +1764,9 @@ function matchSimpleGlob(pattern, name) {
1717
1764
  return new RegExp(`^${escaped}$`).test(name);
1718
1765
  }
1719
1766
  function readWorktreeInclude(repoPath) {
1720
- const path2 = join2(repoPath, ".worktreeinclude");
1767
+ const path2 = join3(repoPath, ".worktreeinclude");
1721
1768
  if (!existsSync2(path2)) return [];
1722
- return readFileSync2(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1769
+ return readFileSync3(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1723
1770
  }
1724
1771
  function resolveFilesToCopy(repoPath) {
1725
1772
  const fromInclude = readWorktreeInclude(repoPath);
@@ -1758,9 +1805,9 @@ function copyConfiguredFiles(repoPath, worktreePath) {
1758
1805
  const patterns = resolveFilesToCopy(repoPath);
1759
1806
  const copied = [];
1760
1807
  for (const rel of patterns) {
1761
- const src = join2(repoPath, rel);
1808
+ const src = join3(repoPath, rel);
1762
1809
  if (!existsSync2(src)) continue;
1763
- const dest = join2(worktreePath, rel);
1810
+ const dest = join3(worktreePath, rel);
1764
1811
  mkdirSync(dirname(dest), { recursive: true });
1765
1812
  copyFileSync(src, dest);
1766
1813
  copied.push(rel);
@@ -2055,8 +2102,8 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
2055
2102
  }
2056
2103
 
2057
2104
  // src/diff/diff.ts
2058
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, statSync as statSync2, writeFileSync } from "fs";
2059
- import { dirname as dirname2, join as join3 } from "path";
2105
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
2106
+ import { dirname as dirname2, join as join4 } from "path";
2060
2107
  async function inspectGitWorktree(worktreePath) {
2061
2108
  if (!worktreePath || !existsSync3(worktreePath)) return "missing_worktree";
2062
2109
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
@@ -2202,11 +2249,11 @@ new file mode 100644
2202
2249
  };
2203
2250
  }
2204
2251
  async function untrackedPatch(worktreePath, path2, maxHunk) {
2205
- const abs = join3(worktreePath, path2);
2252
+ const abs = join4(worktreePath, path2);
2206
2253
  try {
2207
2254
  const st = statSync2(abs);
2208
2255
  if (st.isFile() && st.size > maxHunk) {
2209
- const buf = readFileSync3(abs).subarray(0, maxHunk);
2256
+ const buf = readFileSync4(abs).subarray(0, maxHunk);
2210
2257
  return syntheticAddPatch(path2, buf.toString("utf8"), maxHunk);
2211
2258
  }
2212
2259
  } catch {
@@ -2707,7 +2754,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
2707
2754
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
2708
2755
  assertSafeRelativePath(relativePath);
2709
2756
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
2710
- const abs = join3(worktreePath, relativePath);
2757
+ const abs = join4(worktreePath, relativePath);
2711
2758
  const st = statSync2(abs);
2712
2759
  if (!st.isFile()) {
2713
2760
  throw new Error(`Not a file: ${relativePath}`);
@@ -2717,7 +2764,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
2717
2764
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
2718
2765
  );
2719
2766
  }
2720
- const buf = readFileSync3(abs);
2767
+ const buf = readFileSync4(abs);
2721
2768
  return {
2722
2769
  path: relativePath,
2723
2770
  contentBase64: buf.toString("base64"),
@@ -2727,12 +2774,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
2727
2774
  function readWorktreeFile(worktreePath, relativePath, opts) {
2728
2775
  assertSafeRelativePath(relativePath);
2729
2776
  const maxBytes = opts?.maxBytes ?? 2e5;
2730
- const abs = join3(worktreePath, relativePath);
2777
+ const abs = join4(worktreePath, relativePath);
2731
2778
  const st = statSync2(abs);
2732
2779
  if (!st.isFile()) {
2733
2780
  throw new Error(`Not a file: ${relativePath}`);
2734
2781
  }
2735
- const buf = readFileSync3(abs);
2782
+ const buf = readFileSync4(abs);
2736
2783
  if (isImageRelativePath(relativePath)) {
2737
2784
  const maxImageBytes = Math.max(maxBytes, 15e6);
2738
2785
  const truncated2 = buf.length > maxImageBytes;
@@ -2775,9 +2822,9 @@ function assertSafeRelativePath(relativePath) {
2775
2822
  }
2776
2823
  function writeWorktreeFile(worktreePath, relativePath, content) {
2777
2824
  assertSafeRelativePath(relativePath);
2778
- const abs = join3(worktreePath, relativePath);
2825
+ const abs = join4(worktreePath, relativePath);
2779
2826
  mkdirSync2(dirname2(abs), { recursive: true });
2780
- writeFileSync(abs, content, "utf8");
2827
+ writeFileSync2(abs, content, "utf8");
2781
2828
  return { path: relativePath };
2782
2829
  }
2783
2830
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -2796,9 +2843,9 @@ async function getDiffSummary(worktreePath, repoPath, opts) {
2796
2843
  }
2797
2844
 
2798
2845
  // src/skills/discover.ts
2799
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
2846
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
2800
2847
  import { homedir } from "os";
2801
- import { join as join4 } from "path";
2848
+ import { join as join5 } from "path";
2802
2849
  function toCommand(name) {
2803
2850
  return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2804
2851
  }
@@ -2830,7 +2877,7 @@ function parseFrontmatter(content) {
2830
2877
  }
2831
2878
  function readSkill(skillMd, source) {
2832
2879
  try {
2833
- const content = readFileSync4(skillMd, "utf8");
2880
+ const content = readFileSync5(skillMd, "utf8");
2834
2881
  const { name: fmName, description } = parseFrontmatter(content);
2835
2882
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
2836
2883
  const name = fmName || dirName;
@@ -2858,7 +2905,7 @@ function scanSkillsDir(dir, source, out) {
2858
2905
  }
2859
2906
  for (const entry of entries) {
2860
2907
  if (entry.startsWith(".")) continue;
2861
- const skillMd = join4(dir, entry, "SKILL.md");
2908
+ const skillMd = join5(dir, entry, "SKILL.md");
2862
2909
  if (!existsSync4(skillMd)) continue;
2863
2910
  try {
2864
2911
  if (!statSync3(skillMd).isFile()) continue;
@@ -2880,12 +2927,12 @@ function scanClaudePluginSkills(pluginsRoot, out) {
2880
2927
  return;
2881
2928
  }
2882
2929
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
2883
- const skill = readSkill(join4(dir, "SKILL.md"), "cli");
2930
+ const skill = readSkill(join5(dir, "SKILL.md"), "cli");
2884
2931
  if (skill) out.push(skill);
2885
2932
  }
2886
2933
  for (const entry of entries) {
2887
2934
  if (entry === "node_modules" || entry === ".git") continue;
2888
- const full = join4(dir, entry);
2935
+ const full = join5(dir, entry);
2889
2936
  try {
2890
2937
  if (!statSync3(full).isDirectory()) continue;
2891
2938
  } catch {
@@ -2905,17 +2952,17 @@ function discoverSkills(worktreePath) {
2905
2952
  const home = homedir();
2906
2953
  const collected = [];
2907
2954
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
2908
- scanSkillsDir(join4(worktreePath, rel), "workspace", collected);
2955
+ scanSkillsDir(join5(worktreePath, rel), "workspace", collected);
2909
2956
  }
2910
2957
  for (const abs of [
2911
- join4(home, ".claude/skills"),
2912
- join4(home, ".cursor/skills"),
2913
- join4(home, ".sideboard/skills"),
2914
- join4(home, ".brightsy/skills")
2958
+ join5(home, ".claude/skills"),
2959
+ join5(home, ".cursor/skills"),
2960
+ join5(home, ".sideboard/skills"),
2961
+ join5(home, ".brightsy/skills")
2915
2962
  ]) {
2916
2963
  scanSkillsDir(abs, "user", collected);
2917
2964
  }
2918
- scanClaudePluginSkills(join4(home, ".claude/plugins"), collected);
2965
+ scanClaudePluginSkills(join5(home, ".claude/plugins"), collected);
2919
2966
  const rank = { workspace: 0, user: 1, cli: 2 };
2920
2967
  const byCommand = /* @__PURE__ */ new Map();
2921
2968
  for (const skill of collected) {
@@ -2927,7 +2974,7 @@ function discoverSkills(worktreePath) {
2927
2974
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
2928
2975
  }
2929
2976
  function readSkillBody(skillPath, maxChars = 12e3) {
2930
- const raw = readFileSync4(skillPath, "utf8");
2977
+ const raw = readFileSync5(skillPath, "utf8");
2931
2978
  if (raw.startsWith("---")) {
2932
2979
  const end = raw.indexOf("\n---", 3);
2933
2980
  if (end >= 0) {
@@ -3068,8 +3115,8 @@ function buildDiffCommentAttachment(input) {
3068
3115
  }
3069
3116
 
3070
3117
  // src/composer/stage-files.ts
3071
- import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
3072
- import { basename as basename2, extname, join as join5 } from "path";
3118
+ import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
3119
+ import { basename as basename2, extname, join as join6 } from "path";
3073
3120
  import { randomUUID } from "crypto";
3074
3121
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
3075
3122
  "png",
@@ -3104,22 +3151,22 @@ function imageMimeType(filePath) {
3104
3151
  return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
3105
3152
  }
3106
3153
  function ensureAttachmentsDir(worktreePath) {
3107
- const dir = join5(worktreePath, ATTACHMENTS_DIR);
3154
+ const dir = join6(worktreePath, ATTACHMENTS_DIR);
3108
3155
  mkdirSync3(dir, { recursive: true });
3109
- const gi = join5(dir, ".gitignore");
3156
+ const gi = join6(dir, ".gitignore");
3110
3157
  if (!existsSync5(gi)) {
3111
- writeFileSync2(gi, attachmentsGitignoreBody(), "utf8");
3158
+ writeFileSync3(gi, attachmentsGitignoreBody(), "utf8");
3112
3159
  }
3113
3160
  return dir;
3114
3161
  }
3115
3162
  function uniqueAttachmentName(dir, originalName) {
3116
3163
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
3117
- if (!existsSync5(join5(dir, safe))) return safe;
3164
+ if (!existsSync5(join6(dir, safe))) return safe;
3118
3165
  const ext = extname(safe);
3119
3166
  const stem = ext ? safe.slice(0, -ext.length) : safe;
3120
3167
  for (let i = 1; i < 1e4; i++) {
3121
3168
  const candidate = `${stem}-${i}${ext}`;
3122
- if (!existsSync5(join5(dir, candidate))) return candidate;
3169
+ if (!existsSync5(join6(dir, candidate))) return candidate;
3123
3170
  }
3124
3171
  return `${stem}-${randomUUID()}${ext}`;
3125
3172
  }
@@ -3182,7 +3229,7 @@ function attachmentFromAbsolutePath(absolutePath) {
3182
3229
  content: `(not a file: ${absolutePath})`
3183
3230
  };
3184
3231
  }
3185
- const buf = readFileSync5(absolutePath);
3232
+ const buf = readFileSync6(absolutePath);
3186
3233
  return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
3187
3234
  } catch (err) {
3188
3235
  return {
@@ -3203,10 +3250,10 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
3203
3250
  const st = statSync4(abs);
3204
3251
  if (!st.isFile()) continue;
3205
3252
  const name = uniqueAttachmentName(dir, originalName);
3206
- const destAbs = join5(dir, name);
3253
+ const destAbs = join6(dir, name);
3207
3254
  copyFileSync2(abs, destAbs);
3208
3255
  const rel = `${ATTACHMENTS_DIR}/${name}`;
3209
- const buf = readFileSync5(destAbs);
3256
+ const buf = readFileSync6(destAbs);
3210
3257
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
3211
3258
  } catch (err) {
3212
3259
  out.push({
@@ -3228,8 +3275,8 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
3228
3275
  try {
3229
3276
  const buf = Buffer.from(item.dataBase64, "base64");
3230
3277
  const name = uniqueAttachmentName(dir, originalName);
3231
- const destAbs = join5(dir, name);
3232
- writeFileSync2(destAbs, buf);
3278
+ const destAbs = join6(dir, name);
3279
+ writeFileSync3(destAbs, buf);
3233
3280
  const rel = `${ATTACHMENTS_DIR}/${name}`;
3234
3281
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
3235
3282
  } catch (err) {
@@ -3273,10 +3320,10 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
3273
3320
  }
3274
3321
  const name = basename2(rel);
3275
3322
  try {
3276
- const abs = join5(worktreePath, rel);
3323
+ const abs = join6(worktreePath, rel);
3277
3324
  const st = statSync4(abs);
3278
3325
  if (!st.isFile()) continue;
3279
- const buf = readFileSync5(abs);
3326
+ const buf = readFileSync6(abs);
3280
3327
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
3281
3328
  } catch (err) {
3282
3329
  out.push({
@@ -3833,7 +3880,7 @@ async function createThread(input, _onSetupLine) {
3833
3880
  return readThread(thread.id) ?? thread;
3834
3881
  }
3835
3882
  async function listLinearIssues(agent, repoPath) {
3836
- const { getAdapter: getAdapter2 } = await import("./agents-WS5QV6LE.js");
3883
+ const { getAdapter: getAdapter2 } = await import("./agents-3MWWWSMF.js");
3837
3884
  await requireAgent(agent, { requireLinear: true });
3838
3885
  const adapter = getAdapter2(agent);
3839
3886
  if (!adapter.listLinearIssues) {
@@ -3913,6 +3960,7 @@ function createChatTab(input) {
3913
3960
  if (isOrchestratorThread(from) || binding.sourceType === "orchestration") {
3914
3961
  assertOrchestratorCapableAgent(nextAgent);
3915
3962
  }
3963
+ const singletonInbox = isSlackCoordinatorThread(from) || isCloudCoordinatorThread(from);
3916
3964
  const thread = createEmptyThread({
3917
3965
  title,
3918
3966
  // Chat-tab nicknames (soccer team or explicit) must stick. Post-turn
@@ -3920,6 +3968,10 @@ function createChatTab(input) {
3920
3968
  // shared worktree folder name (e.g. fork "Arsenal" → "Monaco").
3921
3969
  userSetTitle: true,
3922
3970
  ...binding,
3971
+ // Slack / Brightsy cloud identity stays on the original chat. A + tab
3972
+ // that copies `slack:team:user` would steal inbound DMs after you close
3973
+ // the connected chat.
3974
+ sourceRef: singletonInbox ? title : binding.sourceRef,
3923
3975
  agent: nextAgent,
3924
3976
  model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
3925
3977
  effort: input.effort !== void 0 ? input.effort : from.effort,
@@ -4294,20 +4346,20 @@ import {
4294
4346
  existsSync as existsSync8,
4295
4347
  mkdtempSync,
4296
4348
  readdirSync as readdirSync3,
4297
- readFileSync as readFileSync6,
4349
+ readFileSync as readFileSync7,
4298
4350
  rmSync
4299
4351
  } from "fs";
4300
4352
  import { tmpdir } from "os";
4301
- import { join as join6 } from "path";
4353
+ import { join as join7 } from "path";
4302
4354
  import { createRequire } from "module";
4303
- var CONDUCTOR_APP_SUPPORT = join6(
4355
+ var CONDUCTOR_APP_SUPPORT = join7(
4304
4356
  process.env.HOME ?? "",
4305
4357
  "Library",
4306
4358
  "Application Support",
4307
4359
  "com.conductor.app"
4308
4360
  );
4309
- var CONDUCTOR_DB = join6(CONDUCTOR_APP_SUPPORT, "conductor.db");
4310
- var CURSOR_SDK_STORE = join6(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
4361
+ var CONDUCTOR_DB = join7(CONDUCTOR_APP_SUPPORT, "conductor.db");
4362
+ var CURSOR_SDK_STORE = join7(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
4311
4363
  function openReadonlySqlite(file) {
4312
4364
  const req = createRequire(import.meta.url);
4313
4365
  const Database = req("better-sqlite3");
@@ -4334,11 +4386,11 @@ function resolveConductorCursorAgentId(workspacePath) {
4334
4386
  return null;
4335
4387
  }
4336
4388
  for (const hash of hashes) {
4337
- const agentsFile = join6(CURSOR_SDK_STORE, hash, "agents.ndjson");
4389
+ const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
4338
4390
  if (!existsSync8(agentsFile)) continue;
4339
4391
  let text3;
4340
4392
  try {
4341
- text3 = readFileSync6(agentsFile, "utf8");
4393
+ text3 = readFileSync7(agentsFile, "utf8");
4342
4394
  } catch {
4343
4395
  continue;
4344
4396
  }
@@ -4381,7 +4433,7 @@ async function adoptThread(input) {
4381
4433
  messages: input.messages ?? []
4382
4434
  });
4383
4435
  writeThread(thread);
4384
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ERZC7ULY.js");
4436
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4ZY4QPWQ.js");
4385
4437
  await ensureWorkspace2(repoPath);
4386
4438
  return thread;
4387
4439
  }
@@ -4392,8 +4444,8 @@ function listConductorWorkspaces() {
4392
4444
  if (!existsSync8(CONDUCTOR_DB)) {
4393
4445
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
4394
4446
  }
4395
- const tmp = mkdtempSync(join6(tmpdir(), "sideboard-conductor-"));
4396
- const snapshot = join6(tmp, "conductor.db");
4447
+ const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
4448
+ const snapshot = join7(tmp, "conductor.db");
4397
4449
  try {
4398
4450
  copyFileSync3(CONDUCTOR_DB, snapshot);
4399
4451
  for (const suffix of ["-wal", "-shm"]) {
@@ -4483,8 +4535,8 @@ function importConductorWorkspace(workspaceId) {
4483
4535
  if (!existsSync8(CONDUCTOR_DB)) {
4484
4536
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
4485
4537
  }
4486
- const tmp = mkdtempSync(join6(tmpdir(), "sideboard-conductor-"));
4487
- const snapshot = join6(tmp, "conductor.db");
4538
+ const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
4539
+ const snapshot = join7(tmp, "conductor.db");
4488
4540
  try {
4489
4541
  copyFileSync3(CONDUCTOR_DB, snapshot);
4490
4542
  for (const suffix of ["-wal", "-shm"]) {
@@ -4583,8 +4635,8 @@ async function importConductorWorkspaceAsync(workspaceId) {
4583
4635
  import { EventEmitter } from "events";
4584
4636
 
4585
4637
  // src/slack/outbound-watch.ts
4586
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
4587
- import { join as join9 } from "path";
4638
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
4639
+ import { join as join10 } from "path";
4588
4640
 
4589
4641
  // src/slack/api.ts
4590
4642
  var SLACK_API = "https://slack.com/api";
@@ -4626,16 +4678,16 @@ async function slackAuthTest(token) {
4626
4678
  }
4627
4679
 
4628
4680
  // src/slack/reply-target.ts
4629
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
4630
- import { join as join7 } from "path";
4681
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
4682
+ import { join as join8 } from "path";
4631
4683
  function storePath() {
4632
- return join7(appDataDir(), "slack-reply-to.json");
4684
+ return join8(appDataDir(), "slack-reply-to.json");
4633
4685
  }
4634
4686
  function readStore() {
4635
4687
  const path2 = storePath();
4636
4688
  if (!existsSync9(path2)) return {};
4637
4689
  try {
4638
- const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync7(path2, "utf8"));
4690
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync8(path2, "utf8"));
4639
4691
  return parsed?.targets && typeof parsed.targets === "object" ? parsed.targets : {};
4640
4692
  } catch {
4641
4693
  return {};
@@ -4652,9 +4704,9 @@ function getSlackReplyTarget(threadId) {
4652
4704
  }
4653
4705
 
4654
4706
  // src/slack/workspaces.ts
4655
- import { join as join8 } from "path";
4707
+ import { join as join9 } from "path";
4656
4708
  function storePath2() {
4657
- return join8(appDataDir(), "slack-workspaces.json");
4709
+ return join9(appDataDir(), "slack-workspaces.json");
4658
4710
  }
4659
4711
  function readStore2() {
4660
4712
  try {
@@ -4770,7 +4822,7 @@ var POLL_INTERVAL_MS = 12e3;
4770
4822
  var lastPollMs = 0;
4771
4823
  var nameCache = /* @__PURE__ */ new Map();
4772
4824
  function storePath3() {
4773
- return join9(appDataDir(), "slack-outbound-watch.json");
4825
+ return join10(appDataDir(), "slack-outbound-watch.json");
4774
4826
  }
4775
4827
  function watchId(teamId, channelId, ts) {
4776
4828
  return `${teamId}:${channelId}:${ts}`;
@@ -4802,7 +4854,7 @@ function readStore3() {
4802
4854
  const path2 = storePath3();
4803
4855
  if (!existsSync10(path2)) return [];
4804
4856
  try {
4805
- const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync8(path2, "utf8"));
4857
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync9(path2, "utf8"));
4806
4858
  return Array.isArray(parsed?.watches) ? parsed.watches : [];
4807
4859
  } catch {
4808
4860
  return [];
@@ -5165,7 +5217,7 @@ function shouldAutoArchiveOnPrMerge(opts) {
5165
5217
 
5166
5218
  // src/git/orphan-cleanup.ts
5167
5219
  import { existsSync as existsSync11, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
5168
- import { join as join10 } from "path";
5220
+ import { join as join11 } from "path";
5169
5221
  function isSideboardWorktreePath(path2) {
5170
5222
  return path2.includes("/.sideboard/worktrees/") || path2.includes("/sideboard/workspaces/");
5171
5223
  }
@@ -5212,9 +5264,9 @@ async function findOrphanWorktrees(repoPaths) {
5212
5264
  if (existsSync11(root)) {
5213
5265
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
5214
5266
  if (!entry.isDirectory()) continue;
5215
- const path2 = join10(root, entry.name).replace(/\/$/, "");
5267
+ const path2 = join11(root, entry.name).replace(/\/$/, "");
5216
5268
  if (known.has(path2) || seen.has(path2)) continue;
5217
- if (!existsSync11(join10(path2, ".git"))) continue;
5269
+ if (!existsSync11(join11(path2, ".git"))) continue;
5218
5270
  seen.add(path2);
5219
5271
  let mtimeMs = 0;
5220
5272
  try {
@@ -5364,7 +5416,7 @@ async function applyThreadIntoMain(thread, opts) {
5364
5416
 
5365
5417
  // src/git/clone-repo.ts
5366
5418
  import { existsSync as existsSync12 } from "fs";
5367
- import { basename as basename3, join as join11 } from "path";
5419
+ import { basename as basename3, join as join12 } from "path";
5368
5420
  import { execa as execa4 } from "execa";
5369
5421
  async function cloneRepoIntoSideboard(opts) {
5370
5422
  const url = opts.url.trim();
@@ -5375,7 +5427,7 @@ async function cloneRepoIntoSideboard(opts) {
5375
5427
  name = leaf || "repo";
5376
5428
  }
5377
5429
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
5378
- const dest = join11(sideboardReposDir(), name);
5430
+ const dest = join12(sideboardReposDir(), name);
5379
5431
  if (existsSync12(dest)) {
5380
5432
  const repoPath2 = await resolveRepoRoot(dest);
5381
5433
  const workspace2 = await ensureWorkspace(repoPath2);
@@ -5394,8 +5446,8 @@ async function cloneRepoIntoSideboard(opts) {
5394
5446
 
5395
5447
  // src/review/request-review.ts
5396
5448
  import { randomUUID as randomUUID4 } from "crypto";
5397
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
5398
- import { dirname as dirname3, join as join12 } from "path";
5449
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
5450
+ import { dirname as dirname3, join as join13 } from "path";
5399
5451
 
5400
5452
  // src/review/review-request-template.ts
5401
5453
  var REVIEW_REQUEST_TEMPLATE = `# Review guidelines:
@@ -5542,20 +5594,20 @@ function shouldRefreshReviewRequestTemplate(content) {
5542
5594
  function readTextIfPresent(abs) {
5543
5595
  if (!existsSync13(abs)) return null;
5544
5596
  try {
5545
- const content = readFileSync9(abs, "utf8");
5597
+ const content = readFileSync10(abs, "utf8");
5546
5598
  return content.trim() ? content : null;
5547
5599
  } catch {
5548
5600
  return null;
5549
5601
  }
5550
5602
  }
5551
5603
  function ensureAttachmentsGitignore(worktreePath) {
5552
- const gitignoreAbs = join12(worktreePath, ATTACHMENTS_DIR, ".gitignore");
5604
+ const gitignoreAbs = join13(worktreePath, ATTACHMENTS_DIR, ".gitignore");
5553
5605
  if (existsSync13(gitignoreAbs)) return;
5554
5606
  mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
5555
- writeFileSync3(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
5607
+ writeFileSync4(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
5556
5608
  }
5557
5609
  function resolveReviewGuidelines(worktreePath) {
5558
- const repoAbs = join12(worktreePath, REPO_REVIEW_PATH);
5610
+ const repoAbs = join13(worktreePath, REPO_REVIEW_PATH);
5559
5611
  const repoContent = readTextIfPresent(repoAbs);
5560
5612
  if (repoContent) {
5561
5613
  return {
@@ -5565,7 +5617,7 @@ function resolveReviewGuidelines(worktreePath) {
5565
5617
  source: "repo"
5566
5618
  };
5567
5619
  }
5568
- const localAbs = join12(worktreePath, REVIEW_REQUEST_PATH);
5620
+ const localAbs = join13(worktreePath, REVIEW_REQUEST_PATH);
5569
5621
  const localContent = readTextIfPresent(localAbs);
5570
5622
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
5571
5623
  return {
@@ -5575,7 +5627,7 @@ function resolveReviewGuidelines(worktreePath) {
5575
5627
  source: "local"
5576
5628
  };
5577
5629
  }
5578
- const legacyAbs = join12(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
5630
+ const legacyAbs = join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
5579
5631
  const legacyContent = readTextIfPresent(legacyAbs);
5580
5632
  if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
5581
5633
  return {
@@ -5587,7 +5639,7 @@ function resolveReviewGuidelines(worktreePath) {
5587
5639
  }
5588
5640
  ensureAttachmentsGitignore(worktreePath);
5589
5641
  mkdirSync4(dirname3(localAbs), { recursive: true });
5590
- writeFileSync3(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
5642
+ writeFileSync4(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
5591
5643
  return {
5592
5644
  path: REVIEW_REQUEST_PATH,
5593
5645
  name: REVIEW_REQUEST_NAME,
@@ -5596,7 +5648,7 @@ function resolveReviewGuidelines(worktreePath) {
5596
5648
  };
5597
5649
  }
5598
5650
  function ensureReviewRequestFile(worktreePath) {
5599
- const repoAbs = join12(worktreePath, REPO_REVIEW_PATH);
5651
+ const repoAbs = join13(worktreePath, REPO_REVIEW_PATH);
5600
5652
  const repoContent = readTextIfPresent(repoAbs);
5601
5653
  if (repoContent) {
5602
5654
  return {
@@ -5606,8 +5658,8 @@ function ensureReviewRequestFile(worktreePath) {
5606
5658
  source: "repo"
5607
5659
  };
5608
5660
  }
5609
- const localAbs = join12(worktreePath, REVIEW_REQUEST_PATH);
5610
- const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join12(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
5661
+ const localAbs = join13(worktreePath, REVIEW_REQUEST_PATH);
5662
+ const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
5611
5663
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
5612
5664
  const path2 = existsSync13(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
5613
5665
  return {
@@ -5618,7 +5670,7 @@ function ensureReviewRequestFile(worktreePath) {
5618
5670
  };
5619
5671
  }
5620
5672
  mkdirSync4(dirname3(repoAbs), { recursive: true });
5621
- writeFileSync3(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
5673
+ writeFileSync4(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
5622
5674
  return {
5623
5675
  path: REPO_REVIEW_PATH,
5624
5676
  name: REPO_REVIEW_NAME,
@@ -5638,7 +5690,7 @@ function buildReviewRequestAttachment(content, opts) {
5638
5690
  };
5639
5691
  }
5640
5692
  function readExistingReviewRequestFile(worktreePath) {
5641
- return readTextIfPresent(join12(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join12(worktreePath, REVIEW_REQUEST_PATH)) ?? readTextIfPresent(join12(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
5693
+ return readTextIfPresent(join13(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join13(worktreePath, REVIEW_REQUEST_PATH)) ?? readTextIfPresent(join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
5642
5694
  }
5643
5695
  async function requestReview(threadRef, send2) {
5644
5696
  const from = findThreadByRef(threadRef);
@@ -6126,6 +6178,9 @@ var Orchestrator = class {
6126
6178
  }
6127
6179
  return withThreadLock(thread.id, async () => {
6128
6180
  const current = this.requireThread(thread.id);
6181
+ if (current.status === "archived") {
6182
+ throw new Error(`Thread is archived: ${thread.id}`);
6183
+ }
6129
6184
  const queue = [...current.queue, prompt];
6130
6185
  this.haltDrain.delete(thread.id);
6131
6186
  const patch = { queue, status: "queued" };
@@ -6136,7 +6191,9 @@ var Orchestrator = class {
6136
6191
  updateThread(thread.id, patch);
6137
6192
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
6138
6193
  this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
6139
- void this.drainQueue(thread.id);
6194
+ if (thisProcessShouldDrainAgentQueues()) {
6195
+ void this.drainQueue(thread.id);
6196
+ }
6140
6197
  return this.requireThread(thread.id);
6141
6198
  });
6142
6199
  }
@@ -6189,10 +6246,10 @@ var Orchestrator = class {
6189
6246
  async sendQueuedMessageNow(threadRef, index) {
6190
6247
  const thread = this.requireThread(threadRef);
6191
6248
  const promoted = await withThreadLock(thread.id, async () => {
6192
- const current = this.requireThread(thread.id);
6193
- if (index < 0 || index >= current.queue.length) return false;
6194
- const item = current.queue[index];
6195
- const rest = current.queue.filter((_, i) => i !== index);
6249
+ const current2 = this.requireThread(thread.id);
6250
+ if (index < 0 || index >= current2.queue.length) return false;
6251
+ const item = current2.queue[index];
6252
+ const rest = current2.queue.filter((_, i) => i !== index);
6196
6253
  const queue = [item, ...rest];
6197
6254
  this.haltDrain.delete(thread.id);
6198
6255
  updateThread(thread.id, { queue });
@@ -6200,10 +6257,16 @@ var Orchestrator = class {
6200
6257
  return true;
6201
6258
  });
6202
6259
  if (!promoted) return this.requireThread(thread.id);
6260
+ const current = this.requireThread(thread.id);
6203
6261
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
6262
+ const livePid = current.agentPid;
6263
+ const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
6204
6264
  if (inFlight) {
6205
6265
  this.stop(thread.id, { clearQueue: false, continueQueue: true });
6206
6266
  } else {
6267
+ if (foreignLive) {
6268
+ this.stop(thread.id, { clearQueue: false, continueQueue: true });
6269
+ }
6207
6270
  void this.drainQueue(thread.id);
6208
6271
  }
6209
6272
  return this.requireThread(thread.id);
@@ -6638,6 +6701,13 @@ var Orchestrator = class {
6638
6701
  if (handle) handle.kill();
6639
6702
  const proc = this.processes.get(`${thread.id}:agent`);
6640
6703
  if (proc) proc.kill();
6704
+ const pid = thread.agentPid;
6705
+ if (typeof pid === "number" && pid > 0 && isPidAlive(pid)) {
6706
+ try {
6707
+ process.kill(pid, "SIGTERM");
6708
+ } catch {
6709
+ }
6710
+ }
6641
6711
  const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
6642
6712
  if (stopped.status === "stopped") {
6643
6713
  this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
@@ -7376,7 +7446,7 @@ var Orchestrator = class {
7376
7446
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7377
7447
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7378
7448
  try {
7379
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ERZC7ULY.js");
7449
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4ZY4QPWQ.js");
7380
7450
  await ensureWorkspace2(thread.repoPath);
7381
7451
  } catch {
7382
7452
  }
@@ -9623,14 +9693,14 @@ async function runCloudConnect(opts) {
9623
9693
  }
9624
9694
 
9625
9695
  // src/agents/user-mcp-config.ts
9626
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
9696
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
9627
9697
  import { homedir as homedir2 } from "os";
9628
- import { dirname as dirname4, join as join13 } from "path";
9698
+ import { dirname as dirname4, join as join14 } from "path";
9629
9699
  function userCursorMcpConfigPath() {
9630
- return join13(homedir2(), ".cursor", "mcp.json");
9700
+ return join14(homedir2(), ".cursor", "mcp.json");
9631
9701
  }
9632
9702
  function userClaudeMcpConfigPath() {
9633
- return join13(homedir2(), ".claude.json");
9703
+ return join14(homedir2(), ".claude.json");
9634
9704
  }
9635
9705
  function asObject(value) {
9636
9706
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
@@ -9659,14 +9729,14 @@ function writeMergedMcpServersJson(configPath, sideboard) {
9659
9729
  let existing = {};
9660
9730
  if (existsSync15(configPath)) {
9661
9731
  try {
9662
- existing = JSON.parse(readFileSync10(configPath, "utf8"));
9732
+ existing = JSON.parse(readFileSync11(configPath, "utf8"));
9663
9733
  } catch {
9664
9734
  existing = {};
9665
9735
  }
9666
9736
  }
9667
9737
  const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
9668
9738
  mkdirSync5(dirname4(configPath), { recursive: true });
9669
- writeFileSync4(configPath, `${JSON.stringify(next, null, 2)}
9739
+ writeFileSync5(configPath, `${JSON.stringify(next, null, 2)}
9670
9740
  `);
9671
9741
  }
9672
9742
  function launchFromResolved(server) {
@@ -10480,11 +10550,16 @@ function slackInboundSuperseded(opts) {
10480
10550
  }
10481
10551
  return opts.currentInboundGeneration() !== opts.inboundGeneration;
10482
10552
  }
10483
- function interruptSlackCoordinatorForInbound(msg, agent, log = () => void 0) {
10553
+ function slackCoordinatorGone(err) {
10554
+ const errMsg = err instanceof Error ? err.message : String(err);
10555
+ return /thread not found|thread is archived/i.test(errMsg);
10556
+ }
10557
+ function interruptSlackCoordinatorForInbound(msg, _agent, log = () => void 0) {
10484
10558
  const userId = msg.userId?.trim();
10485
10559
  if (!userId) return false;
10486
10560
  try {
10487
- const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
10561
+ const coordinator = findSlackCoordinator(msg.teamId, userId);
10562
+ if (!coordinator) return false;
10488
10563
  const fresh = readThread(coordinator.id) ?? coordinator;
10489
10564
  if (fresh.status !== "running" && fresh.status !== "queued") return false;
10490
10565
  getOrchestrator().stop(fresh.id, { clearQueue: true });
@@ -10650,15 +10725,16 @@ async function handleSlackInbound(msg, opts) {
10650
10725
  log(`skip superseded ${msg.kind} ${msg.ts}`);
10651
10726
  return;
10652
10727
  }
10653
- const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
10654
- let fresh = readThread(coordinator.id) ?? coordinator;
10655
10728
  if (isSlackStopCommand(msg.text)) {
10656
- try {
10657
- getOrchestrator().stop(fresh.id, { clearQueue: true });
10658
- log(`stop ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)}`);
10659
- } catch (err) {
10660
- const errMsg = err instanceof Error ? err.message : String(err);
10661
- log(`stop ${msg.ts}: ${errMsg}`);
10729
+ const live = findSlackCoordinator(msg.teamId, userId);
10730
+ if (live) {
10731
+ try {
10732
+ getOrchestrator().stop(live.id, { clearQueue: true });
10733
+ log(`stop ${msg.kind} ${msg.ts} \u2192 coordinator ${live.id.slice(0, 8)}`);
10734
+ } catch (err) {
10735
+ const errMsg = err instanceof Error ? err.message : String(err);
10736
+ log(`stop ${msg.ts}: ${errMsg}`);
10737
+ }
10662
10738
  }
10663
10739
  if (slackInboundSuperseded(opts)) {
10664
10740
  log(`skip superseded stop reply ${msg.ts}`);
@@ -10668,34 +10744,65 @@ async function handleSlackInbound(msg, opts) {
10668
10744
  log(`replied stopped ${msg.ts}`);
10669
10745
  return;
10670
10746
  }
10747
+ const opened = ensureSlackCoordinator(msg.teamId, userId, agent);
10748
+ let fresh = readThread(opened.id) ?? opened;
10671
10749
  if (fresh.status === "running" || fresh.status === "queued") {
10672
10750
  interruptSlackCoordinatorForInbound(msg, agent, log);
10673
- fresh = readThread(coordinator.id) ?? coordinator;
10751
+ fresh = readThread(fresh.id) ?? fresh;
10674
10752
  }
10675
10753
  log(
10676
10754
  `run ${msg.kind} ${msg.ts} user ${userId} \u2192 coordinator ${fresh.id.slice(0, 8)} (${inventory.length} workspace${inventory.length === 1 ? "" : "s"})`
10677
10755
  );
10678
10756
  const prompt = formatSlackInboundPrompt(msg);
10679
- setSlackReplyTarget({
10680
- threadId: fresh.id,
10681
- teamId: msg.teamId,
10682
- channelId: msg.channelId,
10683
- threadTs: slackReplyThreadTs(msg)
10684
- });
10685
10757
  const orch = getOrchestrator();
10758
+ const bindReplyTarget = (threadId) => {
10759
+ setSlackReplyTarget({
10760
+ threadId,
10761
+ teamId: msg.teamId,
10762
+ channelId: msg.channelId,
10763
+ threadTs: slackReplyThreadTs(msg)
10764
+ });
10765
+ };
10766
+ bindReplyTarget(fresh.id);
10767
+ const runTurn = async (threadId) => {
10768
+ await orch.send(threadId, prompt);
10769
+ await orch.waitForTurn(threadId, 14 * 60 * 1e3);
10770
+ };
10686
10771
  let reply;
10687
10772
  try {
10688
- await orch.send(fresh.id, prompt);
10689
- await orch.waitForTurn(fresh.id, 14 * 60 * 1e3);
10773
+ try {
10774
+ await runTurn(fresh.id);
10775
+ } catch (err) {
10776
+ if (!slackCoordinatorGone(err)) throw err;
10777
+ log(`coordinator ${fresh.id.slice(0, 8)} gone, opening a new chat`);
10778
+ fresh = ensureSlackCoordinator(msg.teamId, userId, agent, { forceNew: true });
10779
+ bindReplyTarget(fresh.id);
10780
+ await runTurn(fresh.id);
10781
+ }
10690
10782
  if (slackInboundSuperseded(opts)) {
10691
10783
  log(`turn finished ${msg.ts} (superseded)`);
10692
10784
  return;
10693
10785
  }
10694
- const after = readThread(fresh.id);
10695
- if (!after || after.status === "stopped" || after.status === "archived") {
10786
+ let after = readThread(fresh.id);
10787
+ if (after?.status === "stopped") {
10696
10788
  log(`turn finished ${msg.ts} (interrupted, skip post)`);
10697
10789
  return;
10698
10790
  }
10791
+ if (!after || after.status === "archived") {
10792
+ log(`coordinator ${fresh.id.slice(0, 8)} closed, opening a new chat`);
10793
+ fresh = ensureSlackCoordinator(msg.teamId, userId, agent, { forceNew: true });
10794
+ bindReplyTarget(fresh.id);
10795
+ await runTurn(fresh.id);
10796
+ if (slackInboundSuperseded(opts)) {
10797
+ log(`turn finished ${msg.ts} (superseded)`);
10798
+ return;
10799
+ }
10800
+ after = readThread(fresh.id);
10801
+ if (!after || after.status === "stopped" || after.status === "archived") {
10802
+ log(`turn finished ${msg.ts} (interrupted, skip post)`);
10803
+ return;
10804
+ }
10805
+ }
10699
10806
  reply = orch.getTurnResult(fresh.id).text.trim();
10700
10807
  } catch (err) {
10701
10808
  if (slackInboundSuperseded(opts)) {
@@ -10703,8 +10810,7 @@ async function handleSlackInbound(msg, opts) {
10703
10810
  return;
10704
10811
  }
10705
10812
  const errMsg = err instanceof Error ? err.message : String(err);
10706
- const gone = /thread not found/i.test(errMsg);
10707
- if (gone) {
10813
+ if (slackCoordinatorGone(err)) {
10708
10814
  log(`turn finished ${msg.ts} (thread gone, skip post)`);
10709
10815
  return;
10710
10816
  }
@@ -11447,6 +11553,7 @@ export {
11447
11553
  captureTurnBaseline,
11448
11554
  checkoutPrStackLayer,
11449
11555
  childEnvWithAppSettings,
11556
+ claimDesktopHost,
11450
11557
  claudeAdapter,
11451
11558
  claudeChromeEnabled,
11452
11559
  claudeUserSettingsPath,
@@ -11485,6 +11592,7 @@ export {
11485
11592
  decodeBrightsyTarget,
11486
11593
  deleteBranchOnPurgeEnabled,
11487
11594
  deleteThreadRecord,
11595
+ desktopHostPidPath,
11488
11596
  detectAgents,
11489
11597
  detectGhStack,
11490
11598
  detectLocalMergeConflicts,
@@ -11518,6 +11626,7 @@ export {
11518
11626
  findConventionSetup,
11519
11627
  findInvalidCacheControlTtlOrder,
11520
11628
  findOrphanWorktrees,
11629
+ findSlackCoordinator,
11521
11630
  findThreadByRef,
11522
11631
  findThreadForStackLayer,
11523
11632
  flattenTurnInput,
@@ -11609,6 +11718,7 @@ export {
11609
11718
  isConductorBundledCli,
11610
11719
  isCursorAutoModel,
11611
11720
  isDefaultishSourceRef,
11721
+ isDesktopHostAlive,
11612
11722
  isDirty,
11613
11723
  isGhRateLimitError,
11614
11724
  isGlobalRepoPath,
@@ -11630,6 +11740,7 @@ export {
11630
11740
  isSlackExternalReplyPrompt,
11631
11741
  isSlackOAuthCancelled,
11632
11742
  isThinkingEffort,
11743
+ isThisProcessDesktopHost,
11633
11744
  isThreadCaffeinated,
11634
11745
  isWorkspaceScratchPath,
11635
11746
  linearAuthorizationHeader,
@@ -11731,6 +11842,7 @@ export {
11731
11842
  refreshSlackReplyBadges,
11732
11843
  registerPackagedUserMcpClients,
11733
11844
  releaseCaffeinateHoldForThread,
11845
+ releaseDesktopHost,
11734
11846
  removeWorkspace,
11735
11847
  removeWorktree,
11736
11848
  repoSlug,
@@ -11831,6 +11943,7 @@ export {
11831
11943
  taskMessageText,
11832
11944
  thinkingEffortBars,
11833
11945
  thinkingEffortLabel,
11946
+ thisProcessShouldDrainAgentQueues,
11834
11947
  threadDisplayLabel,
11835
11948
  threadFilePath,
11836
11949
  threadLockPath,