@sideboard-ai/core 0.1.81 → 0.1.84

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 (35) hide show
  1. package/dist/agents/cursor-runner.cjs +106 -53
  2. package/dist/agents/cursor-runner.js +96 -55
  3. package/dist/{agents-UQ7D6U6L.js → agents-JFXM66QA.js} +7 -6
  4. package/dist/{agents-EBKJ55PN.js → agents-OPTHS4IX.js} +5 -5
  5. package/dist/{app-settings-6IOQYGBK.js → app-settings-K6RFCRF6.js} +2 -1
  6. package/dist/{app-settings-NILNWNNQ.js → app-settings-PEXK5VEX.js} +1 -1
  7. package/dist/chunk-5KLC2MWZ.js +40 -0
  8. package/dist/{chunk-XKNBSYA7.js → chunk-AY53MPDE.js} +39 -1
  9. package/dist/{chunk-4AVU4QXO.js → chunk-BBQ6HXKS.js} +67 -17
  10. package/dist/{chunk-SEOICVGB.js → chunk-CBJSPTBG.js} +30 -6
  11. package/dist/{chunk-FUUVPN4A.js → chunk-CKBIQ54F.js} +78 -49
  12. package/dist/{chunk-IFPN6TER.js → chunk-I3FRXL7J.js} +4 -1
  13. package/dist/{chunk-CQBQWX73.js → chunk-JPHG2KCC.js} +8 -3
  14. package/dist/{chunk-4HJK57XU.js → chunk-NW7MEJHO.js} +8 -3
  15. package/dist/{chunk-K553XK7M.js → chunk-ODZ2ZOA4.js} +2 -2
  16. package/dist/{chunk-6F2TQFJE.js → chunk-OIEFHOP7.js} +3 -3
  17. package/dist/{chunk-XNECBC6T.js → chunk-R753UFSX.js} +39 -11
  18. package/dist/{chunk-TDJ43HUD.js → chunk-UN3LVQB2.js} +2 -2
  19. package/dist/{chunk-P5VDPGAN.js → chunk-WJ5TINR6.js} +3 -3
  20. package/dist/{chunk-YVVXWWCC.js → chunk-XW47PL6A.js} +81 -49
  21. package/dist/{coordinator-prompt-NYNEFKIV.js → coordinator-prompt-DOIOSLUN.js} +3 -3
  22. package/dist/{coordinator-prompt-JQF72JKX.js → coordinator-prompt-RCTIIZ6C.js} +4 -3
  23. package/dist/{global-workspace-NUMUJRWG.js → global-workspace-33BIG7KK.js} +4 -4
  24. package/dist/{global-workspace-DZM4ZFGU.js → global-workspace-73OAKD3C.js} +5 -4
  25. package/dist/index.cjs +235 -88
  26. package/dist/index.d.cts +36 -9
  27. package/dist/index.d.ts +36 -9
  28. package/dist/index.js +69 -45
  29. package/dist/mcp/run-stdio.cjs +233 -88
  30. package/dist/mcp/run-stdio.js +60 -43
  31. package/dist/{workspaces-QLRCOXAY.js → workspaces-SRITISKA.js} +6 -5
  32. package/dist/{workspaces-SUU2GO3A.js → workspaces-WDLY34MX.js} +5 -5
  33. package/dist/{worktree-5VLLFI5W.js → worktree-SGZVAWOQ.js} +3 -2
  34. package/dist/{worktree-UNSOCYZU.js → worktree-TUAO74SI.js} +2 -2
  35. package/package.json +1 -1
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-VROPG6QF.js";
10
10
  import {
11
11
  isOrchestratorThread
12
- } from "./chunk-P5VDPGAN.js";
12
+ } from "./chunk-WJ5TINR6.js";
13
13
  import {
14
14
  enrichPathWithNpmGlobalBin,
15
15
  isConductorBundledCli,
@@ -19,10 +19,12 @@ import {
19
19
  } from "./chunk-AE5VBOFE.js";
20
20
  import {
21
21
  claudeChromeEnabled,
22
+ isStrippedElectronLaunch,
22
23
  loadAppSettings,
23
24
  resolveAgentExecutable,
24
- resolveClaudeExecutable
25
- } from "./chunk-XKNBSYA7.js";
25
+ resolveClaudeExecutable,
26
+ wrapElectronAsNodeLaunch
27
+ } from "./chunk-AY53MPDE.js";
26
28
  import {
27
29
  appDataDir
28
30
  } from "./chunk-7MV3RXSC.js";
