@sideboard-ai/core 0.1.149 → 0.1.151

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 (60) hide show
  1. package/dist/{abletime-QEVAE67D.js → abletime-223A3LY6.js} +2 -2
  2. package/dist/{abletime-DI55AJX3.js → abletime-CLC5UJC2.js} +2 -2
  3. package/dist/agents/cursor-runner.cjs +40 -35
  4. package/dist/agents/cursor-runner.js +2 -2
  5. package/dist/{agents-V4MTDWXS.js → agents-JAOMXDXN.js} +10 -9
  6. package/dist/{agents-HSVQ2Z6Y.js → agents-U2JCFXW4.js} +10 -9
  7. package/dist/{app-settings-WJDCJFSY.js → app-settings-3CHCNIMD.js} +5 -1
  8. package/dist/{app-settings-ZCN632OJ.js → app-settings-J3JBCUOT.js} +5 -1
  9. package/dist/{chunk-N5PM7HGQ.js → chunk-2SDMENAL.js} +1 -1
  10. package/dist/{chunk-JTPDWTWJ.js → chunk-3JBMACYX.js} +137 -114
  11. package/dist/{chunk-I77RYPOH.js → chunk-4XNSGDXE.js} +12 -0
  12. package/dist/{chunk-JDYKQ6OI.js → chunk-6H4TJ5TA.js} +136 -75
  13. package/dist/{chunk-EGDRPA4B.js → chunk-77MP647F.js} +6 -6
  14. package/dist/{chunk-KBWND62T.js → chunk-7F4XE6UG.js} +5 -5
  15. package/dist/{chunk-R4JP32P5.js → chunk-ADCEGG4A.js} +4 -4
  16. package/dist/{chunk-BOD5V52W.js → chunk-B7XK37WZ.js} +1 -1
  17. package/dist/{chunk-BGQBELGJ.js → chunk-CZD4NRDZ.js} +12 -0
  18. package/dist/{chunk-KPIYENTF.js → chunk-FNQNJOHY.js} +89 -5
  19. package/dist/{chunk-ED4UPEJX.js → chunk-G7SIQFBQ.js} +1 -1
  20. package/dist/{chunk-TZWO7J4W.js → chunk-GCWAAPJ5.js} +115 -54
  21. package/dist/{chunk-YP3CSOZ6.js → chunk-GE7M3QTE.js} +142 -114
  22. package/dist/{chunk-LAFA56WD.js → chunk-HEII7W3Q.js} +1 -1
  23. package/dist/{chunk-3QRGCKYL.js → chunk-HWZWBJWL.js} +2 -2
  24. package/dist/{chunk-FKOIHGKV.js → chunk-JRH62XC4.js} +5 -1
  25. package/dist/{chunk-Q5DHVMXB.js → chunk-LZAOX76N.js} +18 -7
  26. package/dist/{chunk-B3SJXYIJ.js → chunk-NVXVEGGM.js} +1 -1
  27. package/dist/{chunk-ZVYLEF72.js → chunk-OXOU2FRS.js} +6 -6
  28. package/dist/chunk-QQKDGPUI.js +63 -0
  29. package/dist/{chunk-QTUESPAW.js → chunk-RFMQY56R.js} +1 -1
  30. package/dist/chunk-SYFVUF2C.js +65 -0
  31. package/dist/{chunk-Q62SDB53.js → chunk-TSCWSAQL.js} +2 -2
  32. package/dist/{chunk-DVM4ID64.js → chunk-UFHNSFAS.js} +89 -5
  33. package/dist/{chunk-PTI5H3RK.js → chunk-UIS7BVCX.js} +24 -13
  34. package/dist/{chunk-VZJVKQHH.js → chunk-WLV7TRMR.js} +1 -1
  35. package/dist/{chunk-N3VNOFXQ.js → chunk-Y2FNDKHF.js} +4 -4
  36. package/dist/{connected-teams-IJUJQ7R6.js → connected-teams-I2WZZZFA.js} +2 -2
  37. package/dist/{connected-teams-4A2AXDNN.js → connected-teams-XO766HB4.js} +2 -2
  38. package/dist/{coordinator-prompt-ZWBMAIWA.js → coordinator-prompt-LWYUZAXR.js} +6 -5
  39. package/dist/{coordinator-prompt-5GXGBGEZ.js → coordinator-prompt-ZK5MII7W.js} +6 -5
  40. package/dist/{global-workspace-OJRXWXEA.js → global-workspace-IB2CCEJF.js} +7 -6
  41. package/dist/{global-workspace-2SIPM4PW.js → global-workspace-T7RL3XEC.js} +7 -6
  42. package/dist/index.cjs +1010 -577
  43. package/dist/index.d.cts +96 -11
  44. package/dist/index.d.ts +96 -11
  45. package/dist/index.js +272 -93
  46. package/dist/mcp/run-stdio.cjs +981 -581
  47. package/dist/mcp/run-stdio.js +196 -43
  48. package/dist/{orchestrator-Z2O5OVJA.js → orchestrator-DPTRBKEH.js} +16 -13
  49. package/dist/{orchestrator-6TXQWBXN.js → orchestrator-Y5MWUU7C.js} +14 -11
  50. package/dist/{plan-file-6O7G4VPQ.js → plan-file-LSSSQGTQ.js} +2 -2
  51. package/dist/{plan-file-PHVKUAEE.js → plan-file-OZEBFSGQ.js} +2 -2
  52. package/dist/{run-XQKGHQZ2.js → run-CAGKZLHA.js} +1 -1
  53. package/dist/{run-USF6FRKV.js → run-EBWL2Q4N.js} +1 -1
  54. package/dist/{workspaces-UUJNOO4T.js → workspaces-VICQYI2N.js} +8 -7
  55. package/dist/{workspaces-3NUUZTQK.js → workspaces-ZXCUVCZC.js} +8 -7
  56. package/dist/{worktree-RZRJNHYL.js → worktree-DPIREDFR.js} +5 -4
  57. package/dist/{worktree-UCWWTVLS.js → worktree-HTUM35NT.js} +5 -4
  58. package/dist/worktree-exclude-N5X5I422.js +13 -0
  59. package/dist/worktree-exclude-X54WSOXM.js +11 -0
  60. package/package.json +1 -1
