@sideboard-ai/core 0.1.77 → 0.1.81

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 (31) hide show
  1. package/dist/{agents-LKUHPPEQ.js → agents-EBKJ55PN.js} +5 -5
  2. package/dist/{agents-XJV6J3K2.js → agents-UQ7D6U6L.js} +6 -6
  3. package/dist/{app-settings-3TLA2EJ7.js → app-settings-6IOQYGBK.js} +7 -1
  4. package/dist/{app-settings-P42Z3VOB.js → app-settings-NILNWNNQ.js} +7 -1
  5. package/dist/{chunk-2FJI5JXX.js → chunk-4AVU4QXO.js} +54 -20
  6. package/dist/{chunk-XJNCWYFR.js → chunk-4HJK57XU.js} +8 -14
  7. package/dist/{chunk-VERLUKN2.js → chunk-6F2TQFJE.js} +6 -6
  8. package/dist/{chunk-EEKSZTMU.js → chunk-CQBQWX73.js} +10 -16
  9. package/dist/{chunk-OE7YBB5X.js → chunk-FUUVPN4A.js} +108 -3
  10. package/dist/{chunk-W7YOTDVO.js → chunk-IFPN6TER.js} +47 -2
  11. package/dist/{chunk-75U65MBE.js → chunk-K553XK7M.js} +2 -2
  12. package/dist/{chunk-GWTLKXXP.js → chunk-P5VDPGAN.js} +3 -3
  13. package/dist/{chunk-2ZIPJRJE.js → chunk-TDJ43HUD.js} +2 -2
  14. package/dist/{chunk-PTJJIQK7.js → chunk-XKNBSYA7.js} +47 -2
  15. package/dist/{chunk-QD37IILI.js → chunk-XNECBC6T.js} +66 -20
  16. package/dist/{chunk-MF2YMEGD.js → chunk-YVVXWWCC.js} +110 -3
  17. package/dist/{coordinator-prompt-LLFJUO2R.js → coordinator-prompt-JQF72JKX.js} +4 -4
  18. package/dist/{coordinator-prompt-MRCMMRSF.js → coordinator-prompt-NYNEFKIV.js} +3 -3
  19. package/dist/{global-workspace-YNMNO4CL.js → global-workspace-DZM4ZFGU.js} +5 -5
  20. package/dist/{global-workspace-TWHMYLTZ.js → global-workspace-NUMUJRWG.js} +4 -4
  21. package/dist/index.cjs +585 -251
  22. package/dist/index.d.cts +97 -8
  23. package/dist/index.d.ts +97 -8
  24. package/dist/index.js +363 -222
  25. package/dist/mcp/run-stdio.cjs +2502 -2286
  26. package/dist/mcp/run-stdio.js +162 -133
  27. package/dist/{workspaces-O3U5BENH.js → workspaces-QLRCOXAY.js} +6 -6
  28. package/dist/{workspaces-GQ4XKBD3.js → workspaces-SUU2GO3A.js} +5 -5
  29. package/dist/{worktree-VOCH3WS3.js → worktree-5VLLFI5W.js} +5 -1
  30. package/dist/{worktree-KWXHMKRN.js → worktree-UNSOCYZU.js} +5 -1
  31. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -79,11 +79,11 @@ var init_thinking_effort = __esm({
79
79
  });
80
80
 
81
81
  // src/hook/settings.ts
82
- function expandHome(path) {
83
- if (path.startsWith("~/") || path === "~") {
84
- return (0, import_node_path.join)((0, import_node_os.homedir)(), path.slice(2));
82
+ function expandHome(path2) {
83
+ if (path2.startsWith("~/") || path2 === "~") {
84
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), path2.slice(2));
85
85
  }
86
- return path;
86
+ return path2;
87
87
  }
88
88
  function parseAvailableIn(raw) {
89
89
  if (!Array.isArray(raw)) return void 0;
@@ -93,9 +93,9 @@ function parseAvailableIn(raw) {
93
93
  }
94
94
  return out.length ? out : void 0;
95
95
  }
