@sideboard-ai/core 0.1.79 → 0.1.83

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 (34) hide show
  1. package/dist/agents/cursor-runner.cjs +18 -0
  2. package/dist/agents/cursor-runner.js +4 -0
  3. package/dist/{agents-EWPHOM6Y.js → agents-3YPUZME7.js} +6 -5
  4. package/dist/{agents-GRAPUXWW.js → agents-4KRD46KG.js} +5 -5
  5. package/dist/{app-settings-NILNWNNQ.js → app-settings-GVSOIJLZ.js} +1 -1
  6. package/dist/{app-settings-6IOQYGBK.js → app-settings-MQXL7OUF.js} +2 -1
  7. package/dist/{chunk-6T2SKGCS.js → chunk-DJFGX4RT.js} +3 -3
  8. package/dist/{chunk-HCGEFU3J.js → chunk-E2MIA7DO.js} +13 -14
  9. package/dist/{chunk-HSLQXL6M.js → chunk-ERJS3ZDP.js} +13 -14
  10. package/dist/{chunk-YVVXWWCC.js → chunk-GD2FM6FN.js} +81 -49
  11. package/dist/{chunk-FUUVPN4A.js → chunk-HBBXS2FR.js} +78 -49
  12. package/dist/chunk-IZ7RPF54.js +36 -0
  13. package/dist/{chunk-P33ZQ7ZC.js → chunk-JMRJ4F5B.js} +70 -25
  14. package/dist/{chunk-SHCR6RPU.js → chunk-JW6YFPQE.js} +83 -24
  15. package/dist/{chunk-PW5YHHP3.js → chunk-LVTNWH7B.js} +2 -2
  16. package/dist/{chunk-5LXWTU3J.js → chunk-NXXT5SE3.js} +3 -3
  17. package/dist/{chunk-5VTWBI3I.js → chunk-RS54WYYH.js} +2 -2
  18. package/dist/{chunk-XKNBSYA7.js → chunk-UHGN4KCL.js} +35 -1
  19. package/dist/{chunk-IFPN6TER.js → chunk-YO3CYL6B.js} +4 -1
  20. package/dist/{coordinator-prompt-2OVON2LQ.js → coordinator-prompt-46JE4NQR.js} +4 -3
  21. package/dist/{coordinator-prompt-K2R34A5T.js → coordinator-prompt-6ICA54JR.js} +3 -3
  22. package/dist/{global-workspace-VG44RZUH.js → global-workspace-GSLCEB5E.js} +5 -4
  23. package/dist/{global-workspace-RI367AM6.js → global-workspace-OBRWUPZG.js} +4 -4
  24. package/dist/index.cjs +268 -151
  25. package/dist/index.d.cts +52 -11
  26. package/dist/index.d.ts +52 -11
  27. package/dist/index.js +87 -87
  28. package/dist/mcp/run-stdio.cjs +249 -141
  29. package/dist/mcp/run-stdio.js +66 -76
  30. package/dist/{workspaces-TYPJNUSM.js → workspaces-7B3PTEF4.js} +6 -5
  31. package/dist/{workspaces-BQWQEZWE.js → workspaces-RJIWQB6J.js} +5 -5
  32. package/dist/{worktree-5VLLFI5W.js → worktree-HSN5LWY6.js} +3 -2
  33. package/dist/{worktree-UNSOCYZU.js → worktree-U3UIID2K.js} +2 -2
  34. package/package.json +1 -1
@@ -31,6 +31,43 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
+ // src/hook/nested-electron-env.ts
35
+ function isNestedElectronEnvKey(key) {
36
+ return NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
37
+ }
38
+ function stripNestedElectronEnv(env) {
39
+ const out = { ...env };
40
+ for (const key of Object.keys(out)) {
41
+ if (isNestedElectronEnvKey(key)) delete out[key];
42
+ }
43
+ return out;
44
+ }
45
+ function dropNestedElectronEnvFromProcess(env = process.env) {
46
+ for (const key of Object.keys(env)) {
47
+ if (isNestedElectronEnvKey(key)) delete env[key];
48
+ }
49
+ }
50
+ function wrapElectronAsNodeLaunch(file, args) {
51
+ if (process.platform === "win32") return { file, args };
52
+ return {
53
+ file: "/bin/sh",
54
+ args: ["-c", STRIP_NESTED_ELECTRON_THEN_EXEC, "sh", file, ...args]
55
+ };
56
+ }
57
+ var NESTED_ELECTRON_ENV_PREFIXES, STRIP_NESTED_ELECTRON_THEN_EXEC;
58
+ var init_nested_electron_env = __esm({
59
+ "src/hook/nested-electron-env.ts"() {
60
+ "use strict";
61
+ NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
62
+ STRIP_NESTED_ELECTRON_THEN_EXEC = [
63
+ "vars=`printenv | awk -F= '/^(ELECTRON_|CHROME_)/{print $1}'`",
64
+ '[ -n "$vars" ] && unset $vars',
65
+ "export ELECTRON_RUN_AS_NODE=1",
66
+ 'exec "$@"'
67
+ ].join("; ");
68
+ }
69
+ });
70
+
34
71
  // src/hook/settings.ts