@@ -1,25 +1,35 @@
1
1
  import {
2
2
  isWorkspaceScratchPath
3
- } from "./chunk-FKOIHGKV.js";
3
+ } from "./chunk-JRH62XC4.js";
4
4
  import {
5
5
  getGithubGitAuthMode,
6
6
  getGithubPat
7
- } from "./chunk-I77RYPOH.js";
7
+ } from "./chunk-4XNSGDXE.js";
8
8
  import {
9
9
  listThreads
10
10
  } from "./chunk-TUGKX5BD.js";
11
11
  import {
12
12
  worktreesRoot
13
13
  } from "./chunk-QZQEXME2.js";
14
+ import {
15
+ ensureWorktreeSideboardIgnored
16
+ } from "./chunk-QQKDGPUI.js";
14
17
  import {
15
18
  gh,
16
19
  git,
20
+ githubAgentAuthDir,
21
+ githubAgentAuthReady,
22
+ githubCredentialHelperGitConfig,
23
+ githubGhConfigEnv,
24
+ materializeGithubAgentAuth,
17
25
  resolveGhAuthToken
18
- } from "./chunk-DVM4ID64.js";
26
+ } from "./chunk-UFHNSFAS.js";
19
27
 
20
28
  // src/git/worktree.ts
21
- import { existsSync as existsSync2, readdirSync, realpathSync } from "fs";
22
- import { join as join2 } from "path";
29
+ import { randomBytes } from "crypto";
30
+ import { existsSync, readdirSync, realpathSync, unlinkSync, writeFileSync } from "fs";
31
+ import { tmpdir } from "os";
32
+ import { join } from "path";
23
33
 
24
34
  // src/git/team-meta.ts
25
35
  var SOCCER_TEAM_META = {
@@ -833,9 +843,23 @@ function worktreeDisplayLabelForGroup(threads) {
833
843
  }
834
844
 
835
845
  // src/git/gh-errors.ts
846
+ var GITHUB_PR_BODY_MAX_CHARS = 65536;
847
+ var GITHUB_PR_BODY_SAFE_CHARS = 6e4;
836
848
  function isGhRateLimitError(text) {
837
849
  return /API rate limit (already )?exceeded/i.test(text) || /rate limit exceeded/i.test(text);
838
850
  }
851
+ function isGhPrBodyTooLongError(text) {
852
+ return /body is too long|body.{0,20}too long|maximum is 65536|exceeds the maximum allowed size|request (entity |is )?too large|payload too large|413 Request Entity Too Large/i.test(
853
+ text
854
+ );
855
+ }
856
+ function clampGithubPrBody(body, max = GITHUB_PR_BODY_SAFE_CHARS) {
857
+ const t = body.replace(/\s+$/u, "");
858
+ if (t.length <= max) return t;
859
+ const note = "\n\n_(truncated to fit GitHub\u2019s PR body limit)_";
860
+ const keep = Math.max(0, max - note.length);
861
+ return `${t.slice(0, keep).replace(/\s+$/u, "")}${note}`;
862
+ }
839
863
  function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
840
864
  const ms = resetEpochSec * 1e3 - nowMs;
841
865
  if (ms <= 0) return "soon";
@@ -846,18 +870,32 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
846
870
  const hours = Math.ceil(mins / 60);
847
871
  return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
848
872
  }