96
- function parseSettingsFile(path, source) {
97
- if (!(0, import_node_fs.existsSync)(path)) return null;
98
- const raw = (0, import_node_fs.readFileSync)(path, "utf8");
96
+ function parseSettingsFile(path2, source) {
97
+ if (!(0, import_node_fs.existsSync)(path2)) return null;
98
+ const raw = (0, import_node_fs.readFileSync)(path2, "utf8");
99
99
  const data = (0, import_smol_toml.parse)(raw);
100
100
  const scripts = data.scripts ?? {};
101
101
  const setup = typeof scripts.setup === "string" ? scripts.setup : void 0;
@@ -386,14 +386,14 @@ var init_paths = __esm({
386
386
  });
387
387
 
388
388
  // src/store/private-file.ts
389
- function writePrivateFile(path, contents) {
390
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
391
- (0, import_node_fs3.writeFileSync)(path, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
392
- chmodOwnerOnly(path);
389
+ function writePrivateFile(path2, contents) {
390
+ (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path2), { recursive: true });
391
+ (0, import_node_fs3.writeFileSync)(path2, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
392
+ chmodOwnerOnly(path2);
393
393
  }
394
- function chmodOwnerOnly(path) {
394
+ function chmodOwnerOnly(path2) {
395
395
  try {
396
- (0, import_node_fs3.chmodSync)(path, PRIVATE_FILE_MODE);
396
+ (0, import_node_fs3.chmodSync)(path2, PRIVATE_FILE_MODE);
397
397
  } catch {
398
398
  }
399
399
  }
@@ -493,12 +493,12 @@ function decryptEnvelope(envelope, key) {
493
493
  ]);
494
494
  return JSON.parse(plaintext.toString("utf8"));
495
495
  }
496
- function readSecureJson(path) {
497
- if (!(0, import_node_fs4.existsSync)(path)) return null;
498
- chmodOwnerOnly(path);
496
+ function readSecureJson(path2) {
497
+ if (!(0, import_node_fs4.existsSync)(path2)) return null;
498
+ chmodOwnerOnly(path2);
499
499
  let parsed;
500
500
  try {
501
- parsed = JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
501
+ parsed = JSON.parse((0, import_node_fs4.readFileSync)(path2, "utf8"));
502
502
  } catch {
503
503
  return null;
504
504
  }
@@ -511,24 +511,24 @@ function readSecureJson(path) {
511
511
  }
512
512
  return decryptEnvelope(parsed, key);
513
513
  }
514
- function writeSecureJson(path, value) {
514
+ function writeSecureJson(path2, value) {
515
515
  const key = resolveVaultKey();
516
516
  const body = key ? encryptJson(value, key) : value;
517
- writePrivateFile(path, `${JSON.stringify(body, null, 2)}
517
+ writePrivateFile(path2, `${JSON.stringify(body, null, 2)}
518
518
  `);
519
519
  }
520
- function isSecureFileEncrypted(path) {
521
- if (!(0, import_node_fs4.existsSync)(path)) return false;
520
+ function isSecureFileEncrypted(path2) {
521
+ if (!(0, import_node_fs4.existsSync)(path2)) return false;
522
522
  try {
523
- return isEnvelope(JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8")));
523
+ return isEnvelope(JSON.parse((0, import_node_fs4.readFileSync)(path2, "utf8")));
524
524
  } catch {
525
525
  return false;
526
526
  }
527
527
  }
528
- function secureFileUnlocksWith(path, key) {
529
- if (!(0, import_node_fs4.existsSync)(path) || key.length !== 32) return true;
528
+ function secureFileUnlocksWith(path2, key) {
529
+ if (!(0, import_node_fs4.existsSync)(path2) || key.length !== 32) return true;
530
530
  try {
531
- const parsed = JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
531
+ const parsed = JSON.parse((0, import_node_fs4.readFileSync)(path2, "utf8"));
532
532
  if (!isEnvelope(parsed)) return true;
533
533
  decryptEnvelope(parsed, key);
534
534
  return true;
@@ -583,6 +583,9 @@ function normalizeVault(raw) {
583
583
  if (typeof raw.slackAppToken === "string" && raw.slackAppToken.trim()) {
584
584
  out.slackAppToken = raw.slackAppToken.trim();
585
585
  }
586
+ if (typeof raw.githubPat === "string" && raw.githubPat.trim()) {
587
+ out.githubPat = raw.githubPat.trim();
588
+ }
586
589
  if (raw.environment && typeof raw.environment === "object") {
587
590
  const environment = {};
588
591
  for (const [key, value] of Object.entries(raw.environment)) {
@@ -607,6 +610,7 @@ var init_secret_vault = __esm({
607
610
  // src/store/app-settings.ts
608
611
  var app_settings_exports = {};
609
612
  __export(app_settings_exports, {
613
+ GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
610
614
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
611
615
  appSettingsPath: () => appSettingsPath,
612
616
  applyAppEnvironment: () => applyAppEnvironment,
@@ -630,6 +634,8 @@ __export(app_settings_exports, {
630
634
  getDefaultEffort: () => getDefaultEffort,
631
635
  getDefaultFast: () => getDefaultFast,
632
636
  getDefaultModel: () => getDefaultModel,
637
+ getGithubGitAuthMode: () => getGithubGitAuthMode,
638
+ getGithubPat: () => getGithubPat,
633
639
  getIssueSource: () => getIssueSource,
634
640
  getLinearApiKey: () => getLinearApiKey,
635
641
  harnessEnvKey: () => harnessEnvKey,
@@ -674,12 +680,14 @@ function toPublicAppSettings(settings) {
674
680
  const integrations = { ...settings.integrations };
675
681
  const slackClientSecret = integrations.slackClientSecret;
676
682
  const slackAppToken = integrations.slackAppToken;
683
+ const githubPat = integrations.githubPat;
677
684
  delete integrations.linearApiKey;
678
685
  delete integrations.linearAccessToken;
679
686
  delete integrations.linearRefreshToken;
680
687
  delete integrations.linearClientSecret;
681
688
  delete integrations.slackClientSecret;
682
689
  delete integrations.slackAppToken;
690
+ delete integrations.githubPat;
683
691
  return {
684
692
  ...settings,
685
693
  environment,
@@ -688,7 +696,8 @@ function toPublicAppSettings(settings) {
688
696
  hasLinearApiKey: hasLinearCredentials(settings.integrations),
689
697
  hasLinearOAuth: hasLinearOAuth(settings.integrations),
690
698
  hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
691
- hasSlackAppToken: Boolean(slackAppToken?.trim())
699
+ hasSlackAppToken: Boolean(slackAppToken?.trim()),
700
+ hasGithubPat: Boolean(githubPat?.trim())
692
701
  }
693
702
  };
694
703
  }
@@ -703,8 +712,8 @@ function normalizeClaude(raw) {
703
712
  const source = raw;
704
713
  const out = {};
705
714
  if (typeof source.executablePath === "string") {
706
- const path = source.executablePath.trim();
707
- if (path) out.executablePath = path;
715
+ const path2 = source.executablePath.trim();
716
+ if (path2) out.executablePath = path2;
708
717
  }
709
718
  if (typeof source.chromeEnabled === "boolean") {
710
719
  out.chromeEnabled = source.chromeEnabled;
@@ -716,8 +725,8 @@ function normalizeCliExecutable(raw) {
716
725
  const source = raw;
717
726
  const out = {};
718
727
  if (typeof source.executablePath === "string") {
719
- const path = source.executablePath.trim();
720
- if (path) out.executablePath = path;
728
+ const path2 = source.executablePath.trim();
729
+ if (path2) out.executablePath = path2;
721
730
  }
722
731
  return out;
723
732
  }
@@ -726,8 +735,8 @@ function normalizeBrightsy(raw) {
726
735
  const source = raw;
727
736
  const out = {};
728
737
  if (typeof source.executablePath === "string") {
729
- const path = source.executablePath.trim();
730
- if (path) out.executablePath = path;
738
+ const path2 = source.executablePath.trim();
739
+ if (path2) out.executablePath = path2;
731
740
  }
732
741
  if (typeof source.cloudConnectEnabled === "boolean") {
733
742
  out.cloudConnectEnabled = source.cloudConnectEnabled;
@@ -804,6 +813,13 @@ function normalizeIntegrations(raw) {
804
813
  const label = source.slackDeviceLabel.trim().slice(0, 64);
805
814
  if (label) out.slackDeviceLabel = label;
806
815
  }
816
+ if (typeof source.githubGitAuthMode === "string" && GIT_AUTH_MODES.has(source.githubGitAuthMode)) {
817
+ out.githubGitAuthMode = source.githubGitAuthMode;
818
+ }
819
+ if (typeof source.githubPat === "string") {
820
+ const pat = source.githubPat.trim();
821
+ if (pat) out.githubPat = pat;
822
+ }
807
823
  return out;
808
824
  }
809
825
  function normalizeDefaults(raw) {
@@ -931,11 +947,11 @@ function normalizeSettings(raw) {
931
947
  };
932
948
  }
933
949
  function readSettingsFile() {
934
- const path = appSettingsPath();
935
- if (!(0, import_node_fs5.existsSync)(path)) return { ...EMPTY_SETTINGS };
950
+ const path2 = appSettingsPath();
951
+ if (!(0, import_node_fs5.existsSync)(path2)) return { ...EMPTY_SETTINGS };
936
952
  try {
937
- chmodOwnerOnly(path);
938
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
953
+ chmodOwnerOnly(path2);
954
+ const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path2, "utf8"));
939
955
  return normalizeSettings(parsed);
940
956
  } catch {
941
957
  return { ...EMPTY_SETTINGS };
@@ -943,7 +959,7 @@ function readSettingsFile() {
943
959
  }
944
960
  function diskHoldsSecrets(disk) {
945
961
  return Boolean(
946
- disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || Object.keys(disk.environment).length > 0
962
+ disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || Object.keys(disk.environment).length > 0
947
963
  );
948
964
  }
949
965
  function mergeVault(disk) {
@@ -968,6 +984,9 @@ function mergeVault(disk) {
968
984
  if (vault.slackAppToken && !integrations.slackAppToken) {
969
985
  integrations.slackAppToken = vault.slackAppToken;
970
986
  }
987
+ if (vault.githubPat && !integrations.githubPat) {
988
+ integrations.githubPat = vault.githubPat;
989
+ }
971
990
  return { ...disk, environment, integrations };
972
991
  }
973
992
  function persistSplit(settings) {
@@ -978,12 +997,14 @@ function persistSplit(settings) {
978
997
  const linearClientSecret = integrations.linearClientSecret;
979
998
  const slackClientSecret = integrations.slackClientSecret;
980
999
  const slackAppToken = integrations.slackAppToken;
1000
+ const githubPat = integrations.githubPat;
981
1001
  delete integrations.linearApiKey;
982
1002
  delete integrations.linearAccessToken;
983
1003
  delete integrations.linearRefreshToken;
984
1004
  delete integrations.linearClientSecret;
985
1005
  delete integrations.slackClientSecret;
986
1006
  delete integrations.slackAppToken;
1007
+ delete integrations.githubPat;
987
1008
  writePrivateFile(
988
1009
  appSettingsPath(),
989
1010
  `${JSON.stringify({ ...settings, environment: {}, integrations }, null, 2)}
@@ -996,6 +1017,7 @@ function persistSplit(settings) {
996
1017
  linearClientSecret,
997
1018
  slackClientSecret,
998
1019
  slackAppToken,
1020
+ githubPat,
999
1021
  environment: settings.environment
1000
1022
  });
1001
1023
  }
@@ -1161,6 +1183,20 @@ function updateIntegrationsSettings(patch) {
1161
1183
  integrations.slackDeviceLabel = patch.slackDeviceLabel.trim().slice(0, 64);
1162
1184
  }
1163
1185
  }
1186
+ if ("githubGitAuthMode" in patch) {
1187
+ if (patch.githubGitAuthMode == null) {
1188
+ delete integrations.githubGitAuthMode;
1189
+ } else if (GIT_AUTH_MODES.has(patch.githubGitAuthMode)) {
1190
+ integrations.githubGitAuthMode = patch.githubGitAuthMode;
1191
+ }
1192
+ }
1193
+ if ("githubPat" in patch) {
1194
+ if (patch.githubPat == null || patch.githubPat.trim() === "") {
1195
+ delete integrations.githubPat;
1196
+ } else {
1197
+ integrations.githubPat = patch.githubPat.trim();
1198
+ }
1199
+ }
1164
1200
  return saveAppSettings({ ...current, integrations });
1165
1201
  }
1166
1202
  function updateDefaultsSettings(patch) {
@@ -1268,6 +1304,13 @@ function disconnectLinearConnection() {
1268
1304
  function getIssueSource(settings = loadAppSettings()) {
1269
1305
  return settings.integrations.issueSource ?? "github";
1270
1306
  }
1307
+ function getGithubGitAuthMode(settings = loadAppSettings()) {
1308
+ return settings.integrations.githubGitAuthMode ?? "auto";
1309
+ }
1310
+ function getGithubPat(settings = loadAppSettings()) {
1311
+ const pat = settings.integrations.githubPat?.trim();
1312
+ return pat || null;
1313
+ }
1271
1314
  function resolveEffectiveIssueSource(settings = loadAppSettings()) {
1272
1315
  const preferred = getIssueSource(settings);
1273
1316
  if (preferred === "linear" && !isLinearConnected(settings)) return "github";
@@ -1468,7 +1511,7 @@ function childEnvWithAppSettings(extra) {
1468
1511
  function harnessEnvKey(harness) {
1469
1512
  return HARNESS_ENV_KEYS[harness];
1470
1513
  }
1471
- var import_node_crypto2, import_node_fs5, import_node_os3, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
1514
+ var import_node_crypto2, import_node_fs5, import_node_os3, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
1472
1515
  var init_app_settings = __esm({
1473
1516
  "src/store/app-settings.ts"() {
1474
1517
  "use strict";
@@ -1494,6 +1537,7 @@ var init_app_settings = __esm({
1494
1537
  "brightsy",
1495
1538
  "cursor"
1496
1539
  ]);
1540
+ GITHUB_GIT_AUTH_MODES = ["auto", "gh", "ssh", "token"];
1497
1541
  CLOUD_CONNECT_AGENTS = /* @__PURE__ */ new Set([
1498
1542
  "claude",
1499
1543
  "codex",
@@ -1501,6 +1545,7 @@ var init_app_settings = __esm({
1501
1545
  "cursor"
1502
1546
  ]);
1503
1547
  ISSUE_SOURCES = /* @__PURE__ */ new Set(["linear", "github"]);
1548
+ GIT_AUTH_MODES = new Set(GITHUB_GIT_AUTH_MODES);
1504
1549
  EMPTY_SETTINGS = {
1505
1550
  environment: {},
1506
1551
  claude: {},
@@ -1567,10 +1612,10 @@ function uniqueIds(ids) {
1567
1612
  return out;
1568
1613
  }
1569
1614
  function readHold() {
1570
- const path = caffeinateHoldPath();
1571
- if (!(0, import_node_fs6.existsSync)(path)) return null;
1615
+ const path2 = caffeinateHoldPath();
1616
+ if (!(0, import_node_fs6.existsSync)(path2)) return null;
1572
1617
  try {
1573
- const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
1618
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path2, "utf8"));
1574
1619
  if (typeof parsed?.pid === "number" && parsed.pid > 0) {
1575
1620
  return {
1576
1621
  pid: parsed.pid,
@@ -1790,17 +1835,17 @@ async function withThreadLock(id, fn) {
1790
1835
  }
1791
1836
  }
1792
1837
  function readThread(id) {
1793
- const path = threadFilePath(id);
1794
- if (!(0, import_node_fs7.existsSync)(path)) return null;
1795
- const raw = (0, import_node_fs7.readFileSync)(path, "utf8");
1838
+ const path2 = threadFilePath(id);
1839
+ if (!(0, import_node_fs7.existsSync)(path2)) return null;
1840
+ const raw = (0, import_node_fs7.readFileSync)(path2, "utf8");
1796
1841
  return normalizeThread(JSON.parse(raw));
1797
1842
  }
1798
1843
  function writeThread(thread) {
1799
- const path = threadFilePath(idPath(thread.id));
1800
- const tmp = `${path}.${process.pid}.tmp`;
1844
+ const path2 = threadFilePath(idPath(thread.id));
1845
+ const tmp = `${path2}.${process.pid}.tmp`;
1801
1846
  const next = { ...thread, updatedAt: nowIso() };
1802
1847
  (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(next, null, 2), "utf8");
1803
- (0, import_node_fs7.renameSync)(tmp, path);
1848
+ (0, import_node_fs7.renameSync)(tmp, path2);
1804
1849
  }
1805
1850
  function idPath(id) {
1806
1851
  return id;
@@ -1820,8 +1865,8 @@ function listThreads(opts) {
1820
1865
  return threads.filter((t) => t.status !== "archived");
1821
1866
  }
1822
1867
  function deleteThreadRecord(id) {
1823
- const path = threadFilePath(id);
1824
- if ((0, import_node_fs7.existsSync)(path)) (0, import_node_fs7.unlinkSync)(path);
1868
+ const path2 = threadFilePath(id);
1869
+ if ((0, import_node_fs7.existsSync)(path2)) (0, import_node_fs7.unlinkSync)(path2);
1825
1870
  const lock = threadLockPath(id);
1826
1871
  if ((0, import_node_fs7.existsSync)(lock)) {
1827
1872
  try {
@@ -2820,6 +2865,80 @@ var init_gh_errors = __esm({
2820
2865
  }
2821
2866
  });
2822
2867
 
2868
+ // src/git/git-auth-mode.ts
2869
+ function appendIndexedGitConfig(existing, entries) {
2870
+ const start = Number.parseInt(String(existing?.GIT_CONFIG_COUNT ?? "0"), 10);
2871
+ const count = Number.isFinite(start) && start > 0 ? start : 0;
2872
+ const out = {};
2873
+ entries.forEach((entry, i) => {
2874
+ const n = count + i;
2875
+ out[`GIT_CONFIG_KEY_${n}`] = entry.key;
2876
+ out[`GIT_CONFIG_VALUE_${n}`] = entry.value;
2877
+ });
2878
+ out.GIT_CONFIG_COUNT = String(count + entries.length);
2879
+ return out;
2880
+ }
2881
+ function githubAgentGitEnv(existing) {
2882
+ return appendIndexedGitConfig(existing, HTTPS_REWRITE);
2883
+ }
2884
+ function applyGithubGitAuthEnv(existing, opts) {
2885
+ const out = {};
2886
+ if (opts.mode === "gh" || opts.mode === "token") {
2887
+ Object.assign(out, githubAgentGitEnv(existing));
2888
+ }
2889
+ if (opts.mode === "token") {
2890
+ const token = opts.token?.trim();
2891
+ if (token && !existing?.GH_TOKEN?.trim()) {
2892
+ out.GH_TOKEN = token;
2893
+ }
2894
+ }
2895
+ return out;
2896
+ }
2897
+ function formatGitAuthModeDirective(mode) {
2898
+ switch (mode) {
2899
+ case "gh":
2900
+ return [
2901
+ "Git authentication (Account \u2192 GitHub mode: gh CLI):",
2902
+ "- This process rewrites `git@github.com:` and `ssh://git@github.com/` to HTTPS.",
2903
+ "- `gh` supplies credentials (`gh auth git-credential`). Do not switch remotes to SSH.",
2904
+ "- Push with `git push -u origin HEAD`. Create/update PRs with `gh` as below."
2905
+ ].join("\n");
2906
+ case "ssh":
2907
+ return [
2908
+ "Git authentication (Account \u2192 GitHub mode: SSH):",
2909
+ "- Keep SSH remotes (`git@github.com:\u2026`). Do not rewrite them to HTTPS.",
2910
+ "- Use this Mac\u2019s SSH agent / keys. If push fails with `Permission denied (publickey)`, tell the user to start ssh-agent or switch Account \u2192 GitHub to Auto / gh CLI \u2014 do not rewrite remotes yourself.",
2911
+ "- Push with `git push -u origin HEAD`. Create/update PRs with `gh` as below (API still uses `gh`)."
2912
+ ].join("\n");
2913
+ case "token":
2914
+ return [
2915
+ "Git authentication (Account \u2192 GitHub mode: personal access token):",
2916
+ "- This process rewrites GitHub SSH remotes to HTTPS.",
2917
+ "- `GH_TOKEN` is set in the environment. Use HTTPS git and `gh`; do not paste the token into commands or chat.",
2918
+ "- Push with `git push -u origin HEAD`. Create/update PRs with `gh` as below."
2919
+ ].join("\n");
2920
+ case "auto":
2921
+ default:
2922
+ return [
2923
+ "Git authentication (Account \u2192 GitHub mode: auto):",
2924
+ "- Prefer the existing remote URL (SSH or HTTPS). Do not rewrite remotes unless a push/fetch fails.",
2925
+ '- If `git push` fails with `Permission denied (publickey)`, this shell has no ssh-agent. Retry over HTTPS (do not stop): `git -c url.https://github.com/.insteadOf=git@github.com: -c http.extraHeader="AUTHORIZATION: bearer $(gh auth token)" push -u origin HEAD`.',
2926
+ "- Then create/update the PR with `gh` as below."
2927
+ ].join("\n");
2928
+ }
2929
+ }
2930
+ var HTTPS_REWRITE;
2931
+ var init_git_auth_mode = __esm({
2932
+ "src/git/git-auth-mode.ts"() {
2933
+ "use strict";
2934
+ HTTPS_REWRITE = [
2935
+ { key: "url.https://github.com/.insteadOf", value: "git@github.com:" },
2936
+ { key: "url.https://github.com/.insteadOf", value: "ssh://git@github.com/" },
2937
+ { key: "credential.https://github.com.helper", value: "!gh auth git-credential" }
2938
+ ];
2939
+ }
2940
+ });
2941
+
2823
2942
  // src/agents/path.ts
2824
2943
  function prependPathDir(env, dir) {
2825
2944
  if (!dir || !(0, import_node_fs8.existsSync)(dir)) return;
@@ -3357,6 +3476,7 @@ __export(worktree_exports, {
3357
3476
  getPrMeta: () => getPrMeta,
3358
3477
  ghHeadRef: () => ghHeadRef,
3359
3478
  ghRepoSelectArgs: () => ghRepoSelectArgs,
3479
+ githubAgentGitEnv: () => githubAgentGitEnv,
3360
3480
  isDirty: () => isDirty,
3361
3481
  isPlaceholderBranch: () => isPlaceholderBranch,
3362
3482
  isSideboardScratchPath: () => isSideboardScratchPath,
@@ -3478,10 +3598,15 @@ ${view.stderr}`;
3478
3598
  timeoutMs: 15e3
3479
3599
  });
3480
3600
  }
3481
- async function originGhRepoEnv(cwd) {
3601
+ async function originGhRepoEnv(cwd, opts) {
3482
3602
  await ensureGhPreferOrigin(cwd);
3483
3603
  const slug = await resolveGithubRepoSlug(cwd);
3484
- return slug ? { GH_REPO: slug } : {};
3604
+ const mode = opts?.mode ?? getGithubGitAuthMode();
3605
+ const token = mode === "token" ? getGithubPat() : null;
3606
+ return {
3607
+ ...applyGithubGitAuthEnv(opts?.env, { mode, token }),
3608
+ ...slug ? { GH_REPO: slug } : {}
3609
+ };
3485
3610
  }
3486
3611
  async function resolveDefaultBranch(repoPath, opts) {
3487
3612
  const viaOrigin = await git(
@@ -4230,7 +4355,33 @@ async function commitAll(worktreePath, message) {
4230
4355
  return true;
4231
4356
  }
4232
4357
  async function pushBranch(worktreePath, branchName) {
4233
- await git(["push", "-u", "origin", branchName], worktreePath);
4358
+ const ssh = await git(["push", "-u", "origin", branchName], worktreePath, {
4359
+ reject: false
4360
+ });
4361
+ if (ssh.exitCode === 0) return;
4362
+ const sshErr = (ssh.stderr || ssh.stdout).trim();
4363
+ const slug = await resolveGithubRepoSlug(worktreePath);
4364
+ const token = await resolveGhAuthToken(worktreePath);
4365
+ if (!slug || !token) {
4366
+ throw new Error(
4367
+ sshErr || `git push origin ${branchName} failed` + (!token ? " (no SSH agent and gh auth token unavailable \u2014 run: gh auth login)" : "")
4368
+ );
4369
+ }
4370
+ const tryHttps = async (header) => git(["push", "-u", "origin", branchName], worktreePath, {
4371
+ reject: false,
4372
+ env: { GIT_TERMINAL_PROMPT: "0" },
4373
+ config: {
4374
+ "url.https://github.com/.insteadOf": "git@github.com:",
4375
+ "http.extraHeader": header
4376
+ }
4377
+ });
4378
+ const bearer = await tryHttps(`AUTHORIZATION: bearer ${token}`);
4379
+ if (bearer.exitCode === 0) return;
4380
+ const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
4381
+ const basicPush = await tryHttps(`Authorization: Basic ${basic}`);
4382
+ if (basicPush.exitCode === 0) return;
4383
+ const httpsErr = (basicPush.stderr || bearer.stderr || bearer.stdout).trim();
4384
+ throw new Error(httpsErr || sshErr || `git push origin ${branchName} failed`);
4234
4385
  }
4235
4386
  async function mergePr(cwd, selector, opts) {
4236
4387
  const slug = await resolveGithubRepoSlug(cwd);
@@ -4418,8 +4569,8 @@ function allocateTeamSlug(repoPath) {
4418
4569
  const taken = collectTakenTeamSlugs(repoPath);
4419
4570
  for (let attempt = 0; attempt < 32; attempt++) {
4420
4571
  const team = allocateTeamName(taken);
4421
- const path = (0, import_node_path9.join)(worktreesRoot(repoPath), team.slug);
4422
- if (!(0, import_node_fs9.existsSync)(path)) return team;
4572
+ const path2 = (0, import_node_path9.join)(worktreesRoot(repoPath), team.slug);
4573
+ if (!(0, import_node_fs9.existsSync)(path2)) return team;
4423
4574
  taken.add(team.slug);
4424
4575
  }
4425
4576
  throw new Error("No available soccer team worktree directories left");
@@ -4435,10 +4586,13 @@ var init_worktree = __esm({
4435
4586
  init_teams();
4436
4587
  init_worktree_labels();
4437
4588
  init_gh_errors();
4589
+ init_git_auth_mode();
4438
4590
  init_run();
4591
+ init_app_settings();
4439
4592
  init_pr_gates();
4440
4593
  init_stack();
4441
4594
  init_teams();
4595
+ init_git_auth_mode();
4442
4596
  init_workspace_scratch();
4443
4597
  init_worktree_labels();
4444
4598
  }
@@ -4501,7 +4655,7 @@ function coordinatorTurnReminder(opts) {
4501
4655
  goal ? `- Goal / title: ${goal}` : null,
4502
4656
  accountDefaultsPlaybookLine(),
4503
4657
  `- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
4504
- "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Land with ask_git (commit-push / create-draft / merge) on the child, then wait_for_turn \u2014 never git/gh from this cwd.",
4658
+ "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
4505
4659
  "- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
4506
4660
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
4507
4661
  "- If they will wait on Slack or leave the Mac, call set_caffeinate enabled=true. When they say they are done / wrapping up / going to sleep, call set_caffeinate enabled=false. Closing this chat also turns it off."
@@ -4555,9 +4709,9 @@ function ensureGlobalCoordinatorCwd(opts) {
4555
4709
  orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
4556
4710
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
4557
4711
  "Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
4558
- "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn.",
4712
+ "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
4559
4713
  "Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft.",
4560
- "Always ask worktree agents to commit, push, open draft PRs, and merge (`ask_git` / `send_to_thread`). The worktree agent runs git/gh; never merge from this orchestration cwd."
4714
+ "Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
4561
4715
  ].join("\n");
4562
4716
  try {
4563
4717
  (0, import_node_fs10.writeFileSync)((0, import_node_path10.join)(dir, "CLAUDE.md"), `${body}
@@ -4570,7 +4724,6 @@ function ensureGlobalCoordinatorCwd(opts) {
4570
4724
  }
4571
4725
  function coordinatorSystemPrompt(opts) {
4572
4726
  const audience = opts.audience ?? "cloud";
4573
- const reposDir = sideboardReposDir();
4574
4727
  const intro = audience === "cloud" ? [
4575
4728
  "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
4576
4729
  "Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
@@ -4588,14 +4741,8 @@ function coordinatorSystemPrompt(opts) {
4588
4741
  ...intro,
4589
4742
  "You operate across ALL registered workspaces below.",
4590
4743
  "You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
4591
- COORDINATOR_TOOL_PLAYBOOK,
4592
- accountDefaultsPlaybookLine(),
4593
- coordinatorGreenfieldPlaybook(reposDir),
4594
- "When creating threads, pass the correct repoPath for the target workspace.",
4744
+ "Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
4595
4745
  `YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit parentThreadId (Sideboard binds it). Never invent a uuid.`,
4596
- "Omit agent/model on create_thread unless you need to override Account defaults.",
4597
- "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn. Never target upstream. Never git/gh from this orchestration cwd.",
4598
- "Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
4599
4746
  `Goal: ${opts.goal}`,
4600
4747
  "Registered workspaces:",
4601
4748
  formatWorkspaceInventory(opts.workspaces)
@@ -4639,7 +4786,8 @@ var init_coordinator_prompt = __esm({
4639
4786
  "Inspect / review / PRs:",
4640
4787
  "- get_diff \u2014 compact diff summary",
4641
4788
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
4642
- "- ask_git \u2014 tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge (`Merge PR.`). You only queue that prompt \u2014 the worktree agent runs git/gh (including `gh pr merge`). Then wait_for_turn. Prefer this over paraphrasing.",
4789
+ "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
4790
+ '- 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.',
4643
4791
  '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
4644
4792
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4645
4793
  "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.",
@@ -4912,10 +5060,10 @@ function removedWorkspacesFile() {
4912
5060
  return (0, import_node_path11.join)(appDataDir(), "removed-workspaces.json");
4913
5061
  }
4914
5062
  function readAll() {
4915
- const path = workspacesFile();
4916
- if (!(0, import_node_fs11.existsSync)(path)) return [];
5063
+ const path2 = workspacesFile();
5064
+ if (!(0, import_node_fs11.existsSync)(path2)) return [];
4917
5065
  try {
4918
- const raw = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
5066
+ const raw = JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
4919
5067
  return Array.isArray(raw) ? raw : [];
4920
5068
  } catch {
4921
5069
  return [];
@@ -4926,10 +5074,10 @@ function writeAll(list) {
4926
5074
  (0, import_node_fs11.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
4927
5075
  }
4928
5076
  function readRemoved() {
4929
- const path = removedWorkspacesFile();
4930
- if (!(0, import_node_fs11.existsSync)(path)) return /* @__PURE__ */ new Set();
5077
+ const path2 = removedWorkspacesFile();
5078
+ if (!(0, import_node_fs11.existsSync)(path2)) return /* @__PURE__ */ new Set();
4931
5079
  try {
4932
- const raw = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
5080
+ const raw = JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
4933
5081
  return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
4934
5082
  } catch {
4935
5083
  return /* @__PURE__ */ new Set();
@@ -4986,17 +5134,17 @@ function syncWorkspacesFromThreads(repoPaths) {
4986
5134
  const removed = readRemoved();
4987
5135
  const byPath = new Map(current.map((w) => [w.path, w]));
4988
5136
  let dirty = false;
4989
- for (const path of repoPaths) {
4990
- if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
5137
+ for (const path2 of repoPaths) {
5138
+ if (!path2 || path2 === "/" || isGlobalRepoPath(path2) || byPath.has(path2) || removed.has(path2)) {
4991
5139
  continue;
4992
5140
  }
4993
- if (!(0, import_node_fs11.existsSync)(path)) continue;
5141
+ if (!(0, import_node_fs11.existsSync)(path2)) continue;
4994
5142
  const ws = {
4995
- path,
4996
- name: (0, import_node_path11.basename)(path),
5143
+ path: path2,
5144
+ name: (0, import_node_path11.basename)(path2),
4997
5145
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
4998
5146
  };
4999
- byPath.set(path, ws);
5147
+ byPath.set(path2, ws);
5000
5148
  dirty = true;
5001
5149
  }
5002
5150
  const next = [...byPath.values()];
@@ -5020,11 +5168,11 @@ function brightsyConfigPath() {
5020
5168
  return (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".brightsy", "config.json");
5021
5169
  }
5022
5170
  function loadBrightsyConfig() {
5023
- const path = brightsyConfigPath();
5024
- if (!(0, import_node_fs12.existsSync)(path)) {
5171
+ const path2 = brightsyConfigPath();
5172
+ if (!(0, import_node_fs12.existsSync)(path2)) {
5025
5173
  throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
5026
5174
  }
5027
- const raw = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
5175
+ const raw = JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
5028
5176
  if (!raw.access_token || !raw.account_id) {
5029
5177
  throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
5030
5178
  }
@@ -5148,10 +5296,10 @@ function storePath() {
5148
5296
  return (0, import_node_path13.join)(appDataDir(), "brightsy-teams.json");
5149
5297
  }
5150
5298
  function readStore() {
5151
- const path = storePath();
5152
- if (!(0, import_node_fs13.existsSync)(path)) return [];
5299
+ const path2 = storePath();
5300
+ if (!(0, import_node_fs13.existsSync)(path2)) return [];
5153
5301
  try {
5154
- const parsed = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
5302
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
5155
5303
  return Array.isArray(parsed.teams) ? parsed.teams : [];
5156
5304
  } catch {
5157
5305
  return [];
@@ -5159,8 +5307,8 @@ function readStore() {
5159
5307
  }
5160
5308
  function writeStore(teams) {
5161
5309
  (0, import_node_fs13.mkdirSync)(appDataDir(), { recursive: true });
5162
- const path = storePath();
5163
- (0, import_node_fs13.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
5310
+ const path2 = storePath();
5311
+ (0, import_node_fs13.writeFileSync)(path2, `${JSON.stringify({ teams }, null, 2)}
5164
5312
  `, {
5165
5313
  mode: 384
5166
5314
  });
@@ -5334,6 +5482,58 @@ var init_connected_teams = __esm({
5334
5482
  }
5335
5483
  });
5336
5484
 
5485
+ // src/agents/usage.ts
5486
+ function sumOptional(a, b) {
5487
+ if (a == null && b == null) return void 0;
5488
+ return (a ?? 0) + (b ?? 0);
5489
+ }
5490
+ function fromInclusiveInputUsage(opts) {
5491
+ const totalInput = Number(opts.inputTokens) || 0;
5492
+ const outputTokens = Number(opts.outputTokens) || 0;
5493
+ const cached = Number(opts.cachedInputTokens) || 0;
5494
+ if (!totalInput && !outputTokens) return null;
5495
+ const cacheReadTokens = cached > 0 ? Math.min(cached, totalInput) : 0;
5496
+ return {
5497
+ inputTokens: Math.max(0, totalInput - cacheReadTokens),
5498
+ outputTokens,
5499
+ cacheReadTokens: cacheReadTokens || void 0
5500
+ };
5501
+ }
5502
+ function requestOccupancy(u) {
5503
+ return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
5504
+ }
5505
+ function mergeUsage(a, b) {
5506
+ return {
5507
+ inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
5508
+ outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
5509
+ cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
5510
+ cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
5511
+ lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
5512
+ };
5513
+ }
5514
+ function applyTurnUsage(current, incoming, scope = "request") {
5515
+ if (scope === "turn") {
5516
+ return {
5517
+ ...incoming,
5518
+ lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
5519
+ };
5520
+ }
5521
+ const merged = mergeUsage(current, incoming);
5522
+ return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
5523
+ }
5524
+ function totalTokens(u) {
5525
+ return u.inputTokens + u.outputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
5526
+ }
5527
+ function contextTokens(u) {
5528
+ if (u.lastRequestTokens != null && u.lastRequestTokens > 0) return u.lastRequestTokens;
5529
+ return requestOccupancy(u);
5530
+ }
5531
+ var init_usage = __esm({
5532
+ "src/agents/usage.ts"() {
5533
+ "use strict";
5534
+ }
5535
+ });
5536
+
5337
5537
  // src/agents/brightsy-targets.ts
5338
5538
  function encodeBrightsyTarget(type, id, accountId) {
5339
5539
  if (accountId) return `team:${accountId}:${type}:${id}`;
@@ -5569,15 +5769,11 @@ var init_turn_input = __esm({
5569
5769
  // src/agents/brightsy.ts
5570
5770
  function usageFromBrightsy(usage) {
5571
5771
  if (!usage) return null;
5572
- const inputTokens = Number(usage.prompt_tokens ?? 0);
5573
- const outputTokens = Number(usage.completion_tokens ?? 0);
5574
- if (!inputTokens && !outputTokens) return null;
5575
- const cached = Number(usage.prompt_tokens_details?.cached_tokens ?? 0);
5576
- return {
5577
- inputTokens,
5578
- outputTokens,
5579
- cacheReadTokens: cached || void 0
5580
- };
5772
+ return fromInclusiveInputUsage({
5773
+ inputTokens: Number(usage.prompt_tokens ?? 0),
5774
+ outputTokens: Number(usage.completion_tokens ?? 0),
5775
+ cachedInputTokens: Number(usage.prompt_tokens_details?.cached_tokens ?? 0)
5776
+ });
5581
5777
  }
5582
5778
  function parseBrightsyCliLine(line) {
5583
5779
  const trimmed = line.trim();
@@ -5793,6 +5989,7 @@ var init_brightsy = __esm({
5793
5989
  init_connected_teams();
5794
5990
  init_config();
5795
5991
  init_app_settings();
5992
+ init_usage();
5796
5993
  init_brightsy_targets();
5797
5994
  init_error_detail();
5798
5995
  init_turn_input();
@@ -6686,23 +6883,20 @@ async function listCodexModels() {
6686
6883
  }
6687
6884
  function usageFromCodex(usage) {
6688
6885
  if (!usage) return null;
6689
- const inputTokens = Number(usage.input_tokens ?? 0);
6690
- const outputTokens = Number(usage.output_tokens ?? 0) + Number(usage.reasoning_output_tokens ?? 0);
6691
- if (!inputTokens && !outputTokens) return null;
6692
- return {
6693
- inputTokens,
6694
- outputTokens,
6695
- cacheReadTokens: usage.cached_input_tokens ? Number(usage.cached_input_tokens) : void 0
6696
- };
6886
+ return fromInclusiveInputUsage({
6887
+ inputTokens: Number(usage.input_tokens ?? 0),
6888
+ outputTokens: Number(usage.output_tokens ?? 0),
6889
+ cachedInputTokens: Number(usage.cached_input_tokens ?? 0)
6890
+ });
6697
6891
  }
6698
6892
  function codexConfigHasNetworkAccess() {
6699
6893
  const candidates = [
6700
6894
  (0, import_node_path15.join)((0, import_node_os7.homedir)(), ".codex", "config.toml"),
6701
6895
  (0, import_node_path15.join)((0, import_node_os7.homedir)(), ".config", "codex", "config.toml")
6702
6896
  ];
6703
- for (const path of candidates) {
6704
- if (!(0, import_node_fs17.existsSync)(path)) continue;
6705
- const text3 = (0, import_node_fs17.readFileSync)(path, "utf8");
6897
+ for (const path2 of candidates) {
6898
+ if (!(0, import_node_fs17.existsSync)(path2)) continue;
6899
+ const text3 = (0, import_node_fs17.readFileSync)(path2, "utf8");
6706
6900
  if (/network_access\s*=\s*true/.test(text3)) return true;
6707
6901
  }
6708
6902
  return false;
@@ -6753,6 +6947,7 @@ var init_codex = __esm({
6753
6947
  init_app_settings();
6754
6948
  init_global_workspace();
6755
6949
  init_error_detail();
6950
+ init_usage();
6756
6951
  init_injected_mcp();
6757
6952
  init_turn_input();
6758
6953
  init_types();
@@ -8166,8 +8361,8 @@ function extractPresentedPlan(parts) {
8166
8361
  const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
8167
8362
  if (!content.trim()) continue;
8168
8363
  const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
8169
- const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
8170
- return { title, content: content.trim(), path, source: "present_plan" };
8364
+ const path2 = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
8365
+ return { title, content: content.trim(), path: path2, source: "present_plan" };
8171
8366
  }
8172
8367
  return null;
8173
8368
  }
@@ -8356,6 +8551,7 @@ __export(index_exports, {
8356
8551
  CONTEXT_MIN_MESSAGES: () => CONTEXT_MIN_MESSAGES,
8357
8552
  COORDINATOR_TOOL_PLAYBOOK: () => COORDINATOR_TOOL_PLAYBOOK,
8358
8553
  FAMOUS_SOCCER_TEAMS: () => FAMOUS_SOCCER_TEAMS,
8554
+ GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
8359
8555
  GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
8360
8556
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
8361
8557
  LEGACY_ATTACHMENTS_DIR: () => LEGACY_ATTACHMENTS_DIR,
@@ -8405,10 +8601,12 @@ __export(index_exports, {
8405
8601
  allocateTeamSlug: () => allocateTeamSlug,
8406
8602
  appDataDir: () => appDataDir,
8407
8603
  appSettingsPath: () => appSettingsPath,
8604
+ appendIndexedGitConfig: () => appendIndexedGitConfig,
8408
8605
  appendMessage: () => appendMessage,
8409
8606
  applyAgentEvent: () => applyAgentEvent,
8410
8607
  applyAppEnvironment: () => applyAppEnvironment,
8411
8608
  applyCompaction: () => applyCompaction,
8609
+ applyGithubGitAuthEnv: () => applyGithubGitAuthEnv,
8412
8610
  applyThreadIntoMain: () => applyThreadIntoMain,
8413
8611
  applyTurnUsage: () => applyTurnUsage,
8414
8612
  assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
@@ -8523,6 +8721,7 @@ __export(index_exports, {
8523
8721
  formatBrightsyFetchError: () => formatBrightsyFetchError,
8524
8722
  formatFetchError: () => formatFetchError,
8525
8723
  formatGhLandError: () => formatGhLandError,
8724
+ formatGitAuthModeDirective: () => formatGitAuthModeDirective,
8526
8725
  formatIpcInvokeError: () => formatIpcInvokeError,
8527
8726
  formatMessagesAsTranscript: () => formatMessagesAsTranscript,
8528
8727
  formatPlanQuestionAnswers: () => formatPlanQuestionAnswers,
@@ -8537,6 +8736,7 @@ __export(index_exports, {
8537
8736
  formatWorkspaceInventory: () => formatWorkspaceInventory,
8538
8737
  formatWorktreeDirective: () => formatWorktreeDirective,
8539
8738
  formatWorktreeReminder: () => formatWorktreeReminder,
8739
+ fromInclusiveInputUsage: () => fromInclusiveInputUsage,
8540
8740
  getAdapter: () => getAdapter,
8541
8741
  getAgentSetupInfo: () => getAgentSetupInfo,
8542
8742
  getBrightsySession: () => getBrightsySession,
@@ -8549,6 +8749,8 @@ __export(index_exports, {
8549
8749
  getDiff: () => getDiff,
8550
8750
  getDiffSummary: () => getDiffSummary,
8551
8751
  getGitHubStatus: () => getGitHubStatus,
8752
+ getGithubGitAuthMode: () => getGithubGitAuthMode,
8753
+ getGithubPat: () => getGithubPat,
8552
8754
  getIssueSource: () => getIssueSource,
8553
8755
  getLinearApiKey: () => getLinearApiKey,
8554
8756
  getLinearAuthToken: () => getLinearAuthToken,
@@ -8567,6 +8769,7 @@ __export(index_exports, {
8567
8769
  ghHeadRef: () => ghHeadRef,
8568
8770
  ghRepoSelectArgs: () => ghRepoSelectArgs,
8569
8771
  git: () => git,
8772
+ githubAgentGitEnv: () => githubAgentGitEnv,
8570
8773
  globalAgentCwd: () => globalAgentCwd,
8571
8774
  handleSlackInbound: () => handleSlackInbound,
8572
8775
  harnessEnvKey: () => harnessEnvKey,
@@ -8789,6 +8992,7 @@ __export(index_exports, {
8789
8992
  startSlackOAuth: () => startSlackOAuth,
8790
8993
  startSlackRelayServer: () => startSlackRelayServer,
8791
8994
  stripBrightsyNdjsonNoise: () => stripBrightsyNdjsonNoise,
8995
+ stripNestedElectronEnv: () => stripNestedElectronEnv,
8792
8996
  submitPrStack: () => submitPrStack,
8793
8997
  suggestSlug: () => suggestSlug,
8794
8998
  summarizeConversation: () => summarizeConversation,
@@ -8847,6 +9051,7 @@ init_workspaces();
8847
9051
  init_global_workspace();
8848
9052
  init_run();
8849
9053
  init_gh_errors();
9054
+ init_git_auth_mode();
8850
9055
  init_worktree();
8851
9056
  init_stack();
8852
9057
 
@@ -9613,8 +9818,8 @@ function toolDetail(name, input) {
9613
9818
  if (!input) return void 0;
9614
9819
  const command = str2(input.command) ?? str2(input.cmd);
9615
9820
  if (command) return command;
9616
- const path = str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
9617
- if (path) return path;
9821
+ const path2 = str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
9822
+ if (path2) return path2;
9618
9823
  const pattern = str2(input.pattern) ?? str2(input.glob) ?? str2(input.glob_pattern);
9619
9824
  if (pattern) return pattern;
9620
9825
  const query = str2(input.query) ?? str2(input.prompt);
@@ -9658,12 +9863,12 @@ function toolDescription(name, input) {
9658
9863
  return "Run shell command";
9659
9864
  }
9660
9865
  if (/edit|write|apply/i.test(name)) {
9661
- const path = str2(input.file_path) ?? str2(input.path);
9662
- return path ? `Edit ${path.split("/").pop()}` : "Edit file";
9866
+ const path2 = str2(input.file_path) ?? str2(input.path);
9867
+ return path2 ? `Edit ${path2.split("/").pop()}` : "Edit file";
9663
9868
  }
9664
9869
  if (/read/i.test(name)) {
9665
- const path = str2(input.file_path) ?? str2(input.path);
9666
- return path ? `Read ${path.split("/").pop()}` : "Read file";
9870
+ const path2 = str2(input.file_path) ?? str2(input.path);
9871
+ return path2 ? `Read ${path2.split("/").pop()}` : "Read file";
9667
9872
  }
9668
9873
  if (/grep/i.test(name)) return "Search files";
9669
9874
  if (/glob/i.test(name)) return "Find files";
@@ -9829,43 +10034,7 @@ function normalizeParseResult(parsed) {
9829
10034
 
9830
10035
  // src/agents/spawn.ts
9831
10036
  init_path();
9832
-
9833
- // src/agents/usage.ts
9834
- function sumOptional(a, b) {
9835
- if (a == null && b == null) return void 0;
9836
- return (a ?? 0) + (b ?? 0);
9837
- }
9838
- function requestOccupancy(u) {
9839
- return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
9840
- }
9841
- function mergeUsage(a, b) {
9842
- return {
9843
- inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
9844
- outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
9845
- cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
9846
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
9847
- lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
9848
- };
9849
- }
9850
- function applyTurnUsage(current, incoming, scope = "request") {
9851
- if (scope === "turn") {
9852
- return {
9853
- ...incoming,
9854
- lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
9855
- };
9856
- }
9857
- const merged = mergeUsage(current, incoming);
9858
- return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
9859
- }
9860
- function totalTokens(u) {
9861
- return u.inputTokens + u.outputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
9862
- }
9863
- function contextTokens(u) {
9864
- if (u.lastRequestTokens != null && u.lastRequestTokens > 0) return u.lastRequestTokens;
9865
- return requestOccupancy(u);
9866
- }
9867
-
9868
- // src/agents/spawn.ts
10037
+ init_usage();
9869
10038
  async function spawnAgentTurn(thread, input, onEvent) {
9870
10039
  ensureAgentPath();
9871
10040
  if (!thread.worktreePath?.trim()) {
@@ -9892,8 +10061,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
9892
10061
  }
9893
10062
  const env = childEnvWithAppSettings(cmd.env);
9894
10063
  if (!isOrchestratorThread(thread)) {
9895
- const originEnv = await originGhRepoEnv(thread.worktreePath).catch(() => ({}));
9896
- Object.assign(env, originEnv);
10064
+ try {
10065
+ const originEnv = await originGhRepoEnv(thread.worktreePath, { env });
10066
+ Object.assign(env, originEnv);
10067
+ } catch (err) {
10068
+ const detail = err instanceof Error ? err.message : String(err);
10069
+ console.warn(`[sideboard] originGhRepoEnv failed (${thread.worktreePath}): ${detail}`);
10070
+ }
9897
10071
  }
9898
10072
  const child = (0, import_execa2.execa)(cmd.file, cmd.args, {
9899
10073
  cwd: cmd.cwd,
@@ -9978,11 +10152,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
9978
10152
  }
9979
10153
 
9980
10154
  // src/index.ts
10155
+ init_usage();
9981
10156
  init_claude_mcp();
9982
10157
 
9983
10158
  // src/agents/instructions.ts
9984
10159
  var import_node_fs20 = require("fs");
9985
10160
  var import_node_path17 = require("path");
10161
+ init_git_auth_mode();
9986
10162
  init_worktree_labels();
9987
10163
  function normPath2(p) {
9988
10164
  return p.replace(/\/+$/, "");
@@ -10032,6 +10208,8 @@ function formatWorktreeDirective(thread, opts) {
10032
10208
  lines.push(
10033
10209
  "- Push with `git push -u origin HEAD` (or the current branch name). Do not `git push upstream`."
10034
10210
  );
10211
+ lines.push("");
10212
+ lines.push(formatGitAuthModeDirective(opts?.gitAuthMode ?? "auto"));
10035
10213
  lines.push(
10036
10214
  "- Derive the PR title and body from what the changes actually do and why \u2014 inspect the diff/commits and the user request. Do not use the soccer-team worktree nickname or placeholder branch as the PR title."
10037
10215
  );
@@ -10039,7 +10217,7 @@ function formatWorktreeDirective(thread, opts) {
10039
10217
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
10040
10218
  );
10041
10219
  lines.push(
10042
- "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. When asked to merge the PR, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
10220
+ "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. Do not merge the PR unless this turn is a Merge PR request or the user explicitly asked. When merging, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
10043
10221
  );
10044
10222
  if (thread.prUrl) {
10045
10223
  lines.push(
@@ -10077,7 +10255,7 @@ function formatWorktreeDirective(thread, opts) {
10077
10255
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
10078
10256
  );
10079
10257
  lines.push(
10080
- '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
10258
+ '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub (this phrase is the explicit ask). If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
10081
10259
  );
10082
10260
  return lines.join("\n");
10083
10261
  }
@@ -10120,10 +10298,11 @@ var FILES_BY_AGENT = {
10120
10298
  var MAX_CHARS_PER_FILE = 48e3;
10121
10299
  function loadAgentInstructions(worktreePath, agent) {
10122
10300
  const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
10123
- const seen = /* @__PURE__ */ new Set();
10301
+ const seenPaths = /* @__PURE__ */ new Set();
10302
+ const seenBodies = /* @__PURE__ */ new Set();
10124
10303
  const out = [];
10125
10304
  for (const rel of candidates) {
10126
- if (seen.has(rel)) continue;
10305
+ if (seenPaths.has(rel)) continue;
10127
10306
  const abs = (0, import_node_path17.join)(worktreePath, rel);
10128
10307
  if (!(0, import_node_fs20.existsSync)(abs)) continue;
10129
10308
  try {
@@ -10135,7 +10314,10 @@ function loadAgentInstructions(worktreePath, agent) {
10135
10314
 
10136
10315
  \u2026(truncated)`;
10137
10316
  }
10138
- seen.add(rel);
10317
+ const body = content.trim().replace(/\r\n/g, "\n");
10318
+ if (seenBodies.has(body)) continue;
10319
+ seenPaths.add(rel);
10320
+ seenBodies.add(body);
10139
10321
  out.push({ relativePath: rel, content });
10140
10322
  } catch {
10141
10323
  }
@@ -10219,9 +10401,9 @@ function matchSimpleGlob(pattern, name) {
10219
10401
  return new RegExp(`^${escaped}$`).test(name);
10220
10402
  }
10221
10403
  function readWorktreeInclude(repoPath) {
10222
- const path = (0, import_node_path18.join)(repoPath, ".worktreeinclude");
10223
- if (!(0, import_node_fs21.existsSync)(path)) return [];
10224
- return (0, import_node_fs21.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
10404
+ const path2 = (0, import_node_path18.join)(repoPath, ".worktreeinclude");
10405
+ if (!(0, import_node_fs21.existsSync)(path2)) return [];
10406
+ return (0, import_node_fs21.readFileSync)(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
10225
10407
  }
10226
10408
  function resolveFilesToCopy(repoPath) {
10227
10409
  const fromInclude = readWorktreeInclude(repoPath);
@@ -10296,8 +10478,20 @@ async function captureLoginEnv() {
10296
10478
  cachedLoginEnv = { ...process.env };
10297
10479
  return { ...cachedLoginEnv };
10298
10480
  }
10481
+ var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
10482
+ function stripNestedElectronEnv(env) {
10483
+ const out = { ...env };
10484
+ for (const key of Object.keys(out)) {
10485
+ if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
10486
+ delete out[key];
10487
+ }
10488
+ }
10489
+ return out;
10490
+ }
10299
10491
  function buildWorkspaceScriptEnv(opts, baseEnv) {
10300
- const env = { ...baseEnv ?? process.env };
10492
+ const env = stripNestedElectronEnv({
10493
+ ...baseEnv ?? process.env
10494
+ });
10301
10495
  const name = opts.workspaceName ?? (0, import_node_path18.basename)(opts.worktreePath);
10302
10496
  const ports = opts.ports ?? [];
10303
10497
  const primary = ports[0];
@@ -10522,10 +10716,10 @@ var import_node_path19 = require("path");
10522
10716
  var import_execa4 = require("execa");
10523
10717
  var import_node_readline3 = require("readline");
10524
10718
  function loadCursorWorktreesJson(rootPath) {
10525
- const path = (0, import_node_path19.join)(rootPath, ".cursor", "worktrees.json");
10526
- if (!(0, import_node_fs22.existsSync)(path)) return null;
10719
+ const path2 = (0, import_node_path19.join)(rootPath, ".cursor", "worktrees.json");
10720
+ if (!(0, import_node_fs22.existsSync)(path2)) return null;
10527
10721
  try {
10528
- return JSON.parse((0, import_node_fs22.readFileSync)(path, "utf8"));
10722
+ return JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8"));
10529
10723
  } catch {
10530
10724
  return null;
10531
10725
  }
@@ -10628,9 +10822,9 @@ function parseNameStatus(stdout) {
10628
10822
  const out = [];
10629
10823
  for (const line of stdout.split("\n").filter(Boolean)) {
10630
10824
  const [status, ...pathParts] = line.split(" ");
10631
- const path = pathParts[pathParts.length - 1];
10632
- if (!status || !path) continue;
10633
- out.push({ status: status[0] ?? status, path });
10825
+ const path2 = pathParts[pathParts.length - 1];
10826
+ if (!status || !path2) continue;
10827
+ out.push({ status: status[0] ?? status, path: path2 });
10634
10828
  }
10635
10829
  return out;
10636
10830
  }
@@ -10638,12 +10832,12 @@ function parseNumstat(stdout) {
10638
10832
  const map = /* @__PURE__ */ new Map();
10639
10833
  for (const line of stdout.split("\n").filter(Boolean)) {
10640
10834
  const [addRaw, delRaw, ...pathParts] = line.split(" ");
10641
- const path = pathParts[pathParts.length - 1];
10642
- if (!path) continue;
10835
+ const path2 = pathParts[pathParts.length - 1];
10836
+ if (!path2) continue;
10643
10837
  const additions = addRaw === "-" ? 0 : Number(addRaw);
10644
10838
  const deletions = delRaw === "-" ? 0 : Number(delRaw);
10645
10839
  if (!Number.isFinite(additions) && !Number.isFinite(deletions)) continue;
10646
- map.set(path, {
10840
+ map.set(path2, {
10647
10841
  additions: Number.isFinite(additions) ? additions : 0,
10648
10842
  deletions: Number.isFinite(deletions) ? deletions : 0
10649
10843
  });
@@ -10720,70 +10914,70 @@ function splitCombinedDiff(stdout) {
10720
10914
  }
10721
10915
  return map;
10722
10916
  }
10723
- function syntheticAddPatch(path, content, maxHunk) {
10917
+ function syntheticAddPatch(path2, content, maxHunk) {
10724
10918
  const lines = content.split("\n");
10725
10919
  const body = lines.map((l) => `+${l}`).join("\n");
10726
- const header = `diff --git a/${path} b/${path}
10920
+ const header = `diff --git a/${path2} b/${path2}
10727
10921
  new file mode 100644
10728
10922
  --- /dev/null
10729
- +++ b/${path}
10923
+ +++ b/${path2}
10730
10924
  @@ -0,0 +1,${lines.length} @@
10731
10925
  `;
10732
10926
  return {
10733
- path,
10927
+ path: path2,
10734
10928
  status: "A",
10735
10929
  patch: capPatch(`${header}${body}`, maxHunk),
10736
10930
  additions: lines.length,
10737
10931
  deletions: 0
10738
10932
  };
10739
10933
  }
10740
- async function untrackedPatch(worktreePath, path, maxHunk) {
10741
- const abs = (0, import_node_path20.join)(worktreePath, path);
10934
+ async function untrackedPatch(worktreePath, path2, maxHunk) {
10935
+ const abs = (0, import_node_path20.join)(worktreePath, path2);
10742
10936
  try {
10743
10937
  const st = (0, import_node_fs23.statSync)(abs);
10744
10938
  if (st.isFile() && st.size > maxHunk) {
10745
10939
  const buf = (0, import_node_fs23.readFileSync)(abs).subarray(0, maxHunk);
10746
- return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
10940
+ return syntheticAddPatch(path2, buf.toString("utf8"), maxHunk);
10747
10941
  }
10748
10942
  } catch {
10749
10943
  }
10750
10944
  const { stdout: patch } = await git(
10751
- ["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path],
10945
+ ["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path2],
10752
10946
  worktreePath,
10753
10947
  { reject: false, timeoutMs: 4e3 }
10754
10948
  );
10755
- const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path} b/${path}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path}`);
10949
+ const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path2} b/${path2}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path2}`);
10756
10950
  const addLines = normalized.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
10757
10951
  return {
10758
- path,
10952
+ path: path2,
10759
10953
  status: "A",
10760
10954
  patch: capPatch(normalized, maxHunk),
10761
10955
  additions: addLines,
10762
10956
  deletions: 0
10763
10957
  };
10764
10958
  }
10765
- async function listUntrackedPaths(worktreePath, path) {
10959
+ async function listUntrackedPaths(worktreePath, path2) {
10766
10960
  const args = ["ls-files", "-z", "--others", "--exclude-standard"];
10767
- if (path) args.push("--", path);
10961
+ if (path2) args.push("--", path2);
10768
10962
  const { stdout } = await git(args, worktreePath, { reject: false });
10769
10963
  return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
10770
10964
  }
10771
10965
  async function listUntrackedFiles(worktreePath, maxHunk, paths) {
10772
10966
  const list = paths ?? await listUntrackedPaths(worktreePath);
10773
10967
  if (list.length === 0) return [];
10774
- return Promise.all(list.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
10968
+ return Promise.all(list.map((path2) => untrackedPatch(worktreePath, path2, maxHunk)));
10775
10969
  }
10776
10970
  function untrackedStubs(paths) {
10777
- return paths.map((path) => ({
10778
- path,
10971
+ return paths.map((path2) => ({
10972
+ path: path2,
10779
10973
  status: "A",
10780
10974
  patch: "",
10781
10975
  additions: 0,
10782
10976
  deletions: 0
10783
10977
  }));
10784
10978
  }
10785
- function pathspec(path) {
10786
- return path ? ["--", path] : [];
10979
+ function pathspec(path2) {
10980
+ return path2 ? ["--", path2] : [];
10787
10981
  }
10788
10982
  function gitDiff(args, cwd, opts) {
10789
10983
  return git(["diff", "--no-ext-diff", "--no-color", ...args], cwd, {
@@ -10791,7 +10985,7 @@ function gitDiff(args, cwd, opts) {
10791
10985
  timeoutMs: opts?.timeoutMs
10792
10986
  });
10793
10987
  }
10794
- function fileFromPatch(path, patch, maxHunk) {
10988
+ function fileFromPatch(path2, patch, maxHunk) {
10795
10989
  let status = "M";
10796
10990
  if (/^new file mode /m.test(patch) || /^--- \/dev\/null/m.test(patch)) {
10797
10991
  status = "A";
@@ -10803,15 +10997,15 @@ function fileFromPatch(path, patch, maxHunk) {
10803
10997
  const additions = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
10804
10998
  const deletions = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
10805
10999
  return {
10806
- path,
11000
+ path: path2,
10807
11001
  status,
10808
11002
  patch: capPatch(patch, maxHunk),
10809
11003
  additions,
10810
11004
  deletions
10811
11005
  };
10812
11006
  }
10813
- async function gitDiffParts(worktreePath, diffArgs, includePatches, path) {
10814
- const extra = pathspec(path);
11007
+ async function gitDiffParts(worktreePath, diffArgs, includePatches, path2) {
11008
+ const extra = pathspec(path2);
10815
11009
  if (includePatches) {
10816
11010
  const diff = await gitDiff([...diffArgs, ...extra], worktreePath, {
10817
11011
  timeoutMs: 4e3
@@ -10849,17 +11043,17 @@ function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
10849
11043
  const patches = splitCombinedDiff(combinedDiff);
10850
11044
  if (!nameStatus.trim() && patches.size > 0) {
10851
11045
  return [...patches.entries()].map(
10852
- ([path, patch]) => fileFromPatch(path, patch, maxHunk)
11046
+ ([path2, patch]) => fileFromPatch(path2, patch, maxHunk)
10853
11047
  );
10854
11048
  }
10855
11049
  const counts = parseNumstat(numstat);
10856
11050
  const files = [];
10857
- for (const { status, path } of parseNameStatus(nameStatus)) {
10858
- const n = counts.get(path);
11051
+ for (const { status, path: path2 } of parseNameStatus(nameStatus)) {
11052
+ const n = counts.get(path2);
10859
11053
  files.push({
10860
- path,
11054
+ path: path2,
10861
11055
  status,
10862
- patch: capPatch(patches.get(path) ?? "", maxHunk),
11056
+ patch: capPatch(patches.get(path2) ?? "", maxHunk),
10863
11057
  additions: n?.additions,
10864
11058
  deletions: n?.deletions
10865
11059
  });
@@ -10943,7 +11137,7 @@ function emptyPathDiff(scope, base, hasLastTurnBase, commitSha = null) {
10943
11137
  };
10944
11138
  }
10945
11139
  async function getPathDiff(worktreePath, repoPath, opts) {
10946
- const { path, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
11140
+ const { path: path2, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
10947
11141
  if (scope === "last_turn" && !lastTurnBase) {
10948
11142
  return emptyPathDiff(scope, "last turn", false);
10949
11143
  }
@@ -10982,7 +11176,7 @@ async function getPathDiff(worktreePath, repoPath, opts) {
10982
11176
  labelBase = "HEAD";
10983
11177
  }
10984
11178
  const diff = await gitDiff(
10985
- includePatches ? [...diffArgs, "--", path] : ["--name-status", ...diffArgs, "--", path],
11179
+ includePatches ? [...diffArgs, "--", path2] : ["--name-status", ...diffArgs, "--", path2],
10986
11180
  worktreePath,
10987
11181
  { timeoutMs: 4e3 }
10988
11182
  );
@@ -10990,8 +11184,8 @@ async function getPathDiff(worktreePath, repoPath, opts) {
10990
11184
  if (includePatches) {
10991
11185
  const parsed = filesFromDiff("", "", diff.stdout, maxHunk);
10992
11186
  for (const file of parsed) filesMap.set(file.path, file);
10993
- if (diff.stdout.trim() && !filesMap.has(path)) {
10994
- filesMap.set(path, fileFromPatch(path, diff.stdout, maxHunk));
11187
+ if (diff.stdout.trim() && !filesMap.has(path2)) {
11188
+ filesMap.set(path2, fileFromPatch(path2, diff.stdout, maxHunk));
10995
11189
  }
10996
11190
  } else {
10997
11191
  for (const file of filesFromDiff(diff.stdout, "", "", maxHunk)) {
@@ -10999,7 +11193,7 @@ async function getPathDiff(worktreePath, repoPath, opts) {
10999
11193
  }
11000
11194
  }
11001
11195
  if (filesMap.size === 0) {
11002
- const maybe = await listUntrackedPaths(worktreePath, path);
11196
+ const maybe = await listUntrackedPaths(worktreePath, path2);
11003
11197
  if (maybe.length > 0) {
11004
11198
  const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
11005
11199
  for (const file of extra) filesMap.set(file.path, file);
@@ -12025,8 +12219,8 @@ function splitForCompaction(messages, thresholds = {}) {
12025
12219
  function formatToolPart(t, detail) {
12026
12220
  if (detail === "summary") {
12027
12221
  const label = t.description || t.detail || t.name;
12028
- const path = t.filePath ? ` (${t.filePath})` : "";
12029
- return `- ${t.name}: ${label}${path}`;
12222
+ const path2 = t.filePath ? ` (${t.filePath})` : "";
12223
+ return `- ${t.name}: ${label}${path2}`;
12030
12224
  }
12031
12225
  const lines = [`#### Tool: ${t.name}`, `Status: ${t.status}`];
12032
12226
  if (t.description) lines.push(`Description: ${t.description}`);
@@ -13182,10 +13376,10 @@ function storePath2() {
13182
13376
  return (0, import_node_path24.join)(appDataDir(), "slack-reply-to.json");
13183
13377
  }
13184
13378
  function readStore2() {
13185
- const path = storePath2();
13186
- if (!(0, import_node_fs29.existsSync)(path)) return {};
13379
+ const path2 = storePath2();
13380
+ if (!(0, import_node_fs29.existsSync)(path2)) return {};
13187
13381
  try {
13188
- const parsed = isSecureFileEncrypted(path) ? readSecureJson(path) : JSON.parse((0, import_node_fs29.readFileSync)(path, "utf8"));
13382
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse((0, import_node_fs29.readFileSync)(path2, "utf8"));
13189
13383
  return parsed?.targets && typeof parsed.targets === "object" ? parsed.targets : {};
13190
13384
  } catch {
13191
13385
  return {};
@@ -13210,12 +13404,12 @@ function storePath3() {
13210
13404
  }
13211
13405
  function readStore3() {
13212
13406
  try {
13213
- const path = storePath3();
13214
- const wasEncrypted = isSecureFileEncrypted(path);
13215
- const parsed = readSecureJson(path);
13407
+ const path2 = storePath3();
13408
+ const wasEncrypted = isSecureFileEncrypted(path2);
13409
+ const parsed = readSecureJson(path2);
13216
13410
  const workspaces = Array.isArray(parsed?.workspaces) ? parsed.workspaces : [];
13217
13411
  if (workspaces.length > 0 && !wasEncrypted && resolveVaultKey()) {
13218
- writeSecureJson(path, { workspaces });
13412
+ writeSecureJson(path2, { workspaces });
13219
13413
  }
13220
13414
  return workspaces;
13221
13415
  } catch {
@@ -13351,10 +13545,10 @@ function tsNewer(a, b) {
13351
13545
  return Number(a) > Number(b);
13352
13546
  }
13353
13547
  function readStore4() {
13354
- const path = storePath4();
13355
- if (!(0, import_node_fs30.existsSync)(path)) return [];
13548
+ const path2 = storePath4();
13549
+ if (!(0, import_node_fs30.existsSync)(path2)) return [];
13356
13550
  try {
13357
- const parsed = isSecureFileEncrypted(path) ? readSecureJson(path) : JSON.parse((0, import_node_fs30.readFileSync)(path, "utf8"));
13551
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse((0, import_node_fs30.readFileSync)(path2, "utf8"));
13358
13552
  return Array.isArray(parsed?.watches) ? parsed.watches : [];
13359
13553
  } catch {
13360
13554
  return [];
@@ -13726,8 +13920,8 @@ init_worktree();
13726
13920
  init_thread_store();
13727
13921
  init_paths();
13728
13922
  init_app_settings();
13729
- function isSideboardWorktreePath(path) {
13730
- return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
13923
+ function isSideboardWorktreePath(path2) {
13924
+ return path2.includes("/.sideboard/worktrees/") || path2.includes("/sideboard/workspaces/");
13731
13925
  }
13732
13926
  async function findOrphanWorktrees(repoPaths) {
13733
13927
  const threads = listThreads({ includeArchived: true });
@@ -13752,18 +13946,18 @@ async function findOrphanWorktrees(repoPaths) {
13752
13946
  try {
13753
13947
  const wts = await listWorktrees(repoPath);
13754
13948
  for (const wt of wts) {
13755
- const path = wt.path.replace(/\/$/, "");
13756
- if (!isSideboardWorktreePath(path)) continue;
13757
- if (known.has(path)) continue;
13758
- if (seen.has(path)) continue;
13759
- seen.add(path);
13949
+ const path2 = wt.path.replace(/\/$/, "");
13950
+ if (!isSideboardWorktreePath(path2)) continue;
13951
+ if (known.has(path2)) continue;
13952
+ if (seen.has(path2)) continue;
13953
+ seen.add(path2);
13760
13954
  let mtimeMs = 0;
13761
13955
  try {
13762
- mtimeMs = (0, import_node_fs31.statSync)(path).mtimeMs;
13956
+ mtimeMs = (0, import_node_fs31.statSync)(path2).mtimeMs;
13763
13957
  } catch {
13764
13958
  mtimeMs = 0;
13765
13959
  }
13766
- orphans.push({ path, repoPath, mtimeMs });
13960
+ orphans.push({ path: path2, repoPath, mtimeMs });
13767
13961
  }
13768
13962
  } catch {
13769
13963
  }
@@ -13772,17 +13966,17 @@ async function findOrphanWorktrees(repoPaths) {
13772
13966
  if ((0, import_node_fs31.existsSync)(root)) {
13773
13967
  for (const entry of (0, import_node_fs31.readdirSync)(root, { withFileTypes: true })) {
13774
13968
  if (!entry.isDirectory()) continue;
13775
- const path = (0, import_node_path27.join)(root, entry.name).replace(/\/$/, "");
13776
- if (known.has(path) || seen.has(path)) continue;
13777
- if (!(0, import_node_fs31.existsSync)((0, import_node_path27.join)(path, ".git"))) continue;
13778
- seen.add(path);
13969
+ const path2 = (0, import_node_path27.join)(root, entry.name).replace(/\/$/, "");
13970
+ if (known.has(path2) || seen.has(path2)) continue;
13971
+ if (!(0, import_node_fs31.existsSync)((0, import_node_path27.join)(path2, ".git"))) continue;
13972
+ seen.add(path2);
13779
13973
  let mtimeMs = 0;
13780
13974
  try {
13781
- mtimeMs = (0, import_node_fs31.statSync)(path).mtimeMs;
13975
+ mtimeMs = (0, import_node_fs31.statSync)(path2).mtimeMs;
13782
13976
  } catch {
13783
13977
  mtimeMs = Date.now();
13784
13978
  }
13785
- orphans.push({ path, repoPath, mtimeMs });
13979
+ orphans.push({ path: path2, repoPath, mtimeMs });
13786
13980
  }
13787
13981
  }
13788
13982
  } catch {
@@ -14176,9 +14370,9 @@ function ensureReviewRequestFile(worktreePath) {
14176
14370
  const localAbs = (0, import_node_path29.join)(worktreePath, REVIEW_REQUEST_PATH);
14177
14371
  const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent((0, import_node_path29.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
14178
14372
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
14179
- const path = (0, import_node_fs33.existsSync)(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
14373
+ const path2 = (0, import_node_fs33.existsSync)(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
14180
14374
  return {
14181
- path,
14375
+ path: path2,
14182
14376
  name: REVIEW_REQUEST_NAME,
14183
14377
  content: localContent,
14184
14378
  source: "local"
@@ -14194,13 +14388,13 @@ function ensureReviewRequestFile(worktreePath) {
14194
14388
  };
14195
14389
  }
14196
14390
  function buildReviewRequestAttachment(content, opts) {
14197
- const path = opts?.path ?? REVIEW_REQUEST_PATH;
14198
- const name = opts?.name ?? (path === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
14391
+ const path2 = opts?.path ?? REVIEW_REQUEST_PATH;
14392
+ const name = opts?.name ?? (path2 === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
14199
14393
  return {
14200
14394
  id: (0, import_node_crypto8.randomUUID)(),
14201
14395
  name,
14202
14396
  kind: "file",
14203
- path,
14397
+ path: path2,
14204
14398
  content
14205
14399
  };
14206
14400
  }
@@ -14920,14 +15114,16 @@ var Orchestrator = class {
14920
15114
  }
14921
15115
  const isBrightsy = fresh.agent === "brightsy";
14922
15116
  const isOrchestration = isOrchestratorThread(fresh);
15117
+ const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
15118
+ const gitAuthMode = getGithubGitAuthMode2();
14923
15119
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
14924
15120
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
14925
15121
  () => null
14926
- )
15122
+ ),
15123
+ gitAuthMode
14927
15124
  });
14928
15125
  const artifactDirective = isBrightsy ? null : formatArtifactDirective();
14929
15126
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
14930
- const { autoRenameBranchEnabled: autoRenameBranchEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
14931
15127
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
14932
15128
  customPrompt: settings?.prompts?.renameBranch
14933
15129
  }) : null;
@@ -15764,8 +15960,9 @@ var Orchestrator = class {
15764
15960
  return tab;
15765
15961
  }
15766
15962
  /**
15767
- * Queue a desktop-git-button prompt on a worktree agent (commit/push/PR/merge).
15768
- * Orchestrators use this instead of running git/gh from the synthetic home.
15963
+ * Desktop git buttons + MCP `ask_git`.
15964
+ * When the worktree is clean, push / open the PR here (HTTPS via `gh` if SSH
15965
+ * is missing). When dirty, queue the worktree agent to commit first.
15769
15966
  */
15770
15967
  async askGit(threadRef, action) {
15771
15968
  if (!AGENT_GIT_ACTIONS.includes(action)) {
@@ -15783,6 +15980,10 @@ var Orchestrator = class {
15783
15980
  "No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
15784
15981
  );
15785
15982
  }
15983
+ if ((action === "commit-push" || action === "create-draft" || action === "create-web") && thread.worktreePath?.trim() && !await isDirty(thread.worktreePath)) {
15984
+ await this.pushAndMaybeOpenPr(thread, action);
15985
+ return this.requireThread(threadRef);
15986
+ }
15786
15987
  let prBase;
15787
15988
  if (action === "resolve-conflicts") {
15788
15989
  try {
@@ -15793,6 +15994,34 @@ var Orchestrator = class {
15793
15994
  }
15794
15995
  return this.send(threadRef, agentGitPrompt(action, { prBase }));
15795
15996
  }
15997
+ /** Push origin (gh HTTPS fallback) and create/update the PR when requested. */
15998
+ async pushAndMaybeOpenPr(thread, action) {
15999
+ const cwd = thread.worktreePath;
16000
+ const branch = await currentBranch(cwd);
16001
+ await pushBranch(cwd, branch);
16002
+ if (action === "commit-push") return;
16003
+ const base = await resolveDefaultBranch(thread.repoPath);
16004
+ const meta = await suggestPrMetadata(cwd, {
16005
+ base,
16006
+ fallbackTitle: thread.prTitle ?? thread.title
16007
+ });
16008
+ const url = await createOrUpdatePr(cwd, {
16009
+ title: meta.title,
16010
+ body: meta.body,
16011
+ base,
16012
+ head: branch,
16013
+ draft: action === "create-draft",
16014
+ web: action === "create-web"
16015
+ });
16016
+ if (!url) return;
16017
+ const patch = { prUrl: url, prTitle: meta.title };
16018
+ try {
16019
+ const fetched = await getPrMeta(cwd, url);
16020
+ if (fetched?.title) patch.prTitle = fetched.title;
16021
+ } catch {
16022
+ }
16023
+ updateThread(thread.id, patch);
16024
+ }
15796
16025
  setThreadOptions(threadRef, patch) {
15797
16026
  const thread = this.requireThread(threadRef);
15798
16027
  const next = {};
@@ -16373,9 +16602,9 @@ function labelGithubUrl(url) {
16373
16602
  return null;
16374
16603
  }
16375
16604
  if (!/(^|\.)github\.com$/i.test(parsed.hostname)) return null;
16376
- const path = parsed.pathname;
16605
+ const path2 = parsed.pathname;
16377
16606
  const hash = parsed.hash || "";
16378
- const pr = path.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/i);
16607
+ const pr = path2.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/i);
16379
16608
  if (pr) {
16380
16609
  const n = pr[3];
16381
16610
  if (/#discussion_r\d+/i.test(hash) || /#pullrequestreview-\d+/i.test(hash)) {
@@ -16386,11 +16615,11 @@ function labelGithubUrl(url) {
16386
16615
  }
16387
16616
  return { kind: "pr", label: `PR #${n}`, url: raw };
16388
16617
  }
16389
- const issueComment = path.match(/\/([^/]+)\/([^/]+)\/issues\/(\d+)/i);
16618
+ const issueComment = path2.match(/\/([^/]+)\/([^/]+)\/issues\/(\d+)/i);
16390
16619
  if (issueComment && /#issuecomment-\d+/i.test(hash)) {
16391
16620
  return { kind: "comment", label: `Issue #${issueComment[3]} comment`, url: raw };
16392
16621
  }
16393
- const blob = path.match(/\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/i);
16622
+ const blob = path2.match(/\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/i);
16394
16623
  if (blob) {
16395
16624
  const filePath = decodeURIComponent(blob[3]);
16396
16625
  const fileName = filePath.split("/").pop() || filePath;
@@ -16952,10 +17181,10 @@ async function startMcpServer() {
16952
17181
  const t = orch.getThread(thread_id.trim());
16953
17182
  if (t?.worktreePath?.trim()) root = t.worktreePath;
16954
17183
  }
16955
- const path = writePlanFile2(root, content);
17184
+ const path2 = writePlanFile2(root, content);
16956
17185
  const payload = {
16957
17186
  ok: true,
16958
- path,
17187
+ path: path2,
16959
17188
  title: title?.trim() || "Plan",
16960
17189
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
16961
17190
  };
@@ -17175,7 +17404,7 @@ async function startMcpServer() {
17175
17404
  );
17176
17405
  server.tool(
17177
17406
  "send_to_thread",
17178
- "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
17407
+ 'Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.',
17179
17408
  {
17180
17409
  ref: import_zod3.z.string(),
17181
17410
  prompt: import_zod3.z.string(),
@@ -17271,7 +17500,7 @@ async function startMcpServer() {
17271
17500
  );
17272
17501
  server.tool(
17273
17502
  "archive_thread",
17274
- "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
17503
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
17275
17504
  { ref: import_zod3.z.string() },
17276
17505
  async ({ ref }) => {
17277
17506
  const t = orch.getThread(ref);
@@ -17379,7 +17608,7 @@ async function startMcpServer() {
17379
17608
  );
17380
17609
  server.tool(
17381
17610
  "ask_git",
17382
- "Tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge the linked PR \u2014 same short prompts as the desktop git buttons. The worktree agent runs git/gh (`gh pr merge`); this only queues the prompt. Pass a worktree thread ref (not the orchestrator). Then wait_for_turn / get_turn_result. Do not run git or gh from the orchestration cwd.",
17611
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
17383
17612
  {
17384
17613
  ref: import_zod3.z.string().describe("Worktree thread id/ref"),
17385
17614
  action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
@@ -17680,7 +17909,7 @@ async function startMcpServer() {
17680
17909
  );
17681
17910
  server.tool(
17682
17911
  "get_pr_stack",
17683
- "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs.",
17912
+ "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
17684
17913
  { ref: import_zod3.z.string() },
17685
17914
  async ({ ref }) => {
17686
17915
  const stack = await orch.getPrStack(ref);
@@ -17852,9 +18081,9 @@ var BrightsySideboardApi = class {
17852
18081
  get endpoint() {
17853
18082
  return (this.cfg.endpoint || "https://brightsy.ai").replace(/\/$/, "");
17854
18083
  }
17855
- async request(path, init = {}) {
18084
+ async request(path2, init = {}) {
17856
18085
  await this.refreshIfNeeded();
17857
- const url = `${this.endpoint}${path}`;
18086
+ const url = `${this.endpoint}${path2}`;
17858
18087
  const headers = {
17859
18088
  Authorization: `Bearer ${this.cfg.access_token}`,
17860
18089
  Accept: "application/json"
@@ -19461,6 +19690,83 @@ var SlackRelayHub = class {
19461
19690
  // src/slack/relay-server.ts
19462
19691
  var import_node_http3 = require("http");
19463
19692
  var import_ws3 = require("ws");
19693
+
19694
+ // src/slack/relay-static.ts
19695
+ var import_node_fs37 = require("fs");
19696
+ var import_promises = require("fs/promises");
19697
+ var import_node_path33 = __toESM(require("path"), 1);
19698
+ var TYPES = {
19699
+ ".css": "text/css; charset=utf-8",
19700
+ ".html": "text/html; charset=utf-8",
19701
+ ".ico": "image/x-icon",
19702
+ ".jpeg": "image/jpeg",
19703
+ ".jpg": "image/jpeg",
19704
+ ".js": "text/javascript; charset=utf-8",
19705
+ ".json": "application/json; charset=utf-8",
19706
+ ".png": "image/png",
19707
+ ".svg": "image/svg+xml",
19708
+ ".txt": "text/plain; charset=utf-8",
19709
+ ".webp": "image/webp",
19710
+ ".woff2": "font/woff2"
19711
+ };
19712
+ function requestHostname(req) {
19713
+ const raw = req.headers.host ?? "";
19714
+ return raw.split(":")[0]?.toLowerCase() ?? "";
19715
+ }
19716
+ function hostnameAllowed(host, allowed) {
19717
+ if (!allowed || allowed.length === 0) return true;
19718
+ return allowed.includes(host);
19719
+ }
19720
+ function resolveStaticPath(root, requestUrl) {
19721
+ let pathname;
19722
+ try {
19723
+ pathname = decodeURIComponent(new URL(requestUrl, "http://relay.local").pathname);
19724
+ } catch {
19725
+ return null;
19726
+ }
19727
+ if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
19728
+ const rootResolved = import_node_path33.default.resolve(root);
19729
+ const candidate = import_node_path33.default.resolve(rootResolved, `.${pathname}`);
19730
+ if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path33.default.sep)) {
19731
+ return null;
19732
+ }
19733
+ return candidate;
19734
+ }
19735
+ async function fileSize(file) {
19736
+ try {
19737
+ const st = await (0, import_promises.stat)(file);
19738
+ return st.isFile() ? st.size : null;
19739
+ } catch {
19740
+ return null;
19741
+ }
19742
+ }
19743
+ function sendFile(req, res, file, size) {
19744
+ const ext = import_node_path33.default.extname(file).toLowerCase();
19745
+ res.writeHead(200, {
19746
+ "Content-Type": TYPES[ext] ?? "application/octet-stream",
19747
+ "Content-Length": size,
19748
+ "Cache-Control": ext === ".html" ? "no-cache" : "public, max-age=3600"
19749
+ });
19750
+ if (req.method === "HEAD") {
19751
+ res.end();
19752
+ return true;
19753
+ }
19754
+ (0, import_node_fs37.createReadStream)(file).pipe(res);
19755
+ return true;
19756
+ }
19757
+ async function tryServeStatic(req, res, root) {
19758
+ if (req.method !== "GET" && req.method !== "HEAD") return false;
19759
+ const candidate = resolveStaticPath(root, req.url || "/");
19760
+ if (!candidate) return false;
19761
+ const direct = await fileSize(candidate);
19762
+ if (direct != null) return sendFile(req, res, candidate, direct);
19763
+ const asIndex = import_node_path33.default.join(candidate, "index.html");
19764
+ const indexSize = await fileSize(asIndex);
19765
+ if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
19766
+ return false;
19767
+ }
19768
+
19769
+ // src/slack/relay-server.ts
19464
19770
  function sendHtml(res, status, title, body) {
19465
19771
  res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
19466
19772
  res.end(slackOAuthHtmlPage(title, body));
@@ -19559,7 +19865,26 @@ async function startSlackRelayServer(opts) {
19559
19865
  const reqUrl = req.url || "/";
19560
19866
  if (await handleCallback(reqUrl, res)) return;
19561
19867
  if (handleResult(reqUrl, res)) return;
19562
- if (req.url === "/health" || req.url === "/") {
19868
+ if (req.url === "/health") {
19869
+ sendJson(res, 200, {
19870
+ ok: true,
19871
+ service: "sideboard-slack-relay",
19872
+ sessions: hub.listSessions().length,
19873
+ oauth: Boolean(clientSecret)
19874
+ });
19875
+ return;
19876
+ }
19877
+ const hostname2 = requestHostname(req);
19878
+ const canonical = (opts.canonicalSiteHost ?? "www.sideboard.cloud").toLowerCase();
19879
+ if (opts.redirectHosts?.includes(hostname2) && hostname2 !== canonical) {
19880
+ res.writeHead(301, { Location: `https://${canonical}${reqUrl}` });
19881
+ res.end();
19882
+ return;
19883
+ }
19884
+ if (opts.staticRoot && hostnameAllowed(hostname2, opts.staticHosts) && await tryServeStatic(req, res, opts.staticRoot)) {
19885
+ return;
19886
+ }
19887
+ if (req.url === "/") {
19563
19888
  sendJson(res, 200, {
19564
19889
  ok: true,
19565
19890
  service: "sideboard-slack-relay",
@@ -19669,6 +19994,7 @@ async function startSlackRelayServer(opts) {
19669
19994
  CONTEXT_MIN_MESSAGES,
19670
19995
  COORDINATOR_TOOL_PLAYBOOK,
19671
19996
  FAMOUS_SOCCER_TEAMS,
19997
+ GITHUB_GIT_AUTH_MODES,
19672
19998
  GLOBAL_WORKSPACE_ID,
19673
19999
  HARNESS_ENV_KEYS,
19674
20000
  LEGACY_ATTACHMENTS_DIR,
@@ -19718,10 +20044,12 @@ async function startSlackRelayServer(opts) {
19718
20044
  allocateTeamSlug,
19719
20045
  appDataDir,
19720
20046
  appSettingsPath,
20047
+ appendIndexedGitConfig,
19721
20048
  appendMessage,
19722
20049
  applyAgentEvent,
19723
20050
  applyAppEnvironment,
19724
20051
  applyCompaction,
20052
+ applyGithubGitAuthEnv,
19725
20053
  applyThreadIntoMain,
19726
20054
  applyTurnUsage,
19727
20055
  assertOrchestratorCapableAgent,
@@ -19836,6 +20164,7 @@ async function startSlackRelayServer(opts) {
19836
20164
  formatBrightsyFetchError,
19837
20165
  formatFetchError,
19838
20166
  formatGhLandError,
20167
+ formatGitAuthModeDirective,
19839
20168
  formatIpcInvokeError,
19840
20169
  formatMessagesAsTranscript,
19841
20170
  formatPlanQuestionAnswers,
@@ -19850,6 +20179,7 @@ async function startSlackRelayServer(opts) {
19850
20179
  formatWorkspaceInventory,
19851
20180
  formatWorktreeDirective,
19852
20181
  formatWorktreeReminder,
20182
+ fromInclusiveInputUsage,
19853
20183
  getAdapter,
19854
20184
  getAgentSetupInfo,
19855
20185
  getBrightsySession,
@@ -19862,6 +20192,8 @@ async function startSlackRelayServer(opts) {
19862
20192
  getDiff,
19863
20193
  getDiffSummary,
19864
20194
  getGitHubStatus,
20195
+ getGithubGitAuthMode,
20196
+ getGithubPat,
19865
20197
  getIssueSource,
19866
20198
  getLinearApiKey,
19867
20199
  getLinearAuthToken,
@@ -19880,6 +20212,7 @@ async function startSlackRelayServer(opts) {
19880
20212
  ghHeadRef,
19881
20213
  ghRepoSelectArgs,
19882
20214
  git,
20215
+ githubAgentGitEnv,
19883
20216
  globalAgentCwd,
19884
20217
  handleSlackInbound,
19885
20218
  harnessEnvKey,
@@ -20102,6 +20435,7 @@ async function startSlackRelayServer(opts) {
20102
20435
  startSlackOAuth,
20103
20436
  startSlackRelayServer,
20104
20437
  stripBrightsyNdjsonNoise,
20438
+ stripNestedElectronEnv,
20105
20439
  submitPrStack,
20106
20440
  suggestSlug,
20107
20441
  summarizeConversation,