@sideboard-ai/core 0.1.191 → 0.1.193

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 (68) hide show
  1. package/dist/{abletime-JFMBCSIQ.js → abletime-QHKWGTBY.js} +4 -3
  2. package/dist/{abletime-53T7G667.js → abletime-UX53JTIH.js} +4 -3
  3. package/dist/agents/cursor-runner.js +4 -4
  4. package/dist/{agents-DTPVTDB5.js → agents-MN4GSQTL.js} +19 -17
  5. package/dist/{agents-5OBZETUV.js → agents-OK5VD25T.js} +10 -8
  6. package/dist/{app-settings-663VQWUV.js → app-settings-LO7UFHQ6.js} +5 -2
  7. package/dist/{app-settings-74HBAAH7.js → app-settings-QYLNKRK4.js} +5 -2
  8. package/dist/branch-prefix-7F37FIPX.js +10 -0
  9. package/dist/branch-prefix-EVW2KHGQ.js +12 -0
  10. package/dist/{caffeinate-hold-SK2QISPE.js → caffeinate-hold-22VFDPSO.js} +2 -2
  11. package/dist/{caffeinate-hold-DBKMRZZO.js → caffeinate-hold-OK4JJ5PP.js} +2 -2
  12. package/dist/{chunk-GPSIZGEZ.js → chunk-3DX2T2KR.js} +181 -172
  13. package/dist/{chunk-HVIEYTUK.js → chunk-3OHR73VK.js} +1 -1
  14. package/dist/{chunk-VIODP5PT.js → chunk-4OVCJPVI.js} +13 -11
  15. package/dist/{chunk-UBQIELFS.js → chunk-4QJRH5DL.js} +1 -1
  16. package/dist/{chunk-AO35MGOE.js → chunk-4TYXWPAJ.js} +1 -1
  17. package/dist/chunk-64QSHOKV.js +869 -0
  18. package/dist/{chunk-FY64F2ZQ.js → chunk-7AJ7OZZF.js} +4 -4
  19. package/dist/{chunk-WLPXZFRE.js → chunk-7J72QJOM.js} +1 -1
  20. package/dist/chunk-7SNJMKD6.js +20 -0
  21. package/dist/{chunk-WXKXSZN4.js → chunk-BIKCWYRP.js} +16 -873
  22. package/dist/{chunk-J7LQVLFQ.js → chunk-BWRS6GAS.js} +19 -1
  23. package/dist/{chunk-GOZ6T3AG.js → chunk-CJFBSQIB.js} +1 -1
  24. package/dist/{chunk-GBWKMWUQ.js → chunk-EBPBGQ4P.js} +13 -13
  25. package/dist/{chunk-JTJHOYCU.js → chunk-EQNRWDLN.js} +10 -4
  26. package/dist/{chunk-5RRAKGPK.js → chunk-EQXXGOMD.js} +192 -182
  27. package/dist/{chunk-XXEIF2IL.js → chunk-HIX2VDRE.js} +13 -11
  28. package/dist/chunk-ILWQT2UN.js +55 -0
  29. package/dist/chunk-JTWKGOFI.js +23 -0
  30. package/dist/{chunk-TCQAFTSV.js → chunk-KJBYIIJU.js} +1 -1
  31. package/dist/{chunk-OKZFTOM2.js → chunk-KORE4WOF.js} +19 -1
  32. package/dist/{chunk-OLDZXO53.js → chunk-MQC2VA5U.js} +14 -14
  33. package/dist/{chunk-4M2AK3O5.js → chunk-O4BMPY5Y.js} +1 -1
  34. package/dist/chunk-PBEPPXMY.js +57 -0
  35. package/dist/{chunk-IEEAVWAP.js → chunk-PE337IM2.js} +7 -7
  36. package/dist/chunk-RCWHL4TK.js +866 -0
  37. package/dist/{chunk-EFVFB2AH.js → chunk-WFUDY6PC.js} +12 -5
  38. package/dist/{chunk-XBLN52RZ.js → chunk-XPKKCFRT.js} +9 -866
  39. package/dist/{chunk-OCQPTUR7.js → chunk-YWI3IBP2.js} +1 -1
  40. package/dist/{connected-teams-WOYCH7FR.js → connected-teams-PU4AMFNA.js} +2 -2
  41. package/dist/{connected-teams-ON2KIKQX.js → connected-teams-SYQQGCXD.js} +2 -2
  42. package/dist/{coordinator-prompt-LWKOL3AZ.js → coordinator-prompt-GXJIPWNA.js} +7 -5
  43. package/dist/{coordinator-prompt-MSC44OOM.js → coordinator-prompt-WB65HIZ3.js} +9 -7
  44. package/dist/{cursor-recover-NDMLLNYX.js → cursor-recover-DTQAUDGO.js} +2 -2
  45. package/dist/{cursor-recover-L5MD6MBO.js → cursor-recover-TXVTLAMA.js} +1 -1
  46. package/dist/github-DWROJOB7.js +11 -0
  47. package/dist/github-IIF7AQNO.js +9 -0
  48. package/dist/{global-workspace-6G5JJH2E.js → global-workspace-BZ6V6TEX.js} +7 -5
  49. package/dist/{global-workspace-XATJTV3N.js → global-workspace-MMK5UFUZ.js} +9 -7
  50. package/dist/index.cjs +332 -205
  51. package/dist/index.d.cts +53 -9
  52. package/dist/index.d.ts +53 -9
  53. package/dist/index.js +147 -136
  54. package/dist/mcp/run-stdio.cjs +316 -201
  55. package/dist/mcp/run-stdio.js +84 -85
  56. package/dist/{orchestrator-F3CXDBZB.js → orchestrator-4KI6VIFG.js} +16 -12
  57. package/dist/{orchestrator-2X6LYBQI.js → orchestrator-SFOT6Q6F.js} +15 -11
  58. package/dist/{paths-RRCLNSJB.js → paths-C2VERLAE.js} +1 -1
  59. package/dist/{paths-53TLA7VQ.js → paths-LOXCISLX.js} +1 -1
  60. package/dist/{thread-store-ZL6GDIDP.js → thread-store-CO77HV2K.js} +2 -2
  61. package/dist/{thread-store-RXXNSRVO.js → thread-store-M4B52FUS.js} +2 -2
  62. package/dist/{workspaces-LMFCRXTT.js → workspaces-ACMZMKPD.js} +7 -5
  63. package/dist/{workspaces-FWKPWSXV.js → workspaces-KPJS7GLY.js} +9 -7
  64. package/dist/{worktree-7LOSSD7G.js → worktree-6IEVF3GN.js} +15 -12
  65. package/dist/{worktree-LPWTX2NA.js → worktree-UW6DNBIE.js} +20 -17
  66. package/dist/worktree-labels-3XDS6O3U.js +22 -0
  67. package/dist/worktree-labels-PEDOJBMD.js +24 -0
  68. package/package.json +1 -1