873
+ function omitGhBodyArg(text) {
874
+ return text.replace(
875
+ /--body(?:-file)?(\s+|=)(?:'[^']*'|"[^"]*"|\S+)/g,
876
+ "--body <omitted>"
877
+ );
878
+ }
879
+ function capGhErrorText(text, max = 400) {
880
+ const t = text.trim();
881
+ if (t.length <= max) return t;
882
+ return `${t.slice(0, max - 1).trimEnd()}\u2026`;
883
+ }
849
884
  function extractGhErrorDetail(text) {
850
- const trimmed = text.trim();
885
+ const trimmed = omitGhBodyArg(text.trim());
851
886
  if (!trimmed) return "";
887
+ if (isGhPrBodyTooLongError(text) || isGhPrBodyTooLongError(trimmed)) {
888
+ return "GraphQL: body is too long (GitHub PR body / request size limit).";
889
+ }
852
890
  const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
853
- if (graphql?.[1]) return `GraphQL: ${graphql[1].trim()}`;
891
+ if (graphql?.[1]) return capGhErrorText(`GraphQL: ${graphql[1].trim()}`);
854
892
  const http = trimmed.match(/\bHTTP\s+\d{3}:\s*(.+)$/im);
855
- if (http?.[1]) return `HTTP: ${http[1].trim()}`;
893
+ if (http?.[1]) return capGhErrorText(`HTTP: ${http[1].trim()}`);
856
894
  const lines = trimmed.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
857
895
  if (lines.length > 1 && /^Command failed with exit code/i.test(lines[0])) {
858
- return lines.slice(1).join(" ").trim() || lines[0];
896
+ return capGhErrorText(lines.slice(1).join(" ").trim() || lines[0]);
859
897
  }
860
- return trimmed;
898
+ return capGhErrorText(trimmed);
861
899
  }
