@sideboard-ai/core 0.1.10 → 0.1.19

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.
@@ -11,7 +11,7 @@ import {
11
11
  listBrightsyChatTargets,
12
12
  opencodeAdapter,
13
13
  permissionMode
14
- } from "./chunk-WMCPLDW3.js";
14
+ } from "./chunk-JQJZTL2Q.js";
15
15
  import "./chunk-ILQK4P5R.js";
16
16
  import {
17
17
  cursorSdkMessageToEvents,
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  ensureGlobalCoordinatorCwd
3
- } from "./chunk-2R5VV4BA.js";
3
+ } from "./chunk-SNHWAARD.js";
4
4
  import {
5
5
  allocateTeamName,
6
6
  takenSlugsFromThread,
7
7
  teamSlugFromName
8
- } from "./chunk-LL7DTZ5B.js";
8
+ } from "./chunk-LXHSRNJJ.js";
9
9
  import {
10
10
  createEmptyThread,
11
11
  listThreads,
@@ -719,7 +719,7 @@ var claudeAdapter = {
719
719
  );
720
720
  }
721
721
  const mode = permissionMode(thread);
722
- const { isOrchestratorThread } = await import("./global-workspace-R44HGBU6.js");
722
+ const { isOrchestratorThread } = await import("./global-workspace-IV6LIDTO.js");
723
723
  const isOrchestrator = isOrchestratorThread(thread);
724
724
  const injectedServers = await buildInjectedMcpServers({
725
725
  includeSideboard: isOrchestrator,
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  isGlobalRepoPath
3
- } from "./chunk-2M4OHXYX.js";
3
+ } from "./chunk-5263JXQY.js";
4
4
  import {
5
+ ensureGhPreferOrigin,
5
6
  resolveRepoRoot
6
- } from "./chunk-LL7DTZ5B.js";
7
+ } from "./chunk-LXHSRNJJ.js";
7
8
  import {
8
9
  appDataDir
9
10
  } from "./chunk-M37RITA6.js";
@@ -14,6 +15,9 @@ import { basename, join } from "path";
14
15
  function workspacesFile() {
15
16
  return join(appDataDir(), "workspaces.json");
16
17
  }
