@sideboard-ai/core 0.1.98 → 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 +69 -24
  2. package/dist/agents/cursor-runner.js +48 -3
  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 +662 -440
  19. package/dist/index.d.cts +16 -2
  20. package/dist/index.d.ts +16 -2
  21. package/dist/index.js +194 -128
  22. package/dist/mcp/run-stdio.cjs +424 -227
  23. package/dist/mcp/run-stdio.js +141 -92
  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
@@ -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
  );
@@ -1745,6 +1746,41 @@ async function cloneRepoIntoSideboard(opts) {
1745
1746
  return { repoPath, workspace };
1746
1747
  }
1747
1748
 
1749
+ // src/store/desktop-host.ts
1750
+ import { readFileSync as readFileSync4, unlinkSync, writeFileSync } from "fs";
1751
+ import { join as join7 } from "path";
1752
+ function desktopHostPidPath() {
1753
+ return join7(appDataDir(), "desktop-host.pid");
1754
+ }
1755
+ function readDesktopHostPid() {
1756
+ try {
1757
+ const pid = Number.parseInt(readFileSync4(desktopHostPidPath(), "utf8").trim(), 10);
1758
+ if (!Number.isFinite(pid) || pid <= 0) return null;
1759
+ return pid;
1760
+ } catch {
1761
+ return null;
1762
+ }
1763
+ }
1764
+ function pidAlive(pid) {
1765
+ try {
1766
+ process.kill(pid, 0);
1767
+ return true;
1768
+ } catch {
1769
+ return false;
1770
+ }
1771
+ }
1772
+ function isDesktopHostAlive() {
1773
+ const pid = readDesktopHostPid();
1774
+ return pid != null && pidAlive(pid);
1775
+ }
1776
+ function isThisProcessDesktopHost() {
1777
+ return readDesktopHostPid() === process.pid && pidAlive(process.pid);
1778
+ }
1779
+ function thisProcessShouldDrainAgentQueues() {
1780
+ if (isThisProcessDesktopHost()) return true;
1781
+ return !isDesktopHostAlive();
1782
+ }
1783
+
1748
1784
  // src/threads/create.ts
1749
1785
  import { existsSync as existsSync6 } from "fs";
1750
1786
 
@@ -1852,7 +1888,7 @@ async function createThread(input, _onSetupLine) {
1852
1888
  return readThread(thread.id) ?? thread;
1853
1889
  }