35
72
  function expandHome(path) {
36
73
  if (path.startsWith("~/") || path === "~") {
@@ -1649,6 +1686,19 @@ function formatGhLandError(raw, opts) {
1649
1686
  }
1650
1687
  return detail || "Failed to create or update pull request";
1651
1688
  }
1689
+ function isPrNotMergeableError(text3) {
1690
+ return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
1691
+ text3
1692
+ );
1693
+ }
1694
+ function formatMergePrError(raw) {
1695
+ const trimmed = raw.trim();
1696
+ if (!trimmed) return "gh pr merge failed";
1697
+ if (isPrNotMergeableError(trimmed)) {
1698
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
1699
+ }
1700
+ return extractGhErrorDetail(trimmed) || trimmed;
1701
+ }
1652
1702
  var init_gh_errors = __esm({
1653
1703
  "src/git/gh-errors.ts"() {
1654
1704
  "use strict";
@@ -2855,7 +2905,7 @@ function applyAppEnvironment(target = process.env, settings = loadAppSettings())
2855
2905
  }
2856
2906
  function childEnvWithAppSettings(extra) {
2857
2907
  const settings = loadAppSettings();
2858
- const env = { ...process.env };
2908
+ const env = stripNestedElectronEnv({ ...process.env });
2859
2909
  applyAppEnvironment(env, settings);
2860
2910
  if (extra) {
2861
2911
  for (const [k, v] of Object.entries(extra)) {
@@ -2876,6 +2926,7 @@ var init_app_settings = __esm({
2876
2926
  import_node_os4 = require("os");
2877
2927
  import_node_path10 = require("path");
2878
2928
  init_thinking_effort();
2929
+ init_nested_electron_env();
2879
2930
  init_paths();
2880
2931
  init_private_file();
2881
2932
  init_secret_vault();
@@ -3615,6 +3666,41 @@ ${result.stderr}`;
3615
3666
  ].slice(0, 20) : [];
3616
3667
  return { conflicting, base: baseName, files };
3617
3668
  }
3669
+ async function probeLocalMergeGate(cwd, gate) {
3670
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
3671
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3672
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3673
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
3674
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
3675
+ try {
3676
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3677
+ if (local.conflicting) {
3678
+ return {
3679
+ gate: {
3680
+ mergeable: "CONFLICTING",
3681
+ mergeStateStatus: "DIRTY",
3682
+ reviewDecision: gate?.reviewDecision ?? null,
3683
+ baseRefName: local.base,
3684
+ url: gate?.url ?? null,
3685
+ isInMergeQueue: false
3686
+ },
3687
+ files: local.files
3688
+ };
3689
+ }
3690
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3691
+ return {
3692
+ gate: {
3693
+ ...gate,
3694
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3695
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3696
+ },
3697
+ files: []
3698
+ };
3699
+ }
3700
+ } catch {
3701
+ }
3702
+ return { gate, files: [] };
3703
+ }
3618
3704
  async function getPrChecks(cwd, selector) {
3619
3705
  const slug = await resolveGithubRepoSlug(cwd);
3620
3706
  const args = [
@@ -3650,47 +3736,9 @@ async function getPrChecks(cwd, selector) {
3650
3736
  }
3651
3737
  }
3652
3738
  let gate = await fetchPrMergeGate(cwd, selector, slug);
3653
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
3654
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3655
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3656
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
3657
- const needsLocalProbe = !alreadyConflicting && !inQueue;
3658
- if (needsLocalProbe) {
3659
- try {
3660
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3661
- if (local.conflicting) {
3662
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
3663
- gate = {
3664
- mergeable: "CONFLICTING",
3665
- mergeStateStatus: "DIRTY",
3666
- reviewDecision: gate?.reviewDecision ?? null,
3667
- baseRefName: local.base,
3668
- url: gate?.url ?? null,
3669
- isInMergeQueue: false
3670
- };
3671
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
3672
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
3673
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
3674
- const gateRows2 = buildMergeGateChecks(gate, {
3675
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
3676
- }).map(
3677
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3678
- ...row,
3679
- description: `${row.description ?? ""}${fileHint}`.trim()
3680
- } : row
3681
- );
3682
- return [...gateRows2, ...ciChecks];
3683
- }
3684
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3685
- gate = {
3686
- ...gate,
3687
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3688
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3689
- };
3690
- }
3691
- } catch {
3692
- }
3693
- }
3739
+ const probed = await probeLocalMergeGate(cwd, gate);
3740
+ gate = probed.gate;
3741
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
3694
3742
  if (!gate) {
3695
3743
  return ciChecks;
3696
3744
  }
@@ -3700,7 +3748,12 @@ async function getPrChecks(cwd, selector) {
3700
3748
  const gateRows = buildMergeGateChecks(gate, {
3701
3749
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
3702
3750
  suppressGenericBlocked: ciFailed || hasReviewRow
3703
- });
3751
+ }).map(
3752
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3753
+ ...row,
3754
+ description: `${row.description ?? ""}${fileHint}`.trim()
3755
+ } : row
3756
+ );
3704
3757
  return [...gateRows, ...ciChecks];
3705
3758
  }
3706
3759
  async function getPrMeta(cwd, selector) {
@@ -3710,7 +3763,7 @@ async function getPrMeta(cwd, selector) {
3710
3763
  "view",
3711
3764
  selector,
3712
3765
  "--json",
3713
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
3766
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
3714
3767
  ];
3715
3768
  if (slug) viewArgs.push("--repo", slug);
3716
3769
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -3720,7 +3773,7 @@ async function getPrMeta(cwd, selector) {
3720
3773
  "view",
3721
3774
  selector,
3722
3775
  "--json",
3723
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
3776
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
3724
3777
  ];
3725
3778
  if (slug) retry.push("--repo", slug);
3726
3779
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -3731,16 +3784,28 @@ async function getPrMeta(cwd, selector) {
3731
3784
  }
3732
3785
  try {
3733
3786
  const view = JSON.parse(stdout);
3787
+ const gate = {
3788
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
3789
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
3790
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
3791
+ baseRefName: String(view.baseRefName ?? ""),
3792
+ url: String(view.url ?? ""),
3793
+ isInMergeQueue: parseInMergeQueue(view)
3794
+ };
3795
+ const probed = await probeLocalMergeGate(cwd, gate);
3796
+ const resolved = probed.gate ?? gate;
3734
3797
  return {
3735
3798
  number: Number(view.number),
3736
3799
  title: String(view.title ?? ""),
3737
3800
  url: String(view.url ?? ""),
3738
3801
  state: String(view.state ?? ""),
3739
3802
  isDraft: Boolean(view.isDraft),
3740
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
3741
- baseRefName: String(view.baseRefName ?? ""),
3803
+ reviewDecision: resolved.reviewDecision,
3804
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
3742
3805
  headRefName: String(view.headRefName ?? ""),
3743
- isInMergeQueue: parseInMergeQueue(view)
3806
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
3807
+ mergeable: resolved.mergeable,
3808
+ mergeStateStatus: resolved.mergeStateStatus
3744
3809
  };
3745
3810
  } catch {
3746
3811
  return null;
@@ -4208,7 +4273,9 @@ async function mergePr(cwd, selector, opts) {
4208
4273
  if (slug) args.push("--repo", slug);
4209
4274
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
4210
4275
  if (exitCode !== 0) {
4211
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
4276
+ throw new Error(
4277
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
4278
+ );
4212
4279
  }
4213
4280
  const after = await gh(viewArgs, cwd, { reject: false });
4214
4281
  if (after.exitCode === 0 && after.stdout.trim()) {
@@ -4489,7 +4556,7 @@ function coordinatorTurnReminder(opts) {
4489
4556
  goal ? `- Goal / title: ${goal}` : null,
4490
4557
  accountDefaultsPlaybookLine(),
4491
4558
  `- 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.`,
4492
- "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Land with ask_git (commit-push / create-draft / merge) on the child, then wait_for_turn \u2014 never git/gh from this cwd.",
4559
+ "- 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.",
4493
4560
  "- 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.",
4494
4561
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
4495
4562
  "- 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."
@@ -4543,9 +4610,9 @@ function ensureGlobalCoordinatorCwd(opts) {
4543
4610
  orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
4544
4611
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
4545
4612
  "Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
4546
- "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn.",
4613
+ "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
4547
4614
  "Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft.",
4548
- "Always ask worktree agents to commit, push, open draft PRs, and merge (`ask_git` / `send_to_thread`). The worktree agent runs git/gh; never merge from this orchestration cwd."
4615
+ "Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
4549
4616
  ].join("\n");
4550
4617
  try {
4551
4618
  (0, import_node_fs11.writeFileSync)((0, import_node_path12.join)(dir, "CLAUDE.md"), `${body}
@@ -4558,7 +4625,6 @@ function ensureGlobalCoordinatorCwd(opts) {
4558
4625
  }
4559
4626
  function coordinatorSystemPrompt(opts) {
4560
4627
  const audience = opts.audience ?? "cloud";
4561
- const reposDir = sideboardReposDir();
4562
4628
  const intro = audience === "cloud" ? [
4563
4629
  "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
4564
4630
  "Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
@@ -4576,14 +4642,8 @@ function coordinatorSystemPrompt(opts) {
4576
4642
  ...intro,
4577
4643
  "You operate across ALL registered workspaces below.",
4578
4644
  "You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
4579
- COORDINATOR_TOOL_PLAYBOOK,
4580
- accountDefaultsPlaybookLine(),
4581
- coordinatorGreenfieldPlaybook(reposDir),
4582
- "When creating threads, pass the correct repoPath for the target workspace.",
4645
+ "Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
4583
4646
  `YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit parentThreadId (Sideboard binds it). Never invent a uuid.`,
4584
- "Omit agent/model on create_thread unless you need to override Account defaults.",
4585
- "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn. Never target upstream. Never git/gh from this orchestration cwd.",
4586
- "Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
4587
4647
  `Goal: ${opts.goal}`,
4588
4648
  "Registered workspaces:",
4589
4649
  formatWorkspaceInventory(opts.workspaces)
@@ -4610,6 +4670,7 @@ var init_coordinator_prompt = __esm({
4610
4670
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
4611
4671
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
4612
4672
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
4673
+ "- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
4613
4674
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
4614
4675
  "Workspaces:",
4615
4676
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -4628,7 +4689,12 @@ var init_coordinator_prompt = __esm({
4628
4689
  "- get_diff \u2014 compact diff summary",
4629
4690
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
4630
4691
  "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
4631
- '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
4692
+ '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
4693
+ '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
4694
+ "Process guides:",
4695
+ "- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
4696
+ "- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
4697
+ "- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
4632
4698
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4633
4699
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
4634
4700
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
@@ -5065,6 +5131,51 @@ var init_connected_teams = __esm({
5065
5131
  }
5066
5132
  });
5067
5133
 
5134
+ // src/agents/usage.ts
5135
+ function sumOptional(a, b) {
5136
+ if (a == null && b == null) return void 0;
5137
+ return (a ?? 0) + (b ?? 0);
5138
+ }
5139
+ function fromInclusiveInputUsage(opts) {
5140
+ const totalInput = Number(opts.inputTokens) || 0;
5141
+ const outputTokens = Number(opts.outputTokens) || 0;
5142
+ const cached = Number(opts.cachedInputTokens) || 0;
5143
+ if (!totalInput && !outputTokens) return null;
5144
+ const cacheReadTokens = cached > 0 ? Math.min(cached, totalInput) : 0;
5145
+ return {
5146
+ inputTokens: Math.max(0, totalInput - cacheReadTokens),
5147
+ outputTokens,
5148
+ cacheReadTokens: cacheReadTokens || void 0
5149
+ };
5150
+ }
5151
+ function requestOccupancy(u) {
5152
+ return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
5153
+ }
5154
+ function mergeUsage(a, b) {
5155
+ return {
5156
+ inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
5157
+ outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
5158
+ cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
5159
+ cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
5160
+ lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
5161
+ };
5162
+ }
5163
+ function applyTurnUsage(current, incoming, scope = "request") {
5164
+ if (scope === "turn") {
5165
+ return {
5166
+ ...incoming,
5167
+ lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
5168
+ };
5169
+ }
5170
+ const merged = mergeUsage(current, incoming);
5171
+ return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
5172
+ }
5173
+ var init_usage = __esm({
5174
+ "src/agents/usage.ts"() {
5175
+ "use strict";
5176
+ }
5177
+ });
5178
+
5068
5179
  // src/agents/brightsy-targets.ts
5069
5180
  function encodeBrightsyTarget(type, id, accountId) {
5070
5181
  if (accountId) return `team:${accountId}:${type}:${id}`;
@@ -5129,15 +5240,11 @@ var init_turn_input = __esm({
5129
5240
  // src/agents/brightsy.ts
5130
5241
  function usageFromBrightsy(usage) {
5131
5242
  if (!usage) return null;
5132
- const inputTokens = Number(usage.prompt_tokens ?? 0);
5133
- const outputTokens = Number(usage.completion_tokens ?? 0);
5134
- if (!inputTokens && !outputTokens) return null;
5135
- const cached = Number(usage.prompt_tokens_details?.cached_tokens ?? 0);
5136
- return {
5137
- inputTokens,
5138
- outputTokens,
5139
- cacheReadTokens: cached || void 0
5140
- };
5243
+ return fromInclusiveInputUsage({
5244
+ inputTokens: Number(usage.prompt_tokens ?? 0),
5245
+ outputTokens: Number(usage.completion_tokens ?? 0),
5246
+ cachedInputTokens: Number(usage.prompt_tokens_details?.cached_tokens ?? 0)
5247
+ });
5141
5248
  }
5142
5249
  function parseBrightsyCliLine(line) {
5143
5250
  const trimmed = line.trim();
@@ -5353,6 +5460,7 @@ var init_brightsy = __esm({
5353
5460
  init_connected_teams();
5354
5461
  init_config();
5355
5462
  init_app_settings();
5463
+ init_usage();
5356
5464
  init_brightsy_targets();
5357
5465
  init_error_detail();
5358
5466
  init_turn_input();
@@ -5492,6 +5600,13 @@ var init_profile = __esm({
5492
5600
  function isAsarPath(filePath) {
5493
5601
  return /\.asar([/\\]|$)/.test(filePath);
5494
5602
  }
5603
+ function applyNodeLaunch(launch, args) {
5604
+ if (!launch.env.ELECTRON_RUN_AS_NODE) {
5605
+ return { file: launch.file, args, env: launch.env };
5606
+ }
5607
+ const wrapped = wrapElectronAsNodeLaunch(launch.file, args);
5608
+ return { file: wrapped.file, args: wrapped.args, env: launch.env };
5609
+ }
5495
5610
  async function resolveNodeLaunch(scriptPath) {
5496
5611
  if (isAsarPath(scriptPath)) {
5497
5612
  return {
@@ -5512,6 +5627,7 @@ async function resolveNodeLaunch(scriptPath) {
5512
5627
  var init_node_launch = __esm({
5513
5628
  "src/agents/node-launch.ts"() {
5514
5629
  "use strict";
5630
+ init_nested_electron_env();
5515
5631
  init_run();
5516
5632
  }
5517
5633
  });
@@ -5638,11 +5754,11 @@ async function resolveSideboardMcpServer() {
5638
5754
  if (entry) {
5639
5755
  const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
5640
5756
  const scriptArgs = isCli ? [entry, "mcp"] : [entry];
5641
- const launch = await resolveNodeLaunch(entry);
5757
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
5642
5758
  return {
5643
5759
  name: "sideboard",
5644
5760
  command: launch.file,
5645
- args: scriptArgs,
5761
+ args: launch.args,
5646
5762
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
5647
5763
  };
5648
5764
  }
@@ -6232,14 +6348,11 @@ async function listCodexModels() {
6232
6348
  }
6233
6349
  function usageFromCodex(usage) {
6234
6350
  if (!usage) return null;
6235
- const inputTokens = Number(usage.input_tokens ?? 0);
6236
- const outputTokens = Number(usage.output_tokens ?? 0) + Number(usage.reasoning_output_tokens ?? 0);
6237
- if (!inputTokens && !outputTokens) return null;
6238
- return {
6239
- inputTokens,
6240
- outputTokens,
6241
- cacheReadTokens: usage.cached_input_tokens ? Number(usage.cached_input_tokens) : void 0
6242
- };
6351
+ return fromInclusiveInputUsage({
6352
+ inputTokens: Number(usage.input_tokens ?? 0),
6353
+ outputTokens: Number(usage.output_tokens ?? 0),
6354
+ cachedInputTokens: Number(usage.cached_input_tokens ?? 0)
6355
+ });
6243
6356
  }
6244
6357
  function codexConfigHasNetworkAccess() {
6245
6358
  const candidates = [
@@ -6299,6 +6412,7 @@ var init_codex = __esm({
6299
6412
  init_app_settings();
6300
6413
  init_global_workspace();
6301
6414
  init_error_detail();
6415
+ init_usage();
6302
6416
  init_injected_mcp();
6303
6417
  init_turn_input();
6304
6418
  init_types();
@@ -6849,10 +6963,13 @@ var init_cursor = __esm({
6849
6963
  };
6850
6964
  const runner = cursorRunnerPath();
6851
6965
  const isTs = runner.endsWith(".ts");
6852
- const launch = await resolveNodeLaunch(runner);
6966
+ const launch = applyNodeLaunch(
6967
+ await resolveNodeLaunch(runner),
6968
+ isTs ? ["--import", "tsx", runner] : [runner]
6969
+ );
6853
6970
  return {
6854
6971
  file: launch.file,
6855
- args: isTs ? ["--import", "tsx", runner] : [runner],
6972
+ args: launch.args,
6856
6973
  cwd: thread.worktreePath,
6857
6974
  stdin: JSON.stringify(req),
6858
6975
  env: {
@@ -8141,6 +8258,9 @@ var init_cursor_recover = __esm({
8141
8258
  }
8142
8259
  });
8143
8260
 
8261
+ // src/mcp/run-stdio.ts
8262
+ init_nested_electron_env();
8263
+
8144
8264
  // src/mcp/server.ts
8145
8265
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
8146
8266
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
@@ -8899,36 +9019,7 @@ function normalizeParseResult(parsed) {
8899
9019
 
8900
9020
  // src/agents/spawn.ts
8901
9021
  init_path();
8902
-
8903
- // src/agents/usage.ts
8904
- function sumOptional(a, b) {
8905
- if (a == null && b == null) return void 0;
8906
- return (a ?? 0) + (b ?? 0);
8907
- }
8908
- function requestOccupancy(u) {
8909
- return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
8910
- }
8911
- function mergeUsage(a, b) {
8912
- return {
8913
- inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
8914
- outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
8915
- cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
8916
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
8917
- lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
8918
- };
8919
- }
8920
- function applyTurnUsage(current, incoming, scope = "request") {
8921
- if (scope === "turn") {
8922
- return {
8923
- ...incoming,
8924
- lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
8925
- };
8926
- }
8927
- const merged = mergeUsage(current, incoming);
8928
- return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
8929
- }
8930
-
8931
- // src/agents/spawn.ts
9022
+ init_usage();
8932
9023
  async function spawnAgentTurn(thread, input, onEvent) {
8933
9024
  ensureAgentPath();
8934
9025
  if (!thread.worktreePath?.trim()) {
@@ -9145,7 +9236,8 @@ function agentGitPrompt(action, opts) {
9145
9236
  return "Commit, push, and open a PR in the browser.";
9146
9237
  case "resolve-conflicts": {
9147
9238
  const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
9148
- return base ? `Merge origin/${base} into this branch. Then push.` : "Fix merge conflicts.";
9239
+ const named = base ? ` (${base})` : "";
9240
+ return `Merge the remote branch${named} into your branch and resolve conflicts. Then, commit and push your changes.`;
9149
9241
  }
9150
9242
  case "merge":
9151
9243
  return "Merge PR.";
@@ -9173,6 +9265,8 @@ var import_node_path18 = require("path");
9173
9265
  var import_execa3 = require("execa");
9174
9266
  var import_node_readline2 = require("readline");
9175
9267
  init_settings();
9268
+ init_nested_electron_env();
9269
+ init_nested_electron_env();
9176
9270
  var PORT_RANGE_SIZE = 10;
9177
9271
  function matchSimpleGlob(pattern, name) {
9178
9272
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
@@ -9256,16 +9350,6 @@ async function captureLoginEnv() {
9256
9350
  cachedLoginEnv = { ...process.env };
9257
9351
  return { ...cachedLoginEnv };
9258
9352
  }
9259
- var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
9260
- function stripNestedElectronEnv(env) {
9261
- const out = { ...env };
9262
- for (const key of Object.keys(out)) {
9263
- if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
9264
- delete out[key];
9265
- }
9266
- }
9267
- return out;
9268
- }
9269
9353
  function buildWorkspaceScriptEnv(opts, baseEnv) {
9270
9354
  const env = stripNestedElectronEnv({
9271
9355
  ...baseEnv ?? process.env
@@ -10416,6 +10500,10 @@ File: src/client/frontends/desktop/core/UserData.ts
10416
10500
 
10417
10501
  **Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
10418
10502
  </example>
10503
+
10504
+ ## Growing the rules
10505
+
10506
+ If a blocking issue is a missing or ambiguous repo rule that will recur, say so and propose one sentence for \`.sideboard/review.md\` or a \`.claude/skills/<name>/SKILL.md\`. Do not only patch this diff when the same miss will happen again. New skills go under \`.claude/skills\` (Claude Code / attach) \u2014 not \`.sideboard/skills\`.
10419
10507
  `;
10420
10508
 
10421
10509
  // src/review/request-review.ts
@@ -12553,7 +12641,7 @@ function formatWorktreeDirective(thread, opts) {
12553
12641
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
12554
12642
  );
12555
12643
  lines.push(
12556
- "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. When asked to merge the PR, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
12644
+ "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. Do not merge the PR unless this turn is a Merge PR request or the user explicitly asked. When merging, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
12557
12645
  );
12558
12646
  if (thread.prUrl) {
12559
12647
  lines.push(
@@ -12585,19 +12673,30 @@ function formatWorktreeDirective(thread, opts) {
12585
12673
  '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push.'
12586
12674
  );
12587
12675
  lines.push(
12588
- '- "Update the branch." / "Fix merge conflicts." / "Merge origin/<base> into this branch. Then push." \u2192 sync with the PR base (merge or rebase), resolve conflicts carefully, commit, and push until the PR is mergeable.'
12676
+ '- "Merge the remote branch (<base>) into your branch and resolve conflicts. Then, commit and push your changes." \u2192 fetch the PR base, merge it into this branch, resolve conflicts carefully, commit, and push until the PR is mergeable.'
12589
12677
  );
12590
12678
  lines.push(
12591
12679
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
12592
12680
  );
12593
12681
  lines.push(
12594
- '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
12682
+ '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub (this phrase is the explicit ask). If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
12595
12683
  );
12684
+ lines.push("");
12685
+ lines.push(formatProcessGuideDirective());
12596
12686
  return lines.join("\n");
12597
12687
  }
12598
12688
  function formatWorktreeReminder() {
12599
12689
  return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout.";
12600
12690
  }
12691
+ function formatProcessGuideDirective() {
12692
+ return [
12693
+ "Process guides (recurring work only):",
12694
+ "- If `.claude/skills/graph-engineering/SKILL.md` exists, follow it (`/graph-engineering`) for migrations, ports, batch fixes, and other fan-out. Judge first; state on disk; grow the rulebook; do not patch around it.",
12695
+ "- If this same shape of work will happen again, write `.claude/skills/<kebab-name>/SKILL.md` in this worktree (Claude Code project skill). Sideboard `/name`, Claude Code, and `attach` all load that path. Do not leave the method only in chat.",
12696
+ "- Do not write new skills under `.sideboard/skills` \u2014 native agents do not scan it. Point Codex/OpenCode at the file from `AGENTS.md`. Optional: symlink `.cursor/skills/<name>` to the Claude skill.",
12697
+ "- Skip a guide for a one-off. If a matching skill exists, follow it. Same miss twice \u2192 edit the skill (or `.sideboard/review.md`), do not patch around it."
12698
+ ].join("\n");
12699
+ }
12601
12700
  function formatArtifactDirective() {
12602
12701
  return [
12603
12702
  "Sideboard side column (desktop UI):",
@@ -12615,9 +12714,22 @@ function formatArtifactDirective() {
12615
12714
  "Files / media browser (CMS file manager column):",
12616
12715
  "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
12617
12716
  " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
12618
- "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages."
12717
+ "Multiple-choice questions (any mode \u2014 not only Plan):",
12718
+ "5) Call Sideboard MCP `ask_user` when the user should pick from predefined options (approach forks, requirements, which API). First write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it). Include a description on every option. After calling, stop and wait for their next message with answers. Do not ask multiple-choice questions as plain chat bullets \u2014 use ask_user so Sideboard shows the composer picker.",
12719
+ "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages; ask_user for predefined-option questions."
12619
12720
  ].join("\n");
12620
12721
  }
12722
+ function formatUiReminder() {
12723
+ return [
12724
+ "Sideboard side column (important):",
12725
+ "There is no claude.ai Artifact tool here \u2014 that is normal.",
12726
+ "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
12727
+ "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
12728
+ "Files column: MCP present_files (brightsy account storage or memory).",
12729
+ "Do not say artifacts/CMS UI are unavailable.",
12730
+ "Multiple-choice questions: MCP ask_user (composer picker, any mode). Explain options in chat first, then wait for answers."
12731
+ ].join(" ");
12732
+ }
12621
12733
 
12622
12734
  // src/orchestrator/orchestrator.ts
12623
12735
  init_types();
@@ -13161,14 +13273,7 @@ var Orchestrator = class {
13161
13273
  SLACK_REPLY_FORMATTING
13162
13274
  ].join("\n") : null
13163
13275
  ].filter(Boolean).join("\n") : null;
13164
- const artifactReminder = thread.agent !== "brightsy" ? [
13165
- "Sideboard side column (important):",
13166
- "There is no claude.ai Artifact tool here \u2014 that is normal.",
13167
- "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
13168
- "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
13169
- "Files column: MCP present_files (brightsy account storage or memory).",
13170
- "Do not say artifacts/CMS UI are unavailable."
13171
- ].join(" ") : null;
13276
+ const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
13172
13277
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
13173
13278
  const slackReplyContext = formatSlackRepliesForTurn(
13174
13279
  pendingSlackExternalReplies(thread.messages)
@@ -13862,7 +13967,9 @@ var Orchestrator = class {
13862
13967
  reviewDecision: null,
13863
13968
  baseRefName: "",
13864
13969
  headRefName: "",
13865
- isInMergeQueue: false
13970
+ isInMergeQueue: false,
13971
+ mergeable: null,
13972
+ mergeStateStatus: null
13866
13973
  };
13867
13974
  await this.persistPrMetaAndMaybeArchive(thread, metaLike);
13868
13975
  return { url: metaLike.url, state };
@@ -15536,7 +15643,7 @@ async function startMcpServer() {
15536
15643
  );
15537
15644
  server.tool(
15538
15645
  "ask_user",
15539
- "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
15646
+ "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (any mode \u2014 not only Plan). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use when the user should pick from predefined options (approach forks, requirements, which API) \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
15540
15647
  {
15541
15648
  questions: import_zod3.z.array(
15542
15649
  import_zod3.z.object({
@@ -15799,7 +15906,7 @@ async function startMcpServer() {
15799
15906
  );
15800
15907
  server.tool(
15801
15908
  "send_to_thread",
15802
- "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
15909
+ 'Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.',
15803
15910
  {
15804
15911
  ref: import_zod3.z.string(),
15805
15912
  prompt: import_zod3.z.string(),
@@ -15895,7 +16002,7 @@ async function startMcpServer() {
15895
16002
  );
15896
16003
  server.tool(
15897
16004
  "archive_thread",
15898
- "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
16005
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
15899
16006
  { ref: import_zod3.z.string() },
15900
16007
  async ({ ref }) => {
15901
16008
  const t = orch.getThread(ref);
@@ -16003,7 +16110,7 @@ async function startMcpServer() {
16003
16110
  );
16004
16111
  server.tool(
16005
16112
  "ask_git",
16006
- "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). Do not run git or gh from the orchestration cwd.",
16113
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
16007
16114
  {
16008
16115
  ref: import_zod3.z.string().describe("Worktree thread id/ref"),
16009
16116
  action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
@@ -16304,7 +16411,7 @@ async function startMcpServer() {
16304
16411
  );
16305
16412
  server.tool(
16306
16413
  "get_pr_stack",
16307
- "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs.",
16414
+ "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
16308
16415
  { ref: import_zod3.z.string() },
16309
16416
  async ({ ref }) => {
16310
16417
  const stack = await orch.getPrStack(ref);
@@ -16450,6 +16557,7 @@ async function startMcpServer() {
16450
16557
  }
16451
16558
 
16452
16559
  // src/mcp/run-stdio.ts
16560
+ dropNestedElectronEnvFromProcess();
16453
16561
  startMcpServer().catch((err) => {
16454
16562
  console.error(err);
16455
16563
  process.exitCode = 1;