@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
package/dist/index.cjs CHANGED
@@ -385,6 +385,38 @@ var init_paths = __esm({
385
385
  }
386
386
  });
387
387
 
388
+ // src/hook/nested-electron-env.ts
389
+ function isNestedElectronEnvKey(key) {
390
+ return NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
391
+ }
392
+ function stripNestedElectronEnv(env) {
393
+ const out = { ...env };
394
+ for (const key of Object.keys(out)) {
395
+ if (isNestedElectronEnvKey(key)) delete out[key];
396
+ }
397
+ return out;
398
+ }
399
+ function wrapElectronAsNodeLaunch(file, args) {
400
+ if (process.platform === "win32") return { file, args };
401
+ return {
402
+ file: "/bin/sh",
403
+ args: ["-c", STRIP_NESTED_ELECTRON_THEN_EXEC, "sh", file, ...args]
404
+ };
405
+ }
406
+ var NESTED_ELECTRON_ENV_PREFIXES, STRIP_NESTED_ELECTRON_THEN_EXEC;
407
+ var init_nested_electron_env = __esm({
408
+ "src/hook/nested-electron-env.ts"() {
409
+ "use strict";
410
+ NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
411
+ STRIP_NESTED_ELECTRON_THEN_EXEC = [
412
+ "vars=`printenv | awk -F= '/^(ELECTRON_|CHROME_)/{print $1}'`",
413
+ '[ -n "$vars" ] && unset $vars',
414
+ "export ELECTRON_RUN_AS_NODE=1",
415
+ 'exec "$@"'
416
+ ].join("; ");
417
+ }
418
+ });
419
+
388
420
  // src/store/private-file.ts
389
421
  function writePrivateFile(path2, contents) {
390
422
  (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path2), { recursive: true });
@@ -1499,7 +1531,7 @@ function applyAppEnvironment(target = process.env, settings = loadAppSettings())
1499
1531
  }
