@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
@@ -883,6 +883,13 @@ function looksLikeMinifiedJsDump(line) {
883
883
  function looksLikeNestedElectronCrash(line) {
884
884
  return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
885
885
  }
886
+ function looksLikeHomebrewLibuvCrash(line) {
887
+ const uvRun = /uv_run/i.test(line);
888
+ const spin = /SpinEventLoopInternal/i.test(line);
889
+ const homebrewUv = /Cellar\/libuv|libuv\.\d\.dylib/i.test(line);
890
+ const homebrewNode = /Cellar\/node\//i.test(line);
891
+ return uvRun && (homebrewUv || spin || homebrewNode) || spin && (homebrewUv || homebrewNode);
892
+ }
886
893
  function clipStderr(text3, maxChars) {
887
894
  const trimmed = text3.trim();
888
895
  if (trimmed.length <= maxChars) return trimmed;
@@ -893,6 +900,7 @@ function summarizeTurnStderr(tail, maxChars = 500) {
893
900
  const cursorStartup = [...tail].reverse().find((line) => /cursor startup failed:/i.test(line));
894
901
  if (cursorStartup) return clipStderr(cursorStartup, maxChars);
895
902
  if (tail.some(looksLikeNestedElectronCrash)) return NESTED_ELECTRON_SUMMARY;
903
+ if (tail.some(looksLikeHomebrewLibuvCrash)) return HOMEBREW_LIBUV_SUMMARY;
896
904
  if (tail.some(
897
905
  (line) => /\[resource_exhausted\]|resource_exhausted/i.test(line) || /findFilesWithRipgrep/.test(line)
898
906
  )) {
@@ -915,6 +923,20 @@ function looksLikeInvalidAgentSession(text3) {
915
923
  if (!lower) return false;
916
924
  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);
917
925
  }
926
+ function looksLikeRetryableRunnerCrash(text3) {
927
+ if (looksLikeAgentFailureMessage(text3)) return false;
928
+ if (looksLikeInvalidAgentSession(text3)) return false;
929
+ const lower = text3.trim().toLowerCase();
930
+ if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
931
+ if (!lower) return true;
932
+ 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(
933
+ lower
934
+ );
935
+ }
936
+ function shouldRetryFailedAgentTurn(detail, opts) {
937
+ if (looksLikeInvalidAgentSession(detail) && opts.hasSession) return true;
938
+ return looksLikeRetryableRunnerCrash(detail);
939
+ }
918
940
  function looksLikeAgentFailureMessage(text3) {
919
941
  const lower = text3.trim().toLowerCase();
920
942
  if (!lower) return false;
@@ -959,11 +981,22 @@ function humanizeAgentFailDetail(detail) {
959
981
  if (/hascustomhostobject|electroninitializeicuandstartnode|nested chromium/i.test(lower)) {
960
982
  return `${raw} \u2014 retry the turn; if it keeps failing, pick another agent.`;
961
983
  }
984
+ if (/homebrew node \+ shared libuv|uv_run|spineventloopinternal/i.test(lower)) {
985
+ return /brew install node@22/i.test(raw) ? raw : `${raw} \u2014 install Node 22 LTS (\`brew install node@22\`) and retry.`;
986
+ }
962
987
  if (/corrupt local agent checkpoint|missing root blob|truncated crash dump/.test(lower)) {
963
988
  return `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
964
989
  }
965
990
  return raw;
966
991
  }
992
+ function turnFailChatText(opts) {
993
+ const chat = opts.assistantText.trim();
994
+ if (chat) return chat;
995
+ if (opts.exitCode === 0) return "";
996
+ const detail = opts.detail.trim();
997
+ if (detail) return humanizeAgentFailDetail(detail);
998
+ return formatTurnExitError(opts.exitCode ?? 1, "");
999
+ }
967
1000
  function formatTurnExitError(exitCode, stderrSummary) {
968
1001
  const code = exitCode ?? 1;
969
1002
  const raw = stderrSummary.trim();
@@ -977,12 +1010,13 @@ function formatTurnExitError(exitCode, stderrSummary) {
977
1010
  if (looksLikeAgentFailureMessage(raw)) return detail;
978
1011
  return `exit ${code}: ${detail}`;
979
1012
  }
980
- var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, MINIFIED_DUMP_SUMMARY;
1013
+ var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, HOMEBREW_LIBUV_SUMMARY, MINIFIED_DUMP_SUMMARY;
981
1014
  var init_error_detail = __esm({
982
1015
  "src/agents/error-detail.ts"() {
983
1016
  "use strict";
984
1017
  NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
985
1018
  NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
1019
+ HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 22 LTS (`brew install node@22`) and retry.";
986
1020
  MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
987
1021
  }
988
1022
  });
@@ -5007,7 +5041,7 @@ function coordinatorTurnReminder(opts) {
5007
5041
  goal ? `- Goal / title: ${goal}` : null,
5008
5042
  accountDefaultsPlaybookLine(),
5009
5043
  `- 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.`,
5010
- "- 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.",
5044
+ "- 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.",
5011
5045
  "- 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.",
5012
5046
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
5013
5047
  "- 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."
@@ -5130,7 +5164,7 @@ var init_coordinator_prompt = __esm({
5130
5164
  "- 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.",
5131
5165
  "- 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.",
5132
5166
  "- 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",
5133
- "- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
5167
+ "- 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.",
5134
5168
  "- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
5135
5169
  "- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
5136
5170
  "Setup / run:",
@@ -6046,6 +6080,55 @@ var init_profile = __esm({
6046
6080
  }
6047
6081
  });
6048
6082
 
6083
+ // src/agents/packaged-runtime.ts
6084
+ function electronResourcesPath() {
6085
+ const resources = process.resourcesPath;
6086
+ if (typeof resources !== "string" || !resources) return null;
6087
+ return resources;
6088
+ }
6089
+ function packagedCursorRuntimeDir() {
6090
+ const resources = electronResourcesPath();
6091
+ if (!resources) return null;
6092
+ const dir = (0, import_node_path19.join)(resources, "cursor-runtime");
6093
+ if (!(0, import_node_fs18.existsSync)((0, import_node_path19.join)(dir, "core-dist", "agents", "cursor-runner.js"))) return null;
6094
+ return dir;
6095
+ }
6096
+ function packagedCursorRunnerPath() {
6097
+ const dir = packagedCursorRuntimeDir();
6098
+ return dir ? (0, import_node_path19.join)(dir, "core-dist", "agents", "cursor-runner.js") : null;
6099
+ }
6100
+ function packagedMcpDir() {
6101
+ const resources = electronResourcesPath();
6102
+ if (!resources) return null;
6103
+ const dir = (0, import_node_path19.join)(resources, "sideboard-mcp");
6104
+ if (!(0, import_node_fs18.existsSync)((0, import_node_path19.join)(dir, "core-dist", "mcp", "run-stdio.js"))) return null;
6105
+ return dir;
6106
+ }
6107
+ function packagedMcpStdioPath() {
6108
+ const dir = packagedMcpDir();
6109
+ return dir ? (0, import_node_path19.join)(dir, "core-dist", "mcp", "run-stdio.js") : null;
6110
+ }
6111
+ function packagedBundledNodePath() {
6112
+ const resources = electronResourcesPath();
6113
+ if (!resources) return null;
6114
+ const bin = (0, import_node_path19.join)(resources, "node", "bin", "node");
6115
+ if (!(0, import_node_fs18.existsSync)(bin)) return null;
6116
+ return bin;
6117
+ }
6118
+ function packagedCursorRipgrepCandidate(platformPkg, binName) {
6119
+ const dir = packagedCursorRuntimeDir();
6120
+ if (!dir) return null;
6121
+ return (0, import_node_path19.join)(dir, "node_modules", platformPkg, "bin", binName);
6122
+ }
6123
+ var import_node_fs18, import_node_path19;
6124
+ var init_packaged_runtime = __esm({
6125
+ "src/agents/packaged-runtime.ts"() {
6126
+ "use strict";
6127
+ import_node_fs18 = require("fs");
6128
+ import_node_path19 = require("path");
6129
+ }
6130
+ });
6131
+
6049
6132
  // src/agents/node-launch.ts
6050
6133
  function isAsarPath(filePath) {
6051
6134
  if (/\.asar\.unpacked([/\\]|$)/.test(filePath)) return false;
@@ -6055,24 +6138,129 @@ function unpackedAsarPath(filePath) {
6055
6138
  if (!isAsarPath(filePath)) return null;
6056
6139
  const unpacked = filePath.replace(/\.asar(?=[/\\])/, ".asar.unpacked");
6057
6140
  if (unpacked === filePath) return null;
6058
- return (0, import_node_fs18.existsSync)(unpacked) ? unpacked : null;
6141
+ return (0, import_node_fs19.existsSync)(unpacked) ? unpacked : null;
6059
6142
  }
6060
6143
  function nodeReadableScriptPath(scriptPath) {
6061
6144
  return unpackedAsarPath(scriptPath) ?? scriptPath;
6062
6145
  }
6063
- async function findSystemNode() {
6064
- const whichNode = await run("which", ["node"], { reject: false });
6065
- const fromWhich = whichNode.exitCode === 0 && whichNode.stdout.trim() ? whichNode.stdout.trim() : "";
6066
- if (fromWhich && !isElectronLikeCommand(fromWhich)) return fromWhich;
6067
- const fallbacks = [
6068
- ...WELL_KNOWN_NODE_BINS,
6069
- (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".local/share/fnm/aliases/default/bin/node"),
6070
- (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".nvm/current/bin/node")
6146
+ function parseNodeMajor(version) {
6147
+ const match = /^v?(\d+)/.exec(version.trim());
6148
+ if (!match) return null;
6149
+ const major = Number(match[1]);
6150
+ return Number.isInteger(major) ? major : null;
6151
+ }
6152
+ function scoreNodeForAgentRuntime(candidate) {
6153
+ const major = parseNodeMajor(candidate.version);
6154
+ if (major == null) return Number.NEGATIVE_INFINITY;
6155
+ if (major < 20) return major - 100;
6156
+ const posix = candidate.path.replace(/\\/g, "/");
6157
+ let score = 0;
6158
+ if (major % 2 === 0) {
6159
+ score += 1e3 + major * 10;
6160
+ } else {
6161
+ score += major;
6162
+ }
6163
+ if (/\/Cellar\/node\/\d/.test(posix) && !/\/Cellar\/node@\d+/.test(posix)) {
6164
+ score -= 50;
6165
+ }
6166
+ return score;
6167
+ }
6168
+ function pickPreferredNode(candidates) {
6169
+ let best = null;
6170
+ let bestScore = Number.NEGATIVE_INFINITY;
6171
+ for (const candidate of candidates) {
6172
+ const score = scoreNodeForAgentRuntime(candidate);
6173
+ if (score > bestScore) {
6174
+ bestScore = score;
6175
+ best = candidate;
6176
+ }
6177
+ }
6178
+ return best;
6179
+ }
6180
+ function versionDirNodeBins(root, toBin) {
6181
+ if (!(0, import_node_fs19.existsSync)(root)) return [];
6182
+ try {
6183
+ return (0, import_node_fs19.readdirSync)(root).map(toBin);
6184
+ } catch {
6185
+ return [];
6186
+ }
6187
+ }
6188
+ function defaultNodeBinCandidates(home = (0, import_node_os7.homedir)()) {
6189
+ const kegs = ["/opt/homebrew", "/usr/local"].flatMap(
6190
+ (prefix) => PREFERRED_LTS_MAJORS.map((major) => (0, import_node_path20.join)(prefix, "opt", `node@${major}`, "bin", "node"))
6191
+ );
6192
+ return [
6193
+ ...kegs,
6194
+ "/opt/homebrew/bin/node",
6195
+ "/usr/local/bin/node",
6196
+ (0, import_node_path20.join)(home, ".local/share/fnm/aliases/default/bin/node"),
6197
+ (0, import_node_path20.join)(home, ".nvm/current/bin/node"),
6198
+ (0, import_node_path20.join)(home, ".volta/bin/node"),
6199
+ (0, import_node_path20.join)(home, ".asdf/shims/node"),
6200
+ (0, import_node_path20.join)(home, ".local/share/mise/shims/node"),
6201
+ ...versionDirNodeBins(
6202
+ (0, import_node_path20.join)(home, ".nvm", "versions", "node"),
6203
+ (name) => (0, import_node_path20.join)(home, ".nvm", "versions", "node", name, "bin", "node")
6204
+ ),
6205
+ ...versionDirNodeBins(
6206
+ (0, import_node_path20.join)(home, ".local/share/fnm", "node-versions"),
6207
+ (name) => (0, import_node_path20.join)(home, ".local/share/fnm", "node-versions", name, "installation", "bin", "node")
6208
+ ),
6209
+ ...versionDirNodeBins(
6210
+ (0, import_node_path20.join)(home, ".volta", "tools", "image", "node"),
6211
+ (name) => (0, import_node_path20.join)(home, ".volta", "tools", "image", "node", name, "bin", "node")
6212
+ )
6071
6213
  ];
6072
- for (const bin of fallbacks) {
6073
- if ((0, import_node_fs18.existsSync)(bin) && !isElectronLikeCommand(bin)) return bin;
6214
+ }
6215
+ function uniqueExistingNodeBins(paths) {
6216
+ const seen = /* @__PURE__ */ new Set();
6217
+ const out = [];
6218
+ for (const raw of paths) {
6219
+ const p = raw.trim();
6220
+ if (!p || !(0, import_node_fs19.existsSync)(p) || isElectronLikeCommand(p)) continue;
6221
+ let key = p;
6222
+ try {
6223
+ key = (0, import_node_fs19.realpathSync)(p);
6224
+ } catch {
6225
+ continue;
6226
+ }
6227
+ if (seen.has(key)) continue;
6228
+ seen.add(key);
6229
+ out.push(p);
6074
6230
  }
6075
- return null;
6231
+ return out;
6232
+ }
6233
+ async function whichAllNode() {
6234
+ if (process.platform === "win32") {
6235
+ const located = await run("where", ["node"], { reject: false, timeoutMs: 5e3 });
6236
+ if (located.exitCode !== 0) return [];
6237
+ return located.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
6238
+ }
6239
+ const all = await run("which", ["-a", "node"], { reject: false, timeoutMs: 5e3 });
6240
+ if (all.exitCode === 0 && all.stdout.trim()) {
6241
+ return all.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
6242
+ }
6243
+ const single = await run("which", ["node"], { reject: false, timeoutMs: 5e3 });
6244
+ if (single.exitCode !== 0 || !single.stdout.trim()) return [];
6245
+ return [single.stdout.trim()];
6246
+ }
6247
+ async function probeNodeVersion(bin) {
6248
+ const probed = await run(bin, ["-v"], { reject: false, timeoutMs: 4e3 });
6249
+ if (probed.exitCode !== 0) return null;
6250
+ const version = probed.stdout.trim().split(/\r?\n/).find(Boolean);
6251
+ return version || null;
6252
+ }
6253
+ async function findSystemNode() {
6254
+ const fromPath = await whichAllNode();
6255
+ const bins = uniqueExistingNodeBins([...fromPath, ...defaultNodeBinCandidates()]);
6256
+ if (bins.length === 0) return null;
6257
+ const probed = await Promise.all(
6258
+ bins.map(async (path) => {
6259
+ const version = await probeNodeVersion(path);
6260
+ return version ? { path, version } : null;
6261
+ })
6262
+ );
6263
+ return pickPreferredNode(probed.filter((c) => c != null));
6076
6264
  }
6077
6265
  function applyNodeLaunch(launch, args) {
6078
6266
  const readableArgs = args.map(nodeReadableScriptPath);
@@ -6090,9 +6278,13 @@ function applyNodeLaunch(launch, args) {
6090
6278
  async function resolveNodeLaunch(scriptPath) {
6091
6279
  const script = nodeReadableScriptPath(scriptPath);
6092
6280
  if (!isAsarPath(script)) {
6093
- const nodeBin = await findSystemNode();
6094
- if (nodeBin) {
6095
- return { file: nodeBin, env: {} };
6281
+ const bundled = packagedBundledNodePath();
6282
+ if (bundled && !isElectronLikeCommand(bundled)) {
6283
+ return { file: bundled, env: {} };
6284
+ }
6285
+ const node = await findSystemNode();
6286
+ if (node) {
6287
+ return { file: node.path, env: {}, nodeVersion: node.version };
6096
6288
  }
6097
6289
  }
6098
6290
  return {
@@ -6100,61 +6292,17 @@ async function resolveNodeLaunch(scriptPath) {
6100
6292
  env: { ELECTRON_RUN_AS_NODE: "1" }
6101
6293
  };
6102
6294
  }
6103
- var import_node_fs18, import_node_os7, import_node_path19, WELL_KNOWN_NODE_BINS;
6295
+ var import_node_fs19, import_node_os7, import_node_path20, PREFERRED_LTS_MAJORS;
6104
6296
  var init_node_launch = __esm({
6105
6297
  "src/agents/node-launch.ts"() {
6106
6298
  "use strict";
6107
- import_node_fs18 = require("fs");
6299
+ import_node_fs19 = require("fs");
6108
6300
  import_node_os7 = require("os");
6109
- import_node_path19 = require("path");
6301
+ import_node_path20 = require("path");
6110
6302
  init_nested_electron_env();
6111
6303
  init_run();
6112
- WELL_KNOWN_NODE_BINS = [
6113
- "/opt/homebrew/bin/node",
6114
- "/usr/local/bin/node"
6115
- ];
6116
- }
6117
- });
6118
-
6119
- // src/agents/packaged-runtime.ts
6120
- function electronResourcesPath() {
6121
- const resources = process.resourcesPath;
6122
- if (typeof resources !== "string" || !resources) return null;
6123
- return resources;
6124
- }
6125
- function packagedCursorRuntimeDir() {
6126
- const resources = electronResourcesPath();
6127
- if (!resources) return null;
6128
- const dir = (0, import_node_path20.join)(resources, "cursor-runtime");
6129
- if (!(0, import_node_fs19.existsSync)((0, import_node_path20.join)(dir, "core-dist", "agents", "cursor-runner.js"))) return null;
6130
- return dir;
6131
- }
6132
- function packagedCursorRunnerPath() {
6133
- const dir = packagedCursorRuntimeDir();
6134
- return dir ? (0, import_node_path20.join)(dir, "core-dist", "agents", "cursor-runner.js") : null;
6135
- }
6136
- function packagedMcpDir() {
6137
- const resources = electronResourcesPath();
6138
- if (!resources) return null;
6139
- const dir = (0, import_node_path20.join)(resources, "sideboard-mcp");
6140
- if (!(0, import_node_fs19.existsSync)((0, import_node_path20.join)(dir, "core-dist", "mcp", "run-stdio.js"))) return null;
6141
- return dir;
6142
- }
6143
- function packagedMcpStdioPath() {
6144
- const dir = packagedMcpDir();
6145
- return dir ? (0, import_node_path20.join)(dir, "core-dist", "mcp", "run-stdio.js") : null;
6146
- }
6147
- function packagedCursorRipgrepCandidate(platformPkg, binName) {
6148
- const dir = packagedCursorRuntimeDir();
6149
- if (!dir) return null;
6150
- return (0, import_node_path20.join)(dir, "node_modules", platformPkg, "bin", binName);
6151
- }
6152
- var import_node_fs19, import_node_path20;
6153
- var init_packaged_runtime = __esm({
6154
- "src/agents/packaged-runtime.ts"() {
6155
- "use strict";
6156
- import_node_fs19 = require("fs");
6157
- import_node_path20 = require("path");
6304
+ init_packaged_runtime();
6305
+ PREFERRED_LTS_MAJORS = [24, 22, 20];
6158
6306
  }
6159
6307
  });
6160
6308
 
@@ -14161,19 +14309,16 @@ var Orchestrator = class {
14161
14309
  }
14162
14310
  let lastStderr = summarizeTurnStderr(stderrTail);
14163
14311
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
14164
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "brightsy") {
14312
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
14313
+ hasSession: Boolean(this.requireThread(threadId).sessionId)
14314
+ })) {
14165
14315
  updateThread(threadId, { sessionId: null });
14166
- pushTurnStderr(
14167
- stderrTail,
14168
- "Agent session missing \u2014 starting a fresh session"
14169
- );
14316
+ const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
14317
+ pushTurnStderr(stderrTail, retryNote);
14170
14318
  this.emit({
14171
14319
  type: "turn_output",
14172
14320
  threadId,
14173
- event: {
14174
- type: "stderr",
14175
- data: "Agent session missing \u2014 starting a fresh session"
14176
- }
14321
+ event: { type: "stderr", data: retryNote }
14177
14322
  });
14178
14323
  const retryThread = this.requireThread(threadId);
14179
14324
  const prior = retryThread.messages.slice(0, -1);
@@ -14222,10 +14367,7 @@ var Orchestrator = class {
14222
14367
  lastStderr = summarizeTurnStderr(stderrTail);
14223
14368
  detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
14224
14369
  }
14225
- let chatText = assistantText;
14226
- if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
14227
- chatText = humanizeAgentFailDetail(detail);
14228
- }
14370
+ let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
14229
14371
  if (chatText || parts.length > 0) {
14230
14372
  appendMessage(threadId, {
14231
14373
  role: "agent",
@@ -14633,10 +14775,13 @@ var Orchestrator = class {
14633
14775
  getTurnResult(threadRef) {
14634
14776
  const thread = this.requireThread(threadRef);
14635
14777
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
14778
+ const lastError = thread.lastError ?? null;
14779
+ const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
14636
14780
  return {
14637
- text: lastAgent?.text ?? "",
14781
+ text: text3,
14638
14782
  status: thread.status,
14639
- sessionId: thread.sessionId
14783
+ sessionId: thread.sessionId,
14784
+ lastError
14640
14785
  };
14641
14786
  }
14642
14787
  assertNotGlobal(thread, action) {
@@ -16729,7 +16874,7 @@ async function startMcpServer() {
16729
16874
  );
16730
16875
  server.tool(
16731
16876
  "wait_for_turn",
16732
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
16877
+ "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.",
16733
16878
  {
16734
16879
  ref: import_zod3.z.string(),
16735
16880
  timeoutMs: import_zod3.z.number().optional()
@@ -16744,7 +16889,8 @@ async function startMcpServer() {
16744
16889
  text: JSON.stringify({
16745
16890
  id: thread.id,
16746
16891
  status: result.status,
16747
- text: result.text
16892
+ text: result.text,
16893
+ lastError: result.lastError
16748
16894
  })
16749
16895
  }
16750
16896
  ]
@@ -16753,7 +16899,7 @@ async function startMcpServer() {
16753
16899
  );
16754
16900
  server.tool(
16755
16901
  "get_turn_result",
16756
- "Final assistant message only (not full transcript)",
16902
+ "Final assistant message (and lastError when the turn failed). Not the full transcript.",
16757
16903
  { ref: import_zod3.z.string() },
16758
16904
  async ({ ref }) => {
16759
16905
  const result = orch.getTurnResult(ref);
@@ -6,7 +6,6 @@ import {
6
6
  fallbackTurnFailDetail,
7
7
  formatTurnExitError,
8
8
  getAdapter,
9
- humanizeAgentFailDetail,
10
9
  isSessionQuotaLimit,
11
10
  listModelsForAgent,
12
11
  looksLikeAgentFailureMessage,
@@ -15,16 +14,18 @@ import {
15
14
  parseSessionQuotaResetAt,
16
15
  pushTurnStderr,
17
16
  resolveQuotaFallbackAgent,
17
+ shouldRetryFailedAgentTurn,
18
18
  sideboardMcpProfile,
19
- summarizeTurnStderr
20
- } from "../chunk-FYS2BULQ.js";
19
+ summarizeTurnStderr,
20
+ turnFailChatText
21
+ } from "../chunk-EQPQTLW6.js";
21
22
  import "../chunk-DKHGWYWR.js";
22
23
  import {
23
24
  addWorkspace,
24
25
  ensureWorkspace,
25
26
  removeWorkspace,
26
27
  syncWorkspacesFromThreads
27
- } from "../chunk-OANJQTVG.js";
28
+ } from "../chunk-MRBKGFTL.js";
28
29
  import {
29
30
  extractPresentedPlan,
30
31
  readPlanFile,
@@ -43,14 +44,14 @@ import {
43
44
  isOrchestratorThread,
44
45
  isSlackCoordinatorThread,
45
46
  orchestratorSessionPoisonedByBuiltins
46
- } from "../chunk-HI2OTFFR.js";
47
+ } from "../chunk-WQTTUC4N.js";
47
48
  import {
48
49
  SLACK_REPLY_FORMATTING,
49
50
  coordinatorSystemPrompt,
50
51
  coordinatorTurnReminder,
51
52
  enrichWorkspacesWithGithub,
52
53
  ensureGlobalCoordinatorCwd
53
- } from "../chunk-XH2GS2LO.js";
54
+ } from "../chunk-IFHKPZHA.js";
54
55
  import {
55
56
  addPrStackLayer,
56
57
  allocateTeamName,
@@ -944,9 +945,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
944
945
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
945
946
  );
946
947
  }
947
- const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-WMF3BJP5.js");
948
+ const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-YZWYHLQY.js");
948
949
  if (isGlobalThread2(thread)) {
949
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-OQOOD5ET.js");
950
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-OOBSQCWQ.js");
950
951
  ensureGlobalCoordinatorCwd2(
951
952
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
952
953
  );
@@ -1887,7 +1888,7 @@ async function createThread(input, _onSetupLine) {
1887
1888
  return readThread(thread.id) ?? thread;
1888
1889
  }
1889
1890
  async function listLinearIssues(agent, repoPath) {
1890
- const { getAdapter: getAdapter2 } = await import("../agents-ESJKIQQA.js");
1891
+ const { getAdapter: getAdapter2 } = await import("../agents-CLP5BG4O.js");
1891
1892
  await requireAgent(agent, { requireLinear: true });
1892
1893
  const adapter = getAdapter2(agent);
1893
1894
  if (!adapter.listLinearIssues) {
@@ -2765,7 +2766,7 @@ async function adoptThread(input) {
2765
2766
  messages: input.messages ?? []
2766
2767
  });
2767
2768
  writeThread(thread);
2768
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-MZVQHRSJ.js");
2769
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-ENVUDK6C.js");
2769
2770
  await ensureWorkspace2(repoPath);
2770
2771
  return thread;
2771
2772
  }
@@ -5322,19 +5323,16 @@ var Orchestrator = class {
5322
5323
  }
5323
5324
  let lastStderr = summarizeTurnStderr(stderrTail);
5324
5325
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
5325
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "brightsy") {
5326
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
5327
+ hasSession: Boolean(this.requireThread(threadId).sessionId)
5328
+ })) {
5326
5329
  updateThread(threadId, { sessionId: null });
5327
- pushTurnStderr(
5328
- stderrTail,
5329
- "Agent session missing \u2014 starting a fresh session"
5330
- );
5330
+ const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
5331
+ pushTurnStderr(stderrTail, retryNote);
5331
5332
  this.emit({
5332
5333
  type: "turn_output",
5333
5334
  threadId,
5334
- event: {
5335
- type: "stderr",
5336
- data: "Agent session missing \u2014 starting a fresh session"
5337
- }
5335
+ event: { type: "stderr", data: retryNote }
5338
5336
  });
5339
5337
  const retryThread = this.requireThread(threadId);
5340
5338
  const prior = retryThread.messages.slice(0, -1);
@@ -5383,10 +5381,7 @@ var Orchestrator = class {
5383
5381
  lastStderr = summarizeTurnStderr(stderrTail);
5384
5382
  detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
5385
5383
  }
5386
- let chatText = assistantText;
5387
- if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
5388
- chatText = humanizeAgentFailDetail(detail);
5389
- }
5384
+ let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
5390
5385
  if (chatText || parts.length > 0) {
5391
5386
  appendMessage(threadId, {
5392
5387
  role: "agent",
@@ -5794,10 +5789,13 @@ var Orchestrator = class {
5794
5789
  getTurnResult(threadRef) {
5795
5790
  const thread = this.requireThread(threadRef);
5796
5791
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
5792
+ const lastError = thread.lastError ?? null;
5793
+ const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
5797
5794
  return {
5798
- text: lastAgent?.text ?? "",
5795
+ text: text3,
5799
5796
  status: thread.status,
5800
- sessionId: thread.sessionId
5797
+ sessionId: thread.sessionId,
5798
+ lastError
5801
5799
  };
5802
5800
  }
5803
5801
  assertNotGlobal(thread, action) {
@@ -6251,7 +6249,7 @@ var Orchestrator = class {
6251
6249
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
6252
6250
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
6253
6251
  try {
6254
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-MZVQHRSJ.js");
6252
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-ENVUDK6C.js");
6255
6253
  await ensureWorkspace2(thread.repoPath);
6256
6254
  } catch {
6257
6255
  }
@@ -7872,7 +7870,7 @@ async function startMcpServer() {
7872
7870
  );
7873
7871
  server.tool(
7874
7872
  "wait_for_turn",
7875
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
7873
+ "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git. Returns status, text, and lastError. If status is error, lastError/text is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success.",
7876
7874
  {
7877
7875
  ref: z3.string(),
7878
7876
  timeoutMs: z3.number().optional()
@@ -7887,7 +7885,8 @@ async function startMcpServer() {
7887
7885
  text: JSON.stringify({
7888
7886
  id: thread.id,
7889
7887
  status: result.status,
7890
- text: result.text
7888
+ text: result.text,
7889
+ lastError: result.lastError
7891
7890
  })
7892
7891
  }
7893
7892
  ]
@@ -7896,7 +7895,7 @@ async function startMcpServer() {
7896
7895
  );
7897
7896
  server.tool(
7898
7897
  "get_turn_result",
7899
- "Final assistant message only (not full transcript)",
7898
+ "Final assistant message (and lastError when the turn failed). Not the full transcript.",
7900
7899
  { ref: z3.string() },
7901
7900
  async ({ ref }) => {
7902
7901
  const result = orch.getTurnResult(ref);
@@ -6,9 +6,9 @@ import {
6
6
  listWorkspaces,
7
7
  removeWorkspace,
8
8
  syncWorkspacesFromThreads
9
- } from "./chunk-OANJQTVG.js";
10
- import "./chunk-HI2OTFFR.js";
11
- import "./chunk-XH2GS2LO.js";
9
+ } from "./chunk-MRBKGFTL.js";
10
+ import "./chunk-WQTTUC4N.js";
11
+ import "./chunk-IFHKPZHA.js";
12
12
  import "./chunk-R7BQBSDT.js";
13
13
  import "./chunk-B3SJXYIJ.js";
14
14
  import "./chunk-JOF3XIEM.js";
@@ -4,9 +4,9 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-UYQYK2RY.js";
8
- import "./chunk-FO67IJTY.js";
9
- import "./chunk-XUWDLRAE.js";
7
+ } from "./chunk-MHJV4WS3.js";
8
+ import "./chunk-GR4JKWR4.js";
9
+ import "./chunk-HYKZEP5A.js";
10
10
  import "./chunk-CIRXAYWS.js";
11
11
  import "./chunk-FKOIHGKV.js";
12
12
  import "./chunk-FT2SQOL4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.99",
3
+ "version": "0.1.100",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "worktree"
38
38
  ],
39
39
  "dependencies": {
40
- "@cursor/sdk": "^1.0.26",
40
+ "@cursor/sdk": "^1.0.28",
41
41
  "@modelcontextprotocol/sdk": "^1.12.1",
42
42
  "better-sqlite3": "^11.9.1",
43
43
  "chokidar": "^4.0.3",