@@ -152,20 +154,38 @@ function pushTurnStderr(tail, line, maxLines = 12) {
152
154
  tail.push(trimmed);
153
155
  while (tail.length > maxLines) tail.shift();
154
156
  }
157
+ function looksLikeMinifiedJsDump(line) {
158
+ if (line.length < 200) return false;
159
+ return /yield Promise\.all/.test(line) || /\(0,[A-Za-z$]\.\w+\)/.test(line) || /CURSOR_RIPGREP_PATH/.test(line);
160
+ }
161
+ function looksLikeNestedElectronCrash(line) {
162
+ return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
163
+ }
164
+ var NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
165
+ var MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
166
+ function clipStderr(text, maxChars) {
167
+ const trimmed = text.trim();
168
+ if (trimmed.length <= maxChars) return trimmed;
169
+ return trimmed.slice(0, maxChars);
170
+ }
155
171
  function summarizeTurnStderr(tail, maxChars = 500) {
156
172
  if (tail.length === 0) return "";
173
+ const cursorStartup = [...tail].reverse().find((line) => /cursor startup failed:/i.test(line));
174
+ if (cursorStartup) return clipStderr(cursorStartup, maxChars);
175
+ if (tail.some(looksLikeNestedElectronCrash)) return NESTED_ELECTRON_SUMMARY;
157
176
  const moduleMissing = [...tail].reverse().find((line) => /cannot find module/i.test(line));
158
- if (moduleMissing) {
159
- return moduleMissing.length <= maxChars ? moduleMissing : moduleMissing.slice(0, maxChars);
177
+ if (moduleMissing) return clipStderr(moduleMissing, maxChars);
178
+ const useful = tail.filter((line) => !looksLikeMinifiedJsDump(line));
179
+ if (useful.length === 0 && tail.some(looksLikeMinifiedJsDump)) {
180
+ return MINIFIED_DUMP_SUMMARY;
160
181
  }
161
- const joined = tail.slice(-6).join("\n").trim();
162
- if (joined.length <= maxChars) return joined;
163
- return joined.slice(joined.length - maxChars);
182
+ const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
183
+ return clipStderr(joined, maxChars);
164
184
  }
165
185
  function looksLikeInvalidAgentSession(text) {
166
186
  const lower = text.trim().toLowerCase();
167
187
  if (!lower) return false;
168
- return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
188
+ return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower);
169
189
  }