@@ -269,9 +269,12 @@ function parseSettingsFile(path, source) {
269
269
  const prompts = {
270
270
  renameBranch: typeof promptsRaw.rename_branch === "string" ? promptsRaw.rename_branch : void 0,
271
271
  createPr: typeof promptsRaw.create_pr === "string" ? promptsRaw.create_pr : void 0,
272
- general: typeof promptsRaw.general === "string" ? promptsRaw.general : void 0
272
+ general: typeof promptsRaw.general === "string" ? promptsRaw.general : void 0,
273
+ resolveMergeConflicts: typeof promptsRaw.resolve_merge_conflicts === "string" ? promptsRaw.resolve_merge_conflicts : void 0
273
274
  };
274
- const hasPrompts = Boolean(prompts.renameBranch || prompts.createPr || prompts.general);
275
+ const hasPrompts = Boolean(
276
+ prompts.renameBranch || prompts.createPr || prompts.general || prompts.resolveMergeConflicts
277
+ );
275
278
  return {
276
279
  source,
277
280
  setup,
@@ -297,9 +300,12 @@ function mergeSettings(base, overlay) {
297
300
  const prompts = {
298
301
  renameBranch: overlay.prompts?.renameBranch ?? base.prompts?.renameBranch,
299
302
  createPr: overlay.prompts?.createPr ?? base.prompts?.createPr,
300
- general: overlay.prompts?.general ?? base.prompts?.general
303
+ general: overlay.prompts?.general ?? base.prompts?.general,
304
+ resolveMergeConflicts: overlay.prompts?.resolveMergeConflicts ?? base.prompts?.resolveMergeConflicts
301
305
  };
302
- const hasPrompts = Boolean(prompts.renameBranch || prompts.createPr || prompts.general);
306
+ const hasPrompts = Boolean(
307
+ prompts.renameBranch || prompts.createPr || prompts.general || prompts.resolveMergeConflicts
308
+ );
303
309
  return {
304
310
  source: overlay.source,
305
311
  setup: overlay.setup ?? base.setup,
@@ -2980,6 +2986,18 @@ var init_teams = __esm({
2980
2986
  });
2981
2987
 
2982
2988
  // src/git/worktree-labels.ts
2989
+ var worktree_labels_exports = {};
2990
+ __export(worktree_labels_exports, {
2991
+ branchDisplayLabel: () => branchDisplayLabel,
2992
+ isPlaceholderBranch: () => isPlaceholderBranch,
2993
+ normalizeWorktreePath: () => normalizeWorktreePath,
2994
+ threadDisplayLabel: () => threadDisplayLabel,
2995
+ ticketSlugForBranch: () => ticketSlugForBranch,
2996
+ worktreeDisplayLabel: () => worktreeDisplayLabel,
2997
+ worktreeDisplayLabelForGroup: () => worktreeDisplayLabelForGroup,
2998
+ worktreeNameFromPath: () => worktreeNameFromPath,
2999
+ worktreeSlugForTicket: () => worktreeSlugForTicket
3000
+ });
2983
3001
  function normalizeWorktreePath(worktreePath) {
2984
3002
  const trimmed = (worktreePath ?? "").replace(/\/+$/, "") || "/";
2985
3003
  const absolute = trimmed.startsWith("/");
@@ -3434,6 +3452,32 @@ var init_secret_vault = __esm({
3434
3452
  }
3435
3453
  });
3436
3454
 
3455
+ // src/git/branch-prefix.ts
3456
+ var branch_prefix_exports = {};
3457
+ __export(branch_prefix_exports, {
3458
+ examplePrefixedBranch: () => examplePrefixedBranch,
3459
+ resolveGitBranchPrefix: () => resolveGitBranchPrefix,
3460
+ sanitizeGitBranchPrefix: () => sanitizeGitBranchPrefix
3461
+ });
3462
+ function sanitizeGitBranchPrefix(raw) {
3463
+ const first = (raw ?? "").trim().replace(/^\/+|\/+$/g, "").split("/")[0] ?? "";
3464
+ const slug = first.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 39);
3465
+ return slug || null;
3466
+ }
3467
+ function resolveGitBranchPrefix(opts) {
3468
+ return sanitizeGitBranchPrefix(opts.setting) ?? sanitizeGitBranchPrefix(opts.githubLogin);
3469
+ }
3470
+ function examplePrefixedBranch(prefix, ticket) {
3471
+ const head = sanitizeGitBranchPrefix(prefix);
3472
+ const slug = ticket?.trim() ? `${ticket.trim()}-eng-fix-thing` : "bb-1234-eng-fix-thing";
3473
+ return head ? `${head}/${slug}` : slug;
3474
+ }
3475
+ var init_branch_prefix = __esm({
3476
+ "src/git/branch-prefix.ts"() {
3477
+ "use strict";
3478
+ }
3479
+ });
3480
+
3437
3481
  // src/store/issue-source-labels.ts
3438
3482
  function issueSourceLabel(source) {
3439
3483
  if (source && source in ISSUE_SOURCE_LABELS) {
@@ -3502,6 +3546,7 @@ __export(app_settings_exports, {
3502
3546
  getGithubPat: () => getGithubPat,
3503
3547
  getIssueSource: () => getIssueSource,
3504
3548
  getLinearApiKey: () => getLinearApiKey,
3549
+ gitBranchPrefixSetting: () => gitBranchPrefixSetting,
3505
3550
  harnessEnvKey: () => harnessEnvKey,
3506
3551
  isAbleTimeConnected: () => isAbleTimeConnected,
3507
3552
  isIssueSourceConnected: () => isIssueSourceConnected,
@@ -3877,6 +3922,10 @@ function normalizeAdvanced(raw) {
3877
3922
  if (typeof source.autoRenameBranch === "boolean") {
3878
3923
  out.autoRenameBranch = source.autoRenameBranch;
3879
3924
  }
3925
+ if (typeof source.branchPrefix === "string") {
3926
+ const prefix = sanitizeGitBranchPrefix(source.branchPrefix);
3927
+ if (prefix) out.branchPrefix = prefix;
3928
+ }
3880
3929
  if (typeof source.autoRunAfterSetup === "boolean") {
3881
3930
  out.autoRunAfterSetup = source.autoRunAfterSetup;
3882
3931
  }
@@ -4730,6 +4779,13 @@ function updateAdvancedSettings(patch) {
4730
4779
  if (typeof patch.autoRenameBranch === "boolean") {
4731
4780
  advanced.autoRenameBranch = patch.autoRenameBranch;
4732
4781
  }
4782
+ if (patch.branchPrefix === null || patch.branchPrefix === "") {
4783
+ delete advanced.branchPrefix;
4784
+ } else if (typeof patch.branchPrefix === "string") {
4785
+ const prefix = sanitizeGitBranchPrefix(patch.branchPrefix);
4786
+ if (prefix) advanced.branchPrefix = prefix;
4787
+ else delete advanced.branchPrefix;
4788
+ }
4733
4789
  if (typeof patch.autoRunAfterSetup === "boolean") {
4734
4790
  advanced.autoRunAfterSetup = patch.autoRunAfterSetup;
4735
4791
  }
@@ -4790,6 +4846,9 @@ function updateAdvancedSettings(patch) {
4790
4846
  function autoRenameBranchEnabled(settings = loadAppSettings()) {
4791
4847
  return settings.advanced.autoRenameBranch !== false;
4792
4848
  }
4849
+ function gitBranchPrefixSetting(settings = loadAppSettings()) {
4850
+ return sanitizeGitBranchPrefix(settings.advanced.branchPrefix);
4851
+ }
4793
4852
  function autoRunAfterSetupEnabled(settings = loadAppSettings()) {
4794
4853
  return Boolean(settings.advanced.autoRunAfterSetup);
4795
4854
  }
@@ -4935,6 +4994,7 @@ var init_app_settings = __esm({
4935
4994
  init_paths();
4936
4995
  init_private_file();
4937
4996
  init_secret_vault();
4997
+ init_branch_prefix();
4938
4998
  init_issue_source_labels();
4939
4999
  init_account_profile();
4940
5000
  HARNESS_ENV_KEYS = {
@@ -7973,8 +8033,8 @@ var init_coordinator_prompt = __esm({
7973
8033
  "- start_board_card \u2014 same as create_thread for a ticket/PR/named branch (attaches issue text when resolvable). Then send_to_thread.",
7974
8034
  "- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
7975
8035
  "- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
7976
- "- send_to_thread \u2014 queue a prompt (start/continue a chat turn). force_stop: true only to replace a wrong in-flight request \u2014 never to check in, resume after a halt notice, or because wait_for_turn returned stillRunning (that kills the child mid-thought)",
7977
- "- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply (includes last-turn usage / costUsd when the child agent reported it). wait_for_turn returns within ~45s even if the child is still working (MCP clients kill longer tool calls). stillRunning is the source of truth \u2014 if it is false, the child is not working; do not tell the user it is running or \u201Cwaiting for a gate,\u201D even if status still says running/queued. If stillRunning is true, progress is a live snapshot of tools/thinking \u2014 call wait_for_turn again. status=queued with no lastActivityAt means the child has not started yet (concurrency cap) \u2014 keep waiting; do not force_stop or send a check-in. Do not send \u201Care you stuck?\u201D or assume a hang while lastActivityAt is recent. If stillRunning stays true across many waits with the same lastActivityAt, the child is stuck \u2014 tell the user; do not invent a gate. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success. On status stopped or broken (or incomplete=true), the child was interrupted or died \u2014 resume with send_to_thread or tell the user; never treat stopped as a finished turn. Sideboard also injects a notice into this chat when a child stops unexpectedly.",
8036
+ "- send_to_thread \u2014 steer a prompt (start/continue a chat turn; Settings \u2192 Follow-up, default steer: interrupt and start now). force_stop: true only to replace a wrong in-flight request and clear the inbox \u2014 never to check in, resume after a halt notice, or because wait_for_turn returned stillRunning (that interrupts the child mid-thought)",
8037
+ "- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply (includes last-turn usage / costUsd when the child agent reported it). wait_for_turn returns within ~45s even if the child is still working (MCP clients kill longer tool calls). stillRunning is the source of truth \u2014 if it is false, the child is not working; do not tell the user it is running or \u201Cwaiting for a gate,\u201D even if status still says running/queued. If stillRunning is true, progress is a live snapshot of tools/thinking \u2014 call wait_for_turn again. status=queued with no lastActivityAt means the child has not started yet (concurrency cap) \u2014 keep waiting; do not force_stop or send_to_thread a check-in (that steers / interrupts). Do not send \u201Care you stuck?\u201D or assume a hang while lastActivityAt is recent. If stillRunning stays true across many waits with the same lastActivityAt, the child is stuck \u2014 tell the user; do not invent a gate. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success. On status stopped or broken (or incomplete=true), the child was interrupted or died \u2014 resume with send_to_thread or tell the user; never treat stopped as a finished turn. Sideboard also injects a notice into this chat when a child stops unexpectedly.",
7978
8038
  "- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
7979
8039
  "- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
7980
8040
  "Setup / run:",
@@ -7984,7 +8044,7 @@ var init_coordinator_prompt = __esm({
7984
8044
  "- get_diff \u2014 compact diff summary",
7985
8045
  "- get_pr_checks \u2014 snapshot of a worktree thread's PR checks (null = no PR). Use this to inspect status. If the user gave a goal, the worktree agent watches with `gh pr checks --watch` \u2014 do not poll for the human.",
7986
8046
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .claude/skills/review/SKILL.md when present, else .context/review.md copied from .sideboard/review.md / stock; sends "Review changes in this workspace."); then wait_for_turn (loop while stillRunning) / get_turn_result on the returned id. The review stays in that chat so they can read it and type next steps \u2014 do not tell the child to comment or update the PR or ticket.',
7987
- "- ask_git \u2014 commit & push, open a draft PR, mark ready for review, 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 (loop while stillRunning). Prefer this over paraphrasing. If ask_git / get_thread lastError says the GraphQL/PR body is too long, the branch is already pushed \u2014 send_to_thread so the worktree agent runs `gh pr create --body-file` with a short description. Do not invent SSH or auth failures from that error. If the user gave a goal (Greptile 5/5, CI green), send_to_thread that goal so the worktree agent enters the watch-fix-push loop \u2014 do not tell the human to poll.",
8047
+ "- ask_git \u2014 commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (Resolve, Create PR, \u2026). Always steers the worktree agent; then wait_for_turn (loop while stillRunning). Prefer this over paraphrasing. If the user gave a goal (Greptile 5/5, CI green), send_to_thread that goal so the worktree agent enters the watch-fix-push loop \u2014 do not tell the human to poll.",
7988
8048
  '- 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.',
7989
8049
  '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Ready for review.", "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.',
7990
8050
  "Process guides:",
@@ -12431,87 +12491,6 @@ var init_spawn = __esm({
12431
12491
  }
12432
12492
  });
12433
12493
 
12434
- // src/land/pr-metadata.ts
12435
- function looksGeneric(title) {
12436
- const t = title.trim();
12437
- if (!t) return true;
12438
- if (t.length < 4) return true;
12439
- if (GENERIC_TITLE_RE.test(t)) return true;
12440
- if (!/[\s:/\-_]/.test(t) && t.length <= 24) return true;
12441
- return false;
12442
- }
12443
- function truncateTitle(title, max = 72) {
12444
- const t = title.trim().replace(/\s+/g, " ");
12445
- if (t.length <= max) return t;
12446
- return `${t.slice(0, max - 1).trimEnd()}\u2026`;
12447
- }
12448
- function titleFromPaths(paths) {
12449
- if (paths.length === 0) return null;
12450
- const top = paths.slice(0, 3).map((p) => p.replace(/\/$/, ""));
12451
- if (paths.length === 1) return truncateTitle(`Update ${top[0]}`);
12452
- if (paths.length <= 3) return truncateTitle(`Update ${top.join(", ")}`);
12453
- return truncateTitle(`Update ${top[0]} and ${paths.length - 1} more files`);
12454
- }
12455
- async function suggestPrMetadata(worktreePath, opts) {
12456
- const [{ stdout: logOut }, { stdout: nameStatus }, { stdout: statOut }] = await Promise.all([
12457
- git(["log", "--format=%s", `${opts.base}..HEAD`], worktreePath, {
12458
- reject: false
12459
- }),
12460
- git(["diff", "--name-only", `${opts.base}...HEAD`], worktreePath, {
12461
- reject: false
12462
- }),
12463
- git(["diff", "--stat", `${opts.base}...HEAD`], worktreePath, {
12464
- reject: false
12465
- })
12466
- ]);
12467
- const { stdout: dirtyNames } = await git(["diff", "--name-only", "HEAD"], worktreePath, {
12468
- reject: false
12469
- });
12470
- const { stdout: untracked } = await git(
12471
- ["ls-files", "--others", "--exclude-standard"],
12472
- worktreePath,
12473
- { reject: false }
12474
- );
12475
- const subjects = logOut.split("\n").map((s) => s.trim()).filter(Boolean);
12476
- const paths = [
12477
- ...new Set(
12478
- [...nameStatus.split("\n"), ...dirtyNames.split("\n"), ...untracked.split("\n")].map((p) => p.trim()).filter(Boolean)
12479
- )
12480
- ].sort((a, b) => a.localeCompare(b));
12481
- const meaningfulSubjects = subjects.filter((s) => !looksGeneric(s));
12482
- const fallback = opts.fallbackTitle?.trim() || null;
12483
- const fallbackOk = fallback && !looksGeneric(fallback) ? fallback : null;
12484
- const title = truncateTitle(
12485
- meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? "Sideboard changes"
12486
- );
12487
- const commitMessage = meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? `sideboard: ${title}`;
12488
- const summaryLines = meaningfulSubjects.length > 0 ? meaningfulSubjects.slice(0, 12).map((s) => `- ${s}`) : paths.slice(0, 20).map((p) => `- \`${p}\``);
12489
- const bodyParts = [
12490
- "## Summary",
12491
- summaryLines.length > 0 ? summaryLines.join("\n") : "- Changes landed via Sideboard.",
12492
- ""
12493
- ];
12494
- if (statOut.trim()) {
12495
- bodyParts.push("## Diff", "```", statOut.trim(), "```", "");
12496
- }
12497
- if (opts.sourceLabel) {
12498
- bodyParts.push(`_Landed via Sideboard from ${opts.sourceLabel}_`);
12499
- }
12500
- return {
12501
- title,
12502
- body: bodyParts.join("\n").trim() + "\n",
12503
- commitMessage: truncateTitle(commitMessage, 72)
12504
- };
12505
- }
12506
- var GENERIC_TITLE_RE;
12507
- var init_pr_metadata = __esm({
12508
- "src/land/pr-metadata.ts"() {
12509
- "use strict";
12510
- init_run();
12511
- GENERIC_TITLE_RE = /^(sideboard:|thread\/|wip\b|tmp\b|update\b|changes?\b|misc\b|stuff\b)/i;
12512
- }
12513
- });
12514
-
12515
12494
  // src/git/agent-git-actions.ts
12516
12495
  function agentGitPrompt(action, opts) {
12517
12496
  switch (action) {
@@ -12532,6 +12511,19 @@ function agentGitPrompt(action, opts) {
12532
12511
  return "Merge PR.";
12533
12512
  }
12534
12513
  }
12514
+ function resolveSidebarGitPrompt(action, opts) {
12515
+ if (action === "create-pr" || action === "create-draft" || action === "create-web") {
12516
+ const custom = opts?.createPr?.trim();
12517
+ if (custom) return custom;
12518
+ return agentGitPrompt(action === "create-pr" ? "create-draft" : action);
12519
+ }
12520
+ if (action === "resolve-conflicts") {
12521
+ const custom = opts?.resolveMergeConflicts?.trim();
12522
+ if (custom) return custom;
12523
+ return agentGitPrompt("resolve-conflicts", opts);
12524
+ }
12525
+ return agentGitPrompt(action, opts);
12526
+ }
12535
12527
  function gitActionExpansion(action, base) {
12536
12528
  switch (action) {
12537
12529
  case "commit-push":
@@ -14303,8 +14295,8 @@ var init_wait_for_turn = __esm({
14303
14295
  "src/mcp/wait-for-turn.ts"() {
14304
14296
  "use strict";
14305
14297
  MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
14306
- MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
14307
- MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume it failed to start.";
14298
+ MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts) or assume a hang while progress is updating.";
14299
+ MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume it failed to start.";
14308
14300
  MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
14309
14301
  MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
14310
14302
  MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
@@ -17785,6 +17777,87 @@ var init_diff = __esm({
17785
17777
  }
17786
17778
  });
17787
17779
 
17780
+ // src/land/pr-metadata.ts
17781
+ function looksGeneric(title) {
17782
+ const t = title.trim();
17783
+ if (!t) return true;
17784
+ if (t.length < 4) return true;
17785
+ if (GENERIC_TITLE_RE.test(t)) return true;
17786
+ if (!/[\s:/\-_]/.test(t) && t.length <= 24) return true;
17787
+ return false;
17788
+ }
17789
+ function truncateTitle(title, max = 72) {
17790
+ const t = title.trim().replace(/\s+/g, " ");
17791
+ if (t.length <= max) return t;
17792
+ return `${t.slice(0, max - 1).trimEnd()}\u2026`;
17793
+ }
17794
+ function titleFromPaths(paths) {
17795
+ if (paths.length === 0) return null;
17796
+ const top = paths.slice(0, 3).map((p) => p.replace(/\/$/, ""));
17797
+ if (paths.length === 1) return truncateTitle(`Update ${top[0]}`);
17798
+ if (paths.length <= 3) return truncateTitle(`Update ${top.join(", ")}`);
17799
+ return truncateTitle(`Update ${top[0]} and ${paths.length - 1} more files`);
17800
+ }
17801
+ async function suggestPrMetadata(worktreePath, opts) {
17802
+ const [{ stdout: logOut }, { stdout: nameStatus }, { stdout: statOut }] = await Promise.all([
17803
+ git(["log", "--format=%s", `${opts.base}..HEAD`], worktreePath, {
17804
+ reject: false
17805
+ }),
17806
+ git(["diff", "--name-only", `${opts.base}...HEAD`], worktreePath, {
17807
+ reject: false
17808
+ }),
17809
+ git(["diff", "--stat", `${opts.base}...HEAD`], worktreePath, {
17810
+ reject: false
17811
+ })
17812
+ ]);
17813
+ const { stdout: dirtyNames } = await git(["diff", "--name-only", "HEAD"], worktreePath, {
17814
+ reject: false
17815
+ });
17816
+ const { stdout: untracked } = await git(
17817
+ ["ls-files", "--others", "--exclude-standard"],
17818
+ worktreePath,
17819
+ { reject: false }
17820
+ );
17821
+ const subjects = logOut.split("\n").map((s) => s.trim()).filter(Boolean);
17822
+ const paths = [
17823
+ ...new Set(
17824
+ [...nameStatus.split("\n"), ...dirtyNames.split("\n"), ...untracked.split("\n")].map((p) => p.trim()).filter(Boolean)
17825
+ )
17826
+ ].sort((a, b) => a.localeCompare(b));
17827
+ const meaningfulSubjects = subjects.filter((s) => !looksGeneric(s));
17828
+ const fallback = opts.fallbackTitle?.trim() || null;
17829
+ const fallbackOk = fallback && !looksGeneric(fallback) ? fallback : null;
17830
+ const title = truncateTitle(
17831
+ meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? "Sideboard changes"
17832
+ );
17833
+ const commitMessage = meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? `sideboard: ${title}`;
17834
+ const summaryLines = meaningfulSubjects.length > 0 ? meaningfulSubjects.slice(0, 12).map((s) => `- ${s}`) : paths.slice(0, 20).map((p) => `- \`${p}\``);
17835
+ const bodyParts = [
17836
+ "## Summary",
17837
+ summaryLines.length > 0 ? summaryLines.join("\n") : "- Changes landed via Sideboard.",
17838
+ ""
17839
+ ];
17840
+ if (statOut.trim()) {
17841
+ bodyParts.push("## Diff", "```", statOut.trim(), "```", "");
17842
+ }
17843
+ if (opts.sourceLabel) {
17844
+ bodyParts.push(`_Landed via Sideboard from ${opts.sourceLabel}_`);
17845
+ }
17846
+ return {
17847
+ title,
17848
+ body: bodyParts.join("\n").trim() + "\n",
17849
+ commitMessage: truncateTitle(commitMessage, 72)
17850
+ };
17851
+ }
17852
+ var GENERIC_TITLE_RE;
17853
+ var init_pr_metadata = __esm({
17854
+ "src/land/pr-metadata.ts"() {
17855
+ "use strict";
17856
+ init_run();
17857
+ GENERIC_TITLE_RE = /^(sideboard:|thread\/|wip\b|tmp\b|update\b|changes?\b|misc\b|stuff\b)/i;
17858
+ }
17859
+ });
17860
+
17788
17861
  // src/land/land.ts
17789
17862
  async function previewLand(thread) {
17790
17863
  if (thread.sourceIsFork) {
@@ -18315,13 +18388,14 @@ function formatRenameBranchDirective(thread, opts) {
18315
18388
  if (!isPlaceholderBranch(thread.branchName, thread.worktreePath)) return null;
18316
18389
  const dir = worktreeNameFromPath(thread.worktreePath);
18317
18390
  const ticket = thread.sourceType === "ticket" ? ticketSlugForBranch(thread.sourceRef ?? "") : null;
18318
- const example = ticket ? `\`feat/${ticket}-dark-mode\` or \`fix/${ticket}-panel-width\`` : "`fix/panel-width` or `feat/dark-mode`";
18391
+ const prefix = sanitizeGitBranchPrefix(opts?.branchPrefix);
18392
+ const example = examplePrefixedBranch(prefix, ticket);
18393
+ const shape = prefix ? `\`${prefix}/<ticket-or-task>-<short-kebab-description>\`` : "`<ticket-or-task>-<short-kebab-description>`";
18319
18394
  const lines = [
18320
18395
  "Branch naming (do this early in the turn):",
18321
18396
  `- Current branch \`${thread.branchName}\` is a temporary placeholder. The worktree folder \`${dir}\` is a stable nickname \u2014 do not rename or leave that directory.`,
18322
- `- Rename the git branch to a short kebab-case name that describes this task (what you are changing), e.g. ${example}:`,
18397
+ `- Rename the git branch to ${shape}, e.g. \`${example}\`:`,
18323
18398
  " `git branch -m <new-name>`",
18324
- "- Prefer Conventional Commits style prefixes when they fit (`fix/`, `feat/`, `chore/`, `docs/`).",
18325
18399
  ...ticket ? [
18326
18400
  `- Keep ticket \`${ticket}\` in the new branch name so the issue stays findable.`
18327
18401
  ] : [],
@@ -18505,6 +18579,7 @@ var init_instructions = __esm({
18505
18579
  import_node_fs48 = require("fs");
18506
18580
  import_node_path45 = require("path");
18507
18581
  init_git_auth_mode();
18582
+ init_branch_prefix();
18508
18583
  init_worktree_labels();
18509
18584
  init_detached_job_path();
18510
18585
  PR_GOAL_RE = /greptile|\bci\b.*\b(green|pass)|checks?\s+(pass|green)|until\s+.*\b(pass|green|merge|clean)|\bfix ci\b|watch[- ]fix[- ]push/i;
@@ -19337,6 +19412,64 @@ var init_schedule_runner = __esm({
19337
19412
  }
19338
19413
  });
19339
19414
 
19415
+ // src/integrations/github.ts
19416
+ var github_exports = {};
19417
+ __export(github_exports, {
19418
+ getGitHubStatus: () => getGitHubStatus,
19419
+ refreshGitHubAuth: () => refreshGitHubAuth
19420
+ });
19421
+ async function getGitHubStatus() {
19422
+ const which = await run("which", ["gh"], { reject: false });
19423
+ if (which.exitCode !== 0 || !which.stdout.trim()) {
19424
+ return {
19425
+ connected: false,
19426
+ login: null,
19427
+ hosts: [],
19428
+ reason: "gh CLI not found on PATH"
19429
+ };
19430
+ }
19431
+ const loginResult = await run(
19432
+ "gh",
19433
+ ["api", "user", "--jq", ".login"],
19434
+ { reject: false }
19435
+ );
19436
+ if (loginResult.exitCode === 0 && loginResult.stdout.trim()) {
19437
+ return {
19438
+ connected: true,
19439
+ login: loginResult.stdout.trim(),
19440
+ hosts: ["github.com"],
19441
+ reason: null
19442
+ };
19443
+ }
19444
+ const status = await run("gh", ["auth", "status"], { reject: false });
19445
+ const text7 = `${status.stdout}
19446
+ ${status.stderr}`;
19447
+ const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
19448
+ if (loginMatch) {
19449
+ return {
19450
+ connected: true,
19451
+ login: loginMatch[2].replace(/['"]/g, ""),
19452
+ hosts: [loginMatch[1]],
19453
+ reason: null
19454
+ };
19455
+ }
19456
+ return {
19457
+ connected: false,
19458
+ login: null,
19459
+ hosts: [],
19460
+ reason: status.stderr.trim() || status.stdout.trim() || "Not logged in to GitHub (run: gh auth login)"
19461
+ };
19462
+ }
19463
+ async function refreshGitHubAuth() {
19464
+ return getGitHubStatus();
19465
+ }
19466
+ var init_github = __esm({
19467
+ "src/integrations/github.ts"() {
19468
+ "use strict";
19469
+ init_run();
19470
+ }
19471
+ });
19472
+
19340
19473
  // src/agents/cursor-store.ts
19341
19474
  function cursorSdkStoreDir(threadId) {
19342
19475
  const root = (0, import_node_path49.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
@@ -19459,6 +19592,7 @@ __export(orchestrator_exports, {
19459
19592
  Orchestrator: () => Orchestrator,
19460
19593
  getOrchestrator: () => getOrchestrator,
19461
19594
  isPidAlive: () => isPidAlive,
19595
+ resolveOrchChildFollowUp: () => resolveOrchChildFollowUp,
19462
19596
  resolveSendFollowUp: () => resolveSendFollowUp,
19463
19597
  startOrchestration: () => startOrchestration,
19464
19598
  waitForPidExit: () => waitForPidExit
@@ -19489,6 +19623,10 @@ function resolveSendFollowUp(thread, requested) {
19489
19623
  if (requested === "steer" || requested === "queue") return requested;
19490
19624
  return isOrchestratorThread(thread) ? followUpBehavior() : "queue";
19491
19625
  }
19626
+ function resolveOrchChildFollowUp(requested) {
19627
+ if (requested === "steer" || requested === "queue") return requested;
19628
+ return followUpBehavior();
19629
+ }
19492
19630
  function writeLiveStatus(threadId, status, lastError) {
19493
19631
  const latest = readThread(threadId);
19494
19632
  if (!latest || latest.status === "archived") return latest;
@@ -19571,7 +19709,6 @@ var init_orchestrator = __esm({
19571
19709
  init_spawn();
19572
19710
  init_agents();
19573
19711
  init_worktree();
19574
- init_pr_metadata();
19575
19712
  init_stack();
19576
19713
  init_agent_git_actions();
19577
19714
  init_pr_merge_archive();
@@ -20359,7 +20496,7 @@ var init_orchestrator = __esm({
20359
20496
  }
20360
20497
  const isBrightsy = fresh.agent === "brightsy";
20361
20498
  const isOrchestration = isOrchestratorThread(fresh);
20362
- const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
20499
+ const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2, gitBranchPrefixSetting: gitBranchPrefixSetting2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
20363
20500
  const gitAuthMode = getGithubGitAuthMode2();
20364
20501
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
20365
20502
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -20395,9 +20532,26 @@ var init_orchestrator = __esm({
20395
20532
  }
20396
20533
  }
20397
20534
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
20398
- const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
20399
- customPrompt: settings?.prompts?.renameBranch
20400
- }) : null;
20535
+ let renameBranchDirective = null;
20536
+ if (!isBrightsy && !isOrchestration && autoRenameBranchEnabled2()) {
20537
+ const { isPlaceholderBranch: isPlaceholderBranch2 } = await Promise.resolve().then(() => (init_worktree_labels(), worktree_labels_exports));
20538
+ let githubLogin = null;
20539
+ if (isPlaceholderBranch2(fresh.branchName, fresh.worktreePath) && !gitBranchPrefixSetting2()) {
20540
+ try {
20541
+ const { getGitHubStatus: getGitHubStatus2 } = await Promise.resolve().then(() => (init_github(), github_exports));
20542
+ githubLogin = (await getGitHubStatus2()).login;
20543
+ } catch {
20544
+ }
20545
+ }
20546
+ const { resolveGitBranchPrefix: resolveGitBranchPrefix2 } = await Promise.resolve().then(() => (init_branch_prefix(), branch_prefix_exports));
20547
+ renameBranchDirective = formatRenameBranchDirective(fresh, {
20548
+ customPrompt: settings?.prompts?.renameBranch,
20549
+ branchPrefix: resolveGitBranchPrefix2({
20550
+ setting: gitBranchPrefixSetting2(),
20551
+ githubLogin
20552
+ })
20553
+ });
20554
+ }
20401
20555
  let seed = null;
20402
20556
  if (!fresh.sessionId) {
20403
20557
  const prior = fresh.messages.slice(0, -1);
@@ -21427,9 +21581,9 @@ var init_orchestrator = __esm({
21427
21581
  return tab;
21428
21582
  }
21429
21583
  /**
21430
- * Desktop git buttons + MCP `ask_git`.
21431
- * When the worktree is clean, push / open the PR here (HTTPS via `gh` if SSH
21432
- * is missing). When dirty, queue the worktree agent to commit first.
21584
+ * Desktop git buttons + MCP `ask_git`. Always queues the worktree agent
21585
+ * with the action prompt (same path as Resolve). Repository `[prompts]`
21586
+ * overrides apply when set.
21433
21587
  */
21434
21588
  async askGit(threadRef, action) {
21435
21589
  if (!AGENT_GIT_ACTIONS.includes(action)) {
@@ -21447,17 +21601,6 @@ var init_orchestrator = __esm({
21447
21601
  "No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
21448
21602
  );
21449
21603
  }
21450
- if ((action === "commit-push" || action === "create-draft" || action === "create-web") && thread.worktreePath?.trim() && !await isDirty(thread.worktreePath)) {
21451
- try {
21452
- await this.pushAndMaybeOpenPr(thread, action);
21453
- } catch (err) {
21454
- const message = err instanceof Error ? err.message : String(err);
21455
- updateThread(thread.id, { lastError: message });
21456
- this.emit({ type: "error", threadId: thread.id, message });
21457
- throw err;
21458
- }
21459
- return this.requireThread(threadRef);
21460
- }
21461
21604
  let prBase;
21462
21605
  if (action === "resolve-conflicts") {
21463
21606
  try {
@@ -21466,40 +21609,18 @@ var init_orchestrator = __esm({
21466
21609
  } catch {
21467
21610
  }
21468
21611
  }
21469
- return this.send(threadRef, agentGitPrompt(action, { prBase }));
21470
- }
21471
- /** Push origin (gh HTTPS fallback) and create/update the PR when requested. */
21472
- async pushAndMaybeOpenPr(thread, action) {
21473
- const cwd = thread.worktreePath;
21474
- const branch = await currentBranch(cwd);
21475
- await pushBranch(cwd, branch);
21476
- if (action === "commit-push") return;
21477
- const base = await resolveDefaultBranch(thread.repoPath);
21478
- const meta = await suggestPrMetadata(cwd, {
21479
- base,
21480
- fallbackTitle: thread.prTitle ?? thread.title
21481
- });
21482
- const url = await createOrUpdatePr(cwd, {
21483
- title: meta.title,
21484
- body: meta.body,
21485
- base,
21486
- head: branch,
21487
- draft: action === "create-draft",
21488
- web: action === "create-web"
21489
- });
21490
- if (!url) return;
21491
- const patch = {
21492
- prUrl: url,
21493
- prTitle: meta.title,
21494
- prIsDraft: action === "create-draft"
21495
- };
21496
- try {
21497
- const fetched = await getPrMeta(cwd, url);
21498
- if (fetched?.title) patch.prTitle = fetched.title;
21499
- if (fetched) patch.prIsDraft = Boolean(fetched.isDraft);
21500
- } catch {
21501
- }
21502
- updateThread(thread.id, patch);
21612
+ const settings = loadWorkspaceSettings(thread.worktreePath, thread.repoPath);
21613
+ return this.send(
21614
+ threadRef,
21615
+ resolveSidebarGitPrompt(action, {
21616
+ prBase,
21617
+ createPr: settings?.prompts?.createPr,
21618
+ resolveMergeConflicts: settings?.prompts?.resolveMergeConflicts
21619
+ }),
21620
+ {
21621
+ followUp: resolveOrchChildFollowUp()
21622
+ }
21623
+ );
21503
21624
  }
21504
21625
  setThreadOptions(threadRef, patch) {
21505
21626
  const thread = this.requireThread(threadRef);
@@ -22054,7 +22175,7 @@ function parseProjectTitles(raw) {
22054
22175
  }
22055
22176
  async function listGitHubIssueProjects(number, cwd, repoArgs) {
22056
22177
  const result = await gh(
22057
- ["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
22178
+ ["issue", "view", String(number), ...repoArgs, "--json", "projectItems"],
22058
22179
  cwd,
22059
22180
  { reject: false }
22060
22181
  );
@@ -22443,6 +22564,16 @@ query SideboardLabels($first: Int!) {
22443
22564
  }
22444
22565
  }
22445
22566
  `;
22567
+ var TEAM_LABELS_QUERY = `
22568
+ query SideboardTeamLabels($id: String!, $first: Int!, $after: String) {
22569
+ team(id: $id) {
22570
+ labels(first: $first, after: $after) {
22571
+ nodes { id name }
22572
+ pageInfo { hasNextPage endCursor }
22573
+ }
22574
+ }
22575
+ }
22576
+ `;
22446
22577
  var PROJECTS_QUERY = `
22447
22578
  query SideboardProjects($first: Int!, $filter: ProjectFilter) {
22448
22579
  projects(first: $first, filter: $filter) {
@@ -22816,15 +22947,38 @@ function formatRelationList(relations) {
22816
22947
  if (!relations.length) return "(none)";
22817
22948
  return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
22818
22949
  }
22819
- async function listLinearLabels(opts) {
22820
- const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
22821
- return (json.issueLabels?.nodes ?? []).flatMap((node) => {
22950
+ function mapLabelNodes(nodes) {
22951
+ return (nodes ?? []).flatMap((node) => {
22822
22952
  const id = String(node.id ?? "").trim();
22823
22953
  const name = String(node.name ?? "").trim();
22824
22954
  if (!id) return [];
22825
22955
  return [{ id, name }];
22826
22956
  });
22827
22957
  }
22958
+ async function listLinearWorkspaceLabels(opts) {
22959
+ const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
22960
+ return mapLabelNodes(json.issueLabels?.nodes);
22961
+ }
22962
+ async function listLinearLabels(opts) {
22963
+ const teamId = opts?.teamId?.trim();
22964
+ if (teamId) {
22965
+ const out = [];
22966
+ let after;
22967
+ for (let page = 0; page < 4; page++) {
22968
+ const json = await linearGraphql(
22969
+ TEAM_LABELS_QUERY,
22970
+ after ? { id: teamId, first: 250, after } : { id: teamId, first: 250 },
22971
+ opts
22972
+ );
22973
+ const conn = json.team?.labels;
22974
+ out.push(...mapLabelNodes(conn?.nodes));
22975
+ if (!conn?.pageInfo?.hasNextPage || !conn.pageInfo.endCursor) break;
22976
+ after = conn.pageInfo.endCursor;
22977
+ }
22978
+ if (out.length) return out;
22979
+ }
22980
+ return listLinearWorkspaceLabels(opts);
22981
+ }
22828
22982
  async function listLinearProjects(filter, opts) {
22829
22983
  const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
22830
22984
  return (json.projects?.nodes ?? []).flatMap((node) => {
@@ -23113,7 +23267,7 @@ async function updateLinearIssue(input, opts) {
23113
23267
  }
23114
23268
  const addRelations = input.relations?.length ? input.relations : void 0;
23115
23269
  const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
23116
- const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
23270
+ const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || input.labels !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
23117
23271
  const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
23118
23272
  if (input.state?.trim()) {
23119
23273
  const { teams } = await listLinearTeams(opts);
@@ -23148,7 +23302,10 @@ async function updateLinearIssue(input, opts) {
23148
23302
  mutationInput.priority = normalizePriority(input.priority);
23149
23303
  }
23150
23304
  if (input.labels !== void 0) {
23151
- mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
23305
+ mutationInput.labelIds = await resolveLinearLabelIds(input.labels, {
23306
+ ...opts,
23307
+ teamId: existing?.team?.id
23308
+ });
23152
23309
  }
23153
23310
  if (input.project !== void 0) {
23154
23311
  mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
@@ -25330,51 +25487,7 @@ async function getHomeBoardInputs(workspaces, opts) {
25330
25487
 
25331
25488
  // src/mcp/server.ts
25332
25489
  init_home_board();
25333
-
25334
- // src/integrations/github.ts
25335
- init_run();
25336
- async function getGitHubStatus() {
25337
- const which = await run("which", ["gh"], { reject: false });
25338
- if (which.exitCode !== 0 || !which.stdout.trim()) {
25339
- return {
25340
- connected: false,
25341
- login: null,
25342
- hosts: [],
25343
- reason: "gh CLI not found on PATH"
25344
- };
25345
- }
25346
- const loginResult = await run(
25347
- "gh",
25348
- ["api", "user", "--jq", ".login"],
25349
- { reject: false }
25350
- );
25351
- if (loginResult.exitCode === 0 && loginResult.stdout.trim()) {
25352
- return {
25353
- connected: true,
25354
- login: loginResult.stdout.trim(),
25355
- hosts: ["github.com"],
25356
- reason: null
25357
- };
25358
- }
25359
- const status = await run("gh", ["auth", "status"], { reject: false });
25360
- const text7 = `${status.stdout}
25361
- ${status.stderr}`;
25362
- const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
25363
- if (loginMatch) {
25364
- return {
25365
- connected: true,
25366
- login: loginMatch[2].replace(/['"]/g, ""),
25367
- hosts: [loginMatch[1]],
25368
- reason: null
25369
- };
25370
- }
25371
- return {
25372
- connected: false,
25373
- login: null,
25374
- hosts: [],
25375
- reason: status.stderr.trim() || status.stdout.trim() || "Not logged in to GitHub (run: gh auth login)"
25376
- };
25377
- }
25490
+ init_github();
25378
25491
 
25379
25492
  // src/mcp/create-thread-agent.ts
25380
25493
  init_cursor();
@@ -26009,7 +26122,7 @@ async function startMcpServer() {
26009
26122
  );
26010
26123
  server.tool(
26011
26124
  "send_to_thread",
26012
- '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. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not force_stop to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that stops the child mid-thought. Call wait_for_turn again instead.',
26125
+ 'Steer a prompt on a worktree thread chat (Settings \u2192 Follow-up, default steer: interrupt and start now, skip the inbox). 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. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not send_to_thread to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that interrupts the child mid-thought. Call wait_for_turn again instead.',
26013
26126
  {
26014
26127
  ref: import_zod9.z.string(),
26015
26128
  prompt: import_zod9.z.string(),
@@ -26022,7 +26135,9 @@ async function startMcpServer() {
26022
26135
  orch.stop(ref, { clearQueue: true, notifyParent: false });
26023
26136
  }
26024
26137
  }
26025
- const thread = await orch.send(ref, prompt);
26138
+ const thread = await orch.send(ref, prompt, {
26139
+ followUp: resolveOrchChildFollowUp()
26140
+ });
26026
26141
  return mcpJson({
26027
26142
  id: thread.id,
26028
26143
  status: thread.status,
@@ -26033,7 +26148,7 @@ async function startMcpServer() {
26033
26148
  );
26034
26149
  server.tool(
26035
26150
  "wait_for_turn",
26036
- "Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. stillRunning is the source of truth \u2014 if false, the child is not working (do not say it is waiting for a gate). If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume a hang. On status error, lastError/text is the failure. On status stopped or broken, the child did not finish \u2014 resume with send_to_thread or tell the user; do not treat that as success. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
26151
+ "Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. stillRunning is the source of truth \u2014 if false, the child is not working (do not say it is waiting for a gate). If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume a hang. On status error, lastError/text is the failure. On status stopped or broken, the child did not finish \u2014 resume with send_to_thread or tell the user; do not treat that as success. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
26037
26152
  {
26038
26153
  ref: import_zod9.z.string(),
26039
26154
  timeoutMs: import_zod9.z.number().optional()
@@ -26190,7 +26305,7 @@ async function startMcpServer() {
26190
26305
  );
26191
26306
  server.tool(
26192
26307
  "ask_git",
26193
- "Commit & push, open a draft PR, mark ready for review, 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` even when origin is SSH / Settings \u2192 Git is SSH). When dirty, queues the worktree agent to commit; then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). Do not run git or gh from the orchestration cwd. If this tool errors that the GraphQL/PR body is too long, the branch is already pushed \u2014 have the worktree agent retry `gh pr create --body-file` with a short description (GitHub limit 65,536 characters). Do not invent SSH/auth failures from that error.",
26308
+ "Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (including Resolve). Always steers the worktree agent; does not push or open the PR locally. Then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). Do not run git or gh from the orchestration cwd.",
26194
26309
  {
26195
26310
  ref: import_zod9.z.string().describe("Worktree thread id/ref"),
26196
26311
  action: import_zod9.z.enum(AGENT_GIT_ACTIONS).describe(