@sideboard-ai/core 0.1.99 → 0.1.100

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 (26) hide show
  1. package/dist/agents/cursor-runner.cjs +22 -22
  2. package/dist/agents/cursor-runner.js +1 -1
  3. package/dist/{agents-ESJKIQQA.js → agents-CLP5BG4O.js} +3 -3
  4. package/dist/{agents-3MWWWSMF.js → agents-OXZDMPSE.js} +4 -4
  5. package/dist/{chunk-FYS2BULQ.js → chunk-EQPQTLW6.js} +209 -59
  6. package/dist/{chunk-FO67IJTY.js → chunk-GR4JKWR4.js} +1 -1
  7. package/dist/{chunk-GXSYI7FH.js → chunk-HUKCGRAT.js} +209 -59
  8. package/dist/{chunk-XUWDLRAE.js → chunk-HYKZEP5A.js} +2 -2
  9. package/dist/{chunk-XH2GS2LO.js → chunk-IFHKPZHA.js} +2 -2
  10. package/dist/{chunk-UYQYK2RY.js → chunk-MHJV4WS3.js} +1 -1
  11. package/dist/{chunk-OANJQTVG.js → chunk-MRBKGFTL.js} +1 -1
  12. package/dist/{chunk-MBP3XG57.js → chunk-OXC3MEFI.js} +3 -3
  13. package/dist/{chunk-HI2OTFFR.js → chunk-WQTTUC4N.js} +1 -1
  14. package/dist/{coordinator-prompt-AKEY4WSO.js → coordinator-prompt-HHRKQWCX.js} +1 -1
  15. package/dist/{coordinator-prompt-OQOOD5ET.js → coordinator-prompt-OOBSQCWQ.js} +1 -1
  16. package/dist/{global-workspace-RSQXRLT7.js → global-workspace-KYEBFWKB.js} +2 -2
  17. package/dist/{global-workspace-WMF3BJP5.js → global-workspace-YZWYHLQY.js} +2 -2
  18. package/dist/index.cjs +231 -85
  19. package/dist/index.d.cts +2 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.js +29 -30
  22. package/dist/mcp/run-stdio.cjs +231 -85
  23. package/dist/mcp/run-stdio.js +28 -29
  24. package/dist/{workspaces-MZVQHRSJ.js → workspaces-ENVUDK6C.js} +3 -3
  25. package/dist/{workspaces-4ZY4QPWQ.js → workspaces-NNPD7QVV.js} +3 -3
  26. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -5147,7 +5147,7 @@ function coordinatorTurnReminder(opts) {
5147
5147
  goal ? `- Goal / title: ${goal}` : null,
5148
5148
  accountDefaultsPlaybookLine(),
5149
5149
  `- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
5150
- "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
5150
+ "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. If status is error, lastError/text is the failure \u2014 adapt (switch agent, tell the user). Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
5151
5151
  "- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
5152
5152
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
5153
5153
  "- If they will wait on Slack or leave the Mac, call set_caffeinate enabled=true. When they say they are done / wrapping up / going to sleep, call set_caffeinate enabled=false. Closing this chat also turns it off."
@@ -5270,7 +5270,7 @@ var init_coordinator_prompt = __esm({
5270
5270
  "- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
5271
5271
  "- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
5272
5272
  "- send_to_thread \u2014 queue a prompt (start/continue a chat turn); pass force_stop: true to interrupt mid-turn / clear stale queued prompts before replacing with a new request",
5273
- "- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
5273
+ "- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success.",
5274
5274
  "- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
5275
5275
  "- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
5276
5276
  "Setup / run:",
@@ -6133,6 +6133,13 @@ function looksLikeMinifiedJsDump(line) {
6133
6133
  function looksLikeNestedElectronCrash(line) {
6134
6134
  return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
6135
6135
  }
6136
+ function looksLikeHomebrewLibuvCrash(line) {
6137
+ const uvRun = /uv_run/i.test(line);
6138
+ const spin = /SpinEventLoopInternal/i.test(line);
6139
+ const homebrewUv = /Cellar\/libuv|libuv\.\d\.dylib/i.test(line);
6140
+ const homebrewNode = /Cellar\/node\//i.test(line);
6141
+ return uvRun && (homebrewUv || spin || homebrewNode) || spin && (homebrewUv || homebrewNode);
6142
+ }
6136
6143
  function clipStderr(text3, maxChars) {
6137
6144
  const trimmed = text3.trim();
6138
6145
  if (trimmed.length <= maxChars) return trimmed;
@@ -6143,6 +6150,7 @@ function summarizeTurnStderr(tail, maxChars = 500) {
6143
6150
  const cursorStartup = [...tail].reverse().find((line) => /cursor startup failed:/i.test(line));
6144
6151
  if (cursorStartup) return clipStderr(cursorStartup, maxChars);
6145
6152
  if (tail.some(looksLikeNestedElectronCrash)) return NESTED_ELECTRON_SUMMARY;
6153
+ if (tail.some(looksLikeHomebrewLibuvCrash)) return HOMEBREW_LIBUV_SUMMARY;
6146
6154
  if (tail.some(
6147
6155
  (line) => /\[resource_exhausted\]|resource_exhausted/i.test(line) || /findFilesWithRipgrep/.test(line)
6148
6156
  )) {
@@ -6165,6 +6173,20 @@ function looksLikeInvalidAgentSession(text3) {
6165
6173
  if (!lower) return false;
6166
6174
  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);
6167
6175
  }
6176
+ function looksLikeRetryableRunnerCrash(text3) {
6177
+ if (looksLikeAgentFailureMessage(text3)) return false;
6178
+ if (looksLikeInvalidAgentSession(text3)) return false;
6179
+ const lower = text3.trim().toLowerCase();
6180
+ if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
6181
+ if (!lower) return true;
6182
+ return /uv_run|spineventloopinternal|libuv|homebrew node \+ shared libuv|hascustomhostobject|electroninitializeicuandstartnode|nested chromium|truncated crash dump|sig(?:segv|abrt|ill)|segmentation fault|illegal instruction|fatal error/.test(
6183
+ lower
6184
+ );
6185
+ }
6186
+ function shouldRetryFailedAgentTurn(detail, opts) {
6187
+ if (looksLikeInvalidAgentSession(detail) && opts.hasSession) return true;
6188
+ return looksLikeRetryableRunnerCrash(detail);
6189
+ }
6168
6190
  function looksLikeAgentFailureMessage(text3) {
6169
6191
  const lower = text3.trim().toLowerCase();
6170
6192
  if (!lower) return false;
@@ -6209,11 +6231,22 @@ function humanizeAgentFailDetail(detail) {
6209
6231
  if (/hascustomhostobject|electroninitializeicuandstartnode|nested chromium/i.test(lower)) {
6210
6232
  return `${raw} \u2014 retry the turn; if it keeps failing, pick another agent.`;
6211
6233
  }
6234
+ if (/homebrew node \+ shared libuv|uv_run|spineventloopinternal/i.test(lower)) {
6235
+ return /brew install node@22/i.test(raw) ? raw : `${raw} \u2014 install Node 22 LTS (\`brew install node@22\`) and retry.`;
6236
+ }
6212
6237
  if (/corrupt local agent checkpoint|missing root blob|truncated crash dump/.test(lower)) {
6213
6238
  return `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
6214
6239
  }
6215
6240
  return raw;
6216
6241
  }
6242
+ function turnFailChatText(opts) {
6243
+ const chat = opts.assistantText.trim();
6244
+ if (chat) return chat;
6245
+ if (opts.exitCode === 0) return "";
6246
+ const detail = opts.detail.trim();
6247
+ if (detail) return humanizeAgentFailDetail(detail);
6248
+ return formatTurnExitError(opts.exitCode ?? 1, "");
6249
+ }
6217
6250
  function formatTurnExitError(exitCode, stderrSummary) {
6218
6251
  const code = exitCode ?? 1;
6219
6252
  const raw = stderrSummary.trim();
@@ -6227,12 +6260,13 @@ function formatTurnExitError(exitCode, stderrSummary) {
6227
6260
  if (looksLikeAgentFailureMessage(raw)) return detail;
6228
6261
  return `exit ${code}: ${detail}`;
6229
6262
  }
6230
- var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, MINIFIED_DUMP_SUMMARY;
6263
+ var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, HOMEBREW_LIBUV_SUMMARY, MINIFIED_DUMP_SUMMARY;
6231
6264
  var init_error_detail = __esm({
6232
6265
  "src/agents/error-detail.ts"() {
6233
6266
  "use strict";
6234
6267
  NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
6235
6268
  NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
6269
+ HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 22 LTS (`brew install node@22`) and retry.";
6236
6270
  MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
6237
6271
  }
6238
6272
  });
@@ -6673,6 +6707,55 @@ var init_profile = __esm({
6673
6707
  }
6674
6708
  });
6675
6709
 
6710
+ // src/agents/packaged-runtime.ts
6711
+ function electronResourcesPath() {
6712
+ const resources = process.resourcesPath;
6713
+ if (typeof resources !== "string" || !resources) return null;
6714
+ return resources;
6715
+ }
6716
+ function packagedCursorRuntimeDir() {
6717
+ const resources = electronResourcesPath();
6718
+ if (!resources) return null;
6719
+ const dir = (0, import_node_path19.join)(resources, "cursor-runtime");
6720
+ if (!(0, import_node_fs19.existsSync)((0, import_node_path19.join)(dir, "core-dist", "agents", "cursor-runner.js"))) return null;
6721
+ return dir;
6722
+ }
6723
+ function packagedCursorRunnerPath() {
6724
+ const dir = packagedCursorRuntimeDir();
6725
+ return dir ? (0, import_node_path19.join)(dir, "core-dist", "agents", "cursor-runner.js") : null;
6726
+ }
6727
+ function packagedMcpDir() {
6728
+ const resources = electronResourcesPath();
6729
+ if (!resources) return null;
6730
+ const dir = (0, import_node_path19.join)(resources, "sideboard-mcp");
6731
+ if (!(0, import_node_fs19.existsSync)((0, import_node_path19.join)(dir, "core-dist", "mcp", "run-stdio.js"))) return null;
6732
+ return dir;
6733
+ }
6734
+ function packagedMcpStdioPath() {
6735
+ const dir = packagedMcpDir();
6736
+ return dir ? (0, import_node_path19.join)(dir, "core-dist", "mcp", "run-stdio.js") : null;
6737
+ }
6738
+ function packagedBundledNodePath() {
6739
+ const resources = electronResourcesPath();
6740
+ if (!resources) return null;
6741
+ const bin = (0, import_node_path19.join)(resources, "node", "bin", "node");
6742
+ if (!(0, import_node_fs19.existsSync)(bin)) return null;
6743
+ return bin;
6744
+ }
6745
+ function packagedCursorRipgrepCandidate(platformPkg, binName) {
6746
+ const dir = packagedCursorRuntimeDir();
6747
+ if (!dir) return null;
6748
+ return (0, import_node_path19.join)(dir, "node_modules", platformPkg, "bin", binName);
6749
+ }
6750
+ var import_node_fs19, import_node_path19;
6751
+ var init_packaged_runtime = __esm({
6752
+ "src/agents/packaged-runtime.ts"() {
6753
+ "use strict";
6754
+ import_node_fs19 = require("fs");
6755
+ import_node_path19 = require("path");
6756
+ }
6757
+ });
6758
+
6676
6759
  // src/agents/node-launch.ts
6677
6760
  function isAsarPath(filePath) {
6678
6761
  if (/\.asar\.unpacked([/\\]|$)/.test(filePath)) return false;
@@ -6682,24 +6765,129 @@ function unpackedAsarPath(filePath) {
6682
6765
  if (!isAsarPath(filePath)) return null;
6683
6766
  const unpacked = filePath.replace(/\.asar(?=[/\\])/, ".asar.unpacked");
6684
6767
  if (unpacked === filePath) return null;
6685
- return (0, import_node_fs19.existsSync)(unpacked) ? unpacked : null;
6768
+ return (0, import_node_fs20.existsSync)(unpacked) ? unpacked : null;
6686
6769
  }
6687
6770
  function nodeReadableScriptPath(scriptPath) {
6688
6771
  return unpackedAsarPath(scriptPath) ?? scriptPath;
6689
6772
  }
6690
- async function findSystemNode() {
6691
- const whichNode = await run("which", ["node"], { reject: false });
6692
- const fromWhich = whichNode.exitCode === 0 && whichNode.stdout.trim() ? whichNode.stdout.trim() : "";
6693
- if (fromWhich && !isElectronLikeCommand(fromWhich)) return fromWhich;
6694
- const fallbacks = [
6695
- ...WELL_KNOWN_NODE_BINS,
6696
- (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".local/share/fnm/aliases/default/bin/node"),
6697
- (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".nvm/current/bin/node")
6773
+ function parseNodeMajor(version) {
6774
+ const match = /^v?(\d+)/.exec(version.trim());
6775
+ if (!match) return null;
6776
+ const major = Number(match[1]);
6777
+ return Number.isInteger(major) ? major : null;
6778
+ }
6779
+ function scoreNodeForAgentRuntime(candidate) {
6780
+ const major = parseNodeMajor(candidate.version);
6781
+ if (major == null) return Number.NEGATIVE_INFINITY;
6782
+ if (major < 20) return major - 100;
6783
+ const posix = candidate.path.replace(/\\/g, "/");
6784
+ let score = 0;
6785
+ if (major % 2 === 0) {
6786
+ score += 1e3 + major * 10;
6787
+ } else {
6788
+ score += major;
6789
+ }
6790
+ if (/\/Cellar\/node\/\d/.test(posix) && !/\/Cellar\/node@\d+/.test(posix)) {
6791
+ score -= 50;
6792
+ }
6793
+ return score;
6794
+ }
6795
+ function pickPreferredNode(candidates) {
6796
+ let best = null;
6797
+ let bestScore = Number.NEGATIVE_INFINITY;
6798
+ for (const candidate of candidates) {
6799
+ const score = scoreNodeForAgentRuntime(candidate);
6800
+ if (score > bestScore) {
6801
+ bestScore = score;
6802
+ best = candidate;
6803
+ }
6804
+ }
6805
+ return best;
6806
+ }
6807
+ function versionDirNodeBins(root, toBin) {
6808
+ if (!(0, import_node_fs20.existsSync)(root)) return [];
6809
+ try {
6810
+ return (0, import_node_fs20.readdirSync)(root).map(toBin);
6811
+ } catch {
6812
+ return [];
6813
+ }
6814
+ }
6815
+ function defaultNodeBinCandidates(home = (0, import_node_os7.homedir)()) {
6816
+ const kegs = ["/opt/homebrew", "/usr/local"].flatMap(
6817
+ (prefix) => PREFERRED_LTS_MAJORS.map((major) => (0, import_node_path20.join)(prefix, "opt", `node@${major}`, "bin", "node"))
6818
+ );
6819
+ return [
6820
+ ...kegs,
6821
+ "/opt/homebrew/bin/node",
6822
+ "/usr/local/bin/node",
6823
+ (0, import_node_path20.join)(home, ".local/share/fnm/aliases/default/bin/node"),
6824
+ (0, import_node_path20.join)(home, ".nvm/current/bin/node"),
6825
+ (0, import_node_path20.join)(home, ".volta/bin/node"),
6826
+ (0, import_node_path20.join)(home, ".asdf/shims/node"),
6827
+ (0, import_node_path20.join)(home, ".local/share/mise/shims/node"),
6828
+ ...versionDirNodeBins(
6829
+ (0, import_node_path20.join)(home, ".nvm", "versions", "node"),
6830
+ (name) => (0, import_node_path20.join)(home, ".nvm", "versions", "node", name, "bin", "node")
6831
+ ),
6832
+ ...versionDirNodeBins(
6833
+ (0, import_node_path20.join)(home, ".local/share/fnm", "node-versions"),
6834
+ (name) => (0, import_node_path20.join)(home, ".local/share/fnm", "node-versions", name, "installation", "bin", "node")
6835
+ ),
6836
+ ...versionDirNodeBins(
6837
+ (0, import_node_path20.join)(home, ".volta", "tools", "image", "node"),
6838
+ (name) => (0, import_node_path20.join)(home, ".volta", "tools", "image", "node", name, "bin", "node")
6839
+ )
6698
6840
  ];
6699
- for (const bin of fallbacks) {
6700
- if ((0, import_node_fs19.existsSync)(bin) && !isElectronLikeCommand(bin)) return bin;
6841
+ }
6842
+ function uniqueExistingNodeBins(paths) {
6843
+ const seen = /* @__PURE__ */ new Set();
6844
+ const out = [];
6845
+ for (const raw of paths) {
6846
+ const p = raw.trim();
6847
+ if (!p || !(0, import_node_fs20.existsSync)(p) || isElectronLikeCommand(p)) continue;
6848
+ let key = p;
6849
+ try {
6850
+ key = (0, import_node_fs20.realpathSync)(p);
6851
+ } catch {
6852
+ continue;
6853
+ }
6854
+ if (seen.has(key)) continue;
6855
+ seen.add(key);
6856
+ out.push(p);
6701
6857
  }
6702
- return null;
6858
+ return out;
6859
+ }
6860
+ async function whichAllNode() {
6861
+ if (process.platform === "win32") {
6862
+ const located = await run("where", ["node"], { reject: false, timeoutMs: 5e3 });
6863
+ if (located.exitCode !== 0) return [];
6864
+ return located.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
6865
+ }
6866
+ const all = await run("which", ["-a", "node"], { reject: false, timeoutMs: 5e3 });
6867
+ if (all.exitCode === 0 && all.stdout.trim()) {
6868
+ return all.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
6869
+ }
6870
+ const single = await run("which", ["node"], { reject: false, timeoutMs: 5e3 });
6871
+ if (single.exitCode !== 0 || !single.stdout.trim()) return [];
6872
+ return [single.stdout.trim()];
6873
+ }
6874
+ async function probeNodeVersion(bin) {
6875
+ const probed = await run(bin, ["-v"], { reject: false, timeoutMs: 4e3 });
6876
+ if (probed.exitCode !== 0) return null;
6877
+ const version = probed.stdout.trim().split(/\r?\n/).find(Boolean);
6878
+ return version || null;
6879
+ }
6880
+ async function findSystemNode() {
6881
+ const fromPath = await whichAllNode();
6882
+ const bins = uniqueExistingNodeBins([...fromPath, ...defaultNodeBinCandidates()]);
6883
+ if (bins.length === 0) return null;
6884
+ const probed = await Promise.all(
6885
+ bins.map(async (path2) => {
6886
+ const version = await probeNodeVersion(path2);
6887
+ return version ? { path: path2, version } : null;
6888
+ })
6889
+ );
6890
+ return pickPreferredNode(probed.filter((c) => c != null));
6703
6891
  }
6704
6892
  function applyNodeLaunch(launch, args) {
6705
6893
  const readableArgs = args.map(nodeReadableScriptPath);
@@ -6717,9 +6905,13 @@ function applyNodeLaunch(launch, args) {
6717
6905
  async function resolveNodeLaunch(scriptPath) {
6718
6906
  const script = nodeReadableScriptPath(scriptPath);
6719
6907
  if (!isAsarPath(script)) {
6720
- const nodeBin = await findSystemNode();
6721
- if (nodeBin) {
6722
- return { file: nodeBin, env: {} };
6908
+ const bundled = packagedBundledNodePath();
6909
+ if (bundled && !isElectronLikeCommand(bundled)) {
6910
+ return { file: bundled, env: {} };
6911
+ }
6912
+ const node = await findSystemNode();
6913
+ if (node) {
6914
+ return { file: node.path, env: {}, nodeVersion: node.version };
6723
6915
  }
6724
6916
  }
6725
6917
  return {
@@ -6727,61 +6919,17 @@ async function resolveNodeLaunch(scriptPath) {
6727
6919
  env: { ELECTRON_RUN_AS_NODE: "1" }
6728
6920
  };
6729
6921
  }
6730
- var import_node_fs19, import_node_os7, import_node_path19, WELL_KNOWN_NODE_BINS;
6922
+ var import_node_fs20, import_node_os7, import_node_path20, PREFERRED_LTS_MAJORS;
6731
6923
  var init_node_launch = __esm({
6732
6924
  "src/agents/node-launch.ts"() {
6733
6925
  "use strict";
6734
- import_node_fs19 = require("fs");
6926
+ import_node_fs20 = require("fs");
6735
6927
  import_node_os7 = require("os");
6736
- import_node_path19 = require("path");
6928
+ import_node_path20 = require("path");
6737
6929
  init_nested_electron_env();
6738
6930
  init_run();
6739
- WELL_KNOWN_NODE_BINS = [
6740
- "/opt/homebrew/bin/node",
6741
- "/usr/local/bin/node"
6742
- ];
6743
- }
6744
- });
6745
-
6746
- // src/agents/packaged-runtime.ts
6747
- function electronResourcesPath() {
6748
- const resources = process.resourcesPath;
6749
- if (typeof resources !== "string" || !resources) return null;
6750
- return resources;
6751
- }
6752
- function packagedCursorRuntimeDir() {
6753
- const resources = electronResourcesPath();
6754
- if (!resources) return null;
6755
- const dir = (0, import_node_path20.join)(resources, "cursor-runtime");
6756
- if (!(0, import_node_fs20.existsSync)((0, import_node_path20.join)(dir, "core-dist", "agents", "cursor-runner.js"))) return null;
6757
- return dir;
6758
- }
6759
- function packagedCursorRunnerPath() {
6760
- const dir = packagedCursorRuntimeDir();
6761
- return dir ? (0, import_node_path20.join)(dir, "core-dist", "agents", "cursor-runner.js") : null;
6762
- }
6763
- function packagedMcpDir() {
6764
- const resources = electronResourcesPath();
6765
- if (!resources) return null;
6766
- const dir = (0, import_node_path20.join)(resources, "sideboard-mcp");
6767
- if (!(0, import_node_fs20.existsSync)((0, import_node_path20.join)(dir, "core-dist", "mcp", "run-stdio.js"))) return null;
6768
- return dir;
6769
- }
6770
- function packagedMcpStdioPath() {
6771
- const dir = packagedMcpDir();
6772
- return dir ? (0, import_node_path20.join)(dir, "core-dist", "mcp", "run-stdio.js") : null;
6773
- }
6774
- function packagedCursorRipgrepCandidate(platformPkg, binName) {
6775
- const dir = packagedCursorRuntimeDir();
6776
- if (!dir) return null;
6777
- return (0, import_node_path20.join)(dir, "node_modules", platformPkg, "bin", binName);
6778
- }
6779
- var import_node_fs20, import_node_path20;
6780
- var init_packaged_runtime = __esm({
6781
- "src/agents/packaged-runtime.ts"() {
6782
- "use strict";
6783
- import_node_fs20 = require("fs");
6784
- import_node_path20 = require("path");
6931
+ init_packaged_runtime();
6932
+ PREFERRED_LTS_MAJORS = [24, 22, 20];
6785
6933
  }
6786
6934
  });
6787
6935
 
@@ -16133,19 +16281,16 @@ var Orchestrator = class {
16133
16281
  }
16134
16282
  let lastStderr = summarizeTurnStderr(stderrTail);
16135
16283
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
16136
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "brightsy") {
16284
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
16285
+ hasSession: Boolean(this.requireThread(threadId).sessionId)
16286
+ })) {
16137
16287
  updateThread(threadId, { sessionId: null });
16138
- pushTurnStderr(
16139
- stderrTail,
16140
- "Agent session missing \u2014 starting a fresh session"
16141
- );
16288
+ const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
16289
+ pushTurnStderr(stderrTail, retryNote);
16142
16290
  this.emit({
16143
16291
  type: "turn_output",
16144
16292
  threadId,
16145
- event: {
16146
- type: "stderr",
16147
- data: "Agent session missing \u2014 starting a fresh session"
16148
- }
16293
+ event: { type: "stderr", data: retryNote }
16149
16294
  });
16150
16295
  const retryThread = this.requireThread(threadId);
16151
16296
  const prior = retryThread.messages.slice(0, -1);
@@ -16194,10 +16339,7 @@ var Orchestrator = class {
16194
16339
  lastStderr = summarizeTurnStderr(stderrTail);
16195
16340
  detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
16196
16341
  }
16197
- let chatText = assistantText;
16198
- if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
16199
- chatText = humanizeAgentFailDetail(detail);
16200
- }
16342
+ let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
16201
16343
  if (chatText || parts.length > 0) {
16202
16344
  appendMessage(threadId, {
16203
16345
  role: "agent",
@@ -16605,10 +16747,13 @@ var Orchestrator = class {
16605
16747
  getTurnResult(threadRef) {
16606
16748
  const thread = this.requireThread(threadRef);
16607
16749
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
16750
+ const lastError = thread.lastError ?? null;
16751
+ const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
16608
16752
  return {
16609
- text: lastAgent?.text ?? "",
16753
+ text: text3,
16610
16754
  status: thread.status,
16611
- sessionId: thread.sessionId
16755
+ sessionId: thread.sessionId,
16756
+ lastError
16612
16757
  };
16613
16758
  }
16614
16759
  assertNotGlobal(thread, action) {
@@ -18389,7 +18534,7 @@ async function startMcpServer() {
18389
18534
  );
18390
18535
  server.tool(
18391
18536
  "wait_for_turn",
18392
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
18537
+ "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.",
18393
18538
  {
18394
18539
  ref: import_zod3.z.string(),
18395
18540
  timeoutMs: import_zod3.z.number().optional()
@@ -18404,7 +18549,8 @@ async function startMcpServer() {
18404
18549
  text: JSON.stringify({
18405
18550
  id: thread.id,
18406
18551
  status: result.status,
18407
- text: result.text
18552
+ text: result.text,
18553
+ lastError: result.lastError
18408
18554
  })
18409
18555
  }
18410
18556
  ]
@@ -18413,7 +18559,7 @@ async function startMcpServer() {
18413
18559
  );
18414
18560
  server.tool(
18415
18561
  "get_turn_result",
18416
- "Final assistant message only (not full transcript)",
18562
+ "Final assistant message (and lastError when the turn failed). Not the full transcript.",
18417
18563
  { ref: import_zod3.z.string() },
18418
18564
  async ({ ref }) => {
18419
18565
  const result = orch.getTurnResult(ref);
package/dist/index.d.cts CHANGED
@@ -2953,6 +2953,7 @@ declare class Orchestrator {
2953
2953
  text: string;
2954
2954
  status: string;
2955
2955
  sessionId: string | null;
2956
+ lastError: string | null;
2956
2957
  };
2957
2958
  private assertNotGlobal;
2958
2959
  /** MCP set_caffeinate is a detached hold — closing the chat must not leave the Mac awake. */
@@ -4190,7 +4191,7 @@ declare function userClaudeMcpConfigPath(): string;
4190
4191
  /** Merge a `sideboard` stdio server into an mcp.json-shaped document. Does not clobber other servers. */
4191
4192
  declare function mergeSideboardIntoMcpServersJson(existing: unknown, sideboard: UserMcpStdioLaunch): Record<string, unknown>;
4192
4193
  /**
4193
- * Packaged Sideboard.app: upsert the same absolute `node` + extraResources MCP
4194
+ * Packaged Sideboard.app: upsert the same absolute bundled `node` + extraResources MCP
4194
4195
  * into Cursor IDE (always) and Claude Code (only if ~/.claude.json already exists).
4195
4196
  * Codex is documented only — do not rewrite ~/.codex/config.toml.
4196
4197
  */
package/dist/index.d.ts CHANGED
@@ -2953,6 +2953,7 @@ declare class Orchestrator {
2953
2953
  text: string;
2954
2954
  status: string;
2955
2955
  sessionId: string | null;
2956
+ lastError: string | null;
2956
2957
  };
2957
2958
  private assertNotGlobal;
2958
2959
  /** MCP set_caffeinate is a detached hold — closing the chat must not leave the Mac awake. */
@@ -4190,7 +4191,7 @@ declare function userClaudeMcpConfigPath(): string;
4190
4191
  /** Merge a `sideboard` stdio server into an mcp.json-shaped document. Does not clobber other servers. */
4191
4192
  declare function mergeSideboardIntoMcpServersJson(existing: unknown, sideboard: UserMcpStdioLaunch): Record<string, unknown>;
4192
4193
  /**
4193
- * Packaged Sideboard.app: upsert the same absolute `node` + extraResources MCP
4194
+ * Packaged Sideboard.app: upsert the same absolute bundled `node` + extraResources MCP
4194
4195
  * into Cursor IDE (always) and Claude Code (only if ~/.claude.json already exists).
4195
4196
  * Codex is documented only — do not rewrite ~/.codex/config.toml.
4196
4197
  */
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-UYQYK2RY.js";
7
+ } from "./chunk-MHJV4WS3.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-MBP3XG57.js";
68
+ } from "./chunk-OXC3MEFI.js";
69
69
  import {
70
70
  CLOUD_COORDINATOR_BUSY_REPLY,
71
71
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -93,7 +93,7 @@ import {
93
93
  parseForceStopMessage,
94
94
  slackCoordinatorSourceRef,
95
95
  takenTeamSlugsForOrchestration
96
- } from "./chunk-FO67IJTY.js";
96
+ } from "./chunk-GR4JKWR4.js";
97
97
  import {
98
98
  COORDINATOR_TOOL_PLAYBOOK,
99
99
  SLACK_REPLY_FORMATTING,
@@ -102,7 +102,7 @@ import {
102
102
  enrichWorkspacesWithGithub,
103
103
  ensureGlobalCoordinatorCwd,
104
104
  formatWorkspaceInventory
105
- } from "./chunk-XUWDLRAE.js";
105
+ } from "./chunk-HYKZEP5A.js";
106
106
  import {
107
107
  brightsyConfigPath,
108
108
  brightsyMcpServerName,
@@ -133,13 +133,14 @@ import {
133
133
  cursorSdkMessageToEvents,
134
134
  fallbackTurnFailDetail,
135
135
  formatTurnExitError,
136
- humanizeAgentFailDetail,
137
136
  looksLikeAgentFailureMessage,
138
137
  looksLikeInvalidAgentSession,
139
138
  parseCursorRunnerLine,
140
139
  pushTurnStderr,
141
- summarizeTurnStderr
142
- } from "./chunk-GXSYI7FH.js";
140
+ shouldRetryFailedAgentTurn,
141
+ summarizeTurnStderr,
142
+ turnFailChatText
143
+ } from "./chunk-HUKCGRAT.js";
143
144
  import {
144
145
  caffeinateHoldPath,
145
146
  getCaffeinateHold,
@@ -1385,9 +1386,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
1385
1386
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
1386
1387
  );
1387
1388
  }
1388
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-RSQXRLT7.js");
1389
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-KYEBFWKB.js");
1389
1390
  if (isGlobalThread2(thread)) {
1390
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-AKEY4WSO.js");
1391
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-HHRKQWCX.js");
1391
1392
  ensureGlobalCoordinatorCwd2(
1392
1393
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
1393
1394
  );
@@ -3880,7 +3881,7 @@ async function createThread(input, _onSetupLine) {
3880
3881
  return readThread(thread.id) ?? thread;
3881
3882
  }
3882
3883
  async function listLinearIssues(agent, repoPath) {
3883
- const { getAdapter: getAdapter2 } = await import("./agents-3MWWWSMF.js");
3884
+ const { getAdapter: getAdapter2 } = await import("./agents-OXZDMPSE.js");
3884
3885
  await requireAgent(agent, { requireLinear: true });
3885
3886
  const adapter = getAdapter2(agent);
3886
3887
  if (!adapter.listLinearIssues) {
@@ -4433,7 +4434,7 @@ async function adoptThread(input) {
4433
4434
  messages: input.messages ?? []
4434
4435
  });
4435
4436
  writeThread(thread);
4436
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4ZY4QPWQ.js");
4437
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NNPD7QVV.js");
4437
4438
  await ensureWorkspace2(repoPath);
4438
4439
  return thread;
4439
4440
  }
@@ -6517,19 +6518,16 @@ var Orchestrator = class {
6517
6518
  }
6518
6519
  let lastStderr = summarizeTurnStderr(stderrTail);
6519
6520
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
6520
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "brightsy") {
6521
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
6522
+ hasSession: Boolean(this.requireThread(threadId).sessionId)
6523
+ })) {
6521
6524
  updateThread(threadId, { sessionId: null });
6522
- pushTurnStderr(
6523
- stderrTail,
6524
- "Agent session missing \u2014 starting a fresh session"
6525
- );
6525
+ const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
6526
+ pushTurnStderr(stderrTail, retryNote);
6526
6527
  this.emit({
6527
6528
  type: "turn_output",
6528
6529
  threadId,
6529
- event: {
6530
- type: "stderr",
6531
- data: "Agent session missing \u2014 starting a fresh session"
6532
- }
6530
+ event: { type: "stderr", data: retryNote }
6533
6531
  });
6534
6532
  const retryThread = this.requireThread(threadId);
6535
6533
  const prior = retryThread.messages.slice(0, -1);
@@ -6578,10 +6576,7 @@ var Orchestrator = class {
6578
6576
  lastStderr = summarizeTurnStderr(stderrTail);
6579
6577
  detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
6580
6578
  }
6581
- let chatText = assistantText;
6582
- if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
6583
- chatText = humanizeAgentFailDetail(detail);
6584
- }
6579
+ let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
6585
6580
  if (chatText || parts.length > 0) {
6586
6581
  appendMessage(threadId, {
6587
6582
  role: "agent",
@@ -6989,10 +6984,13 @@ var Orchestrator = class {
6989
6984
  getTurnResult(threadRef) {
6990
6985
  const thread = this.requireThread(threadRef);
6991
6986
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
6987
+ const lastError = thread.lastError ?? null;
6988
+ const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
6992
6989
  return {
6993
- text: lastAgent?.text ?? "",
6990
+ text: text3,
6994
6991
  status: thread.status,
6995
- sessionId: thread.sessionId
6992
+ sessionId: thread.sessionId,
6993
+ lastError
6996
6994
  };
6997
6995
  }
6998
6996
  assertNotGlobal(thread, action) {
@@ -7446,7 +7444,7 @@ var Orchestrator = class {
7446
7444
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7447
7445
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7448
7446
  try {
7449
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4ZY4QPWQ.js");
7447
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NNPD7QVV.js");
7450
7448
  await ensureWorkspace2(thread.repoPath);
7451
7449
  } catch {
7452
7450
  }
@@ -8757,7 +8755,7 @@ async function startMcpServer() {
8757
8755
  );
8758
8756
  server.tool(
8759
8757
  "wait_for_turn",
8760
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
8758
+ "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.",
8761
8759
  {
8762
8760
  ref: z3.string(),
8763
8761
  timeoutMs: z3.number().optional()
@@ -8772,7 +8770,8 @@ async function startMcpServer() {
8772
8770
  text: JSON.stringify({
8773
8771
  id: thread.id,
8774
8772
  status: result.status,
8775
- text: result.text
8773
+ text: result.text,
8774
+ lastError: result.lastError
8776
8775
  })
8777
8776
  }
8778
8777
  ]
@@ -8781,7 +8780,7 @@ async function startMcpServer() {
8781
8780
  );
8782
8781
  server.tool(
8783
8782
  "get_turn_result",
8784
- "Final assistant message only (not full transcript)",
8783
+ "Final assistant message (and lastError when the turn failed). Not the full transcript.",
8785
8784
  { ref: z3.string() },
8786
8785
  async ({ ref }) => {
8787
8786
  const result = orch.getTurnResult(ref);