170
190
  function looksLikeAgentFailureMessage(text) {
171
191
  const lower = text.trim().toLowerCase();
@@ -205,6 +225,12 @@ function humanizeAgentFailDetail(detail) {
205
225
  if (/context.*(too long|exceed)|prompt is too long|conversation too long/.test(lower)) {
206
226
  return `${raw} \u2014 start a new chat or compact context, then retry.`;
207
227
  }
228
+ if (/hascustomhostobject|electroninitializeicuandstartnode|nested chromium/i.test(lower)) {
229
+ return `${raw} \u2014 retry the turn; if it keeps failing, pick another agent.`;
230
+ }
231
+ if (/corrupt local agent checkpoint|missing root blob|truncated crash dump/.test(lower)) {
232
+ return `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
233
+ }
208
234
  return raw;
209
235
  }
210
236
  function formatTurnExitError(exitCode, stderrSummary) {
@@ -588,6 +614,18 @@ function sideboardMcpProfile(env = process.env) {
588
614
  function isAsarPath(filePath) {
589
615
  return /\.asar([/\\]|$)/.test(filePath);
590
616
  }
617
+ function applyNodeLaunch(launch, args) {
618
+ if (!launch.env.ELECTRON_RUN_AS_NODE) {
619
+ return { file: launch.file, args, env: launch.env };
620
+ }
621
+ const wrapped = wrapElectronAsNodeLaunch(launch.file, args);
622
+ if (process.platform === "win32") {
623
+ return { file: wrapped.file, args: wrapped.args, env: launch.env };
624
+ }
625
+ const env = { ...launch.env };
626
+ delete env.ELECTRON_RUN_AS_NODE;
627
+ return { file: wrapped.file, args: wrapped.args, env };
628
+ }
591
629
  async function resolveNodeLaunch(scriptPath) {
592
630
  if (isAsarPath(scriptPath)) {
593
631
  return {
@@ -748,11 +786,11 @@ async function resolveSideboardMcpServer() {
748
786
  if (entry) {
749
787
  const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
750
788
  const scriptArgs = isCli ? [entry, "mcp"] : [entry];
751
- const launch = await resolveNodeLaunch(entry);
789
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
752
790
  return {
753
791
  name: "sideboard",
754
792
  command: launch.file,
755
- args: scriptArgs,
793
+ args: launch.args,
756
794
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
757
795
  };
758
796
  }
@@ -798,10 +836,19 @@ async function buildInjectedMcpServers(opts) {
798
836
  function toCursorMcpServers(servers) {
799
837
  const out = {};
800
838
  for (const s of servers) {
839
+ const env = s.env ? { ...s.env } : void 0;
840
+ if (env) delete env.ELECTRON_RUN_AS_NODE;
841
+ let command = s.command;
842
+ let args = s.args;
843
+ if (process.platform !== "win32" && !isStrippedElectronLaunch(command, args)) {
844
+ const wrapped = wrapElectronAsNodeLaunch(command, args ?? []);
845
+ command = wrapped.file;
846
+ args = wrapped.args;
847
+ }
801
848
  out[s.name] = {
802
- command: s.command,
803
- ...s.args ? { args: s.args } : {},
804
- ...s.env ? { env: s.env } : {}
849
+ command,
850
+ ...args && args.length > 0 ? { args } : {},
851
+ ...env && Object.keys(env).length > 0 ? { env } : {}
805
852
  };
806
853
  }
807
854
  return out;
@@ -1045,7 +1092,7 @@ var claudeAdapter = {
1045
1092
  );
1046
1093
  }
1047
1094
  const mode = permissionMode(thread);
1048
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-NUMUJRWG.js");
1095
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-33BIG7KK.js");
1049
1096
  const isOrchestrator = isOrchestratorThread2(thread);
1050
1097
  const injectedServers = await buildInjectedMcpServers({
1051
1098
  includeSideboard: true,
@@ -1867,10 +1914,13 @@ var cursorAdapter = {
1867
1914
  };
1868
1915
  const runner = cursorRunnerPath();
1869
1916
  const isTs = runner.endsWith(".ts");
1870
- const launch = await resolveNodeLaunch(runner);
1917
+ const launch = applyNodeLaunch(
1918
+ await resolveNodeLaunch(runner),
1919
+ isTs ? ["--import", "tsx", runner] : [runner]
1920
+ );
1871
1921
  return {
1872
1922
  file: launch.file,
1873
- args: isTs ? ["--import", "tsx", runner] : [runner],
1923
+ args: launch.args,
1874
1924
  cwd: thread.worktreePath,
1875
1925
  stdin: JSON.stringify(req),
1876
1926
  env: {
@@ -50,20 +50,38 @@ function pushTurnStderr(tail, line, maxLines = 12) {
50
50
  tail.push(trimmed);
51
51
  while (tail.length > maxLines) tail.shift();
52
52
  }
53
+ function looksLikeMinifiedJsDump(line) {
54
+ if (line.length < 200) return false;
55
+ return /yield Promise\.all/.test(line) || /\(0,[A-Za-z$]\.\w+\)/.test(line) || /CURSOR_RIPGREP_PATH/.test(line);
56
+ }
57
+ function looksLikeNestedElectronCrash(line) {
58
+ return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
59
+ }
60
+ var NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
61
+ var MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
62
+ function clipStderr(text, maxChars) {
63
+ const trimmed = text.trim();
64
+ if (trimmed.length <= maxChars) return trimmed;
65
+ return trimmed.slice(0, maxChars);
66
+ }
53
67
  function summarizeTurnStderr(tail, maxChars = 500) {
54
68
  if (tail.length === 0) return "";
69
+ const cursorStartup = [...tail].reverse().find((line) => /cursor startup failed:/i.test(line));
70
+ if (cursorStartup) return clipStderr(cursorStartup, maxChars);
71
+ if (tail.some(looksLikeNestedElectronCrash)) return NESTED_ELECTRON_SUMMARY;
55
72
  const moduleMissing = [...tail].reverse().find((line) => /cannot find module/i.test(line));
56
- if (moduleMissing) {
57
- return moduleMissing.length <= maxChars ? moduleMissing : moduleMissing.slice(0, maxChars);
73
+ if (moduleMissing) return clipStderr(moduleMissing, maxChars);
74
+ const useful = tail.filter((line) => !looksLikeMinifiedJsDump(line));
75
+ if (useful.length === 0 && tail.some(looksLikeMinifiedJsDump)) {
76
+ return MINIFIED_DUMP_SUMMARY;
58
77
  }
59
- const joined = tail.slice(-6).join("\n").trim();
60
- if (joined.length <= maxChars) return joined;
61
- return joined.slice(joined.length - maxChars);
78
+ const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
79
+ return clipStderr(joined, maxChars);
62
80
  }
63
81
  function looksLikeInvalidAgentSession(text) {
64
82
  const lower = text.trim().toLowerCase();
65
83
  if (!lower) return false;
66
- return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
84
+ return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower);
67
85
  }
68
86
  function looksLikeAgentFailureMessage(text) {
69
87
  const lower = text.trim().toLowerCase();
@@ -103,6 +121,12 @@ function humanizeAgentFailDetail(detail) {
103
121
  if (/context.*(too long|exceed)|prompt is too long|conversation too long/.test(lower)) {
104
122
  return `${raw} \u2014 start a new chat or compact context, then retry.`;
105
123
  }
124
+ if (/hascustomhostobject|electroninitializeicuandstartnode|nested chromium/i.test(lower)) {
125
+ return `${raw} \u2014 retry the turn; if it keeps failing, pick another agent.`;
126
+ }
127
+ if (/corrupt local agent checkpoint|missing root blob|truncated crash dump/.test(lower)) {
128
+ return `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
129
+ }
106
130
  return raw;
107
131
  }
108
132
  function formatTurnExitError(exitCode, stderrSummary) {
@@ -14,7 +14,7 @@ import {
14
14
  import {
15
15
  getGithubGitAuthMode,
16
16
  getGithubPat
17
- } from "./chunk-XKNBSYA7.js";
17
+ } from "./chunk-AY53MPDE.js";
18
18
  import {
19
19
  worktreesRoot
20
20
  } from "./chunk-7MV3RXSC.js";
@@ -884,6 +884,19 @@ function formatGhLandError(raw, opts) {
884
884
  }
885
885
  return detail || "Failed to create or update pull request";
886
886
  }
887
+ function isPrNotMergeableError(text) {
888
+ return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
889
+ text
890
+ );
891
+ }
892
+ function formatMergePrError(raw) {
893
+ const trimmed = raw.trim();
894
+ if (!trimmed) return "gh pr merge failed";
895
+ if (isPrNotMergeableError(trimmed)) {
896
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
897
+ }
898
+ return extractGhErrorDetail(trimmed) || trimmed;
899
+ }
887
900
 