18
+ function removedWorkspacesFile() {
19
+ return join(appDataDir(), "removed-workspaces.json");
20
+ }
17
21
  function readAll() {
18
22
  const path = workspacesFile();
19
23
  if (!existsSync(path)) return [];
@@ -28,12 +32,44 @@ function writeAll(list) {
28
32
  mkdirSync(appDataDir(), { recursive: true });
29
33
  writeFileSync(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
30
34
  }
35
+ function readRemoved() {
36
+ const path = removedWorkspacesFile();
37
+ if (!existsSync(path)) return /* @__PURE__ */ new Set();
38
+ try {
39
+ const raw = JSON.parse(readFileSync(path, "utf8"));
40
+ return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
41
+ } catch {
42
+ return /* @__PURE__ */ new Set();
43
+ }
44
+ }
45
+ function writeRemoved(paths) {
46
+ mkdirSync(appDataDir(), { recursive: true });
47
+ writeFileSync(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
48
+ }
49
+ function rememberRemoved(repoPath) {
50
+ const next = readRemoved();
51
+ next.add(repoPath);
52
+ writeRemoved(next);
53
+ }
54
+ function forgetRemoved(repoPath) {
55
+ const next = readRemoved();
56
+ if (!next.delete(repoPath)) return;
57
+ writeRemoved(next);
58
+ }
31
59
  function listWorkspaces() {
32
- return readAll().sort((a, b) => a.name.localeCompare(b.name));
60
+ const all = readAll();
61
+ const valid = all.filter(
62
+ (w) => Boolean(w.path) && w.path !== "/" && w.path !== "." && !isGlobalRepoPath(w.path)
63
+ );
64
+ if (valid.length !== all.length) writeAll(valid);
65
+ return valid.sort((a, b) => a.name.localeCompare(b.name));
33
66
  }
34
67
  async function addWorkspace(repoPath) {
35
68
  const root = await resolveRepoRoot(repoPath);
69
+ if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
36
70
  if (!existsSync(root)) throw new Error(`Repo not found: ${root}`);
71
+ forgetRemoved(root);
72
+ await ensureGhPreferOrigin(root);
37
73
  const current = readAll();
38
74
  const existing = current.find((w) => w.path === root);
39
75
  if (existing) return existing;
@@ -47,16 +83,20 @@ async function addWorkspace(repoPath) {
47
83
  }
48
84
  function removeWorkspace(repoPath) {
49
85
  writeAll(readAll().filter((w) => w.path !== repoPath));
86
+ rememberRemoved(repoPath);
50
87
  }
51
88
  async function ensureWorkspace(repoPath) {
52
89
  return addWorkspace(repoPath);
53
90
  }
54
91
  function syncWorkspacesFromThreads(repoPaths) {
55
92
  const current = readAll();
93
+ const removed = readRemoved();
56
94
  const byPath = new Map(current.map((w) => [w.path, w]));
57
95
  let dirty = false;
58
96
  for (const path of repoPaths) {
59
- if (!path || isGlobalRepoPath(path) || byPath.has(path)) continue;
97
+ if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
98
+ continue;
99
+ }
60
100
  if (!existsSync(path)) continue;
61
101
  const ws = {
62
102
  path,
@@ -459,6 +459,64 @@ function worktreeDisplayLabelForGroup(threads) {
459
459
  return threadDisplayLabel(canonical);
460
460
  }
461
461
 
462
+ // src/git/gh-errors.ts
463
+ function isGhRateLimitError(text) {
464
+ return /API rate limit (already )?exceeded/i.test(text) || /rate limit exceeded/i.test(text);
465
+ }
466
+ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
467
+ const ms = resetEpochSec * 1e3 - nowMs;
468
+ if (ms <= 0) return "soon";
469
+ const mins = Math.max(1, Math.ceil(ms / 6e4));
470
+ if (mins < 60) {
471
+ return `in about ${mins} minute${mins === 1 ? "" : "s"}`;
472
+ }
473
+ const hours = Math.ceil(mins / 60);
474
+ return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
475
+ }
476
+ function extractGhErrorDetail(text) {
477
+ const trimmed = text.trim();
478
+ if (!trimmed) return "";
479
+ const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
480
+ if (graphql?.[1]) return `GraphQL: ${graphql[1].trim()}`;
481
+ const http = trimmed.match(/\bHTTP\s+\d{3}:\s*(.+)$/im);
482
+ if (http?.[1]) return `HTTP: ${http[1].trim()}`;
483
+ const lines = trimmed.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
484
+ if (lines.length > 1 && /^Command failed with exit code/i.test(lines[0])) {
485
+ return lines.slice(1).join(" ").trim() || lines[0];
486
+ }
487
+ return trimmed;
488
+ }
489
+ function formatGhLandError(raw, opts) {
490
+ const trimmed = raw.trim();
491
+ if (trimmed.startsWith("GitHub API rate limit exceeded.")) {
492
+ return trimmed;
493
+ }
494
+ const detail = extractGhErrorDetail(raw);
495
+ if (/Head ref must be a branch|No commits between|Head sha can't be blank/i.test(
496
+ raw
497
+ ) || /Head ref must be a branch|No commits between|Head sha can't be blank/i.test(
498
+ detail
499
+ )) {
500
+ const target = opts?.targetedRepo ? ` Targeted ${opts.targetedRepo}` : "";
501
+ const head = opts?.headRef ? ` with head ${opts.headRef}` : "";
502
+ const hint = opts?.targetedRepo ? ` Branch was pushed to origin \u2014 confirm it exists on GitHub and differs from the base branch.${target}${head}.` : " Often `gh` targeted upstream instead of origin. Branch was pushed \u2014 retry in the latest Sideboard, or run: gh pr create -R <owner/name> --base main --head <branch>.";
503
+ return `Could not create the pull request.${hint}`;
504
+ }
505
+ if (isGhRateLimitError(raw) || isGhRateLimitError(detail)) {
506
+ const when = opts?.resetAt ? ` Try again ${formatRateLimitResetHint(opts.resetAt, opts.nowMs)}.` : " Wait a few minutes and try again.";
507
+ const pushNote = opts?.pushed === false ? "" : " Your branch was already pushed.";
508
+ return `GitHub API rate limit exceeded.${pushNote}${when} Or create the pull request in the browser (Push & open on GitHub).`;
509
+ }
510
+ return detail || "Failed to create or update pull request";
511
+ }
512
+ function formatIpcInvokeError(err) {
513
+ let msg = err instanceof Error ? err.message : String(err);
514
+ msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
515
+ msg = msg.replace(/^ExecaError:\s*/i, "");
516
+ msg = msg.replace(/^Error:\s*/i, "");
517
+ return formatGhLandError(msg);
518
+ }
519
+
462
520
  // src/git/pr-gates.ts
463
521
  function buildMergeGateChecks(gate, opts = {}) {
464
522
  const rows = [];
@@ -547,6 +605,31 @@ function buildMergeGateChecks(gate, opts = {}) {
547
605
  }
548
606
 
549
607
  // src/git/worktree.ts
608
+ async function lookupGithubGraphqlReset(cwd) {
609
+ const result = await gh(["api", "rate_limit"], cwd, { reject: false });
610
+ if (result.exitCode !== 0 || !result.stdout.trim()) return void 0;
611
+ try {
612
+ const data = JSON.parse(result.stdout);
613
+ const reset = data.resources?.graphql?.reset;
614
+ return typeof reset === "number" ? reset : void 0;
615
+ } catch {
616
+ return void 0;
617
+ }
618
+ }
619
+ async function formatPrCreateFailure(raw, cwd, ctx) {
620
+ if (!isGhRateLimitError(raw)) {
621
+ return formatGhLandError(raw, {
622
+ targetedRepo: ctx?.slug,
623
+ headRef: ctx?.head
624
+ });
625
+ }
626
+ const resetAt = await lookupGithubGraphqlReset(cwd);
627
+ return formatGhLandError(raw, {
628
+ resetAt,
629
+ targetedRepo: ctx?.slug,
630
+ headRef: ctx?.head
631
+ });
632
+ }
550
633
  function slugify(input) {
551
634
  return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
552
635
  }
@@ -555,10 +638,13 @@ async function resolveRepoRoot(cwd) {
555
638
  return stdout.trim();
556
639
  }
557
640
  function parseGithubSlugFromRemoteUrl(url) {
558
- const trimmed = url.trim();
559
- const match = trimmed.match(/github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/i) ?? trimmed.match(/github\.com[:/]([^/]+)\/([^/.]+)/i);
560
- if (!match?.[1] || !match[2]) return null;
561
- return `${match[1]}/${match[2]}`;
641
+ const trimmed = url.trim().replace(/\.git$/i, "");
642
+ if (!trimmed) return null;
643
+ const github = trimmed.match(/github\.com[:/]([^/]+)\/([^/]+)$/i);
644
+ if (github?.[1] && github[2]) return `${github[1]}/${github[2]}`;
645
+ const sshAlias = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+)$/i);
646
+ if (sshAlias?.[1] && sshAlias[2]) return `${sshAlias[1]}/${sshAlias[2]}`;
647
+ return null;
562
648
  }
563
649
  async function slugFromGitRemote(repoPath, remote) {
564
650
  const result = await git(["remote", "get-url", remote], repoPath, {
@@ -570,13 +656,16 @@ async function slugFromGitRemote(repoPath, remote) {
570
656
  async function resolveGithubRepoSlug(repoPath) {
571
657
  const fromOrigin = await slugFromGitRemote(repoPath, "origin");
572
658
  if (fromOrigin) return fromOrigin;
573
- const viaGh = await gh(
574
- ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
575
- repoPath,
576
- { reject: false }
577
- );
578
- if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
579
- return viaGh.stdout.trim();
659
+ const hasAlt = Boolean(await slugFromGitRemote(repoPath, "upstream")) || Boolean(await slugFromGitRemote(repoPath, "github"));
660
+ if (!hasAlt) {
661
+ const viaGh = await gh(
662
+ ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
663
+ repoPath,
664
+ { reject: false }
665
+ );
666
+ if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
667
+ return viaGh.stdout.trim();
668
+ }
580
669
  }
581
670
  for (const remote of ["upstream", "github"]) {
582
671
  const slug = await slugFromGitRemote(repoPath, remote);
@@ -584,15 +673,35 @@ async function resolveGithubRepoSlug(repoPath) {
584
673
  }
585
674
  return null;
586
675
  }
676
+ function ghRepoSelectArgs(slug) {
677
+ return ["-R", slug];
678
+ }
679
+ function ghHeadRef(slug, branch) {
680
+ const owner = slug.split("/")[0];
681
+ const head = branch.trim().replace(/^refs\/heads\//, "");
682
+ if (!owner || !head) return head || branch;
683
+ if (head.includes(":")) return head;
684
+ return `${owner}:${head}`;
685
+ }
686
+ async function ensureGhPreferOrigin(cwd) {
687
+ const originSlug = await slugFromGitRemote(cwd, "origin");
688
+ if (!originSlug) return;
689
+ const hasAlt = Boolean(await slugFromGitRemote(cwd, "upstream")) || Boolean(await slugFromGitRemote(cwd, "github"));
690
+ if (!hasAlt) return;
691
+ const view = await gh(["repo", "set-default", "--view"], cwd, {
692
+ reject: false
693
+ });
694
+ const current = `${view.stdout}
695
+ ${view.stderr}`;
696
+ if (view.exitCode === 0 && current.includes(originSlug)) return;
697
+ await gh(["repo", "set-default", "origin"], cwd, { reject: false });
698
+ }
699
+ async function originGhRepoEnv(cwd) {
700
+ await ensureGhPreferOrigin(cwd);
701
+ const slug = await resolveGithubRepoSlug(cwd);
702
+ return slug ? { GH_REPO: slug } : {};
703
+ }
587
704
  async function resolveDefaultBranch(repoPath) {
588
- const viaGh = await gh(
589
- ["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"],
590
- repoPath,
591
- { reject: false }
592
- );
593
- if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
594
- return viaGh.stdout.trim();
595
- }
596
705
  const viaOrigin = await git(
597
706
  ["symbolic-ref", "refs/remotes/origin/HEAD"],
598
707
  repoPath,
@@ -601,6 +710,31 @@ async function resolveDefaultBranch(repoPath) {
601
710
  if (viaOrigin.exitCode === 0 && viaOrigin.stdout.trim()) {
602
711
  return viaOrigin.stdout.trim().replace(/^refs\/remotes\/origin\//, "");
603
712
  }
713
+ const slug = await resolveGithubRepoSlug(repoPath);
714
+ const viaGh = await gh(
715
+ [
716
+ "repo",
717
+ "view",
718
+ ...slug ? ["--repo", slug] : [],
719
+ "--json",
720
+ "defaultBranchRef",
721
+ "--jq",
722
+ ".defaultBranchRef.name"
723
+ ],
724
+ repoPath,
725
+ { reject: false }
726
+ );
727
+ if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
728
+ return viaGh.stdout.trim();
729
+ }
730
+ for (const candidate of ["main", "master"]) {
731
+ const check = await git(
732
+ ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${candidate}`],
733
+ repoPath,
734
+ { reject: false }
735
+ );
736
+ if (check.exitCode === 0) return candidate;
737
+ }
604
738
  for (const candidate of ["main", "master"]) {
605
739
  const check = await git(
606
740
  ["show-ref", "--verify", "--quiet", `refs/heads/${candidate}`],
@@ -861,6 +995,37 @@ async function getPrChecks(cwd, selector) {
861
995
  });
862
996
  return [...gateRows, ...ciChecks];
863
997
  }
998
+ async function getPrMeta(cwd, selector) {
999
+ const slug = await resolveGithubRepoSlug(cwd);
1000
+ const viewArgs = [
1001
+ "pr",
1002
+ "view",
1003
+ selector,
1004
+ "--json",
1005
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName"
1006
+ ];
1007
+ if (slug) viewArgs.push("--repo", slug);
1008
+ const { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
1009
+ if (exitCode !== 0 || !stdout.trim()) {
1010
+ if (/no pull requests found/i.test(stderr)) return null;
1011
+ return null;
1012
+ }
1013
+ try {
1014
+ const view = JSON.parse(stdout);
1015
+ return {
1016
+ number: Number(view.number),
1017
+ title: String(view.title ?? ""),
1018
+ url: String(view.url ?? ""),
1019
+ state: String(view.state ?? ""),
1020
+ isDraft: Boolean(view.isDraft),
1021
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1022
+ baseRefName: String(view.baseRefName ?? ""),
1023
+ headRefName: String(view.headRefName ?? "")
1024
+ };
1025
+ } catch {
1026
+ return null;
1027
+ }
1028
+ }
864
1029
  async function getPrDetails(cwd, selector) {
865
1030
  const slug = await resolveGithubRepoSlug(cwd);
866
1031
  const viewArgs = [
@@ -882,7 +1047,6 @@ async function getPrDetails(cwd, selector) {
882
1047
  "additions",
883
1048
  "deletions",
884
1049
  "changedFiles",
885
- "commits",
886
1050
  "comments",
887
1051
  "reviews"
888
1052
  ].join(",")
@@ -901,14 +1065,7 @@ async function getPrDetails(cwd, selector) {
901
1065
  } catch {
902
1066
  throw new Error(stderr.trim() || "gh pr view returned invalid JSON");
903
1067
  }
904
- let checks = [];
905
- try {
906
- checks = await getPrChecks(cwd, selector) ?? [];
907
- } catch {
908
- checks = [];
909
- }
910
1068
  const author = view.author ?? {};
911
- const commits = Array.isArray(view.commits) ? view.commits : [];
912
1069
  const comments = Array.isArray(view.comments) ? view.comments : [];
913
1070
  const reviews = Array.isArray(view.reviews) ? view.reviews : [];
914
1071
  return {
@@ -925,19 +1082,8 @@ async function getPrDetails(cwd, selector) {
925
1082
  additions: Number(view.additions ?? 0),
926
1083
  deletions: Number(view.deletions ?? 0),
927
1084
  changedFiles: Number(view.changedFiles ?? 0),
928
- commits: commits.map((c) => {
929
- const row = c;
930
- const authors = Array.isArray(row.authors) ? row.authors.map((a) => {
931
- const actor = a;
932
- return { login: actor.login ?? "unknown", name: actor.name ?? null };
933
- }) : [];
934
- return {
935
- oid: String(row.oid ?? ""),
936
- messageHeadline: String(row.messageHeadline ?? ""),
937
- committedDate: String(row.committedDate ?? ""),
938
- authors
939
- };
940
- }),
1085
+ // Commits live in Changes; omit from GraphQL to save rate-limit points.
1086
+ commits: [],
941
1087
  comments: comments.map((c) => {
942
1088
  const row = c;
943
1089
  const a = row.author ?? {};
@@ -957,7 +1103,8 @@ async function getPrDetails(cwd, selector) {
957
1103
  submittedAt: normalizeGhTime(row.submittedAt)
958
1104
  };
959
1105
  }),
960
- checks
1106
+ // CI lives in Checks tab via getPrChecks — nesting burned GraphQL points.
1107
+ checks: []
961
1108
  };
962
1109
  }
963
1110
  async function fetchPrHead(repoPath, number, localBranch) {
@@ -993,6 +1140,7 @@ async function createThreadWorktree(opts) {
993
1140
  if (existsSync(worktreePath)) {
994
1141
  throw new Error(`Worktree already exists at ${worktreePath}`);
995
1142
  }
1143
+ await ensureGhPreferOrigin(opts.repoPath);
996
1144
  await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
997
1145
  if (!opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
998
1146
  await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
@@ -1021,6 +1169,7 @@ ${add.stdout}`;
1021
1169
  );
1022
1170
  if (retry.exitCode === 0) {
1023
1171
  branchName = alt;
1172
+ await ensureGhPreferOrigin(worktreePath);
1024
1173
  return { branchName, worktreePath };
1025
1174
  }
1026
1175
  }
@@ -1029,6 +1178,7 @@ ${add.stdout}`;
1029
1178
  `Failed to create worktree: ${add.stderr.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
1030
1179
  );
1031
1180
  }
1181
+ await ensureGhPreferOrigin(worktreePath);
1032
1182
  return { branchName, worktreePath };
1033
1183
  }
1034
1184
  async function removeWorktree(repoPath, worktreePath, opts) {
@@ -1129,8 +1279,18 @@ async function mergePr(cwd, selector, opts) {
1129
1279
  return { url, state: "MERGED" };
1130
1280
  }
1131
1281
  async function createOrUpdatePr(worktreePath, opts) {
1282
+ await ensureGhPreferOrigin(worktreePath);
1283
+ const slug = await resolveGithubRepoSlug(worktreePath);
1284
+ if (!slug) {
1285
+ throw new Error(
1286
+ "Could not resolve the origin GitHub repo (owner/name) for this worktree. Check that `git remote get-url origin` points at github.com."
1287
+ );
1288
+ }
1289
+ const repoArgs = ghRepoSelectArgs(slug);
1290
+ const headRef = ghHeadRef(slug, opts.head);
1291
+ const branchOnly = opts.head.trim().replace(/^refs\/heads\//, "");
1132
1292
  const existing = await gh(
1133
- ["pr", "view", opts.head, "--json", "url", "--jq", ".url"],
1293
+ [...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
1134
1294
  worktreePath,
1135
1295
  { reject: false }
1136
1296
  );
@@ -1138,9 +1298,10 @@ async function createOrUpdatePr(worktreePath, opts) {
1138
1298
  const url2 = existing.stdout.trim();
1139
1299
  await gh(
1140
1300
  [
1301
+ ...repoArgs,
1141
1302
  "pr",
1142
1303
  "edit",
1143
- opts.head,
1304
+ branchOnly,
1144
1305
  "--title",
1145
1306
  opts.title,
1146
1307
  "--body",
@@ -1153,6 +1314,7 @@ async function createOrUpdatePr(worktreePath, opts) {
1153
1314
  }
1154
1315
  if (opts.web) {
1155
1316
  const args2 = [
1317
+ ...repoArgs,
1156
1318
  "pr",
1157
1319
  "create",
1158
1320
  "--web",
@@ -1163,18 +1325,19 @@ async function createOrUpdatePr(worktreePath, opts) {
1163
1325
  "--base",
1164
1326
  opts.base,
1165
1327
  "--head",
1166
- opts.head
1328
+ headRef
1167
1329
  ];
1168
1330
  if (opts.draft) args2.push("--draft");
1169
1331
  await gh(args2, worktreePath, { reject: false });
1170
1332
  const again = await gh(
1171
- ["pr", "view", opts.head, "--json", "url", "--jq", ".url"],
1333
+ [...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
1172
1334
  worktreePath,
1173
1335
  { reject: false }
1174
1336
  );
1175
1337
  return again.stdout.trim() || "";
1176
1338
  }
1177
1339
  const args = [
1340
+ ...repoArgs,
1178
1341
  "pr",
1179
1342
  "create",
1180
1343
  "--title",
@@ -1184,11 +1347,15 @@ async function createOrUpdatePr(worktreePath, opts) {
1184
1347
  "--base",
1185
1348
  opts.base,
1186
1349
  "--head",
1187
- opts.head
1350
+ headRef
1188
1351
  ];
1189
1352
  if (opts.draft) args.push("--draft");
1190
- const { stdout } = await gh(args, worktreePath);
1191
- const url = stdout.trim().split("\n").find((l) => l.startsWith("http")) ?? stdout.trim();
1353
+ const created = await gh(args, worktreePath, { reject: false });
1354
+ if (created.exitCode !== 0) {
1355
+ const raw = created.stderr.trim() || created.stdout.trim() || "gh pr create failed";
1356
+ throw new Error(await formatPrCreateFailure(raw, worktreePath, { slug, head: headRef }));
1357
+ }
1358
+ const url = created.stdout.trim().split("\n").find((l) => l.startsWith("http")) ?? created.stdout.trim();
1192
1359
  return url;
1193
1360
  }
1194
1361
  function suggestSlug(source) {
@@ -1252,10 +1419,19 @@ export {
1252
1419
  threadDisplayLabel,
1253
1420
  worktreeDisplayLabel,
1254
1421
  worktreeDisplayLabelForGroup,
1422
+ isGhRateLimitError,
1423
+ formatRateLimitResetHint,
1424
+ extractGhErrorDetail,
1425
+ formatGhLandError,
1426
+ formatIpcInvokeError,
1255
1427
  slugify,
1256
1428
  resolveRepoRoot,
1257
1429
  parseGithubSlugFromRemoteUrl,
1258
1430
  resolveGithubRepoSlug,
1431
+ ghRepoSelectArgs,
1432
+ ghHeadRef,
1433
+ ensureGhPreferOrigin,
1434
+ originGhRepoEnv,
1259
1435
  resolveDefaultBranch,
1260
1436
  resolveDiffBaseRef,
1261
1437
  listBranches,
@@ -1264,6 +1440,7 @@ export {
1264
1440
  resolvePrSelector,
1265
1441
  detectLocalMergeConflicts,
1266
1442
  getPrChecks,
1443
+ getPrMeta,
1267
1444
  getPrDetails,
1268
1445
  fetchPrHead,
1269
1446
  resolveWorktreeStartPoint,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveGithubRepoSlug
3
- } from "./chunk-LL7DTZ5B.js";
3
+ } from "./chunk-LXHSRNJJ.js";
4
4
  import {
5
5
  globalAgentCwd,
6
6
  sideboardReposDir
@@ -31,7 +31,8 @@ function coordinatorGreenfieldPlaybook(reposDir) {
31
31
  "- Examples:",
32
32
  ` - Clone: \`git clone <url> ${reposDir}/<name>\``,
33
33
  ` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
34
- "- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 send_to_thread (`gh pr create --draft`) or create_draft_pr.",
34
+ "- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 send_to_thread (`gh pr create --draft -R <origin-owner/name>`) or create_draft_pr.",
35
+ "- Always target the child worktree's **origin** (`github:` slug from list_workspaces / `git remote get-url origin` in that worktree). Never open PRs against `upstream`.",
35
36
  "- Do coding work in the child worktree thread, not by editing files in this home cwd."
36
37
  ].join("\n");
37
38
  }
@@ -56,8 +57,8 @@ var COORDINATOR_TOOL_PLAYBOOK = [
56
57
  "Inspect / PRs:",
57
58
  "- get_diff \u2014 compact diff summary",
58
59
  "- preview_land \u2014 preview push+PR (does not push)",
59
- "- Prefer asking the worktree agent via send_to_thread to open a draft PR (`gh pr create --draft`) so it owns title/body from the diff.",
60
- "- create_draft_pr \u2014 fallback: commit (if dirty), push, open/update a DRAFT PR from the orchestrator",
60
+ "- Prefer asking the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (use the workspace `github:` slug / that worktree's origin \u2014 never upstream) so it owns title/body from the diff.",
61
+ "- create_draft_pr \u2014 fallback: commit (if dirty), push to origin, open/update a DRAFT PR from the orchestrator (always against origin)",
61
62
  "Human-only (do not attempt): ready-for-review confirm_land, purge_thread.",
62
63
  "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.",
63
64
  "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."
@@ -123,7 +124,7 @@ function coordinatorSystemPrompt(opts) {
123
124
  COORDINATOR_TOOL_PLAYBOOK,
124
125
  coordinatorGreenfieldPlaybook(reposDir),
125
126
  "When creating threads, pass the correct repoPath for the target workspace and parentThreadId for children.",
126
- "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 send_to_thread (ask for `gh pr create --draft`) \u2192 wait_for_turn. Use create_draft_pr only if the child cannot open the PR.",
127
+ "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 send_to_thread (ask for `gh pr create --draft -R <origin-owner/name>` using the workspace github slug) \u2192 wait_for_turn. Use create_draft_pr only if the child cannot open the PR. Never target upstream.",
127
128
  "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 draft PR.",
128
129
  `Goal: ${opts.goal}`,
129
130
  `Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,