1500
1532
  function childEnvWithAppSettings(extra) {
1501
1533
  const settings = loadAppSettings();
1502
- const env = { ...process.env };
1534
+ const env = stripNestedElectronEnv({ ...process.env });
1503
1535
  applyAppEnvironment(env, settings);
1504
1536
  if (extra) {
1505
1537
  for (const [k, v] of Object.entries(extra)) {
@@ -1520,6 +1552,7 @@ var init_app_settings = __esm({
1520
1552
  import_node_os3 = require("os");
1521
1553
  import_node_path6 = require("path");
1522
1554
  init_thinking_effort();
1555
+ init_nested_electron_env();
1523
1556
  init_paths();
1524
1557
  init_private_file();
1525
1558
  init_secret_vault();
@@ -2852,11 +2885,25 @@ function formatGhLandError(raw, opts) {
2852
2885
  }
2853
2886
  return detail || "Failed to create or update pull request";
2854
2887
  }
2888
+ function isPrNotMergeableError(text3) {
2889
+ 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(
2890
+ text3
2891
+ );
2892
+ }
2893
+ function formatMergePrError(raw) {
2894
+ const trimmed = raw.trim();
2895
+ if (!trimmed) return "gh pr merge failed";
2896
+ if (isPrNotMergeableError(trimmed)) {
2897
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
2898
+ }
2899
+ return extractGhErrorDetail(trimmed) || trimmed;
2900
+ }
2855
2901
  function formatIpcInvokeError(err) {
2856
2902
  let msg = err instanceof Error ? err.message : String(err);
2857
2903
  msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
2858
2904
  msg = msg.replace(/^ExecaError:\s*/i, "");
2859
2905
  msg = msg.replace(/^Error:\s*/i, "");
2906
+ if (isPrNotMergeableError(msg)) return formatMergePrError(msg);
2860
2907
  return formatGhLandError(msg);
2861
2908
  }
2862
2909
  var init_gh_errors = __esm({
@@ -3839,6 +3886,41 @@ ${result.stderr}`;
3839
3886
  ].slice(0, 20) : [];
3840
3887
  return { conflicting, base: baseName, files };
3841
3888
  }
3889
+ async function probeLocalMergeGate(cwd, gate) {
3890
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
3891
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3892
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3893
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
3894
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
3895
+ try {
3896
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3897
+ if (local.conflicting) {
3898
+ return {
3899
+ gate: {
3900
+ mergeable: "CONFLICTING",
3901
+ mergeStateStatus: "DIRTY",
3902
+ reviewDecision: gate?.reviewDecision ?? null,
3903
+ baseRefName: local.base,
3904
+ url: gate?.url ?? null,
3905
+ isInMergeQueue: false
3906
+ },
3907
+ files: local.files
3908
+ };
3909
+ }
3910
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3911
+ return {
3912
+ gate: {
3913
+ ...gate,
3914
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3915
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3916
+ },
3917
+ files: []
3918
+ };
3919
+ }
3920
+ } catch {
3921
+ }
3922
+ return { gate, files: [] };
3923
+ }
3842
3924
  async function getPrChecks(cwd, selector) {
3843
3925
  const slug = await resolveGithubRepoSlug(cwd);
3844
3926
  const args = [
@@ -3874,47 +3956,9 @@ async function getPrChecks(cwd, selector) {
3874
3956
  }
3875
3957
  }
3876
3958
  let gate = await fetchPrMergeGate(cwd, selector, slug);
3877
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
3878
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3879
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3880
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
3881
- const needsLocalProbe = !alreadyConflicting && !inQueue;
3882
- if (needsLocalProbe) {
3883
- try {
3884
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3885
- if (local.conflicting) {
3886
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
3887
- gate = {
3888
- mergeable: "CONFLICTING",
3889
- mergeStateStatus: "DIRTY",
3890
- reviewDecision: gate?.reviewDecision ?? null,
3891
- baseRefName: local.base,
3892
- url: gate?.url ?? null,
3893
- isInMergeQueue: false
3894
- };
3895
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
3896
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
3897
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
3898
- const gateRows2 = buildMergeGateChecks(gate, {
3899
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
3900
- }).map(
3901
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3902
- ...row,
3903
- description: `${row.description ?? ""}${fileHint}`.trim()
3904
- } : row
3905
- );
3906
- return [...gateRows2, ...ciChecks];
3907
- }
3908
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3909
- gate = {
3910
- ...gate,
3911
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3912
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3913
- };
3914
- }
3915
- } catch {
3916
- }
3917
- }
3959
+ const probed = await probeLocalMergeGate(cwd, gate);
3960
+ gate = probed.gate;
3961
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
3918
3962
  if (!gate) {
3919
3963
  return ciChecks;
3920
3964
  }
@@ -3924,7 +3968,12 @@ async function getPrChecks(cwd, selector) {
3924
3968
  const gateRows = buildMergeGateChecks(gate, {
3925
3969
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
3926
3970
  suppressGenericBlocked: ciFailed || hasReviewRow
3927
- });
3971
+ }).map(
3972
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3973
+ ...row,
3974
+ description: `${row.description ?? ""}${fileHint}`.trim()
3975
+ } : row
3976
+ );
3928
3977
  return [...gateRows, ...ciChecks];
3929
3978
  }
3930
3979
  async function getPrMeta(cwd, selector) {
@@ -3934,7 +3983,7 @@ async function getPrMeta(cwd, selector) {
3934
3983
  "view",
3935
3984
  selector,
3936
3985
  "--json",
3937
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
3986
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
3938
3987
  ];
3939
3988
  if (slug) viewArgs.push("--repo", slug);
3940
3989
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -3944,7 +3993,7 @@ async function getPrMeta(cwd, selector) {
3944
3993
  "view",
3945
3994
  selector,
3946
3995
  "--json",
3947
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
3996
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
3948
3997
  ];
3949
3998
  if (slug) retry.push("--repo", slug);
3950
3999
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -3955,16 +4004,28 @@ async function getPrMeta(cwd, selector) {
3955
4004
  }
3956
4005
  try {
3957
4006
  const view = JSON.parse(stdout);
4007
+ const gate = {
4008
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
4009
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
4010
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
4011
+ baseRefName: String(view.baseRefName ?? ""),
4012
+ url: String(view.url ?? ""),
4013
+ isInMergeQueue: parseInMergeQueue(view)
4014
+ };
4015
+ const probed = await probeLocalMergeGate(cwd, gate);
4016
+ const resolved = probed.gate ?? gate;
3958
4017
  return {
3959
4018
  number: Number(view.number),
3960
4019
  title: String(view.title ?? ""),
3961
4020
  url: String(view.url ?? ""),
3962
4021
  state: String(view.state ?? ""),
3963
4022
  isDraft: Boolean(view.isDraft),
3964
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
3965
- baseRefName: String(view.baseRefName ?? ""),
4023
+ reviewDecision: resolved.reviewDecision,
4024
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
3966
4025
  headRefName: String(view.headRefName ?? ""),
3967
- isInMergeQueue: parseInMergeQueue(view)
4026
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
4027
+ mergeable: resolved.mergeable,
4028
+ mergeStateStatus: resolved.mergeStateStatus
3968
4029
  };
3969
4030
  } catch {
3970
4031
  return null;
@@ -4432,7 +4493,9 @@ async function mergePr(cwd, selector, opts) {
4432
4493
  if (slug) args.push("--repo", slug);
4433
4494
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
4434
4495
  if (exitCode !== 0) {
4435
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
4496
+ throw new Error(
4497
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
4498
+ );
4436
4499
  }
4437
4500
  const after = await gh(viewArgs, cwd, { reject: false });
4438
4501
  if (after.exitCode === 0 && after.stdout.trim()) {
@@ -4655,7 +4718,7 @@ function coordinatorTurnReminder(opts) {
4655
4718
  goal ? `- Goal / title: ${goal}` : null,
4656
4719
  accountDefaultsPlaybookLine(),
4657
4720
  `- 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.`,
4658
- "- 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.",
4721
+ "- 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.",
4659
4722
  "- 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.",
4660
4723
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
4661
4724
  "- 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."
@@ -4709,9 +4772,9 @@ function ensureGlobalCoordinatorCwd(opts) {
4709
4772
  orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
4710
4773
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
4711
4774
  "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.",
4712
- "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.",
4775
+ "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).",
4713
4776
  "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.",
4714
- "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."
4777
+ "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."
4715
4778
  ].join("\n");
4716
4779
  try {
4717
4780
  (0, import_node_fs10.writeFileSync)((0, import_node_path10.join)(dir, "CLAUDE.md"), `${body}
@@ -4724,7 +4787,6 @@ function ensureGlobalCoordinatorCwd(opts) {
4724
4787
  }
4725
4788
  function coordinatorSystemPrompt(opts) {
4726
4789
  const audience = opts.audience ?? "cloud";
4727
- const reposDir = sideboardReposDir();
4728
4790
  const intro = audience === "cloud" ? [
4729
4791
  "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
4730
4792
  "Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
@@ -4742,14 +4804,8 @@ function coordinatorSystemPrompt(opts) {
4742
4804
  ...intro,
4743
4805
  "You operate across ALL registered workspaces below.",
4744
4806
  "You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
4745
- COORDINATOR_TOOL_PLAYBOOK,
4746
- accountDefaultsPlaybookLine(),
4747
- coordinatorGreenfieldPlaybook(reposDir),
4748
- "When creating threads, pass the correct repoPath for the target workspace.",
4807
+ "Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
4749
4808
  `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.`,
4750
- "Omit agent/model on create_thread unless you need to override Account defaults.",
4751
- "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.",
4752
- "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.",
4753
4809
  `Goal: ${opts.goal}`,
4754
4810
  "Registered workspaces:",
4755
4811
  formatWorkspaceInventory(opts.workspaces)
@@ -4776,6 +4832,7 @@ var init_coordinator_prompt = __esm({
4776
4832
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
4777
4833
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
4778
4834
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
4835
+ "- 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.",
4779
4836
  "- 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.",
4780
4837
  "Workspaces:",
4781
4838
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -4794,7 +4851,12 @@ var init_coordinator_prompt = __esm({
4794
4851
  "- get_diff \u2014 compact diff summary",
4795
4852
  '- 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',
4796
4853
  "- 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.",
4797
- '- 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.',
4854
+ '- 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.',
4855
+ '- 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.',
4856
+ "Process guides:",
4857
+ "- 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.",
4858
+ "- 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.",
4859
+ "- 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.",
4798
4860
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4799
4861
  "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.",
4800
4862
  "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."
@@ -5488,6 +5550,58 @@ var init_connected_teams = __esm({
5488
5550
  }
5489
5551
  });
5490
5552
 
5553
+ // src/agents/usage.ts
5554
+ function sumOptional(a, b) {
5555
+ if (a == null && b == null) return void 0;
5556
+ return (a ?? 0) + (b ?? 0);
5557
+ }
5558
+ function fromInclusiveInputUsage(opts) {
5559
+ const totalInput = Number(opts.inputTokens) || 0;
5560
+ const outputTokens = Number(opts.outputTokens) || 0;
5561
+ const cached = Number(opts.cachedInputTokens) || 0;
5562
+ if (!totalInput && !outputTokens) return null;
5563
+ const cacheReadTokens = cached > 0 ? Math.min(cached, totalInput) : 0;
5564
+ return {
5565
+ inputTokens: Math.max(0, totalInput - cacheReadTokens),
5566
+ outputTokens,
5567
+ cacheReadTokens: cacheReadTokens || void 0
5568
+ };
5569
+ }
5570
+ function requestOccupancy(u) {
5571
+ return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
5572
+ }
5573
+ function mergeUsage(a, b) {
5574
+ return {
5575
+ inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
5576
+ outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
5577
+ cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
5578
+ cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
5579
+ lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
5580
+ };
5581
+ }
5582
+ function applyTurnUsage(current, incoming, scope = "request") {
5583
+ if (scope === "turn") {
5584
+ return {
5585
+ ...incoming,
5586
+ lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
5587
+ };
5588
+ }
5589
+ const merged = mergeUsage(current, incoming);
5590
+ return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
5591
+ }
5592
+ function totalTokens(u) {
5593
+ return u.inputTokens + u.outputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
5594
+ }
5595
+ function contextTokens(u) {
5596
+ if (u.lastRequestTokens != null && u.lastRequestTokens > 0) return u.lastRequestTokens;
5597
+ return requestOccupancy(u);
5598
+ }
5599
+ var init_usage = __esm({
5600
+ "src/agents/usage.ts"() {
5601
+ "use strict";
5602
+ }
5603
+ });
5604
+
5491
5605
  // src/agents/brightsy-targets.ts
5492
5606
  function encodeBrightsyTarget(type, id, accountId) {
5493
5607
  if (accountId) return `team:${accountId}:${type}:${id}`;
@@ -5723,15 +5837,11 @@ var init_turn_input = __esm({
5723
5837
  // src/agents/brightsy.ts
5724
5838
  function usageFromBrightsy(usage) {
5725
5839
  if (!usage) return null;
5726
- const inputTokens = Number(usage.prompt_tokens ?? 0);
5727
- const outputTokens = Number(usage.completion_tokens ?? 0);
5728
- if (!inputTokens && !outputTokens) return null;
5729
- const cached = Number(usage.prompt_tokens_details?.cached_tokens ?? 0);
5730
- return {
5731
- inputTokens,
5732
- outputTokens,
5733
- cacheReadTokens: cached || void 0
5734
- };
5840
+ return fromInclusiveInputUsage({
5841
+ inputTokens: Number(usage.prompt_tokens ?? 0),
5842
+ outputTokens: Number(usage.completion_tokens ?? 0),
5843
+ cachedInputTokens: Number(usage.prompt_tokens_details?.cached_tokens ?? 0)
5844
+ });
5735
5845
  }
5736
5846
  function parseBrightsyCliLine(line) {
5737
5847
  const trimmed = line.trim();
@@ -5947,6 +6057,7 @@ var init_brightsy = __esm({
5947
6057
  init_connected_teams();
5948
6058
  init_config();
5949
6059
  init_app_settings();
6060
+ init_usage();
5950
6061
  init_brightsy_targets();
5951
6062
  init_error_detail();
5952
6063
  init_turn_input();
@@ -6093,6 +6204,13 @@ var init_profile = __esm({
6093
6204
  function isAsarPath(filePath) {
6094
6205
  return /\.asar([/\\]|$)/.test(filePath);
6095
6206
  }
6207
+ function applyNodeLaunch(launch, args) {
6208
+ if (!launch.env.ELECTRON_RUN_AS_NODE) {
6209
+ return { file: launch.file, args, env: launch.env };
6210
+ }
6211
+ const wrapped = wrapElectronAsNodeLaunch(launch.file, args);
6212
+ return { file: wrapped.file, args: wrapped.args, env: launch.env };
6213
+ }
6096
6214
  async function resolveNodeLaunch(scriptPath) {
6097
6215
  if (isAsarPath(scriptPath)) {
6098
6216
  return {
@@ -6113,6 +6231,7 @@ async function resolveNodeLaunch(scriptPath) {
6113
6231
  var init_node_launch = __esm({
6114
6232
  "src/agents/node-launch.ts"() {
6115
6233
  "use strict";
6234
+ init_nested_electron_env();
6116
6235
  init_run();
6117
6236
  }
6118
6237
  });
@@ -6239,11 +6358,11 @@ async function resolveSideboardMcpServer() {
6239
6358
  if (entry) {
6240
6359
  const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
6241
6360
  const scriptArgs = isCli ? [entry, "mcp"] : [entry];
6242
- const launch = await resolveNodeLaunch(entry);
6361
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
6243
6362
  return {
6244
6363
  name: "sideboard",
6245
6364
  command: launch.file,
6246
- args: scriptArgs,
6365
+ args: launch.args,
6247
6366
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
6248
6367
  };
6249
6368
  }
@@ -6840,14 +6959,11 @@ async function listCodexModels() {
6840
6959
  }
6841
6960
  function usageFromCodex(usage) {
6842
6961
  if (!usage) return null;
6843
- const inputTokens = Number(usage.input_tokens ?? 0);
6844
- const outputTokens = Number(usage.output_tokens ?? 0) + Number(usage.reasoning_output_tokens ?? 0);
6845
- if (!inputTokens && !outputTokens) return null;
6846
- return {
6847
- inputTokens,
6848
- outputTokens,
6849
- cacheReadTokens: usage.cached_input_tokens ? Number(usage.cached_input_tokens) : void 0
6850
- };
6962
+ return fromInclusiveInputUsage({
6963
+ inputTokens: Number(usage.input_tokens ?? 0),
6964
+ outputTokens: Number(usage.output_tokens ?? 0),
6965
+ cachedInputTokens: Number(usage.cached_input_tokens ?? 0)
6966
+ });
6851
6967
  }
6852
6968
  function codexConfigHasNetworkAccess() {
6853
6969
  const candidates = [
@@ -6907,6 +7023,7 @@ var init_codex = __esm({
6907
7023
  init_app_settings();
6908
7024
  init_global_workspace();
6909
7025
  init_error_detail();
7026
+ init_usage();
6910
7027
  init_injected_mcp();
6911
7028
  init_turn_input();
6912
7029
  init_types();
@@ -7457,10 +7574,13 @@ var init_cursor = __esm({
7457
7574
  };
7458
7575
  const runner = cursorRunnerPath();
7459
7576
  const isTs = runner.endsWith(".ts");
7460
- const launch = await resolveNodeLaunch(runner);
7577
+ const launch = applyNodeLaunch(
7578
+ await resolveNodeLaunch(runner),
7579
+ isTs ? ["--import", "tsx", runner] : [runner]
7580
+ );
7461
7581
  return {
7462
7582
  file: launch.file,
7463
- args: isTs ? ["--import", "tsx", runner] : [runner],
7583
+ args: launch.args,
7464
7584
  cwd: thread.worktreePath,
7465
7585
  stdin: JSON.stringify(req),
7466
7586
  env: {
@@ -8534,6 +8654,7 @@ __export(index_exports, {
8534
8654
  REVIEW_REQUEST_NAME: () => REVIEW_REQUEST_NAME,
8535
8655
  REVIEW_REQUEST_PATH: () => REVIEW_REQUEST_PATH,
8536
8656
  REVIEW_REQUEST_PREFILL: () => REVIEW_REQUEST_PREFILL,
8657
+ REVIEW_REQUEST_TEMPLATE: () => REVIEW_REQUEST_TEMPLATE,
8537
8658
  SIDEBOARD_FORCE_STOP: () => SIDEBOARD_FORCE_STOP,
8538
8659
  SIDEBOARD_MCP_ALLOWED_TOOLS: () => SIDEBOARD_MCP_ALLOWED_TOOLS,
8539
8660
  SIDEBOARD_MCP_PROFILE_ENV: () => SIDEBOARD_MCP_PROFILE_ENV,
@@ -8682,9 +8803,11 @@ __export(index_exports, {
8682
8803
  formatGhLandError: () => formatGhLandError,
8683
8804
  formatGitAuthModeDirective: () => formatGitAuthModeDirective,
8684
8805
  formatIpcInvokeError: () => formatIpcInvokeError,
8806
+ formatMergePrError: () => formatMergePrError,
8685
8807
  formatMessagesAsTranscript: () => formatMessagesAsTranscript,
8686
8808
  formatPlanQuestionAnswers: () => formatPlanQuestionAnswers,
8687
8809
  formatPlanQuestionsForChat: () => formatPlanQuestionsForChat,
8810
+ formatProcessGuideDirective: () => formatProcessGuideDirective,
8688
8811
  formatRateLimitResetHint: () => formatRateLimitResetHint,
8689
8812
  formatRenameBranchDirective: () => formatRenameBranchDirective,
8690
8813
  formatSlackExternalReplyPrompt: () => formatSlackExternalReplyPrompt,
@@ -8692,9 +8815,11 @@ __export(index_exports, {
8692
8815
  formatSlackRepliesForTurn: () => formatSlackRepliesForTurn,
8693
8816
  formatSlackSignedReply: () => formatSlackSignedReply,
8694
8817
  formatTranscriptMarkdown: () => formatTranscriptMarkdown,
8818
+ formatUiReminder: () => formatUiReminder,
8695
8819
  formatWorkspaceInventory: () => formatWorkspaceInventory,
8696
8820
  formatWorktreeDirective: () => formatWorktreeDirective,
8697
8821
  formatWorktreeReminder: () => formatWorktreeReminder,
8822
+ fromInclusiveInputUsage: () => fromInclusiveInputUsage,
8698
8823
  getAdapter: () => getAdapter,
8699
8824
  getAgentSetupInfo: () => getAgentSetupInfo,
8700
8825
  getBrightsySession: () => getBrightsySession,
@@ -8765,6 +8890,7 @@ __export(index_exports, {
8765
8890
  isOrchestratorThread: () => isOrchestratorThread,
8766
8891
  isPidAlive: () => isPidAlive,
8767
8892
  isPlaceholderBranch: () => isPlaceholderBranch,
8893
+ isPrNotMergeableError: () => isPrNotMergeableError,
8768
8894
  isPresentPlanToolName: () => isPresentPlanToolName,
8769
8895
  isSessionQuotaLimit: () => isSessionQuotaLimit,
8770
8896
  isSideboardScratchPath: () => isSideboardScratchPath,
@@ -9031,7 +9157,8 @@ function agentGitPrompt(action, opts) {
9031
9157
  return "Commit, push, and open a PR in the browser.";
9032
9158
  case "resolve-conflicts": {
9033
9159
  const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
9034
- return base ? `Merge origin/${base} into this branch. Then push.` : "Fix merge conflicts.";
9160
+ const named = base ? ` (${base})` : "";
9161
+ return `Merge the remote branch${named} into your branch and resolve conflicts. Then, commit and push your changes.`;
9035
9162
  }
9036
9163
  case "merge":
9037
9164
  return "Merge PR.";
@@ -9992,43 +10119,7 @@ function normalizeParseResult(parsed) {
9992
10119
 
9993
10120
  // src/agents/spawn.ts
9994
10121
  init_path();
9995
-
9996
- // src/agents/usage.ts
9997
- function sumOptional(a, b) {
9998
- if (a == null && b == null) return void 0;
9999
- return (a ?? 0) + (b ?? 0);
10000
- }
10001
- function requestOccupancy(u) {
10002
- return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
10003
- }
10004
- function mergeUsage(a, b) {
10005
- return {
10006
- inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
10007
- outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
10008
- cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
10009
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
10010
- lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
10011
- };
10012
- }
10013
- function applyTurnUsage(current, incoming, scope = "request") {
10014
- if (scope === "turn") {
10015
- return {
10016
- ...incoming,
10017
- lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
10018
- };
10019
- }
10020
- const merged = mergeUsage(current, incoming);
10021
- return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
10022
- }
10023
- function totalTokens(u) {
10024
- return u.inputTokens + u.outputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
10025
- }
10026
- function contextTokens(u) {
10027
- if (u.lastRequestTokens != null && u.lastRequestTokens > 0) return u.lastRequestTokens;
10028
- return requestOccupancy(u);
10029
- }
10030
-
10031
- // src/agents/spawn.ts
10122
+ init_usage();
10032
10123
  async function spawnAgentTurn(thread, input, onEvent) {
10033
10124
  ensureAgentPath();
10034
10125
  if (!thread.worktreePath?.trim()) {
@@ -10146,6 +10237,7 @@ async function spawnAgentTurn(thread, input, onEvent) {
10146
10237
  }
10147
10238
 
10148
10239
  // src/index.ts
10240
+ init_usage();
10149
10241
  init_claude_mcp();
10150
10242
 
10151
10243
  // src/agents/instructions.ts
@@ -10210,7 +10302,7 @@ function formatWorktreeDirective(thread, opts) {
10210
10302
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
10211
10303
  );
10212
10304
  lines.push(
10213
- "- 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`)."
10305
+ "- 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`)."
10214
10306
  );
10215
10307
  if (thread.prUrl) {
10216
10308
  lines.push(
@@ -10242,19 +10334,30 @@ function formatWorktreeDirective(thread, opts) {
10242
10334
  '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push.'
10243
10335
  );
10244
10336
  lines.push(
10245
- '- "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.'
10337
+ '- "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.'
10246
10338
  );
10247
10339
  lines.push(
10248
10340
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
10249
10341
  );
10250
10342
  lines.push(
10251
- '- "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.'
10343
+ '- "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.'
10252
10344
  );
10345
+ lines.push("");
10346
+ lines.push(formatProcessGuideDirective());
10253
10347
  return lines.join("\n");
10254
10348
  }
10255
10349
  function formatWorktreeReminder() {
10256
10350
  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.";
10257
10351
  }
10352
+ function formatProcessGuideDirective() {
10353
+ return [
10354
+ "Process guides (recurring work only):",
10355
+ "- 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.",
10356
+ "- 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.",
10357
+ "- 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.",
10358
+ "- 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."
10359
+ ].join("\n");
10360
+ }
10258
10361
  function formatArtifactDirective() {
10259
10362
  return [
10260
10363
  "Sideboard side column (desktop UI):",
@@ -10272,9 +10375,22 @@ function formatArtifactDirective() {
10272
10375
  "Files / media browser (CMS file manager column):",
10273
10376
  "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
10274
10377
  " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
10275
- "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."
10378
+ "Multiple-choice questions (any mode \u2014 not only Plan):",
10379
+ "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.",
10380
+ "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."
10276
10381
  ].join("\n");
10277
10382
  }
10383
+ function formatUiReminder() {
10384
+ return [
10385
+ "Sideboard side column (important):",
10386
+ "There is no claude.ai Artifact tool here \u2014 that is normal.",
10387
+ "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
10388
+ "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
10389
+ "Files column: MCP present_files (brightsy account storage or memory).",
10390
+ "Do not say artifacts/CMS UI are unavailable.",
10391
+ "Multiple-choice questions: MCP ask_user (composer picker, any mode). Explain options in chat first, then wait for answers."
10392
+ ].join(" ");
10393
+ }
10278
10394
  var FILES_BY_AGENT = {
10279
10395
  claude: [
10280
10396
  "CLAUDE.md",
@@ -10291,10 +10407,11 @@ var FILES_BY_AGENT = {
10291
10407
  var MAX_CHARS_PER_FILE = 48e3;
10292
10408
  function loadAgentInstructions(worktreePath, agent) {
10293
10409
  const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
10294
- const seen = /* @__PURE__ */ new Set();
10410
+ const seenPaths = /* @__PURE__ */ new Set();
10411
+ const seenBodies = /* @__PURE__ */ new Set();
10295
10412
  const out = [];
10296
10413
  for (const rel of candidates) {
10297
- if (seen.has(rel)) continue;
10414
+ if (seenPaths.has(rel)) continue;
10298
10415
  const abs = (0, import_node_path17.join)(worktreePath, rel);
10299
10416
  if (!(0, import_node_fs20.existsSync)(abs)) continue;
10300
10417
  try {
@@ -10306,7 +10423,10 @@ function loadAgentInstructions(worktreePath, agent) {
10306
10423
 
10307
10424
  \u2026(truncated)`;
10308
10425
  }
10309
- seen.add(rel);
10426
+ const body = content.trim().replace(/\r\n/g, "\n");
10427
+ if (seenBodies.has(body)) continue;
10428
+ seenPaths.add(rel);
10429
+ seenBodies.add(body);
10310
10430
  out.push({ relativePath: rel, content });
10311
10431
  } catch {
10312
10432
  }
@@ -10384,6 +10504,8 @@ var import_node_path18 = require("path");
10384
10504
  var import_execa3 = require("execa");
10385
10505
  var import_node_readline2 = require("readline");
10386
10506
  init_settings();
10507
+ init_nested_electron_env();
10508
+ init_nested_electron_env();
10387
10509
  var PORT_RANGE_SIZE = 10;
10388
10510
  function matchSimpleGlob(pattern, name) {
10389
10511
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
@@ -10467,16 +10589,6 @@ async function captureLoginEnv() {
10467
10589
  cachedLoginEnv = { ...process.env };
10468
10590
  return { ...cachedLoginEnv };
10469
10591
  }
10470
- var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
10471
- function stripNestedElectronEnv(env) {
10472
- const out = { ...env };
10473
- for (const key of Object.keys(out)) {
10474
- if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
10475
- delete out[key];
10476
- }
10477
- }
10478
- return out;
10479
- }
10480
10592
  function buildWorkspaceScriptEnv(opts, baseEnv) {
10481
10593
  const env = stripNestedElectronEnv({
10482
10594
  ...baseEnv ?? process.env
@@ -14267,6 +14379,10 @@ File: src/client/frontends/desktop/core/UserData.ts
14267
14379
 
14268
14380
  **Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
14269
14381
  </example>
14382
+
14383
+ ## Growing the rules
14384
+
14385
+ 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\`.
14270
14386
  `;
14271
14387
 
14272
14388
  // src/review/request-review.ts
@@ -15067,14 +15183,7 @@ var Orchestrator = class {
15067
15183
  SLACK_REPLY_FORMATTING
15068
15184
  ].join("\n") : null
15069
15185
  ].filter(Boolean).join("\n") : null;
15070
- const artifactReminder = thread.agent !== "brightsy" ? [
15071
- "Sideboard side column (important):",
15072
- "There is no claude.ai Artifact tool here \u2014 that is normal.",
15073
- "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
15074
- "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
15075
- "Files column: MCP present_files (brightsy account storage or memory).",
15076
- "Do not say artifacts/CMS UI are unavailable."
15077
- ].join(" ") : null;
15186
+ const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
15078
15187
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
15079
15188
  const slackReplyContext = formatSlackRepliesForTurn(
15080
15189
  pendingSlackExternalReplies(thread.messages)
@@ -15768,7 +15877,9 @@ var Orchestrator = class {
15768
15877
  reviewDecision: null,
15769
15878
  baseRefName: "",
15770
15879
  headRefName: "",
15771
- isInMergeQueue: false
15880
+ isInMergeQueue: false,
15881
+ mergeable: null,
15882
+ mergeStateStatus: null
15772
15883
  };
15773
15884
  await this.persistPrMetaAndMaybeArchive(thread, metaLike);
15774
15885
  return { url: metaLike.url, state };
@@ -17130,7 +17241,7 @@ async function startMcpServer() {
17130
17241
  );
17131
17242
  server.tool(
17132
17243
  "ask_user",
17133
- "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.",
17244
+ "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.",
17134
17245
  {
17135
17246
  questions: import_zod3.z.array(
17136
17247
  import_zod3.z.object({
@@ -17393,7 +17504,7 @@ async function startMcpServer() {
17393
17504
  );
17394
17505
  server.tool(
17395
17506
  "send_to_thread",
17396
- "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.",
17507
+ '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.',
17397
17508
  {
17398
17509
  ref: import_zod3.z.string(),
17399
17510
  prompt: import_zod3.z.string(),
@@ -17489,7 +17600,7 @@ async function startMcpServer() {
17489
17600
  );
17490
17601
  server.tool(
17491
17602
  "archive_thread",
17492
- "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).",
17603
+ "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.",
17493
17604
  { ref: import_zod3.z.string() },
17494
17605
  async ({ ref }) => {
17495
17606
  const t = orch.getThread(ref);
@@ -17597,7 +17708,7 @@ async function startMcpServer() {
17597
17708
  );
17598
17709
  server.tool(
17599
17710
  "ask_git",
17600
- "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.",
17711
+ "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.",
17601
17712
  {
17602
17713
  ref: import_zod3.z.string().describe("Worktree thread id/ref"),
17603
17714
  action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
@@ -17898,7 +18009,7 @@ async function startMcpServer() {
17898
18009
  );
17899
18010
  server.tool(
17900
18011
  "get_pr_stack",
17901
- "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.",
18012
+ "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).",
17902
18013
  { ref: import_zod3.z.string() },
17903
18014
  async ({ ref }) => {
17904
18015
  const stack = await orch.getPrStack(ref);
@@ -20007,6 +20118,7 @@ async function startSlackRelayServer(opts) {
20007
20118
  REVIEW_REQUEST_NAME,
20008
20119
  REVIEW_REQUEST_PATH,
20009
20120
  REVIEW_REQUEST_PREFILL,
20121
+ REVIEW_REQUEST_TEMPLATE,
20010
20122
  SIDEBOARD_FORCE_STOP,
20011
20123
  SIDEBOARD_MCP_ALLOWED_TOOLS,
20012
20124
  SIDEBOARD_MCP_PROFILE_ENV,
@@ -20155,9 +20267,11 @@ async function startSlackRelayServer(opts) {
20155
20267
  formatGhLandError,
20156
20268
  formatGitAuthModeDirective,
20157
20269
  formatIpcInvokeError,
20270
+ formatMergePrError,
20158
20271
  formatMessagesAsTranscript,
20159
20272
  formatPlanQuestionAnswers,
20160
20273
  formatPlanQuestionsForChat,
20274
+ formatProcessGuideDirective,
20161
20275
  formatRateLimitResetHint,
20162
20276
  formatRenameBranchDirective,
20163
20277
  formatSlackExternalReplyPrompt,
@@ -20165,9 +20279,11 @@ async function startSlackRelayServer(opts) {
20165
20279
  formatSlackRepliesForTurn,
20166
20280
  formatSlackSignedReply,
20167
20281
  formatTranscriptMarkdown,
20282
+ formatUiReminder,
20168
20283
  formatWorkspaceInventory,
20169
20284
  formatWorktreeDirective,
20170
20285
  formatWorktreeReminder,
20286
+ fromInclusiveInputUsage,
20171
20287
  getAdapter,
20172
20288
  getAgentSetupInfo,
20173
20289
  getBrightsySession,
@@ -20238,6 +20354,7 @@ async function startSlackRelayServer(opts) {
20238
20354
  isOrchestratorThread,
20239
20355
  isPidAlive,
20240
20356
  isPlaceholderBranch,
20357
+ isPrNotMergeableError,
20241
20358
  isPresentPlanToolName,
20242
20359
  isSessionQuotaLimit,
20243
20360
  isSideboardScratchPath,