888
901
  // src/git/git-auth-mode.ts
889
902
  var HTTPS_REWRITE = [
@@ -1566,6 +1579,41 @@ ${result.stderr}`;
1566
1579
  ].slice(0, 20) : [];
1567
1580
  return { conflicting, base: baseName, files };
1568
1581
  }
1582
+ async function probeLocalMergeGate(cwd, gate) {
1583
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
1584
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
1585
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1586
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
1587
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
1588
+ try {
1589
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
1590
+ if (local.conflicting) {
1591
+ return {
1592
+ gate: {
1593
+ mergeable: "CONFLICTING",
1594
+ mergeStateStatus: "DIRTY",
1595
+ reviewDecision: gate?.reviewDecision ?? null,
1596
+ baseRefName: local.base,
1597
+ url: gate?.url ?? null,
1598
+ isInMergeQueue: false
1599
+ },
1600
+ files: local.files
1601
+ };
1602
+ }
1603
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
1604
+ return {
1605
+ gate: {
1606
+ ...gate,
1607
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
1608
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
1609
+ },
1610
+ files: []
1611
+ };
1612
+ }
1613
+ } catch {
1614
+ }
1615
+ return { gate, files: [] };
1616
+ }
1569
1617
  async function getPrChecks(cwd, selector) {
1570
1618
  const slug = await resolveGithubRepoSlug(cwd);
1571
1619
  const args = [
@@ -1601,47 +1649,9 @@ async function getPrChecks(cwd, selector) {
1601
1649
  }
1602
1650
  }
1603
1651
  let gate = await fetchPrMergeGate(cwd, selector, slug);
1604
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
1605
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
1606
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1607
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
1608
- const needsLocalProbe = !alreadyConflicting && !inQueue;
1609
- if (needsLocalProbe) {
1610
- try {
1611
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
1612
- if (local.conflicting) {
1613
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
1614
- gate = {
1615
- mergeable: "CONFLICTING",
1616
- mergeStateStatus: "DIRTY",
1617
- reviewDecision: gate?.reviewDecision ?? null,
1618
- baseRefName: local.base,
1619
- url: gate?.url ?? null,
1620
- isInMergeQueue: false
1621
- };
1622
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
1623
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
1624
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
1625
- const gateRows2 = buildMergeGateChecks(gate, {
1626
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
1627
- }).map(
1628
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
1629
- ...row,
1630
- description: `${row.description ?? ""}${fileHint}`.trim()
1631
- } : row
1632
- );
1633
- return [...gateRows2, ...ciChecks];
1634
- }
1635
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
1636
- gate = {
1637
- ...gate,
1638
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
1639
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
1640
- };
1641
- }
1642
- } catch {
1643
- }
1644
- }
1652
+ const probed = await probeLocalMergeGate(cwd, gate);
1653
+ gate = probed.gate;
1654
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
1645
1655
  if (!gate) {
1646
1656
  return ciChecks;
1647
1657
  }
@@ -1651,7 +1661,12 @@ async function getPrChecks(cwd, selector) {
1651
1661
  const gateRows = buildMergeGateChecks(gate, {
1652
1662
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
1653
1663
  suppressGenericBlocked: ciFailed || hasReviewRow
1654
- });
1664
+ }).map(
1665
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
1666
+ ...row,
1667
+ description: `${row.description ?? ""}${fileHint}`.trim()
1668
+ } : row
1669
+ );
1655
1670
  return [...gateRows, ...ciChecks];
1656
1671
  }
1657
1672
  async function getPrMeta(cwd, selector) {
@@ -1661,7 +1676,7 @@ async function getPrMeta(cwd, selector) {
1661
1676
  "view",
1662
1677
  selector,
1663
1678
  "--json",
1664
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
1679
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
1665
1680
  ];
1666
1681
  if (slug) viewArgs.push("--repo", slug);
1667
1682
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -1671,7 +1686,7 @@ async function getPrMeta(cwd, selector) {
1671
1686
  "view",
1672
1687
  selector,
1673
1688
  "--json",
1674
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
1689
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
1675
1690
  ];
1676
1691
  if (slug) retry.push("--repo", slug);
1677
1692
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -1682,16 +1697,28 @@ async function getPrMeta(cwd, selector) {
1682
1697
  }
1683
1698
  try {
1684
1699
  const view = JSON.parse(stdout);
1700
+ const gate = {
1701
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
1702
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
1703
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1704
+ baseRefName: String(view.baseRefName ?? ""),
1705
+ url: String(view.url ?? ""),
1706
+ isInMergeQueue: parseInMergeQueue(view)
1707
+ };
1708
+ const probed = await probeLocalMergeGate(cwd, gate);
1709
+ const resolved = probed.gate ?? gate;
1685
1710
  return {
1686
1711
  number: Number(view.number),
1687
1712
  title: String(view.title ?? ""),
1688
1713
  url: String(view.url ?? ""),
1689
1714
  state: String(view.state ?? ""),
1690
1715
  isDraft: Boolean(view.isDraft),
1691
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1692
- baseRefName: String(view.baseRefName ?? ""),
1716
+ reviewDecision: resolved.reviewDecision,
1717
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
1693
1718
  headRefName: String(view.headRefName ?? ""),
1694
- isInMergeQueue: parseInMergeQueue(view)
1719
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
1720
+ mergeable: resolved.mergeable,
1721
+ mergeStateStatus: resolved.mergeStateStatus
1695
1722
  };
1696
1723
  } catch {
1697
1724
  return null;
@@ -2159,7 +2186,9 @@ async function mergePr(cwd, selector, opts) {
2159
2186
  if (slug) args.push("--repo", slug);
2160
2187
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
2161
2188
  if (exitCode !== 0) {
2162
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
2189
+ throw new Error(
2190
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
2191
+ );
2163
2192
  }
2164
2193
  const after = await gh(viewArgs, cwd, { reject: false });
2165
2194
  if (after.exitCode === 0 && after.stdout.trim()) {
@@ -1,3 +1,6 @@
1
+ import {
2
+ stripNestedElectronEnv
3
+ } from "./chunk-5KLC2MWZ.js";
1
4
  import {
2
5
  chmodOwnerOnly,
3
6
  writePrivateFile
@@ -1077,7 +1080,7 @@ function applyAppEnvironment(target = process.env, settings = loadAppSettings())
1077
1080
  }
1078
1081
  function childEnvWithAppSettings(extra) {
1079
1082
  const settings = loadAppSettings();
1080
- const env = { ...process.env };
1083
+ const env = stripNestedElectronEnv({ ...process.env });
1081
1084
  applyAppEnvironment(env, settings);
1082
1085
  if (extra) {
1083
1086
  for (const [k, v] of Object.entries(extra)) {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  resolveGithubRepoSlug
3
- } from "./chunk-YVVXWWCC.js";
3
+ } from "./chunk-XW47PL6A.js";
4
4
  import {
5
5
  resolveThreadDefaults
6
- } from "./chunk-IFPN6TER.js";
6
+ } from "./chunk-I3FRXL7J.js";
7
7
  import {
8
8
  globalAgentCwd,
9
9
  sideboardReposDir
@@ -51,6 +51,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
51
51
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
52
52
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
53
53
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
54
+ "- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
54
55
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
55
56
  "Workspaces:",
56
57
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -70,7 +71,11 @@ var COORDINATOR_TOOL_PLAYBOOK = [
70
71
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
71
72
  "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
72
73
  '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
73
- '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
74
+ '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
75
+ "Process guides:",
76
+ "- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
77
+ "- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
78
+ "- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
74
79
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
75
80
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
76
81
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
@@ -2,10 +2,10 @@
2
2
 
3
3
  import {
4
4
  resolveGithubRepoSlug
5
- } from "./chunk-FUUVPN4A.js";
5
+ } from "./chunk-CKBIQ54F.js";
6
6
  import {
7
7
  resolveThreadDefaults
8
- } from "./chunk-XKNBSYA7.js";
8
+ } from "./chunk-AY53MPDE.js";
9
9
  import {
10
10
  globalAgentCwd,
11
11
  sideboardReposDir
@@ -53,6 +53,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
53
53
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
54
54
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
55
55
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
56
+ "- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
56
57
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
57
58
  "Workspaces:",
58
59
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -72,7 +73,11 @@ var COORDINATOR_TOOL_PLAYBOOK = [
72
73
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
73
74
  "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
74
75
  '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
75
- '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
76
+ '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
77
+ "Process guides:",
78
+ "- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
79
+ "- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
80
+ "- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
76
81
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
77
82
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
78
83
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  isGlobalRepoPath
3
- } from "./chunk-6F2TQFJE.js";
3
+ } from "./chunk-OIEFHOP7.js";
4
4
  import {
5
5
  ensureGhPreferOrigin,
6
6
  resolveRepoRoot
7
- } from "./chunk-YVVXWWCC.js";
7
+ } from "./chunk-XW47PL6A.js";
8
8
  import {
9
9
  appDataDir
10
10
  } from "./chunk-M37RITA6.js";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  ensureGlobalCoordinatorCwd
3
- } from "./chunk-CQBQWX73.js";
3
+ } from "./chunk-JPHG2KCC.js";
4
4
  import {
5
5
  allocateTeamName,
6
6
  takenSlugsFromThread,
7
7
  teamSlugFromName
8
- } from "./chunk-YVVXWWCC.js";
8
+ } from "./chunk-XW47PL6A.js";
9
9
  import {
10
10
  resolveNewThreadOptions,
11
11
  resolveThreadDefaults
12
- } from "./chunk-IFPN6TER.js";
12
+ } from "./chunk-I3FRXL7J.js";
13
13
  import {
14
14
  createEmptyThread,
15
15
  listThreads,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isOrchestratorThread
3
- } from "./chunk-6F2TQFJE.js";
3
+ } from "./chunk-OIEFHOP7.js";
4
4
  import {
5
5
  applyConnectedTeamToCli,
6
6
  brightsyMcpServerName,
@@ -13,13 +13,17 @@ import {
13
13
  formatUnknownDetail,
14
14
  looksLikeAgentFailureMessage,
15
15
  parseCursorRunnerLine
16
- } from "./chunk-SEOICVGB.js";
16
+ } from "./chunk-CBJSPTBG.js";
17
17
  import {
18
18
  claudeChromeEnabled,
19
19
  loadAppSettings,
20
20
  resolveAgentExecutable,
21
21
  resolveClaudeExecutable
22
- } from "./chunk-IFPN6TER.js";
22
+ } from "./chunk-I3FRXL7J.js";
23
+ import {
24
+ isStrippedElectronLaunch,
25
+ wrapElectronAsNodeLaunch
26
+ } from "./chunk-5KLC2MWZ.js";
23
27
  import {
24
28
  appDataDir
25
29
  } from "./chunk-M37RITA6.js";
@@ -528,6 +532,18 @@ function sideboardMcpProfile(env = process.env) {
528
532
  function isAsarPath(filePath) {
529
533
  return /\.asar([/\\]|$)/.test(filePath);
530
534
  }
535
+ function applyNodeLaunch(launch, args) {
536
+ if (!launch.env.ELECTRON_RUN_AS_NODE) {
537
+ return { file: launch.file, args, env: launch.env };
538
+ }
539
+ const wrapped = wrapElectronAsNodeLaunch(launch.file, args);
540
+ if (process.platform === "win32") {
541
+ return { file: wrapped.file, args: wrapped.args, env: launch.env };
542
+ }
543
+ const env = { ...launch.env };
544
+ delete env.ELECTRON_RUN_AS_NODE;
545
+ return { file: wrapped.file, args: wrapped.args, env };
546
+ }
531
547
  async function resolveNodeLaunch(scriptPath) {
532
548
  if (isAsarPath(scriptPath)) {
533
549
  return {
@@ -692,11 +708,11 @@ async function resolveSideboardMcpServer() {
692
708
  if (entry) {
693
709
  const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
694
710
  const scriptArgs = isCli ? [entry, "mcp"] : [entry];
695
- const launch = await resolveNodeLaunch(entry);
711
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
696
712
  return {
697
713
  name: "sideboard",
698
714
  command: launch.file,
699
- args: scriptArgs,
715
+ args: launch.args,
700
716
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
701
717
  };
702
718
  }
@@ -742,10 +758,19 @@ async function buildInjectedMcpServers(opts) {
742
758
  function toCursorMcpServers(servers) {
743
759
  const out = {};
744
760
  for (const s of servers) {
761
+ const env = s.env ? { ...s.env } : void 0;
762
+ if (env) delete env.ELECTRON_RUN_AS_NODE;
763
+ let command = s.command;
764
+ let args = s.args;
765
+ if (process.platform !== "win32" && !isStrippedElectronLaunch(command, args)) {
766
+ const wrapped = wrapElectronAsNodeLaunch(command, args ?? []);
767
+ command = wrapped.file;
768
+ args = wrapped.args;
769
+ }
745
770
  out[s.name] = {
746
- command: s.command,
747
- ...s.args ? { args: s.args } : {},
748
- ...s.env ? { env: s.env } : {}
771
+ command,
772
+ ...args && args.length > 0 ? { args } : {},
773
+ ...env && Object.keys(env).length > 0 ? { env } : {}
749
774
  };
750
775
  }
751
776
  return out;
@@ -992,7 +1017,7 @@ var claudeAdapter = {
992
1017
  );
993
1018
  }
994
1019
  const mode = permissionMode(thread);
995
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-DZM4ZFGU.js");
1020
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-73OAKD3C.js");
996
1021
  const isOrchestrator = isOrchestratorThread2(thread);
997
1022
  const injectedServers = await buildInjectedMcpServers({
998
1023
  includeSideboard: true,
@@ -1643,10 +1668,13 @@ var cursorAdapter = {
1643
1668
  };
1644
1669
  const runner = cursorRunnerPath();
1645
1670
  const isTs = runner.endsWith(".ts");
1646
- const launch = await resolveNodeLaunch(runner);
1671
+ const launch = applyNodeLaunch(
1672
+ await resolveNodeLaunch(runner),
1673
+ isTs ? ["--import", "tsx", runner] : [runner]
1674
+ );
1647
1675
  return {
1648
1676
  file: launch.file,
1649
- args: isTs ? ["--import", "tsx", runner] : [runner],
1677
+ args: launch.args,
1650
1678
  cwd: thread.worktreePath,
1651
1679
  stdin: JSON.stringify(req),
1652
1680
  env: {
@@ -2,11 +2,11 @@
2
2
 
3
3
  import {
4
4
  isGlobalRepoPath
5
- } from "./chunk-P5VDPGAN.js";
5
+ } from "./chunk-WJ5TINR6.js";
6
6
  import {
7
7
  ensureGhPreferOrigin,
8
8
  resolveRepoRoot
9
- } from "./chunk-FUUVPN4A.js";
9
+ } from "./chunk-CKBIQ54F.js";
10
10
  import {
11
11
  appDataDir
12
12
  } from "./chunk-7MV3RXSC.js";