862
900
  function formatGhLandError(raw, opts) {
863
901
  const trimmed = raw.trim();
@@ -875,6 +913,10 @@ function formatGhLandError(raw, opts) {
875
913
  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>.";
876
914
  return `Could not create the pull request.${hint}`;
877
915
  }
916
+ if (isGhPrBodyTooLongError(raw) || isGhPrBodyTooLongError(detail)) {
917
+ const pushNote = opts?.pushed === false ? "" : " Your branch was already pushed.";
918
+ return `GitHub rejected the pull request: the GraphQL body is too long (PR description / request size, limit ${GITHUB_PR_BODY_MAX_CHARS} characters).${pushNote} Retry with a short title and body via \`gh pr create --body-file\` (or send_to_thread so the worktree agent writes a brief description). Do not paste a changelog or diff into --body.`;
919
+ }
878
920
  if (isGhRateLimitError(raw) || isGhRateLimitError(detail)) {
879
921
  const when = opts?.resetAt ? ` Try again ${formatRateLimitResetHint(opts.resetAt, opts.nowMs)}.` : " Wait a few minutes and try again.";
880
922
  const pushNote = opts?.pushed === false ? "" : " Your branch was already pushed.";
@@ -906,88 +948,29 @@ function formatIpcInvokeError(err) {
906
948
 
907
949
  // src/git/git-auth-mode.ts
908
950
  import { isAbsolute } from "path";
909
-
910
- // src/git/github-agent-auth.ts
911
- import { chmodSync, existsSync, mkdirSync, writeFileSync } from "fs";
912
- import { homedir } from "os";
913
- import { dirname, join } from "path";
914
- function githubAgentAuthDir() {
915
- const override = process.env.SIDEBOARD_GIT_AUTH_DIR?.trim();
916
- if (override) return override;
917
- return join(homedir(), ".sideboard-git-auth");
918
- }
919
- function githubCredentialStorePath() {
920
- return join(githubAgentAuthDir(), "git-credentials");
921
- }
922
- function githubGhConfigDir() {
923
- return join(githubAgentAuthDir(), "gh");
924
- }
925
- function writePrivateFile(file, body) {
926
- mkdirSync(dirname(file), { recursive: true, mode: 448 });
927
- try {
928
- chmodSync(dirname(file), 448);
929
- } catch {
930
- }
931
- writeFileSync(file, body, { encoding: "utf8", mode: 384 });
932
- try {
933
- chmodSync(file, 384);
934
- } catch {
935
- }
936
- }
937
- function gitCredentialStoreContents(token) {
938
- return `https://x-access-token:${encodeURIComponent(token)}@github.com
939
- `;
940
- }
941
- function ghHostsYml(token, user = "x-access-token") {
942
- const u = user.replace(/[^A-Za-z0-9._-]/g, "") || "x-access-token";
943
- return [
944
- "github.com:",
945
- " git_protocol: https",
946
- ` user: ${u}`,
947
- ` oauth_token: ${token}`,
948
- " users:",
949
- ` ${u}:`,
950
- ` oauth_token: ${token}`,
951
- ""
952
- ].join("\n");
953
- }
954
- function materializeGithubAgentAuth(token, user) {
955
- const trimmed = token.trim();
956
- if (!trimmed) return;
957
- const root = githubAgentAuthDir();
958
- mkdirSync(root, { recursive: true, mode: 448 });
959
- try {
960
- chmodSync(root, 448);
961
- } catch {
962
- }
963
- writePrivateFile(githubCredentialStorePath(), gitCredentialStoreContents(trimmed));
964
- const ghDir = githubGhConfigDir();
965
- mkdirSync(ghDir, { recursive: true, mode: 448 });
966
- writePrivateFile(join(ghDir, "hosts.yml"), ghHostsYml(trimmed, user));
967
- writePrivateFile(join(ghDir, "config.yml"), "git_protocol: https\nprompt: disabled\n");
968
- }
969
- function githubAgentAuthReady() {
970
- return existsSync(githubCredentialStorePath()) && existsSync(join(githubGhConfigDir(), "hosts.yml"));
971
- }
972
- function githubCredentialHelperGitConfig() {
973
- const file = githubCredentialStorePath();
974
- return [
975
- { key: "credential.helper", value: "" },
976
- { key: "credential.helper", value: `store --file=${file}` }
977
- ];
978
- }
979
- function githubGhConfigEnv() {
980
- return {
981
- GH_CONFIG_DIR: githubGhConfigDir(),
982
- GH_PROMPT_DISABLED: "1"
983
- };
984
- }
985
-
986
- // src/git/git-auth-mode.ts
987
951
  var HTTPS_REWRITE = [
988
952
  { key: "url.https://github.com/.insteadOf", value: "git@github.com:" },
989
953
  { key: "url.https://github.com/.insteadOf", value: "ssh://git@github.com/" }
990
954
  ];
955
+ function githubHttpsInsteadOfEntries(remoteUrl) {
956
+ const entries = [...HTTPS_REWRITE];
957
+ const url = remoteUrl?.trim() ?? "";
958
+ const scpAlias = url.match(/^(git@github\.com-[^:]+):/i);
959
+ if (scpAlias?.[1]) {
960
+ entries.push({
961
+ key: "url.https://github.com/.insteadOf",
962
+ value: `${scpAlias[1]}:`
963
+ });
964
+ }
965
+ const sshAlias = url.match(/^ssh:\/\/git@(github\.com-[^/]+)\//i);
966
+ if (sshAlias?.[1]) {
967
+ entries.push({
968
+ key: "url.https://github.com/.insteadOf",
969
+ value: `ssh://git@${sshAlias[1]}/`
970
+ });
971
+ }
972
+ return entries;
973
+ }
991
974
  var TOKEN_TTL_MS = 12 * 60 * 60 * 1e3;
992
975
  var tokenMemo = null;
993
976
  var GITHUB_CHILD_TOKEN_KEYS = [
@@ -1017,16 +1000,19 @@ function appendIndexedGitConfig(existing, entries) {
1017
1000
  out.GIT_CONFIG_COUNT = String(count + entries.length);
1018
1001
  return out;
1019
1002
  }
1020
- function githubAgentGitEnv(existing) {
1003
+ function githubAgentGitEnv(existing, opts) {
1021
1004
  const helpers = githubAgentAuthReady() ? githubCredentialHelperGitConfig() : [{ key: "credential.helper", value: "" }];
1022
- return appendIndexedGitConfig(existing, [...HTTPS_REWRITE, ...helpers]);
1005
+ return appendIndexedGitConfig(existing, [
1006
+ ...githubHttpsInsteadOfEntries(opts?.remoteUrl),
1007
+ ...helpers
1008
+ ]);
1023
1009
  }
1024
1010
  function applyGithubGitAuthEnv(existing, opts) {
1025
1011
  const out = { ...nonInteractiveGitProcessEnv() };
1026
1012
  const token = opts.token?.trim() || existing?.GH_TOKEN?.trim() || "" || "";
1027
1013
  if (token) materializeGithubAgentAuth(token);
1028
1014
  Object.assign(out, githubGhConfigEnv());
1029
- if (opts.mode === "ssh") return out;
1015
+ if (opts.mode === "ssh" && !token) return out;
1030
1016
  Object.assign(out, githubAgentGitEnv(existing));
1031
1017
  return out;
1032
1018
  }
@@ -1142,8 +1128,9 @@ function formatGitAuthModeDirective(mode) {
1142
1128
  case "ssh":
1143
1129
  return [
1144
1130
  "Git authentication (Settings \u2192 Git mode: SSH):",
1145
- "- Keep SSH remotes (`git@github.com:\u2026`). Do not rewrite them to HTTPS.",
1146
- "- SSH is batch-mode: it will not prompt for a Keychain password. If push fails with `Permission denied (publickey)`, tell the user to unlock ssh-agent or switch Settings \u2192 Git to Auto / gh CLI \u2014 do not rewrite remotes yourself.",
1131
+ "- Stored remotes stay SSH (`git@github.com:\u2026`). Do not `git remote set-url` or rewrite them to HTTPS.",
1132
+ "- This process may push over HTTPS via `gh` (process-only `insteadOf`) so ask_git / unattended turns work when ssh-agent is missing \u2014 that does not change the stored remote.",
1133
+ "- SSH is batch-mode: it will not prompt for a Keychain password. If both SSH and HTTPS fail, tell the user to unlock ssh-agent or run `gh auth login` (or switch Settings \u2192 Git to Auto).",
1147
1134
  "- `gh` already authenticates for PRs/API (no token in the environment). Do not set GitHub token environment variables or run `gh auth login` from this turn.",
1148
1135
  "- Push with `git push -u origin HEAD`. Create/update PRs with `gh` as below."
1149
1136
  ].join("\n");
@@ -2265,6 +2252,16 @@ function originFetchBranch(sourceRef) {
2265
2252
  if (ref.startsWith("origin/")) return ref.slice("origin/".length) || null;
2266
2253
  return ref;
2267
2254
  }
2255
+ async function originRemoteUrl(cwd) {
2256
+ const result = await git(["remote", "get-url", "origin"], cwd, { reject: false });
2257
+ return result.exitCode === 0 ? result.stdout.trim() : "";
2258
+ }
2259
+ function githubHttpsRetryEnv(remoteUrl) {
2260
+ return {
2261
+ GIT_TERMINAL_PROMPT: "0",
2262
+ ...githubAgentGitEnv(void 0, { remoteUrl })
2263
+ };
2264
+ }
2268
2265
  async function fetchOriginWithAuth(repoPath, args, timeoutMs) {
2269
2266
  const first = await git(args, repoPath, { reject: false, timeoutMs });
2270
2267
  if (first.exitCode === 0) return true;
@@ -2274,15 +2271,14 @@ async function fetchOriginWithAuth(repoPath, args, timeoutMs) {
2274
2271
  } catch {
2275
2272
  return false;
2276
2273
  }
2277
- if (mode === "ssh") return false;
2278
2274
  const token = await resolveGithubAgentToken(mode, repoPath);
2279
2275
  if (!token) return false;
2276
+ const remoteUrl = await originRemoteUrl(repoPath);
2280
2277
  const tryHttps = async (header) => git(args, repoPath, {
2281
2278
  reject: false,
2282
2279
  timeoutMs,
2283
- env: { GIT_TERMINAL_PROMPT: "0" },
2280
+ env: githubHttpsRetryEnv(remoteUrl),
2284
2281
  config: {
2285
- "url.https://github.com/.insteadOf": "git@github.com:",
2286
2282
  "http.extraHeader": header
2287
2283
  }
2288
2284
  });
@@ -2364,8 +2360,8 @@ async function refreshOriginAndMaybeFastForwardMain(repoPath, sourceRef) {
2364
2360
  }
2365
2361
  async function createThreadWorktree(opts) {
2366
2362
  let branchName = `thread/${opts.slug}`;
2367
- const worktreePath = join2(worktreesRoot(opts.repoPath), opts.slug);
2368
- if (existsSync2(worktreePath)) {
2363
+ const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
2364
+ if (existsSync(worktreePath)) {
2369
2365
  throw new Error(`Worktree already exists at ${worktreePath}`);
2370
2366
  }
2371
2367
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2400,13 +2396,14 @@ ${add.stdout}`;
2400
2396
  });
2401
2397
  branchName = added;
2402
2398
  await ensureGhPreferOrigin(worktreePath);
2399
+ await ensureWorktreeSideboardIgnored(worktreePath);
2403
2400
  return { branchName, worktreePath };
2404
2401
  }
2405
2402
  async function createExistingBranchWorktree(opts) {
2406
2403
  const branchName = opts.branchName.trim();
2407
2404
  if (!branchName) throw new Error("branch name required");
2408
- const worktreePath = join2(worktreesRoot(opts.repoPath), opts.slug);
2409
- if (existsSync2(worktreePath)) {
2405
+ const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
2406
+ if (existsSync(worktreePath)) {
2410
2407
  throw new Error(`Worktree already exists at ${worktreePath}`);
2411
2408
  }
2412
2409
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2444,6 +2441,7 @@ async function createExistingBranchWorktree(opts) {
2444
2441
  await git(["checkout", "-B", branchName], worktreePath, { reject: false });
2445
2442
  }
2446
2443
  await ensureGhPreferOrigin(worktreePath);
2444
+ await ensureWorktreeSideboardIgnored(worktreePath);
2447
2445
  return { branchName, worktreePath };
2448
2446
  }
2449
2447
  async function removeWorktree(repoPath, worktreePath, opts) {
@@ -2532,7 +2530,9 @@ async function currentBranch(worktreePath) {
2532
2530
  async function commitAll(worktreePath, message) {
2533
2531
  const dirty = await isDirty(worktreePath);
2534
2532
  if (!dirty) return false;
2533
+ await ensureWorktreeSideboardIgnored(worktreePath);
2535
2534
  await git(["add", "-A"], worktreePath);
2535
+ await git(["reset", "-q", "--", ".sideboard"], worktreePath, { reject: false });
2536
2536
  await git(["commit", "-m", message], worktreePath);
2537
2537
  return true;
2538
2538
  }
@@ -2546,14 +2546,14 @@ async function pushBranch(worktreePath, branchName) {
2546
2546
  const token = await resolveGithubAgentToken(getGithubGitAuthMode(), worktreePath);
2547
2547
  if (!slug || !token) {
2548
2548
  throw new Error(
2549
- sshErr || `git push origin ${branchName} failed` + (!token ? " (no SSH agent and gh auth token unavailable \u2014 run: gh auth login)" : "")
2549
+ sshErr || `git push origin ${branchName} failed` + (!token ? " (SSH remote and gh auth token unavailable \u2014 unlock ssh-agent or run: gh auth login)" : "")
2550
2550
  );
2551
2551
  }
2552
+ const remoteUrl = await originRemoteUrl(worktreePath);
2552
2553
  const tryHttps = async (header) => git(["push", "-u", "origin", branchName], worktreePath, {
2553
2554
  reject: false,
2554
- env: { GIT_TERMINAL_PROMPT: "0" },
2555
+ env: githubHttpsRetryEnv(remoteUrl),
2555
2556
  config: {
2556
- "url.https://github.com/.insteadOf": "git@github.com:",
2557
2557
  "http.extraHeader": header
2558
2558
  }
2559
2559
  });
@@ -2683,6 +2683,32 @@ async function createOrUpdatePr(worktreePath, opts) {
2683
2683
  const repoArgs = ghRepoSelectArgs(slug);
2684
2684
  const headRef = ghHeadRef(slug, opts.head);
2685
2685
  const branchOnly = opts.head.trim().replace(/^refs\/heads\//, "");
2686
+ const bodyText = clampGithubPrBody(opts.body ?? opts.title);
2687
+ const bodyFile = join(
2688
+ tmpdir(),
2689
+ `sideboard-pr-body-${process.pid}-${randomBytes(6).toString("hex")}.md`
2690
+ );
2691
+ writeFileSync(bodyFile, bodyText.endsWith("\n") ? bodyText : `${bodyText}
2692
+ `, "utf8");
2693
+ const bodyArgs = ["--body-file", bodyFile];
2694
+ try {
2695
+ return await createOrUpdatePrWithBodyFile(worktreePath, {
2696
+ ...opts,
2697
+ slug,
2698
+ repoArgs,
2699
+ headRef,
2700
+ branchOnly,
2701
+ bodyArgs
2702
+ });
2703
+ } finally {
2704
+ try {
2705
+ unlinkSync(bodyFile);
2706
+ } catch {
2707
+ }
2708
+ }
2709
+ }
2710
+ async function createOrUpdatePrWithBodyFile(worktreePath, opts) {
2711
+ const { slug, repoArgs, headRef, branchOnly, bodyArgs } = opts;
2686
2712
  const existing = await gh(
2687
2713
  [...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
2688
2714
  worktreePath,
@@ -2698,8 +2724,7 @@ async function createOrUpdatePr(worktreePath, opts) {
2698
2724
  branchOnly,
2699
2725
  "--title",
2700
2726
  opts.title,
2701
- "--body",
2702
- opts.body ?? opts.title
2727
+ ...bodyArgs
2703
2728
  ],
2704
2729
  worktreePath,
2705
2730
  { reject: false }
@@ -2714,8 +2739,7 @@ async function createOrUpdatePr(worktreePath, opts) {
2714
2739
  "--web",
2715
2740
  "--title",
2716
2741
  opts.title,
2717
- "--body",
2718
- opts.body ?? opts.title,
2742
+ ...bodyArgs,
2719
2743
  "--base",
2720
2744
  opts.base,
2721
2745
  "--head",
@@ -2736,8 +2760,7 @@ async function createOrUpdatePr(worktreePath, opts) {
2736
2760
  "create",
2737
2761
  "--title",
2738
2762
  opts.title,
2739
- "--body",
2740
- opts.body ?? opts.title,
2763
+ ...bodyArgs,
2741
2764
  "--base",
2742
2765
  opts.base,
2743
2766
  "--head",
@@ -2761,8 +2784,8 @@ function sameRepoPath(a, b) {
2761
2784
  return normalizeWorktreePath(a) === normalizeWorktreePath(b);
2762
2785
  }
2763
2786
  function listLocalThreadBranchSlugs(repoPath) {
2764
- const refsDir = join2(repoPath, ".git", "refs", "heads", "thread");
2765
- if (!existsSync2(refsDir)) return [];
2787
+ const refsDir = join(repoPath, ".git", "refs", "heads", "thread");
2788
+ if (!existsSync(refsDir)) return [];
2766
2789
  try {
2767
2790
  return readdirSync(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2768
2791
  } catch {
@@ -2772,7 +2795,7 @@ function listLocalThreadBranchSlugs(repoPath) {
2772
2795
  function collectTakenTeamSlugs(repoPath) {
2773
2796
  const taken = /* @__PURE__ */ new Set();
2774
2797
  const root = worktreesRoot(repoPath);
2775
- if (existsSync2(root)) {
2798
+ if (existsSync(root)) {
2776
2799
  for (const entry of readdirSync(root, { withFileTypes: true })) {
2777
2800
  if (entry.isDirectory() && entry.name !== ".DS_Store") {
2778
2801
  taken.add(normalizeTakenSlug(entry.name));
@@ -2794,8 +2817,8 @@ function allocateTeamSlug(repoPath) {
2794
2817
  const taken = collectTakenTeamSlugs(repoPath);
2795
2818
  for (let attempt = 0; attempt < 32; attempt++) {
2796
2819
  const team = allocateTeamName(taken);
2797
- const path = join2(worktreesRoot(repoPath), team.slug);
2798
- if (!existsSync2(path)) return team;
2820
+ const path = join(worktreesRoot(repoPath), team.slug);
2821
+ if (!existsSync(path)) return team;
2799
2822
  taken.add(team.slug);
2800
2823
  }
2801
2824
  throw new Error("No available soccer team worktree directories left");
@@ -2814,13 +2837,18 @@ export {
2814
2837
  threadDisplayLabel,
2815
2838
  worktreeDisplayLabel,
2816
2839
  worktreeDisplayLabelForGroup,
2840
+ GITHUB_PR_BODY_MAX_CHARS,
2841
+ GITHUB_PR_BODY_SAFE_CHARS,
2817
2842
  isGhRateLimitError,
2843
+ isGhPrBodyTooLongError,
2844
+ clampGithubPrBody,
2818
2845
  formatRateLimitResetHint,
2819
2846
  extractGhErrorDetail,
2820
2847
  formatGhLandError,
2821
2848
  isPrNotMergeableError,
2822
2849
  formatMergePrError,
2823
2850
  formatIpcInvokeError,
2851
+ githubHttpsInsteadOfEntries,
2824
2852
  nonInteractiveGitProcessEnv,
2825
2853
  appendIndexedGitConfig,
2826
2854
  githubAgentGitEnv,
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-4TR3HZFT.js";
5
5
  import {
6
6
  run
7
- } from "./chunk-DVM4ID64.js";
7
+ } from "./chunk-UFHNSFAS.js";
8
8
 
9
9
  // src/agents/error-detail.ts
10
10
  function formatUnknownDetail(err) {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  isGlobalRepoPath
3
- } from "./chunk-R4JP32P5.js";
3
+ } from "./chunk-ADCEGG4A.js";
4
4
  import {
5
5
  ensureGhPreferOrigin,
6
6
  resolveRepoRoot
7
- } from "./chunk-YP3CSOZ6.js";
7
+ } from "./chunk-GE7M3QTE.js";
8
8
  import {
9
9
  appDataDir
10
10
  } from "./chunk-QZQEXME2.js";
@@ -1,6 +1,8 @@
1
1
  // src/paths/workspace-scratch.ts
2
2
  var ATTACHMENTS_DIR = ".context/attachments";
3
+ var DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
3
4
  var LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
5
+ var LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
4
6
  var ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
5
7
  *
6
8
  !.gitignore
@@ -10,12 +12,14 @@ function attachmentsGitignoreBody() {
10
12
  }
11
13
  function isWorkspaceScratchPath(relativePath) {
12
14
  const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
13
- return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
15
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
14
16
  }
15
17
 
16
18
  export {
17
19
  ATTACHMENTS_DIR,
20
+ DETACHED_JOBS_DIR,
18
21
  LEGACY_ATTACHMENTS_DIR,
22
+ LEGACY_DETACHED_JOBS_DIR,
19
23
  attachmentsGitignoreBody,
20
24
  isWorkspaceScratchPath
21
25
  };
@@ -5,26 +5,26 @@ import {
5
5
  brightsyMcpServerName,
6
6
  ensureCliTeamTracked,
7
7
  ensureConnectedBrightsyTeamTokens
8
- } from "./chunk-ED4UPEJX.js";
8
+ } from "./chunk-G7SIQFBQ.js";
9
9
  import {
10
10
  loadBrightsyConfig
11
11
  } from "./chunk-S42XV45P.js";
12
12
  import {
13
13
  isOrchestratorThread
14
- } from "./chunk-N3VNOFXQ.js";
14
+ } from "./chunk-Y2FNDKHF.js";
15
15
  import {
16
16
  codexUnattendedGitConfigArgs,
17
17
  mergeAgentGitAuthEnv,
18
18
  resolveAgentGitAuthEnv,
19
19
  resolveCodexGitWritableRoots
20
- } from "./chunk-JTPDWTWJ.js";
20
+ } from "./chunk-3JBMACYX.js";
21
21
  import {
22
22
  enrichPathWithNpmGlobalBin,
23
23
  isConductorBundledCli,
24
24
  resolveCommandBinarySync,
25
25
  run,
26
26
  withExportedPath
27
- } from "./chunk-KPIYENTF.js";
27
+ } from "./chunk-FNQNJOHY.js";
28
28
  import {
29
29
  claudeChromeEnabled,
30
30
  isElectronLikeCommand,
@@ -33,7 +33,7 @@ import {
33
33
  resolveClaudeExecutable,
34
34
  unwrapStrippedElectronLaunch,
35
35
  wrapElectronAsNodeLaunch
36
- } from "./chunk-BGQBELGJ.js";
36
+ } from "./chunk-CZD4NRDZ.js";
37
37
  import {
38
38
  appDataDir
39
39
  } from "./chunk-BD2ICC4D.js";
@@ -879,6 +879,13 @@ function isSubagentToolName(name) {
879
879
  if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
880
880
  return /connectedAgentRequest/i.test(n);
881
881
  }
882
+ function isInternalAgentStatusText(text) {
883
+ const t = text.trim();
884
+ if (!t) return false;
885
+ if (/waiting for gate to pass/i.test(t)) return true;
886
+ if (/^agent is running\b/i.test(t) && t.length < 160) return true;
887
+ return false;
888
+ }
882
889
  function isPollWrapperToolName(name) {
883
890
  const n = (name ?? "").replace(/[_-]/g, "");
884
891
  return /^(taskoutput|taskstop|sleep)$/i.test(n);
@@ -886,7 +893,10 @@ function isPollWrapperToolName(name) {
886
893
  function lastTextPart(parts, type) {
887
894
  for (let i = parts.length - 1; i >= 0; i--) {
888
895
  const p = parts[i];
889
- if (p.type === type && p.text.trim()) return p.text.trim();
896
+ if (p.type !== type) continue;
897
+ const text = p.text.trim();
898
+ if (!text || isInternalAgentStatusText(text)) continue;
899
+ return text;
890
900
  }
891
901
  return "";
892
902
  }
@@ -2088,7 +2098,7 @@ var claudeAdapter = {
2088
2098
  );
2089
2099
  }
2090
2100
  const mode = permissionMode(thread);
2091
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-2SIPM4PW.js");
2101
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-T7RL3XEC.js");
2092
2102
  const isOrchestrator = isOrchestratorThread2(thread);
2093
2103
  const injectedServers = await buildInjectedMcpServers({
2094
2104
  includeSideboard: true,
@@ -3925,6 +3935,7 @@ export {
3925
3935
  listBrightsyChatTargets,
3926
3936
  brightsyAdapter,
3927
3937
  toolDescription,
3938
+ isInternalAgentStatusText,
3928
3939
  isPollWrapperToolName,
3929
3940
  liveActivitySummary,
3930
3941
  applyAgentEvent,
@@ -13,7 +13,7 @@ function attachmentsGitignoreBody() {
13
13
  }
14
14
  function isWorkspaceScratchPath(relativePath) {
15
15
  const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
16
- return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
16
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
17
17
  }
18
18
 
19
19
  export {
@@ -2,10 +2,10 @@
2
2
 
3
3
  import {
4
4
  resolveGithubRepoSlug
5
- } from "./chunk-JTPDWTWJ.js";
5
+ } from "./chunk-3JBMACYX.js";
6
6
  import {
7
7
  resolveThreadDefaults
8
- } from "./chunk-BGQBELGJ.js";
8
+ } from "./chunk-CZD4NRDZ.js";
9
9
  import {
10
10
  globalAgentCwd,
11
11
  sideboardReposDir
@@ -47,8 +47,8 @@ var COORDINATOR_TOOL_PLAYBOOK = [
47
47
  "Discover:",
48
48
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
49
49
  "- list_board \u2014 Home Kanban of worktrees (New / Draft / Review / Merged; one card per checkout). Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind, column, limit.",
50
- "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear, AbleTime MCP, or GitHub Issues)",
51
- "- linear_list_teams / linear_get_issue / linear_create_issue / linear_update_issue / linear_comment \u2014 Linear Account connection; call linear_list_teams for team key and workflow states; pass ENG-123 or uuid. linear_get_issue returns comments, relations, parent/children, and the rest of the issue metadata. If mutations fail with a scope error, Disconnect and Connect Linear in Account settings.",
50
+ "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear, AbleTime MCP, or GitHub Issues). list_issues accepts query and assignee (me / unassigned / all / user id) so Linear is not limited to tickets assigned to you.",
51
+ "- linear_list_teams / linear_search_issues / linear_get_issue / linear_create_issue / linear_update_issue / linear_comment \u2014 Linear Account connection; call linear_list_teams for team key and workflow states; pass ENG-123 or uuid. linear_search_issues finds unassigned and other people's open issues (assignee=unassigned|all|me|user). linear_get_issue returns comments, relations, parent/children, and the rest of the issue metadata. If mutations fail with a scope error, Disconnect and Connect Linear in Account settings.",
52
52
  "- abletime_orientation / abletime_list_projects / abletime_list_tasks / abletime_search_tasks / abletime_get_task / abletime_create_task / abletime_ensure_task \u2014 AbleTime Account personal access token (hosted MCP). Call orientation first. If work has no task yet, abletime_ensure_task creates one to track against (or create_thread from the default branch does that automatically when AbleTime is the preferred issue source).",
53
53
  "- list_teams / slack_list_channels / slack_list_users / slack_search / slack_read / slack_post / slack_replies \u2014 Slack workspaces from Settings \u2192 Remote; pass team_id from list_teams",
54
54
  "- Optional connectors (Vercel, Supabase, PostHog, Sentry) in Settings \u2192 Connectors inject tokens into worktree agent env when connected. Prefer official CLIs (`vercel`, `supabase`, `sentry-cli`) with those env vars. PostHog has no first-class CLI \u2014 use the HTTP API (`POSTHOG_PERSONAL_API_KEY`). If a CLI is missing, the user can Install CLI on that row (not auto-installed on Connect). Do not add vendor MCPs or ask the user to paste tokens again. Git (`gh`) stays Settings \u2192 Git; issue tracking stays Settings \u2192 Issues; Slack stays Settings \u2192 Remote (Sideboard MCP).",
@@ -67,7 +67,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
67
67
  "- 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.",
68
68
  "- 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.",
69
69
  "- 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)",
70
- "- 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). 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. 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.",
70
+ "- 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.",
71
71
  "- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
72
72
  "- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
73
73
  "Setup / run:",
@@ -76,7 +76,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
76
76
  "Inspect / review / PRs:",
77
77
  "- get_diff \u2014 compact diff summary",
78
78
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .claude/skills/review/SKILL.md when present, else seeds that skill / local guidelines; sends "Review changes in this workspace."); then wait_for_turn (loop while stillRunning) / get_turn_result on the returned id',
79
- "- 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 (loop while stillRunning). Prefer this over paraphrasing.",
79
+ "- 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 (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.",
80
80
  '- 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.',
81
81
  '- 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.',
82
82
  "Process guides:",