1854
1890
  async function listLinearIssues(agent, repoPath) {
1855
- const { getAdapter: getAdapter2 } = await import("../agents-ESJKIQQA.js");
1891
+ const { getAdapter: getAdapter2 } = await import("../agents-CLP5BG4O.js");
1856
1892
  await requireAgent(agent, { requireLinear: true });
1857
1893
  const adapter = getAdapter2(agent);
1858
1894
  if (!adapter.listLinearIssues) {
@@ -2251,8 +2287,8 @@ function forkChatTab(input) {
2251
2287
 
2252
2288
  // src/review/request-review.ts
2253
2289
  import { randomUUID as randomUUID2 } from "crypto";
2254
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "fs";
2255
- import { dirname as dirname2, join as join7 } from "path";
2290
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
2291
+ import { dirname as dirname2, join as join8 } from "path";
2256
2292
 
2257
2293
  // src/review/review-request-template.ts
2258
2294
  var REVIEW_REQUEST_TEMPLATE = `# Review guidelines:
@@ -2399,20 +2435,20 @@ function shouldRefreshReviewRequestTemplate(content) {
2399
2435
  function readTextIfPresent(abs) {
2400
2436
  if (!existsSync7(abs)) return null;
2401
2437
  try {
2402
- const content = readFileSync4(abs, "utf8");
2438
+ const content = readFileSync5(abs, "utf8");
2403
2439
  return content.trim() ? content : null;
2404
2440
  } catch {
2405
2441
  return null;
2406
2442
  }
2407
2443
  }
2408
2444
  function ensureAttachmentsGitignore(worktreePath) {
2409
- const gitignoreAbs = join7(worktreePath, ATTACHMENTS_DIR, ".gitignore");
2445
+ const gitignoreAbs = join8(worktreePath, ATTACHMENTS_DIR, ".gitignore");
2410
2446
  if (existsSync7(gitignoreAbs)) return;
2411
2447
  mkdirSync2(dirname2(gitignoreAbs), { recursive: true });
2412
- writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
2448
+ writeFileSync2(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
2413
2449
  }
2414
2450
  function resolveReviewGuidelines(worktreePath) {
2415
- const repoAbs = join7(worktreePath, REPO_REVIEW_PATH);
2451
+ const repoAbs = join8(worktreePath, REPO_REVIEW_PATH);
2416
2452
  const repoContent = readTextIfPresent(repoAbs);
2417
2453
  if (repoContent) {
2418
2454
  return {
@@ -2422,7 +2458,7 @@ function resolveReviewGuidelines(worktreePath) {
2422
2458
  source: "repo"
2423
2459
  };
2424
2460
  }
2425
- const localAbs = join7(worktreePath, REVIEW_REQUEST_PATH);
2461
+ const localAbs = join8(worktreePath, REVIEW_REQUEST_PATH);
2426
2462
  const localContent = readTextIfPresent(localAbs);
2427
2463
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
2428
2464
  return {
@@ -2432,7 +2468,7 @@ function resolveReviewGuidelines(worktreePath) {
2432
2468
  source: "local"
2433
2469
  };
2434
2470
  }
2435
- const legacyAbs = join7(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
2471
+ const legacyAbs = join8(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
2436
2472
  const legacyContent = readTextIfPresent(legacyAbs);
2437
2473
  if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
2438
2474
  return {
@@ -2444,7 +2480,7 @@ function resolveReviewGuidelines(worktreePath) {
2444
2480
  }
2445
2481
  ensureAttachmentsGitignore(worktreePath);
2446
2482
  mkdirSync2(dirname2(localAbs), { recursive: true });
2447
- writeFileSync(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
2483
+ writeFileSync2(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
2448
2484
  return {
2449
2485
  path: REVIEW_REQUEST_PATH,
2450
2486
  name: REVIEW_REQUEST_NAME,
@@ -2643,20 +2679,20 @@ import {
2643
2679
  existsSync as existsSync8,
2644
2680
  mkdtempSync,
2645
2681
  readdirSync as readdirSync3,
2646
- readFileSync as readFileSync5,
2682
+ readFileSync as readFileSync6,
2647
2683
  rmSync
2648
2684
  } from "fs";
2649
2685
  import { tmpdir } from "os";
2650
- import { join as join8 } from "path";
2686
+ import { join as join9 } from "path";
2651
2687
  import { createRequire } from "module";
2652
- var CONDUCTOR_APP_SUPPORT = join8(
2688
+ var CONDUCTOR_APP_SUPPORT = join9(
2653
2689
  process.env.HOME ?? "",
2654
2690
  "Library",
2655
2691
  "Application Support",
2656
2692
  "com.conductor.app"
2657
2693
  );
2658
- var CONDUCTOR_DB = join8(CONDUCTOR_APP_SUPPORT, "conductor.db");
2659
- var CURSOR_SDK_STORE = join8(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
2694
+ var CONDUCTOR_DB = join9(CONDUCTOR_APP_SUPPORT, "conductor.db");
2695
+ var CURSOR_SDK_STORE = join9(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
2660
2696
  function openReadonlySqlite(file) {
2661
2697
  const req = createRequire(import.meta.url);
2662
2698
  const Database = req("better-sqlite3");
@@ -2683,11 +2719,11 @@ function resolveConductorCursorAgentId(workspacePath) {
2683
2719
  return null;
2684
2720
  }
2685
2721
  for (const hash of hashes) {
2686
- const agentsFile = join8(CURSOR_SDK_STORE, hash, "agents.ndjson");
2722
+ const agentsFile = join9(CURSOR_SDK_STORE, hash, "agents.ndjson");
2687
2723
  if (!existsSync8(agentsFile)) continue;
2688
2724
  let text3;
2689
2725
  try {
2690
- text3 = readFileSync5(agentsFile, "utf8");
2726
+ text3 = readFileSync6(agentsFile, "utf8");
2691
2727
  } catch {
2692
2728
  continue;
2693
2729
  }
@@ -2730,7 +2766,7 @@ async function adoptThread(input) {
2730
2766
  messages: input.messages ?? []
2731
2767
  });
2732
2768
  writeThread(thread);
2733
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-MZVQHRSJ.js");
2769
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-ENVUDK6C.js");
2734
2770
  await ensureWorkspace2(repoPath);
2735
2771
  return thread;
2736
2772
  }
@@ -2738,8 +2774,8 @@ function listConductorWorkspaces() {
2738
2774
  if (!existsSync8(CONDUCTOR_DB)) {
2739
2775
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
2740
2776
  }
2741
- const tmp = mkdtempSync(join8(tmpdir(), "sideboard-conductor-"));
2742
- const snapshot = join8(tmp, "conductor.db");
2777
+ const tmp = mkdtempSync(join9(tmpdir(), "sideboard-conductor-"));
2778
+ const snapshot = join9(tmp, "conductor.db");
2743
2779
  try {
2744
2780
  copyFileSync2(CONDUCTOR_DB, snapshot);
2745
2781
  for (const suffix of ["-wal", "-shm"]) {
@@ -2829,8 +2865,8 @@ function importConductorWorkspace(workspaceId) {
2829
2865
  if (!existsSync8(CONDUCTOR_DB)) {
2830
2866
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
2831
2867
  }
2832
- const tmp = mkdtempSync(join8(tmpdir(), "sideboard-conductor-"));
2833
- const snapshot = join8(tmp, "conductor.db");
2868
+ const tmp = mkdtempSync(join9(tmpdir(), "sideboard-conductor-"));
2869
+ const snapshot = join9(tmp, "conductor.db");
2834
2870
  try {
2835
2871
  copyFileSync2(CONDUCTOR_DB, snapshot);
2836
2872
  for (const suffix of ["-wal", "-shm"]) {
@@ -3210,8 +3246,8 @@ async function createPrStack(input, onSetupLine) {
3210
3246
  }
3211
3247
 
3212
3248
  // src/diff/diff.ts
3213
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
3214
- import { dirname as dirname3, join as join9 } from "path";
3249
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync7, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
3250
+ import { dirname as dirname3, join as join10 } from "path";
3215
3251
  async function inspectGitWorktree(worktreePath) {
3216
3252
  if (!worktreePath || !existsSync10(worktreePath)) return "missing_worktree";
3217
3253
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
@@ -3357,11 +3393,11 @@ new file mode 100644
3357
3393
  };
3358
3394
  }
3359
3395
  async function untrackedPatch(worktreePath, path, maxHunk) {
3360
- const abs = join9(worktreePath, path);
3396
+ const abs = join10(worktreePath, path);
3361
3397
  try {
3362
3398
  const st = statSync2(abs);
3363
3399
  if (st.isFile() && st.size > maxHunk) {
3364
- const buf = readFileSync6(abs).subarray(0, maxHunk);
3400
+ const buf = readFileSync7(abs).subarray(0, maxHunk);
3365
3401
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
3366
3402
  }
3367
3403
  } catch {
@@ -3862,7 +3898,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
3862
3898
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
3863
3899
  assertSafeRelativePath(relativePath);
3864
3900
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
3865
- const abs = join9(worktreePath, relativePath);
3901
+ const abs = join10(worktreePath, relativePath);
3866
3902
  const st = statSync2(abs);
3867
3903
  if (!st.isFile()) {
3868
3904
  throw new Error(`Not a file: ${relativePath}`);
@@ -3872,7 +3908,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
3872
3908
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
3873
3909
  );
3874
3910
  }
3875
- const buf = readFileSync6(abs);
3911
+ const buf = readFileSync7(abs);
3876
3912
  return {
3877
3913
  path: relativePath,
3878
3914
  contentBase64: buf.toString("base64"),
@@ -3882,12 +3918,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
3882
3918
  function readWorktreeFile(worktreePath, relativePath, opts) {
3883
3919
  assertSafeRelativePath(relativePath);
3884
3920
  const maxBytes = opts?.maxBytes ?? 2e5;
3885
- const abs = join9(worktreePath, relativePath);
3921
+ const abs = join10(worktreePath, relativePath);
3886
3922
  const st = statSync2(abs);
3887
3923
  if (!st.isFile()) {
3888
3924
  throw new Error(`Not a file: ${relativePath}`);
3889
3925
  }
3890
- const buf = readFileSync6(abs);
3926
+ const buf = readFileSync7(abs);
3891
3927
  if (isImageRelativePath(relativePath)) {
3892
3928
  const maxImageBytes = Math.max(maxBytes, 15e6);
3893
3929
  const truncated2 = buf.length > maxImageBytes;
@@ -3930,9 +3966,9 @@ function assertSafeRelativePath(relativePath) {
3930
3966
  }
3931
3967
  function writeWorktreeFile(worktreePath, relativePath, content) {
3932
3968
  assertSafeRelativePath(relativePath);
3933
- const abs = join9(worktreePath, relativePath);
3969
+ const abs = join10(worktreePath, relativePath);
3934
3970
  mkdirSync3(dirname3(abs), { recursive: true });
3935
- writeFileSync2(abs, content, "utf8");
3971
+ writeFileSync3(abs, content, "utf8");
3936
3972
  return { path: relativePath };
3937
3973
  }
3938
3974
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -4035,9 +4071,9 @@ async function confirmLand(thread, opts) {
4035
4071
  }
4036
4072
 
4037
4073
  // src/skills/discover.ts
4038
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
4074
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
4039
4075
  import { homedir } from "os";
4040
- import { join as join10 } from "path";
4076
+ import { join as join11 } from "path";
4041
4077
  function toCommand(name) {
4042
4078
  return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4043
4079
  }
@@ -4069,7 +4105,7 @@ function parseFrontmatter(content) {
4069
4105
  }
4070
4106
  function readSkill(skillMd, source) {
4071
4107
  try {
4072
- const content = readFileSync7(skillMd, "utf8");
4108
+ const content = readFileSync8(skillMd, "utf8");
4073
4109
  const { name: fmName, description } = parseFrontmatter(content);
4074
4110
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
4075
4111
  const name = fmName || dirName;
@@ -4097,7 +4133,7 @@ function scanSkillsDir(dir, source, out) {
4097
4133
  }
4098
4134
  for (const entry of entries) {
4099
4135
  if (entry.startsWith(".")) continue;
4100
- const skillMd = join10(dir, entry, "SKILL.md");
4136
+ const skillMd = join11(dir, entry, "SKILL.md");
4101
4137
  if (!existsSync11(skillMd)) continue;
4102
4138
  try {
4103
4139
  if (!statSync3(skillMd).isFile()) continue;
@@ -4119,12 +4155,12 @@ function scanClaudePluginSkills(pluginsRoot, out) {
4119
4155
  return;
4120
4156
  }
4121
4157
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
4122
- const skill = readSkill(join10(dir, "SKILL.md"), "cli");
4158
+ const skill = readSkill(join11(dir, "SKILL.md"), "cli");
4123
4159
  if (skill) out.push(skill);
4124
4160
  }
4125
4161
  for (const entry of entries) {
4126
4162
  if (entry === "node_modules" || entry === ".git") continue;
4127
- const full = join10(dir, entry);
4163
+ const full = join11(dir, entry);
4128
4164
  try {
4129
4165
  if (!statSync3(full).isDirectory()) continue;
4130
4166
  } catch {
@@ -4144,17 +4180,17 @@ function discoverSkills(worktreePath) {
4144
4180
  const home = homedir();
4145
4181
  const collected = [];
4146
4182
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
4147
- scanSkillsDir(join10(worktreePath, rel), "workspace", collected);
4183
+ scanSkillsDir(join11(worktreePath, rel), "workspace", collected);
4148
4184
  }
4149
4185
  for (const abs of [
4150
- join10(home, ".claude/skills"),
4151
- join10(home, ".cursor/skills"),
4152
- join10(home, ".sideboard/skills"),
4153
- join10(home, ".brightsy/skills")
4186
+ join11(home, ".claude/skills"),
4187
+ join11(home, ".cursor/skills"),
4188
+ join11(home, ".sideboard/skills"),
4189
+ join11(home, ".brightsy/skills")
4154
4190
  ]) {
4155
4191
  scanSkillsDir(abs, "user", collected);
4156
4192
  }
4157
- scanClaudePluginSkills(join10(home, ".claude/plugins"), collected);
4193
+ scanClaudePluginSkills(join11(home, ".claude/plugins"), collected);
4158
4194
  const rank = { workspace: 0, user: 1, cli: 2 };
4159
4195
  const byCommand = /* @__PURE__ */ new Map();
4160
4196
  for (const skill of collected) {
@@ -4166,7 +4202,7 @@ function discoverSkills(worktreePath) {
4166
4202
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
4167
4203
  }
4168
4204
  function readSkillBody(skillPath, maxChars = 12e3) {
4169
- const raw = readFileSync7(skillPath, "utf8");
4205
+ const raw = readFileSync8(skillPath, "utf8");
4170
4206
  if (raw.startsWith("---")) {
4171
4207
  const end = raw.indexOf("\n---", 3);
4172
4208
  if (end >= 0) {
@@ -4259,8 +4295,8 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
4259
4295
  }
4260
4296
 
4261
4297
  // src/composer/stage-files.ts
4262
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
4263
- import { basename as basename3, extname, join as join11 } from "path";
4298
+ import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
4299
+ import { basename as basename3, extname, join as join12 } from "path";
4264
4300
  import { randomUUID as randomUUID4 } from "crypto";
4265
4301
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
4266
4302
  "png",
@@ -4295,22 +4331,22 @@ function imageMimeType(filePath) {
4295
4331
  return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
4296
4332
  }
4297
4333
  function ensureAttachmentsDir(worktreePath) {
4298
- const dir = join11(worktreePath, ATTACHMENTS_DIR);
4334
+ const dir = join12(worktreePath, ATTACHMENTS_DIR);
4299
4335
  mkdirSync4(dir, { recursive: true });
4300
- const gi = join11(dir, ".gitignore");
4336
+ const gi = join12(dir, ".gitignore");
4301
4337
  if (!existsSync12(gi)) {
4302
- writeFileSync3(gi, attachmentsGitignoreBody(), "utf8");
4338
+ writeFileSync4(gi, attachmentsGitignoreBody(), "utf8");
4303
4339
  }
4304
4340
  return dir;
4305
4341
  }
4306
4342
  function uniqueAttachmentName(dir, originalName) {
4307
4343
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
4308
- if (!existsSync12(join11(dir, safe))) return safe;
4344
+ if (!existsSync12(join12(dir, safe))) return safe;
4309
4345
  const ext = extname(safe);
4310
4346
  const stem = ext ? safe.slice(0, -ext.length) : safe;
4311
4347
  for (let i = 1; i < 1e4; i++) {
4312
4348
  const candidate = `${stem}-${i}${ext}`;
4313
- if (!existsSync12(join11(dir, candidate))) return candidate;
4349
+ if (!existsSync12(join12(dir, candidate))) return candidate;
4314
4350
  }
4315
4351
  return `${stem}-${randomUUID4()}${ext}`;
4316
4352
  }
@@ -4371,10 +4407,10 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
4371
4407
  const st = statSync4(abs);
4372
4408
  if (!st.isFile()) continue;
4373
4409
  const name = uniqueAttachmentName(dir, originalName);
4374
- const destAbs = join11(dir, name);
4410
+ const destAbs = join12(dir, name);
4375
4411
  copyFileSync3(abs, destAbs);
4376
4412
  const rel = `${ATTACHMENTS_DIR}/${name}`;
4377
- const buf = readFileSync8(destAbs);
4413
+ const buf = readFileSync9(destAbs);
4378
4414
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
4379
4415
  } catch (err) {
4380
4416
  out.push({
@@ -4396,8 +4432,8 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
4396
4432
  try {
4397
4433
  const buf = Buffer.from(item.dataBase64, "base64");
4398
4434
  const name = uniqueAttachmentName(dir, originalName);
4399
- const destAbs = join11(dir, name);
4400
- writeFileSync3(destAbs, buf);
4435
+ const destAbs = join12(dir, name);
4436
+ writeFileSync4(destAbs, buf);
4401
4437
  const rel = `${ATTACHMENTS_DIR}/${name}`;
4402
4438
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
4403
4439
  } catch (err) {
@@ -4425,10 +4461,10 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
4425
4461
  }
4426
4462
  const name = basename3(rel);
4427
4463
  try {
4428
- const abs = join11(worktreePath, rel);
4464
+ const abs = join12(worktreePath, rel);
4429
4465
  const st = statSync4(abs);
4430
4466
  if (!st.isFile()) continue;
4431
- const buf = readFileSync8(abs);
4467
+ const buf = readFileSync9(abs);
4432
4468
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
4433
4469
  } catch (err) {
4434
4470
  out.push({
@@ -4443,8 +4479,8 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
4443
4479
  }
4444
4480
 
4445
4481
  // src/agents/instructions.ts
4446
- import { existsSync as existsSync13, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
4447
- import { join as join12 } from "path";
4482
+ import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
4483
+ import { join as join13 } from "path";
4448
4484
  function normPath(p) {
4449
4485
  return p.replace(/\/+$/, "");
4450
4486
  }
@@ -4961,7 +4997,9 @@ var Orchestrator = class {
4961
4997
  updateThread(thread.id, patch);
4962
4998
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
4963
4999
  this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
4964
- void this.drainQueue(thread.id);
5000
+ if (thisProcessShouldDrainAgentQueues()) {
5001
+ void this.drainQueue(thread.id);
5002
+ }
4965
5003
  return this.requireThread(thread.id);
4966
5004
  });
4967
5005
  }
@@ -5014,10 +5052,10 @@ var Orchestrator = class {
5014
5052
  async sendQueuedMessageNow(threadRef, index) {
5015
5053
  const thread = this.requireThread(threadRef);
5016
5054
  const promoted = await withThreadLock(thread.id, async () => {
5017
- const current = this.requireThread(thread.id);
5018
- if (index < 0 || index >= current.queue.length) return false;
5019
- const item = current.queue[index];
5020
- const rest = current.queue.filter((_, i) => i !== index);
5055
+ const current2 = this.requireThread(thread.id);
5056
+ if (index < 0 || index >= current2.queue.length) return false;
5057
+ const item = current2.queue[index];
5058
+ const rest = current2.queue.filter((_, i) => i !== index);
5021
5059
  const queue = [item, ...rest];
5022
5060
  this.haltDrain.delete(thread.id);
5023
5061
  updateThread(thread.id, { queue });
@@ -5025,10 +5063,16 @@ var Orchestrator = class {
5025
5063
  return true;
5026
5064
  });
5027
5065
  if (!promoted) return this.requireThread(thread.id);
5066
+ const current = this.requireThread(thread.id);
5028
5067
  const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
5068
+ const livePid = current.agentPid;
5069
+ const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
5029
5070
  if (inFlight) {
5030
5071
  this.stop(thread.id, { clearQueue: false, continueQueue: true });
5031
5072
  } else {
5073
+ if (foreignLive) {
5074
+ this.stop(thread.id, { clearQueue: false, continueQueue: true });
5075
+ }
5032
5076
  void this.drainQueue(thread.id);
5033
5077
  }
5034
5078
  return this.requireThread(thread.id);
@@ -5279,19 +5323,16 @@ var Orchestrator = class {
5279
5323
  }
5280
5324
  let lastStderr = summarizeTurnStderr(stderrTail);
5281
5325
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
5282
- 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
+ })) {
5283
5329
  updateThread(threadId, { sessionId: null });
5284
- pushTurnStderr(
5285
- stderrTail,
5286
- "Agent session missing \u2014 starting a fresh session"
5287
- );
5330
+ const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
5331
+ pushTurnStderr(stderrTail, retryNote);
5288
5332
  this.emit({
5289
5333
  type: "turn_output",
5290
5334
  threadId,
5291
- event: {
5292
- type: "stderr",
5293
- data: "Agent session missing \u2014 starting a fresh session"
5294
- }
5335
+ event: { type: "stderr", data: retryNote }
5295
5336
  });
5296
5337
  const retryThread = this.requireThread(threadId);
5297
5338
  const prior = retryThread.messages.slice(0, -1);
@@ -5340,10 +5381,7 @@ var Orchestrator = class {
5340
5381
  lastStderr = summarizeTurnStderr(stderrTail);
5341
5382
  detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
5342
5383
  }
5343
- let chatText = assistantText;
5344
- if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
5345
- chatText = humanizeAgentFailDetail(detail);
5346
- }
5384
+ let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
5347
5385
  if (chatText || parts.length > 0) {
5348
5386
  appendMessage(threadId, {
5349
5387
  role: "agent",
@@ -5463,6 +5501,13 @@ var Orchestrator = class {
5463
5501
  if (handle) handle.kill();
5464
5502
  const proc = this.processes.get(`${thread.id}:agent`);
5465
5503
  if (proc) proc.kill();
5504
+ const pid = thread.agentPid;
5505
+ if (typeof pid === "number" && pid > 0 && isPidAlive(pid)) {
5506
+ try {
5507
+ process.kill(pid, "SIGTERM");
5508
+ } catch {
5509
+ }
5510
+ }
5466
5511
  const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
5467
5512
  if (stopped.status === "stopped") {
5468
5513
  this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
@@ -5744,10 +5789,13 @@ var Orchestrator = class {
5744
5789
  getTurnResult(threadRef) {
5745
5790
  const thread = this.requireThread(threadRef);
5746
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 ?? "" : "");
5747
5794
  return {
5748
- text: lastAgent?.text ?? "",
5795
+ text: text3,
5749
5796
  status: thread.status,
5750
- sessionId: thread.sessionId
5797
+ sessionId: thread.sessionId,
5798
+ lastError
5751
5799
  };
5752
5800
  }
5753
5801
  assertNotGlobal(thread, action) {
@@ -6201,7 +6249,7 @@ var Orchestrator = class {
6201
6249
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
6202
6250
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
6203
6251
  try {
6204
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-MZVQHRSJ.js");
6252
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-ENVUDK6C.js");
6205
6253
  await ensureWorkspace2(thread.repoPath);
6206
6254
  } catch {
6207
6255
  }
@@ -7822,7 +7870,7 @@ async function startMcpServer() {
7822
7870
  );
7823
7871
  server.tool(
7824
7872
  "wait_for_turn",
7825
- "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.",
7826
7874
  {
7827
7875
  ref: z3.string(),
7828
7876
  timeoutMs: z3.number().optional()
@@ -7837,7 +7885,8 @@ async function startMcpServer() {
7837
7885
  text: JSON.stringify({
7838
7886
  id: thread.id,
7839
7887
  status: result.status,
7840
- text: result.text
7888
+ text: result.text,
7889
+ lastError: result.lastError
7841
7890
  })
7842
7891
  }
7843
7892
  ]
@@ -7846,7 +7895,7 @@ async function startMcpServer() {
7846
7895
  );
7847
7896
  server.tool(
7848
7897
  "get_turn_result",
7849
- "Final assistant message only (not full transcript)",
7898
+ "Final assistant message (and lastError when the turn failed). Not the full transcript.",
7850
7899
  { ref: z3.string() },
7851
7900
  async ({ ref }) => {
7852
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.98",
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",