@sideboard-ai/core 0.1.68 → 0.1.71

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 (36) hide show
  1. package/dist/{agents-PE3RHQS5.js → agents-GB7XL2LI.js} +6 -5
  2. package/dist/{agents-JBD7IAGK.js → agents-TCAK3MXN.js} +6 -5
  3. package/dist/{app-settings-ONKMFSEJ.js → app-settings-3TLA2EJ7.js} +16 -1
  4. package/dist/{app-settings-GJVZGU3M.js → app-settings-P42Z3VOB.js} +16 -1
  5. package/dist/caffeinate-hold-BEJIYPJ7.js +107 -0
  6. package/dist/caffeinate-hold-KLC6SABD.js +14 -0
  7. package/dist/{chunk-DPDGMMGI.js → chunk-3CY6WJ7O.js} +2 -2
  8. package/dist/{chunk-ROXEENBT.js → chunk-57ROTAFD.js} +62 -3
  9. package/dist/{chunk-N5LMEP65.js → chunk-7NCIHRAU.js} +22 -4
  10. package/dist/{chunk-OICHV4DH.js → chunk-DGPUAZA4.js} +62 -3
  11. package/dist/{chunk-VX6ACNXS.js → chunk-H2IXNFQ2.js} +22 -4
  12. package/dist/chunk-JAWEDVVA.js +106 -0
  13. package/dist/chunk-JT7R45JB.js +20 -0
  14. package/dist/{chunk-EEBDVLDK.js → chunk-MF2YMEGD.js} +61 -9
  15. package/dist/{chunk-47RBLBRK.js → chunk-NG7S3OPX.js} +10 -4
  16. package/dist/chunk-NSTQ6QKD.js +23 -0
  17. package/dist/{chunk-REKGFVUX.js → chunk-OE7YBB5X.js} +61 -9
  18. package/dist/{chunk-BPOTVI5J.js → chunk-PTJJIQK7.js} +472 -18
  19. package/dist/{chunk-6Q7H6SMX.js → chunk-S25IHL5H.js} +10 -4
  20. package/dist/{chunk-QQC2D4MY.js → chunk-UH57WVFP.js} +2 -2
  21. package/dist/{chunk-MHR4OGZ4.js → chunk-W7YOTDVO.js} +493 -18
  22. package/dist/{coordinator-prompt-YZYZ5P6P.js → coordinator-prompt-2J4PIMUF.js} +6 -3
  23. package/dist/{coordinator-prompt-HD6IMCKS.js → coordinator-prompt-QYYG2IBB.js} +6 -3
  24. package/dist/{global-workspace-Z6GF7D2K.js → global-workspace-KHIFQUUM.js} +11 -4
  25. package/dist/{global-workspace-7P5NG3JZ.js → global-workspace-SVRYNJZA.js} +11 -4
  26. package/dist/index.cjs +4184 -850
  27. package/dist/index.d.cts +543 -21
  28. package/dist/index.d.ts +543 -21
  29. package/dist/index.js +3285 -748
  30. package/dist/mcp/run-stdio.cjs +1995 -482
  31. package/dist/mcp/run-stdio.js +943 -151
  32. package/dist/{workspaces-BWJNGEE6.js → workspaces-AHFTHVBT.js} +6 -5
  33. package/dist/{workspaces-O6EZGKQK.js → workspaces-SVQIEVIZ.js} +6 -5
  34. package/dist/{worktree-VDXLUOWR.js → worktree-KWXHMKRN.js} +1 -1
  35. package/dist/{worktree-ZPSKEZFE.js → worktree-VOCH3WS3.js} +1 -1
  36. package/package.json +3 -1
@@ -92,13 +92,13 @@ function summarizeTurnStderr(tail, maxChars = 500) {
92
92
  if (joined.length <= maxChars) return joined;
93
93
  return joined.slice(joined.length - maxChars);
94
94
  }
95
- function looksLikeInvalidAgentSession(text) {
96
- const lower = text.trim().toLowerCase();
95
+ function looksLikeInvalidAgentSession(text2) {
96
+ const lower = text2.trim().toLowerCase();
97
97
  if (!lower) return false;
98
98
  return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
99
99
  }
100
- function looksLikeAgentFailureMessage(text) {
101
- const lower = text.trim().toLowerCase();
100
+ function looksLikeAgentFailureMessage(text2) {
101
+ const lower = text2.trim().toLowerCase();
102
102
  if (!lower) return false;
103
103
  return /you've hit your|hit your (session|weekly|opus) limit|usage limit/.test(lower) || /credit balance is too low|out of credits|insufficient.?quota|quota.?exceeded/.test(lower) || /invalid user api key|invalid api key|not logged in|not authenticated|unauthorized/.test(
104
104
  lower
@@ -1451,8 +1451,8 @@ var init_worktree_labels = __esm({
1451
1451
  });
1452
1452
 
1453
1453
  // src/git/gh-errors.ts
1454
- function isGhRateLimitError(text) {
1455
- return /API rate limit (already )?exceeded/i.test(text) || /rate limit exceeded/i.test(text);
1454
+ function isGhRateLimitError(text2) {
1455
+ return /API rate limit (already )?exceeded/i.test(text2) || /rate limit exceeded/i.test(text2);
1456
1456
  }
1457
1457
  function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
1458
1458
  const ms = resetEpochSec * 1e3 - nowMs;
@@ -1464,8 +1464,8 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
1464
1464
  const hours = Math.ceil(mins / 60);
1465
1465
  return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
1466
1466
  }
1467
- function extractGhErrorDetail(text) {
1468
- const trimmed = text.trim();
1467
+ function extractGhErrorDetail(text2) {
1468
+ const trimmed = text2.trim();
1469
1469
  if (!trimmed) return "";
1470
1470
  const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
1471
1471
  if (graphql?.[1]) return `GraphQL: ${graphql[1].trim()}`;
@@ -1685,6 +1685,10 @@ var init_run = __esm({
1685
1685
  });
1686
1686
 
1687
1687
  // src/git/pr-gates.ts
1688
+ function prIsInMergeQueue(gate) {
1689
+ if (gate.isInMergeQueue) return true;
1690
+ return (gate.mergeStateStatus ?? "").toUpperCase() === "QUEUED";
1691
+ }
1688
1692
  function buildMergeGateChecks(gate, opts = {}) {
1689
1693
  const rows = [];
1690
1694
  const base = gate.baseRefName?.trim() || "the base branch";
@@ -1694,7 +1698,8 @@ function buildMergeGateChecks(gate, opts = {}) {
1694
1698
  const review = (gate.reviewDecision ?? "").toUpperCase();
1695
1699
  const suppressBlocked = opts.suppressGenericBlocked !== false;
1696
1700
  const conflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1697
- if (conflicting) {
1701
+ const inQueue = prIsInMergeQueue(gate);
1702
+ if (conflicting && !inQueue) {
1698
1703
  rows.push({
1699
1704
  name: "Merge conflicts",
1700
1705
  state: mergeable === "CONFLICTING" ? "CONFLICTING" : mergeState || "DIRTY",
@@ -1706,6 +1711,18 @@ function buildMergeGateChecks(gate, opts = {}) {
1706
1711
  workflow: "mergeability",
1707
1712
  kind: "mergeability"
1708
1713
  });
1714
+ } else if (inQueue) {
1715
+ rows.push({
1716
+ name: "Merge queue",
1717
+ state: "QUEUED",
1718
+ bucket: "pending",
1719
+ startedAt: null,
1720
+ completedAt: null,
1721
+ link,
1722
+ description: `This pull request is in the GitHub merge queue for ${base}.`,
1723
+ workflow: "mergeability",
1724
+ kind: "mergeability"
1725
+ });
1709
1726
  } else if (mergeState === "BEHIND") {
1710
1727
  rows.push({
1711
1728
  name: "Branch behind",
@@ -2291,10 +2308,21 @@ async function fetchPrMergeGate(cwd, selector, slug) {
2291
2308
  "view",
2292
2309
  selector,
2293
2310
  "--json",
2294
- "mergeable,mergeStateStatus,reviewDecision,baseRefName,url"
2311
+ "mergeable,mergeStateStatus,reviewDecision,baseRefName,url,isInMergeQueue"
2295
2312
  ];
2296
2313
  if (slug) args.push("--repo", slug);
2297
- const { stdout, exitCode, stderr } = await gh(args, cwd, { reject: false });
2314
+ let { stdout, exitCode, stderr } = await gh(args, cwd, { reject: false });
2315
+ if (exitCode !== 0 && isUnknownJsonField(stderr, "isInMergeQueue")) {
2316
+ const retry = [
2317
+ "pr",
2318
+ "view",
2319
+ selector,
2320
+ "--json",
2321
+ "mergeable,mergeStateStatus,reviewDecision,baseRefName,url"
2322
+ ];
2323
+ if (slug) retry.push("--repo", slug);
2324
+ ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
2325
+ }
2298
2326
  if (exitCode !== 0 || !stdout.trim()) {
2299
2327
  if (/no pull requests found/i.test(stderr)) return null;
2300
2328
  return null;
@@ -2306,12 +2334,22 @@ async function fetchPrMergeGate(cwd, selector, slug) {
2306
2334
  mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
2307
2335
  reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
2308
2336
  baseRefName: typeof view.baseRefName === "string" && view.baseRefName ? view.baseRefName : null,
2309
- url: typeof view.url === "string" && view.url ? view.url : null
2337
+ url: typeof view.url === "string" && view.url ? view.url : null,
2338
+ isInMergeQueue: parseInMergeQueue(view)
2310
2339
  };
2311
2340
  } catch {
2312
2341
  return null;
2313
2342
  }
2314
2343
  }
2344
+ function isUnknownJsonField(stderr, field) {
2345
+ return new RegExp(`unknown json field[^\\n]*${field}`, "i").test(stderr);
2346
+ }
2347
+ function parseInMergeQueue(view) {
2348
+ return prIsInMergeQueue({
2349
+ isInMergeQueue: view.isInMergeQueue === true,
2350
+ mergeStateStatus: typeof view.mergeStateStatus === "string" ? view.mergeStateStatus : null
2351
+ });
2352
+ }
2315
2353
  async function detectLocalMergeConflicts(cwd, baseRefName) {
2316
2354
  const baseName = (baseRefName?.trim() || await resolveDefaultBranch(cwd)).replace(
2317
2355
  /^origin\//,
@@ -2372,7 +2410,8 @@ async function getPrChecks(cwd, selector) {
2372
2410
  const mergeable = (gate?.mergeable ?? "").toUpperCase();
2373
2411
  const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
2374
2412
  const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
2375
- const needsLocalProbe = !alreadyConflicting;
2413
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
2414
+ const needsLocalProbe = !alreadyConflicting && !inQueue;
2376
2415
  if (needsLocalProbe) {
2377
2416
  try {
2378
2417
  const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
@@ -2383,7 +2422,8 @@ async function getPrChecks(cwd, selector) {
2383
2422
  mergeStateStatus: "DIRTY",
2384
2423
  reviewDecision: gate?.reviewDecision ?? null,
2385
2424
  baseRefName: local.base,
2386
- url: gate?.url ?? null
2425
+ url: gate?.url ?? null,
2426
+ isInMergeQueue: false
2387
2427
  };
2388
2428
  const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
2389
2429
  const review2 = (gate.reviewDecision ?? "").toUpperCase();
@@ -2427,10 +2467,21 @@ async function getPrMeta(cwd, selector) {
2427
2467
  "view",
2428
2468
  selector,
2429
2469
  "--json",
2430
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName"
2470
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
2431
2471
  ];
2432
2472
  if (slug) viewArgs.push("--repo", slug);
2433
- const { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
2473
+ let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
2474
+ if (exitCode !== 0 && isUnknownJsonField(stderr, "isInMergeQueue")) {
2475
+ const retry = [
2476
+ "pr",
2477
+ "view",
2478
+ selector,
2479
+ "--json",
2480
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
2481
+ ];
2482
+ if (slug) retry.push("--repo", slug);
2483
+ ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
2484
+ }
2434
2485
  if (exitCode !== 0 || !stdout.trim()) {
2435
2486
  if (/no pull requests found/i.test(stderr)) return null;
2436
2487
  return null;
@@ -2445,7 +2496,8 @@ async function getPrMeta(cwd, selector) {
2445
2496
  isDraft: Boolean(view.isDraft),
2446
2497
  reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
2447
2498
  baseRefName: String(view.baseRefName ?? ""),
2448
- headRefName: String(view.headRefName ?? "")
2499
+ headRefName: String(view.headRefName ?? ""),
2500
+ isInMergeQueue: parseInMergeQueue(view)
2449
2501
  };
2450
2502
  } catch {
2451
2503
  return null;
@@ -3050,6 +3102,208 @@ var init_worktree = __esm({
3050
3102
  }
3051
3103
  });
3052
3104
 
3105
+ // src/store/private-file.ts
3106
+ function writePrivateFile(path, contents) {
3107
+ (0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(path), { recursive: true });
3108
+ (0, import_node_fs6.writeFileSync)(path, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
3109
+ chmodOwnerOnly(path);
3110
+ }
3111
+ function chmodOwnerOnly(path) {
3112
+ try {
3113
+ (0, import_node_fs6.chmodSync)(path, PRIVATE_FILE_MODE);
3114
+ } catch {
3115
+ }
3116
+ }
3117
+ var import_node_fs6, import_node_path6, PRIVATE_FILE_MODE;
3118
+ var init_private_file = __esm({
3119
+ "src/store/private-file.ts"() {
3120
+ "use strict";
3121
+ import_node_fs6 = require("fs");
3122
+ import_node_path6 = require("path");
3123
+ PRIVATE_FILE_MODE = 384;
3124
+ }
3125
+ });
3126
+
3127
+ // src/store/secure-file.ts
3128
+ function persistVaultKeyInKeychain(key) {
3129
+ if (process.platform !== "darwin" || key.length !== 32) return;
3130
+ try {
3131
+ (0, import_node_child_process2.execFileSync)(
3132
+ "security",
3133
+ [
3134
+ "add-generic-password",
3135
+ "-s",
3136
+ KEYCHAIN_SERVICE,
3137
+ "-a",
3138
+ KEYCHAIN_ACCOUNT,
3139
+ "-w",
3140
+ key.toString("hex"),
3141
+ "-U"
3142
+ ],
3143
+ { encoding: "utf8", timeout: 8e3, stdio: ["ignore", "pipe", "pipe"] }
3144
+ );
3145
+ } catch {
3146
+ }
3147
+ }
3148
+ function hexKey(value) {
3149
+ const trimmed = value?.trim() ?? "";
3150
+ if (!/^[0-9a-f]{64}$/i.test(trimmed)) return null;
3151
+ return Buffer.from(trimmed, "hex");
3152
+ }
3153
+ function keychainKey(create) {
3154
+ if (process.platform !== "darwin") return null;
3155
+ try {
3156
+ const out = (0, import_node_child_process2.execFileSync)(
3157
+ "security",
3158
+ ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", KEYCHAIN_ACCOUNT, "-w"],
3159
+ { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "pipe"] }
3160
+ ).trim();
3161
+ const key2 = hexKey(out);
3162
+ if (key2) return key2;
3163
+ } catch {
3164
+ }
3165
+ if (!create) return null;
3166
+ const key = (0, import_node_crypto2.randomBytes)(32);
3167
+ persistVaultKeyInKeychain(key);
3168
+ return key;
3169
+ }
3170
+ function resolveVaultKey() {
3171
+ if (process.env.SIDEBOARD_SECRET_VAULT === "plain") return null;
3172
+ if (injectedKey) return injectedKey;
3173
+ const fromEnv = hexKey(process.env.SIDEBOARD_VAULT_KEY);
3174
+ if (fromEnv) return fromEnv;
3175
+ if (process.env.VITEST) return null;
3176
+ return keychainKey(true);
3177
+ }
3178
+ function isEnvelope(value) {
3179
+ if (!value || typeof value !== "object") return false;
3180
+ const o = value;
3181
+ return o.v === 1 && o.alg === ALG && typeof o.iv === "string" && typeof o.tag === "string" && typeof o.data === "string";
3182
+ }
3183
+ function encryptJson(value, key) {
3184
+ const iv = (0, import_node_crypto2.randomBytes)(12);
3185
+ const cipher = (0, import_node_crypto2.createCipheriv)(ALG, key, iv);
3186
+ const plaintext = Buffer.from(`${JSON.stringify(value)}
3187
+ `, "utf8");
3188
+ const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
3189
+ const tag = cipher.getAuthTag();
3190
+ return {
3191
+ v: 1,
3192
+ alg: ALG,
3193
+ iv: iv.toString("base64"),
3194
+ tag: tag.toString("base64"),
3195
+ data: data.toString("base64")
3196
+ };
3197
+ }
3198
+ function decryptEnvelope(envelope, key) {
3199
+ const decipher = (0, import_node_crypto2.createDecipheriv)(ALG, key, Buffer.from(envelope.iv, "base64"));
3200
+ decipher.setAuthTag(Buffer.from(envelope.tag, "base64"));
3201
+ const plaintext = Buffer.concat([
3202
+ decipher.update(Buffer.from(envelope.data, "base64")),
3203
+ decipher.final()
3204
+ ]);
3205
+ return JSON.parse(plaintext.toString("utf8"));
3206
+ }
3207
+ function readSecureJson(path) {
3208
+ if (!(0, import_node_fs7.existsSync)(path)) return null;
3209
+ chmodOwnerOnly(path);
3210
+ let parsed;
3211
+ try {
3212
+ parsed = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
3213
+ } catch {
3214
+ return null;
3215
+ }
3216
+ if (!isEnvelope(parsed)) return parsed;
3217
+ const key = resolveVaultKey();
3218
+ if (!key) {
3219
+ throw new Error(
3220
+ "Encrypted Sideboard secrets need a vault key (desktop Keychain / safeStorage, or SIDEBOARD_VAULT_KEY)."
3221
+ );
3222
+ }
3223
+ return decryptEnvelope(parsed, key);
3224
+ }
3225
+ function writeSecureJson(path, value) {
3226
+ const key = resolveVaultKey();
3227
+ const body = key ? encryptJson(value, key) : value;
3228
+ writePrivateFile(path, `${JSON.stringify(body, null, 2)}
3229
+ `);
3230
+ }
3231
+ function isSecureFileEncrypted(path) {
3232
+ if (!(0, import_node_fs7.existsSync)(path)) return false;
3233
+ try {
3234
+ return isEnvelope(JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8")));
3235
+ } catch {
3236
+ return false;
3237
+ }
3238
+ }
3239
+ var import_node_child_process2, import_node_crypto2, import_node_fs7, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, ALG, injectedKey;
3240
+ var init_secure_file = __esm({
3241
+ "src/store/secure-file.ts"() {
3242
+ "use strict";
3243
+ import_node_child_process2 = require("child_process");
3244
+ import_node_crypto2 = require("crypto");
3245
+ import_node_fs7 = require("fs");
3246
+ init_private_file();
3247
+ KEYCHAIN_SERVICE = "ai.sideboard.app";
3248
+ KEYCHAIN_ACCOUNT = "secret-vault-v1";
3249
+ ALG = "aes-256-gcm";
3250
+ injectedKey = null;
3251
+ }
3252
+ });
3253
+
3254
+ // src/store/secret-vault.ts
3255
+ function secretVaultPath() {
3256
+ return (0, import_node_path7.join)(appDataDir(), "secrets.json");
3257
+ }
3258
+ function loadSecretVault() {
3259
+ const parsed = readSecureJson(secretVaultPath());
3260
+ if (!parsed || typeof parsed !== "object") return {};
3261
+ return normalizeVault(parsed);
3262
+ }
3263
+ function saveSecretVault(vault) {
3264
+ writeSecureJson(secretVaultPath(), normalizeVault(vault));
3265
+ }
3266
+ function normalizeVault(raw) {
3267
+ const out = {};
3268
+ if (typeof raw.linearApiKey === "string" && raw.linearApiKey.trim()) {
3269
+ out.linearApiKey = raw.linearApiKey.trim();
3270
+ }
3271
+ if (typeof raw.linearAccessToken === "string" && raw.linearAccessToken.trim()) {
3272
+ out.linearAccessToken = raw.linearAccessToken.trim();
3273
+ }
3274
+ if (typeof raw.linearRefreshToken === "string" && raw.linearRefreshToken.trim()) {
3275
+ out.linearRefreshToken = raw.linearRefreshToken.trim();
3276
+ }
3277
+ if (typeof raw.linearClientSecret === "string" && raw.linearClientSecret.trim()) {
3278
+ out.linearClientSecret = raw.linearClientSecret.trim();
3279
+ }
3280
+ if (typeof raw.slackClientSecret === "string" && raw.slackClientSecret.trim()) {
3281
+ out.slackClientSecret = raw.slackClientSecret.trim();
3282
+ }
3283
+ if (typeof raw.slackAppToken === "string" && raw.slackAppToken.trim()) {
3284
+ out.slackAppToken = raw.slackAppToken.trim();
3285
+ }
3286
+ if (raw.environment && typeof raw.environment === "object") {
3287
+ const environment = {};
3288
+ for (const [key, value] of Object.entries(raw.environment)) {
3289
+ const k = key.trim();
3290
+ const v = typeof value === "string" ? value.trim() : "";
3291
+ if (k && v) environment[k] = v;
3292
+ }
3293
+ if (Object.keys(environment).length > 0) out.environment = environment;
3294
+ }
3295
+ return out;
3296
+ }
3297
+ var import_node_path7;
3298
+ var init_secret_vault = __esm({
3299
+ "src/store/secret-vault.ts"() {
3300
+ "use strict";
3301
+ import_node_path7 = require("path");
3302
+ init_paths();
3303
+ init_secure_file();
3304
+ }
3305
+ });
3306
+
3053
3307
  // src/store/app-settings.ts
3054
3308
  var app_settings_exports = {};
3055
3309
  __export(app_settings_exports, {
@@ -3065,10 +3319,13 @@ __export(app_settings_exports, {
3065
3319
  brightsyInjectWorktreeMcpEnabled: () => brightsyInjectWorktreeMcpEnabled,
3066
3320
  caffeinateWhileCloudConnectEnabled: () => caffeinateWhileCloudConnectEnabled,
3067
3321
  caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
3322
+ caffeinateWhileSlackListenEnabled: () => caffeinateWhileSlackListenEnabled,
3068
3323
  childEnvWithAppSettings: () => childEnvWithAppSettings,
3069
3324
  claudeChromeEnabled: () => claudeChromeEnabled,
3070
3325
  claudeUserSettingsPath: () => claudeUserSettingsPath,
3071
3326
  deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
3327
+ disconnectLinearConnection: () => disconnectLinearConnection,
3328
+ ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
3072
3329
  getDefaultAgent: () => getDefaultAgent,
3073
3330
  getDefaultEffort: () => getDefaultEffort,
3074
3331
  getDefaultFast: () => getDefaultFast,
@@ -3087,6 +3344,10 @@ __export(app_settings_exports, {
3087
3344
  resolveNewThreadOptions: () => resolveNewThreadOptions,
3088
3345
  resolveThreadDefaults: () => resolveThreadDefaults,
3089
3346
  saveAppSettings: () => saveAppSettings,
3347
+ saveLinearOAuth: () => saveLinearOAuth,
3348
+ slackAppLevelToken: () => slackAppLevelToken,
3349
+ slackListenEnabled: () => slackListenEnabled,
3350
+ toPublicAppSettings: () => toPublicAppSettings,
3090
3351
  updateAdvancedSettings: () => updateAdvancedSettings,
3091
3352
  updateAgentExecutable: () => updateAgentExecutable,
3092
3353
  updateAppEnvironment: () => updateAppEnvironment,
@@ -3097,11 +3358,45 @@ __export(app_settings_exports, {
3097
3358
  updateIntegrationsSettings: () => updateIntegrationsSettings,
3098
3359
  updateOpencodeSettings: () => updateOpencodeSettings
3099
3360
  });
3361
+ function hasLinearOAuth(integrations) {
3362
+ return Boolean(
3363
+ integrations.linearAccessToken?.trim() || integrations.linearRefreshToken?.trim()
3364
+ );
3365
+ }
3366
+ function hasLinearCredentials(integrations) {
3367
+ return Boolean(integrations.linearApiKey?.trim()) || hasLinearOAuth(integrations);
3368
+ }
3369
+ function toPublicAppSettings(settings) {
3370
+ const environment = {};
3371
+ for (const key of Object.keys(settings.environment)) {
3372
+ if (key.trim()) environment[key] = "";
3373
+ }
3374
+ const integrations = { ...settings.integrations };
3375
+ const slackClientSecret = integrations.slackClientSecret;
3376
+ const slackAppToken = integrations.slackAppToken;
3377
+ delete integrations.linearApiKey;
3378
+ delete integrations.linearAccessToken;
3379
+ delete integrations.linearRefreshToken;
3380
+ delete integrations.linearClientSecret;
3381
+ delete integrations.slackClientSecret;
3382
+ delete integrations.slackAppToken;
3383
+ return {
3384
+ ...settings,
3385
+ environment,
3386
+ integrations: {
3387
+ ...integrations,
3388
+ hasLinearApiKey: hasLinearCredentials(settings.integrations),
3389
+ hasLinearOAuth: hasLinearOAuth(settings.integrations),
3390
+ hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
3391
+ hasSlackAppToken: Boolean(slackAppToken?.trim())
3392
+ }
3393
+ };
3394
+ }
3100
3395
  function appSettingsPath() {
3101
- return (0, import_node_path6.join)(appDataDir(), "settings.json");
3396
+ return (0, import_node_path8.join)(appDataDir(), "settings.json");
3102
3397
  }
3103
3398
  function claudeUserSettingsPath() {
3104
- return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
3399
+ return (0, import_node_path8.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
3105
3400
  }
3106
3401
  function normalizeClaude(raw) {
3107
3402
  if (!raw || typeof raw !== "object") return {};
@@ -3153,9 +3448,62 @@ function normalizeIntegrations(raw) {
3153
3448
  const key = source.linearApiKey.trim();
3154
3449
  if (key) out.linearApiKey = key;
3155
3450
  }
3451
+ if (typeof source.linearAccessToken === "string") {
3452
+ const token = source.linearAccessToken.trim();
3453
+ if (token) out.linearAccessToken = token;
3454
+ }
3455
+ if (typeof source.linearRefreshToken === "string") {
3456
+ const token = source.linearRefreshToken.trim();
3457
+ if (token) out.linearRefreshToken = token;
3458
+ }
3459
+ if (typeof source.linearTokenExpiresAt === "number" && Number.isFinite(source.linearTokenExpiresAt)) {
3460
+ out.linearTokenExpiresAt = source.linearTokenExpiresAt;
3461
+ } else if (typeof source.linearTokenExpiresAt === "string") {
3462
+ const n = Number(source.linearTokenExpiresAt);
3463
+ if (Number.isFinite(n)) out.linearTokenExpiresAt = n;
3464
+ }
3465
+ if (typeof source.linearClientId === "string") {
3466
+ const id = source.linearClientId.trim();
3467
+ if (id) out.linearClientId = id;
3468
+ }
3469
+ if (typeof source.linearClientSecret === "string") {
3470
+ const secret = source.linearClientSecret.trim();
3471
+ if (secret) out.linearClientSecret = secret;
3472
+ }
3473
+ if (typeof source.linearViewerName === "string") {
3474
+ const name = source.linearViewerName.trim().slice(0, 120);
3475
+ if (name) out.linearViewerName = name;
3476
+ }
3477
+ if (typeof source.linearOrganizationName === "string") {
3478
+ const name = source.linearOrganizationName.trim().slice(0, 120);
3479
+ if (name) out.linearOrganizationName = name;
3480
+ }
3156
3481
  if (typeof source.issueSource === "string" && ISSUE_SOURCES.has(source.issueSource)) {
3157
3482
  out.issueSource = source.issueSource;
3158
3483
  }
3484
+ if (typeof source.slackClientId === "string") {
3485
+ const id = source.slackClientId.trim();
3486
+ if (id) out.slackClientId = id;
3487
+ }
3488
+ if (typeof source.slackClientSecret === "string") {
3489
+ const secret = source.slackClientSecret.trim();
3490
+ if (secret) out.slackClientSecret = secret;
3491
+ }
3492
+ if (typeof source.slackAppToken === "string") {
3493
+ const token = source.slackAppToken.trim();
3494
+ if (token) out.slackAppToken = token;
3495
+ }
3496
+ if (typeof source.slackListenEnabled === "boolean") {
3497
+ out.slackListenEnabled = source.slackListenEnabled;
3498
+ }
3499
+ if (typeof source.slackDeviceId === "string") {
3500
+ const id = source.slackDeviceId.trim();
3501
+ if (id) out.slackDeviceId = id;
3502
+ }
3503
+ if (typeof source.slackDeviceLabel === "string") {
3504
+ const label = source.slackDeviceLabel.trim().slice(0, 64);
3505
+ if (label) out.slackDeviceLabel = label;
3506
+ }
3159
3507
  return out;
3160
3508
  }
3161
3509
  function normalizeDefaults(raw) {
@@ -3190,8 +3538,10 @@ function normalizeAdvanced(raw) {
3190
3538
  if (typeof source.caffeinateWhileRunning === "boolean") {
3191
3539
  out.caffeinateWhileRunning = source.caffeinateWhileRunning;
3192
3540
  }
3193
- if (typeof source.caffeinateWhileCloudConnect === "boolean") {
3194
- out.caffeinateWhileCloudConnect = source.caffeinateWhileCloudConnect;
3541
+ if (typeof source.caffeinateWhileSlackListen === "boolean") {
3542
+ out.caffeinateWhileSlackListen = source.caffeinateWhileSlackListen;
3543
+ } else if (typeof source.caffeinateWhileCloudConnect === "boolean") {
3544
+ out.caffeinateWhileSlackListen = source.caffeinateWhileCloudConnect;
3195
3545
  }
3196
3546
  if (typeof source.deleteBranchOnPurge === "boolean") {
3197
3547
  out.deleteBranchOnPurge = source.deleteBranchOnPurge;
@@ -3280,23 +3630,84 @@ function normalizeSettings(raw) {
3280
3630
  advanced
3281
3631
  };
3282
3632
  }
3283
- function loadAppSettings() {
3633
+ function readSettingsFile() {
3284
3634
  const path = appSettingsPath();
3285
- if (!(0, import_node_fs6.existsSync)(path)) {
3286
- return { ...EMPTY_SETTINGS };
3287
- }
3635
+ if (!(0, import_node_fs8.existsSync)(path)) return { ...EMPTY_SETTINGS };
3288
3636
  try {
3289
- const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
3637
+ chmodOwnerOnly(path);
3638
+ const parsed = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
3290
3639
  return normalizeSettings(parsed);
3291
3640
  } catch {
3292
3641
  return { ...EMPTY_SETTINGS };
3293
3642
  }
3294
3643
  }
3644
+ function diskHoldsSecrets(disk) {
3645
+ return Boolean(
3646
+ disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || Object.keys(disk.environment).length > 0
3647
+ );
3648
+ }
3649
+ function mergeVault(disk) {
3650
+ const vault = loadSecretVault();
3651
+ const environment = { ...vault.environment ?? {}, ...disk.environment };
3652
+ const integrations = { ...disk.integrations };
3653
+ if (vault.linearApiKey && !integrations.linearApiKey) {
3654
+ integrations.linearApiKey = vault.linearApiKey;
3655
+ }
3656
+ if (vault.linearAccessToken && !integrations.linearAccessToken) {
3657
+ integrations.linearAccessToken = vault.linearAccessToken;
3658
+ }
3659
+ if (vault.linearRefreshToken && !integrations.linearRefreshToken) {
3660
+ integrations.linearRefreshToken = vault.linearRefreshToken;
3661
+ }
3662
+ if (vault.linearClientSecret && !integrations.linearClientSecret) {
3663
+ integrations.linearClientSecret = vault.linearClientSecret;
3664
+ }
3665
+ if (vault.slackClientSecret && !integrations.slackClientSecret) {
3666
+ integrations.slackClientSecret = vault.slackClientSecret;
3667
+ }
3668
+ if (vault.slackAppToken && !integrations.slackAppToken) {
3669
+ integrations.slackAppToken = vault.slackAppToken;
3670
+ }
3671
+ return { ...disk, environment, integrations };
3672
+ }
3673
+ function persistSplit(settings) {
3674
+ const integrations = { ...settings.integrations };
3675
+ const linearApiKey = integrations.linearApiKey;
3676
+ const linearAccessToken = integrations.linearAccessToken;
3677
+ const linearRefreshToken = integrations.linearRefreshToken;
3678
+ const linearClientSecret = integrations.linearClientSecret;
3679
+ const slackClientSecret = integrations.slackClientSecret;
3680
+ const slackAppToken = integrations.slackAppToken;
3681
+ delete integrations.linearApiKey;
3682
+ delete integrations.linearAccessToken;
3683
+ delete integrations.linearRefreshToken;
3684
+ delete integrations.linearClientSecret;
3685
+ delete integrations.slackClientSecret;
3686
+ delete integrations.slackAppToken;
3687
+ writePrivateFile(
3688
+ appSettingsPath(),
3689
+ `${JSON.stringify({ ...settings, environment: {}, integrations }, null, 2)}
3690
+ `
3691
+ );
3692
+ saveSecretVault({
3693
+ linearApiKey,
3694
+ linearAccessToken,
3695
+ linearRefreshToken,
3696
+ linearClientSecret,
3697
+ slackClientSecret,
3698
+ slackAppToken,
3699
+ environment: settings.environment
3700
+ });
3701
+ }
3702
+ function loadAppSettings() {
3703
+ const disk = readSettingsFile();
3704
+ const merged = mergeVault(disk);
3705
+ if (diskHoldsSecrets(disk)) persistSplit(merged);
3706
+ return merged;
3707
+ }
3295
3708
  function saveAppSettings(settings) {
3296
3709
  const normalized = normalizeSettings(settings);
3297
- (0, import_node_fs6.mkdirSync)(appDataDir(), { recursive: true });
3298
- (0, import_node_fs6.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
3299
- `, "utf8");
3710
+ persistSplit(normalized);
3300
3711
  return normalized;
3301
3712
  }
3302
3713
  function updateAppEnvironment(patch) {
@@ -3387,6 +3798,20 @@ function updateIntegrationsSettings(patch) {
3387
3798
  integrations.linearApiKey = patch.linearApiKey.trim();
3388
3799
  }
3389
3800
  }
3801
+ if ("linearClientId" in patch) {
3802
+ if (patch.linearClientId == null || patch.linearClientId.trim() === "") {
3803
+ delete integrations.linearClientId;
3804
+ } else {
3805
+ integrations.linearClientId = patch.linearClientId.trim();
3806
+ }
3807
+ }
3808
+ if ("linearClientSecret" in patch) {
3809
+ if (patch.linearClientSecret == null || patch.linearClientSecret.trim() === "") {
3810
+ delete integrations.linearClientSecret;
3811
+ } else {
3812
+ integrations.linearClientSecret = patch.linearClientSecret.trim();
3813
+ }
3814
+ }
3390
3815
  if ("issueSource" in patch) {
3391
3816
  if (patch.issueSource == null) {
3392
3817
  delete integrations.issueSource;
@@ -3394,6 +3819,48 @@ function updateIntegrationsSettings(patch) {
3394
3819
  integrations.issueSource = patch.issueSource;
3395
3820
  }
3396
3821
  }
3822
+ if ("slackClientId" in patch) {
3823
+ if (patch.slackClientId == null || patch.slackClientId.trim() === "") {
3824
+ delete integrations.slackClientId;
3825
+ } else {
3826
+ integrations.slackClientId = patch.slackClientId.trim();
3827
+ }
3828
+ }
3829
+ if ("slackClientSecret" in patch) {
3830
+ if (patch.slackClientSecret == null || patch.slackClientSecret.trim() === "") {
3831
+ delete integrations.slackClientSecret;
3832
+ } else {
3833
+ integrations.slackClientSecret = patch.slackClientSecret.trim();
3834
+ }
3835
+ }
3836
+ if ("slackAppToken" in patch) {
3837
+ if (patch.slackAppToken == null || patch.slackAppToken.trim() === "") {
3838
+ delete integrations.slackAppToken;
3839
+ } else {
3840
+ integrations.slackAppToken = patch.slackAppToken.trim();
3841
+ }
3842
+ }
3843
+ if ("slackListenEnabled" in patch) {
3844
+ if (patch.slackListenEnabled == null) {
3845
+ delete integrations.slackListenEnabled;
3846
+ } else {
3847
+ integrations.slackListenEnabled = Boolean(patch.slackListenEnabled);
3848
+ }
3849
+ }
3850
+ if ("slackDeviceId" in patch) {
3851
+ if (patch.slackDeviceId == null || patch.slackDeviceId.trim() === "") {
3852
+ delete integrations.slackDeviceId;
3853
+ } else {
3854
+ integrations.slackDeviceId = patch.slackDeviceId.trim();
3855
+ }
3856
+ }
3857
+ if ("slackDeviceLabel" in patch) {
3858
+ if (patch.slackDeviceLabel == null || patch.slackDeviceLabel.trim() === "") {
3859
+ delete integrations.slackDeviceLabel;
3860
+ } else {
3861
+ integrations.slackDeviceLabel = patch.slackDeviceLabel.trim().slice(0, 64);
3862
+ }
3863
+ }
3397
3864
  return saveAppSettings({ ...current, integrations });
3398
3865
  }
3399
3866
  function updateDefaultsSettings(patch) {
@@ -3467,7 +3934,36 @@ function resolveNewThreadOptions(overrides = {}, settings = loadAppSettings()) {
3467
3934
  };
3468
3935
  }
3469
3936
  function isLinearConnected(settings = loadAppSettings()) {
3470
- return Boolean(settings.integrations.linearApiKey?.trim());
3937
+ return hasLinearCredentials(settings.integrations);
3938
+ }
3939
+ function saveLinearOAuth(input) {
3940
+ const current = loadAppSettings();
3941
+ const integrations = { ...current.integrations };
3942
+ integrations.linearAccessToken = input.accessToken.trim();
3943
+ if (input.refreshToken?.trim()) {
3944
+ integrations.linearRefreshToken = input.refreshToken.trim();
3945
+ }
3946
+ if (typeof input.expiresIn === "number" && Number.isFinite(input.expiresIn)) {
3947
+ integrations.linearTokenExpiresAt = Date.now() + Math.max(0, input.expiresIn) * 1e3;
3948
+ }
3949
+ if (input.viewerName?.trim()) {
3950
+ integrations.linearViewerName = input.viewerName.trim().slice(0, 120);
3951
+ }
3952
+ if (input.organizationName?.trim()) {
3953
+ integrations.linearOrganizationName = input.organizationName.trim().slice(0, 120);
3954
+ }
3955
+ return saveAppSettings({ ...current, integrations });
3956
+ }
3957
+ function disconnectLinearConnection() {
3958
+ const current = loadAppSettings();
3959
+ const integrations = { ...current.integrations };
3960
+ delete integrations.linearApiKey;
3961
+ delete integrations.linearAccessToken;
3962
+ delete integrations.linearRefreshToken;
3963
+ delete integrations.linearTokenExpiresAt;
3964
+ delete integrations.linearViewerName;
3965
+ delete integrations.linearOrganizationName;
3966
+ return saveAppSettings({ ...current, integrations });
3471
3967
  }
3472
3968
  function getIssueSource(settings = loadAppSettings()) {
3473
3969
  return settings.integrations.issueSource ?? "github";
@@ -3484,6 +3980,33 @@ function getLinearApiKey(settings = loadAppSettings()) {
3484
3980
  function brightsyCloudConnectEnabled(settings = loadAppSettings()) {
3485
3981
  return Boolean(settings.brightsy.cloudConnectEnabled);
3486
3982
  }
3983
+ function slackListenEnabled(settings = loadAppSettings()) {
3984
+ return settings.integrations.slackListenEnabled === true;
3985
+ }
3986
+ function ensureSlackDeviceIdentity(settings = loadAppSettings()) {
3987
+ let deviceId = settings.integrations.slackDeviceId?.trim() ?? "";
3988
+ let deviceLabel = settings.integrations.slackDeviceLabel?.trim() ?? "";
3989
+ const patch = {};
3990
+ if (!deviceId) {
3991
+ deviceId = (0, import_node_crypto3.randomUUID)();
3992
+ patch.slackDeviceId = deviceId;
3993
+ }
3994
+ if (!deviceLabel) {
3995
+ try {
3996
+ deviceLabel = (0, import_node_os4.hostname)().split(".")[0]?.trim() || "This Mac";
3997
+ } catch {
3998
+ deviceLabel = "This Mac";
3999
+ }
4000
+ patch.slackDeviceLabel = deviceLabel;
4001
+ }
4002
+ if (Object.keys(patch).length > 0) {
4003
+ updateIntegrationsSettings(patch);
4004
+ }
4005
+ return { deviceId, deviceLabel };
4006
+ }
4007
+ function slackAppLevelToken(settings = loadAppSettings()) {
4008
+ return process.env.SIDEBOARD_SLACK_APP_TOKEN?.trim() || settings.integrations.slackAppToken?.trim() || "";
4009
+ }
3487
4010
  function brightsyInjectWorktreeMcpEnabled(settings = loadAppSettings()) {
3488
4011
  return Boolean(settings.brightsy.injectWorktreeMcp);
3489
4012
  }
@@ -3507,8 +4030,12 @@ function updateAdvancedSettings(patch) {
3507
4030
  if (typeof patch.caffeinateWhileRunning === "boolean") {
3508
4031
  advanced.caffeinateWhileRunning = patch.caffeinateWhileRunning;
3509
4032
  }
3510
- if (typeof patch.caffeinateWhileCloudConnect === "boolean") {
3511
- advanced.caffeinateWhileCloudConnect = patch.caffeinateWhileCloudConnect;
4033
+ if (typeof patch.caffeinateWhileSlackListen === "boolean") {
4034
+ advanced.caffeinateWhileSlackListen = patch.caffeinateWhileSlackListen;
4035
+ delete advanced.caffeinateWhileCloudConnect;
4036
+ } else if (typeof patch.caffeinateWhileCloudConnect === "boolean") {
4037
+ advanced.caffeinateWhileSlackListen = patch.caffeinateWhileCloudConnect;
4038
+ delete advanced.caffeinateWhileCloudConnect;
3512
4039
  }
3513
4040
  if (typeof patch.deleteBranchOnPurge === "boolean") {
3514
4041
  advanced.deleteBranchOnPurge = patch.deleteBranchOnPurge;
@@ -3551,8 +4078,11 @@ function autoRunAfterSetupEnabled(settings = loadAppSettings()) {
3551
4078
  function caffeinateWhileRunningEnabled(settings = loadAppSettings()) {
3552
4079
  return Boolean(settings.advanced.caffeinateWhileRunning);
3553
4080
  }
4081
+ function caffeinateWhileSlackListenEnabled(settings = loadAppSettings()) {
4082
+ return Boolean(settings.advanced.caffeinateWhileSlackListen);
4083
+ }
3554
4084
  function caffeinateWhileCloudConnectEnabled(settings = loadAppSettings()) {
3555
- return Boolean(settings.advanced.caffeinateWhileCloudConnect);
4085
+ return caffeinateWhileSlackListenEnabled(settings);
3556
4086
  }
3557
4087
  function deleteBranchOnPurgeEnabled(settings = loadAppSettings()) {
3558
4088
  return Boolean(settings.advanced.deleteBranchOnPurge);
@@ -3638,15 +4168,18 @@ function childEnvWithAppSettings(extra) {
3638
4168
  function harnessEnvKey(harness) {
3639
4169
  return HARNESS_ENV_KEYS[harness];
3640
4170
  }
3641
- var import_node_fs6, import_node_os4, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
4171
+ var import_node_crypto3, import_node_fs8, import_node_os4, import_node_path8, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
3642
4172
  var init_app_settings = __esm({
3643
4173
  "src/store/app-settings.ts"() {
3644
4174
  "use strict";
3645
- import_node_fs6 = require("fs");
4175
+ import_node_crypto3 = require("crypto");
4176
+ import_node_fs8 = require("fs");
3646
4177
  import_node_os4 = require("os");
3647
- import_node_path6 = require("path");
4178
+ import_node_path8 = require("path");
3648
4179
  init_thinking_effort();
3649
4180
  init_paths();
4181
+ init_private_file();
4182
+ init_secret_vault();
3650
4183
  HARNESS_ENV_KEYS = {
3651
4184
  claude: "ANTHROPIC_API_KEY",
3652
4185
  codex: "CODEX_API_KEY",
@@ -3749,6 +4282,7 @@ var init_orchestrator_capable = __esm({
3749
4282
  var coordinator_prompt_exports = {};
3750
4283
  __export(coordinator_prompt_exports, {
3751
4284
  COORDINATOR_TOOL_PLAYBOOK: () => COORDINATOR_TOOL_PLAYBOOK,
4285
+ SLACK_REPLY_FORMATTING: () => SLACK_REPLY_FORMATTING,
3752
4286
  coordinatorGreenfieldPlaybook: () => coordinatorGreenfieldPlaybook,
3753
4287
  coordinatorSystemPrompt: () => coordinatorSystemPrompt,
3754
4288
  coordinatorTurnReminder: () => coordinatorTurnReminder,
@@ -3803,13 +4337,14 @@ function coordinatorTurnReminder(opts) {
3803
4337
  `- 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.`,
3804
4338
  "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.",
3805
4339
  "- 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 draft PR.",
3806
- "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`."
4340
+ "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
4341
+ "- 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."
3807
4342
  ].filter(Boolean).join("\n");
3808
4343
  }
3809
4344
  function ensureGlobalCoordinatorCwd(opts) {
3810
4345
  const dir = globalAgentCwd();
3811
4346
  try {
3812
- (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
4347
+ (0, import_node_fs9.mkdirSync)(dir, { recursive: true });
3813
4348
  } catch {
3814
4349
  return dir;
3815
4350
  }
@@ -3817,7 +4352,7 @@ function ensureGlobalCoordinatorCwd(opts) {
3817
4352
  let orchId = opts?.orchestratorThreadId?.trim() || "";
3818
4353
  if (!orchId) {
3819
4354
  try {
3820
- const existing = (0, import_node_fs7.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
4355
+ const existing = (0, import_node_fs9.readFileSync)((0, import_node_path9.join)(dir, "AGENTS.md"), "utf8");
3821
4356
  const m = existing.match(
3822
4357
  /YOUR orchestration thread id is `([0-9a-f-]{36})`/i
3823
4358
  );
@@ -3859,9 +4394,9 @@ function ensureGlobalCoordinatorCwd(opts) {
3859
4394
  "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
3860
4395
  ].join("\n");
3861
4396
  try {
3862
- (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
4397
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(dir, "CLAUDE.md"), `${body}
3863
4398
  `, "utf8");
3864
- (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
4399
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(dir, "AGENTS.md"), `${body}
3865
4400
  `, "utf8");
3866
4401
  } catch {
3867
4402
  }
@@ -3871,8 +4406,14 @@ function coordinatorSystemPrompt(opts) {
3871
4406
  const audience = opts.audience ?? "cloud";
3872
4407
  const reposDir = sideboardReposDir();
3873
4408
  const intro = audience === "cloud" ? [
3874
- "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Slack, Discord, Teams, or other chat).",
4409
+ "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
3875
4410
  "Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
4411
+ ] : audience === "slack" ? [
4412
+ "You are a Sideboard coordinator responding to a Slack DM or @mention via the Sideboard Slack app.",
4413
+ "Your reply will be posted back in Slack \u2014 be concise and actionable.",
4414
+ "Sideboard signs the Slack reply with this Mac's destination name (Personal / Work). Do not prefix that name yourself.",
4415
+ "Call set_caffeinate enabled=true when they start a stretch of remote work so the Mac stays awake. Call set_caffeinate enabled=false when they say they are done, wrapping up, or going to sleep.",
4416
+ SLACK_REPLY_FORMATTING
3876
4417
  ] : [
3877
4418
  "You are a Sideboard orchestration agent: you oversee worktree agents across registered workspaces in the Sideboard app.",
3878
4419
  "Stay concise and actionable; prefer Sideboard MCP for fleet status; use Bash for greenfield repo setup and inspecting target worktree paths."
@@ -3894,12 +4435,12 @@ function coordinatorSystemPrompt(opts) {
3894
4435
  formatWorkspaceInventory(opts.workspaces)
3895
4436
  ].join("\n");
3896
4437
  }
3897
- var import_node_fs7, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
4438
+ var import_node_fs9, import_node_path9, COORDINATOR_TOOL_PLAYBOOK, SLACK_REPLY_FORMATTING;
3898
4439
  var init_coordinator_prompt = __esm({
3899
4440
  "src/orchestrator/coordinator-prompt.ts"() {
3900
4441
  "use strict";
3901
- import_node_fs7 = require("fs");
3902
- import_node_path7 = require("path");
4442
+ import_node_fs9 = require("fs");
4443
+ import_node_path9 = require("path");
3903
4444
  init_worktree();
3904
4445
  init_app_settings();
3905
4446
  init_paths();
@@ -3909,9 +4450,12 @@ var init_coordinator_prompt = __esm({
3909
4450
  "Discover:",
3910
4451
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
3911
4452
  "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
4453
+ "- list_teams / slack_list_channels / slack_list_users / slack_search / slack_read / slack_post \u2014 Slack workspaces from Account settings; pass team_id from list_teams",
4454
+ "- Slack notify (only when the user asks): list_teams \u2192 slack_list_users or slack_list_channels \u2192 slack_post with to=@user or #channel and optional github_url (PR, blob permalink, or review/issue comment). Do not notify proactively.",
3912
4455
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
3913
4456
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
3914
4457
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
4458
+ "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake.",
3915
4459
  "Workspaces:",
3916
4460
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
3917
4461
  "Worktree threads (chats):",
@@ -3933,6 +4477,13 @@ var init_coordinator_prompt = __esm({
3933
4477
  "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.",
3934
4478
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
3935
4479
  ].join("\n");
4480
+ SLACK_REPLY_FORMATTING = [
4481
+ "Slack formatting (mandatory \u2014 your reply is posted verbatim to Slack):",
4482
+ "- Use Slack mrkdwn, NOT GitHub/CommonMark markdown. Slack does not render `**bold**`, `# headings`, or `[label](url)`.",
4483
+ "- Bold: *text* (single asterisks). Italic: _text_. Strikethrough: ~text~. Inline code: `code`.",
4484
+ "- Links: <https://example.com|label> or bare URLs. Thread ids: plain `sideboard://thread/<id>` (no markdown link syntax).",
4485
+ "- Prefer short paragraphs and *bold* section labels over markdown headings or bullet trees with `**`."
4486
+ ].join("\n");
3936
4487
  }
3937
4488
  });
3938
4489
 
@@ -3942,15 +4493,18 @@ __export(global_workspace_exports, {
3942
4493
  GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
3943
4494
  createGlobalChat: () => createGlobalChat,
3944
4495
  ensureCloudCoordinator: () => ensureCloudCoordinator,
4496
+ ensureSlackCoordinator: () => ensureSlackCoordinator,
3945
4497
  globalAgentCwd: () => globalAgentCwd,
3946
4498
  healOrchestrationSoccerTitles: () => healOrchestrationSoccerTitles,
3947
4499
  isCloudCoordinatorThread: () => isCloudCoordinatorThread,
3948
4500
  isGlobalRepoPath: () => isGlobalRepoPath,
3949
4501
  isGlobalThread: () => isGlobalThread,
3950
4502
  isOrchestratorThread: () => isOrchestratorThread,
4503
+ isSlackCoordinatorThread: () => isSlackCoordinatorThread,
3951
4504
  listGlobalThreads: () => listGlobalThreads,
3952
4505
  orchestrationTitleNeedsSoccerNickname: () => orchestrationTitleNeedsSoccerNickname,
3953
4506
  orchestratorSessionPoisonedByBuiltins: () => orchestratorSessionPoisonedByBuiltins,
4507
+ slackCoordinatorSourceRef: () => slackCoordinatorSourceRef,
3954
4508
  takenTeamSlugsForOrchestration: () => takenTeamSlugsForOrchestration
3955
4509
  });
3956
4510
  function isGlobalThread(thread) {
@@ -4067,6 +4621,62 @@ function findCloudCoordinator() {
4067
4621
  (t) => t.status !== "archived" && isCloudCoordinatorThread(t)
4068
4622
  );
4069
4623
  }
4624
+ function slackCoordinatorSourceRef(teamId, userId) {
4625
+ const team = teamId.trim();
4626
+ const user = userId.trim();
4627
+ if (!team || !user) {
4628
+ throw new Error("Slack coordinator needs teamId and userId");
4629
+ }
4630
+ return `slack:${team}:${user}`;
4631
+ }
4632
+ function isSlackCoordinatorThread(thread) {
4633
+ const ref = thread.sourceRef?.trim() ?? "";
4634
+ if (!ref.startsWith("slack:")) return false;
4635
+ return isGlobalThread(thread) || thread.sourceType === "orchestration";
4636
+ }
4637
+ function findSlackCoordinator(teamId, userId) {
4638
+ const ref = slackCoordinatorSourceRef(teamId, userId);
4639
+ return listThreads({ includeArchived: true }).find(
4640
+ (t) => t.status !== "archived" && t.sourceRef === ref && isSlackCoordinatorThread(t)
4641
+ );
4642
+ }
4643
+ function ensureSlackCoordinator(teamId, userId, agent) {
4644
+ const defaults = resolveThreadDefaults();
4645
+ const desired = assertOrchestratorCapableAgent(agent);
4646
+ const ref = slackCoordinatorSourceRef(teamId, userId);
4647
+ const existing = findSlackCoordinator(teamId, userId);
4648
+ if (existing) {
4649
+ const patch = {};
4650
+ if (existing.repoPath !== GLOBAL_WORKSPACE_ID) {
4651
+ patch.repoPath = GLOBAL_WORKSPACE_ID;
4652
+ patch.worktreePath = globalAgentCwd();
4653
+ patch.branchName = "global";
4654
+ }
4655
+ const hasAgentTurns = existing.messages.some((m) => m.role === "agent");
4656
+ const canRetarget = !existing.sessionId && !hasAgentTurns && existing.status !== "running" && existing.status !== "queued";
4657
+ if (canRetarget) {
4658
+ if (existing.agent !== desired) patch.agent = desired;
4659
+ if (existing.model !== defaults.model) patch.model = defaults.model;
4660
+ if (existing.effort !== defaults.effort) patch.effort = defaults.effort;
4661
+ if (existing.fast !== defaults.fast) patch.fast = defaults.fast;
4662
+ }
4663
+ if (Object.keys(patch).length > 0) {
4664
+ return updateThread(existing.id, patch);
4665
+ }
4666
+ return existing;
4667
+ }
4668
+ const created = createGlobalChat({
4669
+ sourceRef: ref,
4670
+ agent: desired,
4671
+ model: defaults.model,
4672
+ effort: defaults.effort,
4673
+ fast: defaults.fast
4674
+ });
4675
+ const all = listThreads({ includeArchived: true }).filter(
4676
+ (t) => t.status !== "archived" && t.sourceRef === ref && isSlackCoordinatorThread(t)
4677
+ ).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
4678
+ return all[0] ?? created;
4679
+ }
4070
4680
  function ensureCloudCoordinator(agent) {
4071
4681
  const defaults = resolveThreadDefaults();
4072
4682
  const desired = assertOrchestratorCapableAgent(agent);
@@ -4120,32 +4730,32 @@ var init_global_workspace = __esm({
4120
4730
 
4121
4731
  // src/brightsy/config.ts
4122
4732
  function brightsyConfigPath() {
4123
- return (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".brightsy", "config.json");
4733
+ return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".brightsy", "config.json");
4124
4734
  }
4125
4735
  function loadBrightsyConfig() {
4126
4736
  const path = brightsyConfigPath();
4127
- if (!(0, import_node_fs8.existsSync)(path)) {
4737
+ if (!(0, import_node_fs10.existsSync)(path)) {
4128
4738
  throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
4129
4739
  }
4130
- const raw = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
4740
+ const raw = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
4131
4741
  if (!raw.access_token || !raw.account_id) {
4132
4742
  throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
4133
4743
  }
4134
4744
  return raw;
4135
4745
  }
4136
4746
  function saveBrightsyConfig(cfg) {
4137
- (0, import_node_fs8.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
4747
+ (0, import_node_fs10.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
4138
4748
  `, {
4139
4749
  mode: 384
4140
4750
  });
4141
4751
  }
4142
- var import_node_fs8, import_node_os5, import_node_path8;
4752
+ var import_node_fs10, import_node_os5, import_node_path10;
4143
4753
  var init_config = __esm({
4144
4754
  "src/brightsy/config.ts"() {
4145
4755
  "use strict";
4146
- import_node_fs8 = require("fs");
4756
+ import_node_fs10 = require("fs");
4147
4757
  import_node_os5 = require("os");
4148
- import_node_path8 = require("path");
4758
+ import_node_path10 = require("path");
4149
4759
  }
4150
4760
  });
4151
4761
 
@@ -4160,22 +4770,22 @@ var init_accounts = __esm({
4160
4770
 
4161
4771
  // src/brightsy/connected-teams.ts
4162
4772
  function storePath() {
4163
- return (0, import_node_path9.join)(appDataDir(), "brightsy-teams.json");
4773
+ return (0, import_node_path11.join)(appDataDir(), "brightsy-teams.json");
4164
4774
  }
4165
4775
  function readStore() {
4166
4776
  const path = storePath();
4167
- if (!(0, import_node_fs9.existsSync)(path)) return [];
4777
+ if (!(0, import_node_fs11.existsSync)(path)) return [];
4168
4778
  try {
4169
- const parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
4779
+ const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
4170
4780
  return Array.isArray(parsed.teams) ? parsed.teams : [];
4171
4781
  } catch {
4172
4782
  return [];
4173
4783
  }
4174
4784
  }
4175
4785
  function writeStore(teams) {
4176
- (0, import_node_fs9.mkdirSync)(appDataDir(), { recursive: true });
4786
+ (0, import_node_fs11.mkdirSync)(appDataDir(), { recursive: true });
4177
4787
  const path = storePath();
4178
- (0, import_node_fs9.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4788
+ (0, import_node_fs11.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4179
4789
  `, {
4180
4790
  mode: 384
4181
4791
  });
@@ -4287,12 +4897,12 @@ function brightsyMcpServerName(slug) {
4287
4897
  const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
4288
4898
  return `brightsy_${cleaned || "team"}`;
4289
4899
  }
4290
- var import_node_fs9, import_node_path9;
4900
+ var import_node_fs11, import_node_path11;
4291
4901
  var init_connected_teams = __esm({
4292
4902
  "src/brightsy/connected-teams.ts"() {
4293
4903
  "use strict";
4294
- import_node_fs9 = require("fs");
4295
- import_node_path9 = require("path");
4904
+ import_node_fs11 = require("fs");
4905
+ import_node_path11 = require("path");
4296
4906
  init_paths();
4297
4907
  init_accounts();
4298
4908
  init_config();
@@ -4578,11 +5188,11 @@ async function syncCliForTarget(accountId) {
4578
5188
  }
4579
5189
  applyConnectedTeamToCli(team);
4580
5190
  }
4581
- var import_node_fs10, brightsyAdapter;
5191
+ var import_node_fs12, brightsyAdapter;
4582
5192
  var init_brightsy = __esm({
4583
5193
  "src/agents/brightsy.ts"() {
4584
5194
  "use strict";
4585
- import_node_fs10 = require("fs");
5195
+ import_node_fs12 = require("fs");
4586
5196
  init_run();
4587
5197
  init_connected_teams();
4588
5198
  init_config();
@@ -4596,7 +5206,7 @@ var init_brightsy = __esm({
4596
5206
  async detect() {
4597
5207
  const brightsy = resolveAgentExecutable("brightsy");
4598
5208
  if (brightsy !== "brightsy") {
4599
- if (!(0, import_node_fs10.existsSync)(brightsy)) {
5209
+ if (!(0, import_node_fs12.existsSync)(brightsy)) {
4600
5210
  return {
4601
5211
  agent: "brightsy",
4602
5212
  installed: false,
@@ -4672,10 +5282,10 @@ var init_brightsy = __esm({
4672
5282
  });
4673
5283
 
4674
5284
  // src/agents/claude-mcp.ts
4675
- function parseMcpList(text) {
5285
+ function parseMcpList(text2) {
4676
5286
  const servers = [];
4677
5287
  const seen = /* @__PURE__ */ new Set();
4678
- for (const raw of text.split("\n")) {
5288
+ for (const raw of text2.split("\n")) {
4679
5289
  const line = raw.trim();
4680
5290
  if (!line || /^Checking MCP/i.test(line)) continue;
4681
5291
  const m = line.match(/^(.+?):\s+\S+/);
@@ -4769,8 +5379,8 @@ function isBrightsyConnected() {
4769
5379
  return false;
4770
5380
  }
4771
5381
  }
4772
- function promptMentionsBrightsy(text) {
4773
- return BRIGHTSY_WORD.test(text ?? "");
5382
+ function promptMentionsBrightsy(text2) {
5383
+ return BRIGHTSY_WORD.test(text2 ?? "");
4774
5384
  }
4775
5385
  function isBrightsyMcpToolName(name) {
4776
5386
  const n = name.toLowerCase();
@@ -4833,35 +5443,35 @@ function corePackageDir() {
4833
5443
  try {
4834
5444
  const url = import_meta.url;
4835
5445
  if (typeof url === "string" && url.length > 0) {
4836
- return (0, import_node_path10.dirname)((0, import_node_url.fileURLToPath)(url));
5446
+ return (0, import_node_path12.dirname)((0, import_node_url.fileURLToPath)(url));
4837
5447
  }
4838
5448
  } catch {
4839
5449
  }
4840
5450
  try {
4841
- const req = (0, import_node_module.createRequire)((0, import_node_path10.join)(process.cwd(), "package.json"));
4842
- return (0, import_node_path10.dirname)(req.resolve("@sideboard-ai/core"));
5451
+ const req = (0, import_node_module.createRequire)((0, import_node_path12.join)(process.cwd(), "package.json"));
5452
+ return (0, import_node_path12.dirname)(req.resolve("@sideboard-ai/core"));
4843
5453
  } catch {
4844
5454
  return process.cwd();
4845
5455
  }
4846
5456
  }
4847
5457
  function findSideboardMcpJsEntry() {
4848
5458
  const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
4849
- if (override && (0, import_node_fs11.existsSync)(override)) return override;
5459
+ if (override && (0, import_node_fs13.existsSync)(override)) return override;
4850
5460
  let dir = corePackageDir();
4851
5461
  for (let i = 0; i < 10; i++) {
4852
5462
  const candidates = [
4853
- (0, import_node_path10.join)(dir, "mcp/run-stdio.js"),
4854
- (0, import_node_path10.join)(dir, "mcp/run-stdio.cjs"),
4855
- (0, import_node_path10.join)(dir, "dist/mcp/run-stdio.js"),
4856
- (0, import_node_path10.join)(dir, "dist/mcp/run-stdio.cjs"),
4857
- (0, import_node_path10.join)(dir, "packages/core/dist/mcp/run-stdio.js"),
4858
- (0, import_node_path10.join)(dir, "packages/cli/dist/index.js"),
4859
- (0, import_node_path10.join)(dir, "cli/dist/index.js")
5463
+ (0, import_node_path12.join)(dir, "mcp/run-stdio.js"),
5464
+ (0, import_node_path12.join)(dir, "mcp/run-stdio.cjs"),
5465
+ (0, import_node_path12.join)(dir, "dist/mcp/run-stdio.js"),
5466
+ (0, import_node_path12.join)(dir, "dist/mcp/run-stdio.cjs"),
5467
+ (0, import_node_path12.join)(dir, "packages/core/dist/mcp/run-stdio.js"),
5468
+ (0, import_node_path12.join)(dir, "packages/cli/dist/index.js"),
5469
+ (0, import_node_path12.join)(dir, "cli/dist/index.js")
4860
5470
  ];
4861
5471
  for (const p of candidates) {
4862
- if ((0, import_node_fs11.existsSync)(p)) return p;
5472
+ if ((0, import_node_fs13.existsSync)(p)) return p;
4863
5473
  }
4864
- const parent = (0, import_node_path10.dirname)(dir);
5474
+ const parent = (0, import_node_path12.dirname)(dir);
4865
5475
  if (parent === dir) break;
4866
5476
  dir = parent;
4867
5477
  }
@@ -4971,19 +5581,19 @@ function writeMcpServersConfig(servers) {
4971
5581
  ...s.env ? { env: s.env } : {}
4972
5582
  };
4973
5583
  }
4974
- const dir = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4975
- const cfgPath = (0, import_node_path10.join)(dir, "mcp.json");
4976
- (0, import_node_fs11.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
5584
+ const dir = (0, import_node_fs13.mkdtempSync)((0, import_node_path12.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
5585
+ const cfgPath = (0, import_node_path12.join)(dir, "mcp.json");
5586
+ (0, import_node_fs13.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4977
5587
  return cfgPath;
4978
5588
  }
4979
- var import_node_fs11, import_node_module, import_node_os6, import_node_path10, import_node_url, import_meta, SIDEBOARD_MCP_ALLOWED_TOOLS, SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS, brightsyMcpCommandCache, BRIGHTSY_WORD;
5589
+ var import_node_fs13, import_node_module, import_node_os6, import_node_path12, import_node_url, import_meta, SIDEBOARD_MCP_ALLOWED_TOOLS, SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS, brightsyMcpCommandCache, BRIGHTSY_WORD;
4980
5590
  var init_injected_mcp = __esm({
4981
5591
  "src/agents/injected-mcp.ts"() {
4982
5592
  "use strict";
4983
- import_node_fs11 = require("fs");
5593
+ import_node_fs13 = require("fs");
4984
5594
  import_node_module = require("module");
4985
5595
  import_node_os6 = require("os");
4986
- import_node_path10 = require("path");
5596
+ import_node_path12 = require("path");
4987
5597
  import_node_url = require("url");
4988
5598
  init_run();
4989
5599
  init_config();
@@ -5002,7 +5612,13 @@ var init_injected_mcp = __esm({
5002
5612
  "mcp__sideboard__present_schema",
5003
5613
  "mcp__sideboard__present_files",
5004
5614
  "mcp__sideboard__ask_user",
5005
- "mcp__sideboard__present_plan"
5615
+ "mcp__sideboard__present_plan",
5616
+ "mcp__sideboard__list_teams",
5617
+ "mcp__sideboard__slack_list_channels",
5618
+ "mcp__sideboard__slack_list_users",
5619
+ "mcp__sideboard__slack_search",
5620
+ "mcp__sideboard__slack_read",
5621
+ "mcp__sideboard__slack_post"
5006
5622
  ];
5007
5623
  brightsyMcpCommandCache = null;
5008
5624
  BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
@@ -5136,9 +5752,9 @@ function eventsFromContentBlocks(blocks) {
5136
5752
  return out;
5137
5753
  }
5138
5754
  function parseIssuesJson(raw) {
5139
- const text = raw.trim();
5140
- const candidates = [text];
5141
- const match = text.match(/\[[\s\S]*\]/);
5755
+ const text2 = raw.trim();
5756
+ const candidates = [text2];
5757
+ const match = text2.match(/\[[\s\S]*\]/);
5142
5758
  if (match) candidates.push(match[0]);
5143
5759
  for (const c of candidates) {
5144
5760
  try {
@@ -5160,11 +5776,11 @@ function parseIssuesJson(raw) {
5160
5776
  }
5161
5777
  return [];
5162
5778
  }
5163
- var import_node_fs12, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
5779
+ var import_node_fs14, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
5164
5780
  var init_claude = __esm({
5165
5781
  "src/agents/claude.ts"() {
5166
5782
  "use strict";
5167
- import_node_fs12 = require("fs");
5783
+ import_node_fs14 = require("fs");
5168
5784
  init_run();
5169
5785
  init_app_settings();
5170
5786
  init_claude_mcp();
@@ -5193,7 +5809,7 @@ var init_claude = __esm({
5193
5809
  async detect() {
5194
5810
  const claude = resolveClaudeExecutable();
5195
5811
  if (claude !== "claude") {
5196
- if (!(0, import_node_fs12.existsSync)(claude)) {
5812
+ if (!(0, import_node_fs14.existsSync)(claude)) {
5197
5813
  return {
5198
5814
  agent: "claude",
5199
5815
  installed: false,
@@ -5367,8 +5983,8 @@ var init_claude = __esm({
5367
5983
  if (errorDetail) {
5368
5984
  events.push({ type: "stderr", data: errorDetail });
5369
5985
  } else {
5370
- const text = obj.result;
5371
- if (typeof text === "string" && text) events.push({ type: "stdout", data: text });
5986
+ const text2 = obj.result;
5987
+ if (typeof text2 === "string" && text2) events.push({ type: "stdout", data: text2 });
5372
5988
  }
5373
5989
  const usage = usageFromClaude(obj.usage);
5374
5990
  if (usage) events.push({ type: "usage", data: usage });
@@ -5426,7 +6042,7 @@ async function listCodexModels() {
5426
6042
  if (codex === "codex") {
5427
6043
  const which = await run("which", ["codex"], { reject: false });
5428
6044
  if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
5429
- } else if (!(0, import_node_fs13.existsSync)(codex)) {
6045
+ } else if (!(0, import_node_fs15.existsSync)(codex)) {
5430
6046
  return FALLBACK_CODEX_MODELS;
5431
6047
  }
5432
6048
  const listed = await run(codex, ["debug", "models"], { reject: false });
@@ -5464,13 +6080,13 @@ function usageFromCodex(usage) {
5464
6080
  }
5465
6081
  function codexConfigHasNetworkAccess() {
5466
6082
  const candidates = [
5467
- (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".codex", "config.toml"),
5468
- (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".config", "codex", "config.toml")
6083
+ (0, import_node_path13.join)((0, import_node_os7.homedir)(), ".codex", "config.toml"),
6084
+ (0, import_node_path13.join)((0, import_node_os7.homedir)(), ".config", "codex", "config.toml")
5469
6085
  ];
5470
6086
  for (const path of candidates) {
5471
- if (!(0, import_node_fs13.existsSync)(path)) continue;
5472
- const text = (0, import_node_fs13.readFileSync)(path, "utf8");
5473
- if (/network_access\s*=\s*true/.test(text)) return true;
6087
+ if (!(0, import_node_fs15.existsSync)(path)) continue;
6088
+ const text2 = (0, import_node_fs15.readFileSync)(path, "utf8");
6089
+ if (/network_access\s*=\s*true/.test(text2)) return true;
5474
6090
  }
5475
6091
  return false;
5476
6092
  }
@@ -5483,8 +6099,8 @@ function unwrapCodexMcpResult(result) {
5483
6099
  const texts = [];
5484
6100
  for (const item of rec.content) {
5485
6101
  if (!item || typeof item !== "object") continue;
5486
- const text = item.text;
5487
- if (typeof text === "string") texts.push(text);
6102
+ const text2 = item.text;
6103
+ if (typeof text2 === "string") texts.push(text2);
5488
6104
  }
5489
6105
  if (texts.length) return texts.join("\n");
5490
6106
  }
@@ -5501,21 +6117,21 @@ function asRecord(value) {
5501
6117
  return void 0;
5502
6118
  }
5503
6119
  function codexLooksAuthenticated() {
5504
- const authPath = (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".codex", "auth.json");
5505
- if (!(0, import_node_fs13.existsSync)(authPath)) return false;
6120
+ const authPath = (0, import_node_path13.join)((0, import_node_os7.homedir)(), ".codex", "auth.json");
6121
+ if (!(0, import_node_fs15.existsSync)(authPath)) return false;
5506
6122
  try {
5507
- return (0, import_node_fs13.statSync)(authPath).size > 2;
6123
+ return (0, import_node_fs15.statSync)(authPath).size > 2;
5508
6124
  } catch {
5509
6125
  return false;
5510
6126
  }
5511
6127
  }
5512
- var import_node_fs13, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
6128
+ var import_node_fs15, import_node_os7, import_node_path13, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5513
6129
  var init_codex = __esm({
5514
6130
  "src/agents/codex.ts"() {
5515
6131
  "use strict";
5516
- import_node_fs13 = require("fs");
6132
+ import_node_fs15 = require("fs");
5517
6133
  import_node_os7 = require("os");
5518
- import_node_path11 = require("path");
6134
+ import_node_path13 = require("path");
5519
6135
  init_run();
5520
6136
  init_app_settings();
5521
6137
  init_global_workspace();
@@ -5538,7 +6154,7 @@ var init_codex = __esm({
5538
6154
  async detect() {
5539
6155
  const codex = resolveAgentExecutable("codex");
5540
6156
  if (codex !== "codex") {
5541
- if (!(0, import_node_fs13.existsSync)(codex)) {
6157
+ if (!(0, import_node_fs15.existsSync)(codex)) {
5542
6158
  return {
5543
6159
  agent: "codex",
5544
6160
  installed: false,
@@ -5970,11 +6586,11 @@ function entryDir() {
5970
6586
  const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
5971
6587
  if (cjsDir) return cjsDir;
5972
6588
  try {
5973
- return (0, import_node_path12.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
6589
+ return (0, import_node_path14.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
5974
6590
  } catch {
5975
6591
  try {
5976
6592
  const req = (0, import_node_module2.createRequire)(process.cwd() + "/");
5977
- return (0, import_node_path12.dirname)(req.resolve("@sideboard-ai/core"));
6593
+ return (0, import_node_path14.dirname)(req.resolve("@sideboard-ai/core"));
5978
6594
  } catch {
5979
6595
  return process.cwd();
5980
6596
  }
@@ -5983,27 +6599,27 @@ function entryDir() {
5983
6599
  function cursorRunnerPath() {
5984
6600
  const root = entryDir();
5985
6601
  const candidates = [
5986
- (0, import_node_path12.join)(root, "agents", "cursor-runner.js"),
5987
- (0, import_node_path12.join)(root, "agents", "cursor-runner.cjs"),
6602
+ (0, import_node_path14.join)(root, "agents", "cursor-runner.js"),
6603
+ (0, import_node_path14.join)(root, "agents", "cursor-runner.cjs"),
5988
6604
  // If somehow resolved from package root instead of dist/
5989
- (0, import_node_path12.join)(root, "dist", "agents", "cursor-runner.js"),
5990
- (0, import_node_path12.join)(root, "dist", "agents", "cursor-runner.cjs"),
6605
+ (0, import_node_path14.join)(root, "dist", "agents", "cursor-runner.js"),
6606
+ (0, import_node_path14.join)(root, "dist", "agents", "cursor-runner.cjs"),
5991
6607
  // Source tree (dev): packages/core/src/agents/cursor-runner.ts
5992
- (0, import_node_path12.join)(root, "cursor-runner.ts"),
5993
- (0, import_node_path12.join)(root, "src", "agents", "cursor-runner.ts")
6608
+ (0, import_node_path14.join)(root, "cursor-runner.ts"),
6609
+ (0, import_node_path14.join)(root, "src", "agents", "cursor-runner.ts")
5994
6610
  ];
5995
6611
  for (const candidate of candidates) {
5996
- if ((0, import_node_fs14.existsSync)(candidate)) return candidate;
6612
+ if ((0, import_node_fs16.existsSync)(candidate)) return candidate;
5997
6613
  }
5998
6614
  return candidates[0];
5999
6615
  }
6000
- var import_node_fs14, import_node_module2, import_node_path12, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
6616
+ var import_node_fs16, import_node_module2, import_node_path14, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
6001
6617
  var init_cursor = __esm({
6002
6618
  "src/agents/cursor.ts"() {
6003
6619
  "use strict";
6004
- import_node_fs14 = require("fs");
6620
+ import_node_fs16 = require("fs");
6005
6621
  import_node_module2 = require("module");
6006
- import_node_path12 = require("path");
6622
+ import_node_path14 = require("path");
6007
6623
  import_node_url2 = require("url");
6008
6624
  import_sdk = require("@cursor/sdk");
6009
6625
  init_run();
@@ -6129,7 +6745,7 @@ async function listOpencodeModels() {
6129
6745
  if (opencode === "opencode") {
6130
6746
  const which = await run("which", ["opencode"], { reject: false });
6131
6747
  if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
6132
- } else if (!(0, import_node_fs15.existsSync)(opencode)) {
6748
+ } else if (!(0, import_node_fs17.existsSync)(opencode)) {
6133
6749
  return FALLBACK_OPENCODE_MODELS;
6134
6750
  }
6135
6751
  const listed = await run(opencode, ["models"], { reject: false });
@@ -6159,11 +6775,11 @@ function usageFromOpencode(tokens) {
6159
6775
  cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
6160
6776
  };
6161
6777
  }
6162
- var import_node_fs15, FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
6778
+ var import_node_fs17, FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
6163
6779
  var init_opencode = __esm({
6164
6780
  "src/agents/opencode.ts"() {
6165
6781
  "use strict";
6166
- import_node_fs15 = require("fs");
6782
+ import_node_fs17 = require("fs");
6167
6783
  init_run();
6168
6784
  init_app_settings();
6169
6785
  init_global_workspace();
@@ -6189,7 +6805,7 @@ var init_opencode = __esm({
6189
6805
  async detect() {
6190
6806
  const opencode = resolveAgentExecutable("opencode");
6191
6807
  if (opencode !== "opencode") {
6192
- if (!(0, import_node_fs15.existsSync)(opencode)) {
6808
+ if (!(0, import_node_fs17.existsSync)(opencode)) {
6193
6809
  return {
6194
6810
  agent: "opencode",
6195
6811
  installed: false,
@@ -6279,8 +6895,8 @@ var init_opencode = __esm({
6279
6895
  return { type: "session_id", data: sid };
6280
6896
  }
6281
6897
  if (obj.type === "text") {
6282
- const text = obj.part?.text ?? obj.text;
6283
- if (text) return { type: "stdout", data: text };
6898
+ const text2 = obj.part?.text ?? obj.text;
6899
+ if (text2) return { type: "stdout", data: text2 };
6284
6900
  }
6285
6901
  if (obj.type === "tool_use") {
6286
6902
  const part = obj.part;
@@ -6493,8 +7109,8 @@ var init_list_models = __esm({
6493
7109
  });
6494
7110
 
6495
7111
  // src/agents/session-quota.ts
6496
- function isSessionQuotaLimit(text) {
6497
- const lower = text.trim().toLowerCase();
7112
+ function isSessionQuotaLimit(text2) {
7113
+ const lower = text2.trim().toLowerCase();
6498
7114
  if (!lower) return false;
6499
7115
  if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
6500
7116
  return false;
@@ -6502,10 +7118,10 @@ function isSessionQuotaLimit(text) {
6502
7118
  if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
6503
7119
  return false;
6504
7120
  }
6505
- return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(text);
7121
+ return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(text2);
6506
7122
  }
6507
- function parseSessionQuotaResetAt(text, now = /* @__PURE__ */ new Date()) {
6508
- const absolute = text.match(
7123
+ function parseSessionQuotaResetAt(text2, now = /* @__PURE__ */ new Date()) {
7124
+ const absolute = text2.match(
6509
7125
  /resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
6510
7126
  );
6511
7127
  if (absolute) {
@@ -6523,7 +7139,7 @@ function parseSessionQuotaResetAt(text, now = /* @__PURE__ */ new Date()) {
6523
7139
  }
6524
7140
  return at;
6525
7141
  }
6526
- const relative = text.match(
7142
+ const relative = text2.match(
6527
7143
  /resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
6528
7144
  );
6529
7145
  if (relative) {
@@ -6926,38 +7542,38 @@ __export(workspaces_exports, {
6926
7542
  syncWorkspacesFromThreads: () => syncWorkspacesFromThreads
6927
7543
  });
6928
7544
  function workspacesFile() {
6929
- return (0, import_node_path16.join)(appDataDir(), "workspaces.json");
7545
+ return (0, import_node_path18.join)(appDataDir(), "workspaces.json");
6930
7546
  }
6931
7547
  function removedWorkspacesFile() {
6932
- return (0, import_node_path16.join)(appDataDir(), "removed-workspaces.json");
7548
+ return (0, import_node_path18.join)(appDataDir(), "removed-workspaces.json");
6933
7549
  }
6934
7550
  function readAll() {
6935
7551
  const path = workspacesFile();
6936
- if (!(0, import_node_fs19.existsSync)(path)) return [];
7552
+ if (!(0, import_node_fs21.existsSync)(path)) return [];
6937
7553
  try {
6938
- const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
7554
+ const raw = JSON.parse((0, import_node_fs21.readFileSync)(path, "utf8"));
6939
7555
  return Array.isArray(raw) ? raw : [];
6940
7556
  } catch {
6941
7557
  return [];
6942
7558
  }
6943
7559
  }
6944
7560
  function writeAll(list) {
6945
- (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6946
- (0, import_node_fs19.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
7561
+ (0, import_node_fs21.mkdirSync)(appDataDir(), { recursive: true });
7562
+ (0, import_node_fs21.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
6947
7563
  }
6948
7564
  function readRemoved() {
6949
7565
  const path = removedWorkspacesFile();
6950
- if (!(0, import_node_fs19.existsSync)(path)) return /* @__PURE__ */ new Set();
7566
+ if (!(0, import_node_fs21.existsSync)(path)) return /* @__PURE__ */ new Set();
6951
7567
  try {
6952
- const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
7568
+ const raw = JSON.parse((0, import_node_fs21.readFileSync)(path, "utf8"));
6953
7569
  return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
6954
7570
  } catch {
6955
7571
  return /* @__PURE__ */ new Set();
6956
7572
  }
6957
7573
  }
6958
7574
  function writeRemoved(paths) {
6959
- (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6960
- (0, import_node_fs19.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
7575
+ (0, import_node_fs21.mkdirSync)(appDataDir(), { recursive: true });
7576
+ (0, import_node_fs21.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
6961
7577
  }
6962
7578
  function rememberRemoved(repoPath) {
6963
7579
  const next = readRemoved();
@@ -6980,7 +7596,7 @@ function listWorkspaces() {
6980
7596
  async function addWorkspace(repoPath) {
6981
7597
  const root = await resolveRepoRoot(repoPath);
6982
7598
  if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
6983
- if (!(0, import_node_fs19.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
7599
+ if (!(0, import_node_fs21.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
6984
7600
  forgetRemoved(root);
6985
7601
  await ensureGhPreferOrigin(root);
6986
7602
  const current = readAll();
@@ -6988,7 +7604,7 @@ async function addWorkspace(repoPath) {
6988
7604
  if (existing) return existing;
6989
7605
  const next = {
6990
7606
  path: root,
6991
- name: (0, import_node_path16.basename)(root),
7607
+ name: (0, import_node_path18.basename)(root),
6992
7608
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
6993
7609
  };
6994
7610
  writeAll([...current, next]);
@@ -7010,10 +7626,10 @@ function syncWorkspacesFromThreads(repoPaths) {
7010
7626
  if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
7011
7627
  continue;
7012
7628
  }
7013
- if (!(0, import_node_fs19.existsSync)(path)) continue;
7629
+ if (!(0, import_node_fs21.existsSync)(path)) continue;
7014
7630
  const ws = {
7015
7631
  path,
7016
- name: (0, import_node_path16.basename)(path),
7632
+ name: (0, import_node_path18.basename)(path),
7017
7633
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
7018
7634
  };
7019
7635
  byPath.set(path, ws);
@@ -7023,12 +7639,12 @@ function syncWorkspacesFromThreads(repoPaths) {
7023
7639
  if (dirty) writeAll(next);
7024
7640
  return next.sort((a, b) => a.name.localeCompare(b.name));
7025
7641
  }
7026
- var import_node_fs19, import_node_path16;
7642
+ var import_node_fs21, import_node_path18;
7027
7643
  var init_workspaces = __esm({
7028
7644
  "src/store/workspaces.ts"() {
7029
7645
  "use strict";
7030
- import_node_fs19 = require("fs");
7031
- import_node_path16 = require("path");
7646
+ import_node_fs21 = require("fs");
7647
+ import_node_path18 = require("path");
7032
7648
  init_paths();
7033
7649
  init_global_workspace();
7034
7650
  init_worktree();
@@ -7069,11 +7685,11 @@ function resolvePlanMarkdown(opts) {
7069
7685
  source: "exit_plan"
7070
7686
  };
7071
7687
  }
7072
- const text = opts.text?.trim();
7073
- if (text && text.length >= 80) {
7688
+ const text2 = opts.text?.trim();
7689
+ if (text2 && text2.length >= 80) {
7074
7690
  return {
7075
7691
  title: "Plan",
7076
- content: text,
7692
+ content: text2,
7077
7693
  path: PLAN_FILE_REL,
7078
7694
  source: "text"
7079
7695
  };
@@ -7105,40 +7721,40 @@ __export(plan_file_exports, {
7105
7721
  writePlanFile: () => writePlanFile
7106
7722
  });
7107
7723
  function ensureAttachmentsGitignore2(worktreePath) {
7108
- const gitignoreAbs = (0, import_node_path24.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
7109
- if ((0, import_node_fs29.existsSync)(gitignoreAbs)) return;
7110
- (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
7111
- (0, import_node_fs29.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
7724
+ const gitignoreAbs = (0, import_node_path26.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
7725
+ if ((0, import_node_fs31.existsSync)(gitignoreAbs)) return;
7726
+ (0, import_node_fs31.mkdirSync)((0, import_node_path26.dirname)(gitignoreAbs), { recursive: true });
7727
+ (0, import_node_fs31.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
7112
7728
  }
7113
7729
  function planFileAbs(worktreePath) {
7114
- return (0, import_node_path24.join)(worktreePath, PLAN_FILE_REL);
7730
+ return (0, import_node_path26.join)(worktreePath, PLAN_FILE_REL);
7115
7731
  }
7116
7732
  function readTextIfPresent2(abs) {
7117
- if (!(0, import_node_fs29.existsSync)(abs)) return null;
7733
+ if (!(0, import_node_fs31.existsSync)(abs)) return null;
7118
7734
  try {
7119
- const content = (0, import_node_fs29.readFileSync)(abs, "utf8");
7735
+ const content = (0, import_node_fs31.readFileSync)(abs, "utf8");
7120
7736
  return content.trim() ? content : null;
7121
7737
  } catch {
7122
7738
  return null;
7123
7739
  }
7124
7740
  }
7125
7741
  function readPlanFile(worktreePath) {
7126
- return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path24.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path24.join)(worktreePath, LEGACY_PLAN_FILE_REL));
7742
+ return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path26.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path26.join)(worktreePath, LEGACY_PLAN_FILE_REL));
7127
7743
  }
7128
7744
  function writePlanFile(worktreePath, content) {
7129
7745
  ensureAttachmentsGitignore2(worktreePath);
7130
7746
  const abs = planFileAbs(worktreePath);
7131
- (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
7747
+ (0, import_node_fs31.mkdirSync)((0, import_node_path26.dirname)(abs), { recursive: true });
7132
7748
  const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
7133
- (0, import_node_fs29.writeFileSync)(abs, body, "utf8");
7749
+ (0, import_node_fs31.writeFileSync)(abs, body, "utf8");
7134
7750
  return PLAN_FILE_REL;
7135
7751
  }
7136
- var import_node_fs29, import_node_path24;
7752
+ var import_node_fs31, import_node_path26;
7137
7753
  var init_plan_file = __esm({
7138
7754
  "src/plan/plan-file.ts"() {
7139
7755
  "use strict";
7140
- import_node_fs29 = require("fs");
7141
- import_node_path24 = require("path");
7756
+ import_node_fs31 = require("fs");
7757
+ import_node_path26 = require("path");
7142
7758
  init_workspace_scratch();
7143
7759
  init_plan_present();
7144
7760
  init_plan_present();
@@ -7153,10 +7769,10 @@ __export(cursor_recover_exports, {
7153
7769
  function recoverFinishedCursorRun(opts) {
7154
7770
  const agentId = opts.agentId.trim();
7155
7771
  if (!agentId) return null;
7156
- const runsPath = (0, import_node_path25.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
7157
- if (!(0, import_node_fs30.existsSync)(runsPath)) return null;
7772
+ const runsPath = (0, import_node_path27.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
7773
+ if (!(0, import_node_fs32.existsSync)(runsPath)) return null;
7158
7774
  try {
7159
- const lines = (0, import_node_fs30.readFileSync)(runsPath, "utf8").split("\n");
7775
+ const lines = (0, import_node_fs32.readFileSync)(runsPath, "utf8").split("\n");
7160
7776
  let best = null;
7161
7777
  for (const line of lines) {
7162
7778
  const trimmed = line.trim();
@@ -7182,33 +7798,141 @@ function recoverFinishedCursorRun(opts) {
7182
7798
  return null;
7183
7799
  }
7184
7800
  }
7185
- var import_node_fs30, import_node_path25;
7801
+ var import_node_fs32, import_node_path27;
7186
7802
  var init_cursor_recover = __esm({
7187
7803
  "src/agents/cursor-recover.ts"() {
7188
7804
  "use strict";
7189
- import_node_fs30 = require("fs");
7190
- import_node_path25 = require("path");
7805
+ import_node_fs32 = require("fs");
7806
+ import_node_path27 = require("path");
7191
7807
  init_paths();
7192
7808
  }
7193
7809
  });
7194
7810
 
7195
- // src/mcp/server.ts
7196
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
7197
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
7198
- var import_zod = require("zod");
7199
- var import_node_path26 = require("path");
7200
-
7201
- // src/orchestrator/orchestrator.ts
7202
- var import_node_events = require("events");
7203
- var import_node_fs31 = require("fs");
7204
- init_error_detail();
7205
-
7206
- // src/agents/spawn.ts
7207
- var import_node_readline = require("readline");
7208
- var import_execa2 = require("execa");
7209
- init_worktree();
7210
- init_app_settings();
7211
- init_global_workspace();
7811
+ // src/store/caffeinate-hold.ts
7812
+ var caffeinate_hold_exports = {};
7813
+ __export(caffeinate_hold_exports, {
7814
+ caffeinateHoldPath: () => caffeinateHoldPath,
7815
+ getCaffeinateHold: () => getCaffeinateHold,
7816
+ setCaffeinateHold: () => setCaffeinateHold,
7817
+ setCaffeinateHoldHooks: () => setCaffeinateHoldHooks
7818
+ });
7819
+ function setCaffeinateHoldHooks(next) {
7820
+ hooks = next;
7821
+ }
7822
+ function caffeinateHoldPath() {
7823
+ return (0, import_node_path30.join)(appDataDir(), "caffeinate-hold.json");
7824
+ }
7825
+ function processAlive(pid) {
7826
+ if (hooks.processAlive) return hooks.processAlive(pid);
7827
+ try {
7828
+ process.kill(pid, 0);
7829
+ return true;
7830
+ } catch {
7831
+ return false;
7832
+ }
7833
+ }
7834
+ function killPid(pid) {
7835
+ if (hooks.kill) {
7836
+ hooks.kill(pid);
7837
+ return;
7838
+ }
7839
+ try {
7840
+ process.kill(pid, "SIGTERM");
7841
+ } catch {
7842
+ }
7843
+ }
7844
+ function readHold() {
7845
+ const path = caffeinateHoldPath();
7846
+ if (!(0, import_node_fs35.existsSync)(path)) return null;
7847
+ try {
7848
+ const parsed = JSON.parse((0, import_node_fs35.readFileSync)(path, "utf8"));
7849
+ if (typeof parsed?.pid === "number" && parsed.pid > 0) return parsed;
7850
+ } catch {
7851
+ }
7852
+ return null;
7853
+ }
7854
+ function writeHold(pid) {
7855
+ writePrivateFile(caffeinateHoldPath(), `${JSON.stringify({ pid })}
7856
+ `);
7857
+ }
7858
+ function clearHold() {
7859
+ try {
7860
+ (0, import_node_fs35.unlinkSync)(caffeinateHoldPath());
7861
+ } catch {
7862
+ }
7863
+ }
7864
+ function getCaffeinateHold() {
7865
+ const platform = hooks.platform ?? process.platform;
7866
+ const hold = readHold();
7867
+ if (!hold) {
7868
+ return { held: false, pid: null, running: false, platform };
7869
+ }
7870
+ const running = processAlive(hold.pid);
7871
+ if (!running) {
7872
+ clearHold();
7873
+ return { held: false, pid: null, running: false, platform };
7874
+ }
7875
+ return { held: true, pid: hold.pid, running: true, platform };
7876
+ }
7877
+ function setCaffeinateHold(enabled) {
7878
+ const platform = hooks.platform ?? process.platform;
7879
+ const current = getCaffeinateHold();
7880
+ if (!enabled) {
7881
+ if (current.pid) killPid(current.pid);
7882
+ clearHold();
7883
+ return { held: false, pid: null, running: false, platform };
7884
+ }
7885
+ if (current.running && current.pid) return current;
7886
+ if (platform !== "darwin") {
7887
+ return { held: false, pid: null, running: false, platform };
7888
+ }
7889
+ const spawnImpl = hooks.spawn ?? import_node_child_process4.spawn;
7890
+ const child = spawnImpl("caffeinate", ["-dimsu"], {
7891
+ detached: true,
7892
+ stdio: "ignore"
7893
+ });
7894
+ const pid = child.pid;
7895
+ if (!pid) {
7896
+ try {
7897
+ child.kill();
7898
+ } catch {
7899
+ }
7900
+ return { held: false, pid: null, running: false, platform };
7901
+ }
7902
+ child.unref();
7903
+ writeHold(pid);
7904
+ return { held: true, pid, running: true, platform };
7905
+ }
7906
+ var import_node_child_process4, import_node_fs35, import_node_path30, hooks;
7907
+ var init_caffeinate_hold = __esm({
7908
+ "src/store/caffeinate-hold.ts"() {
7909
+ "use strict";
7910
+ import_node_child_process4 = require("child_process");
7911
+ import_node_fs35 = require("fs");
7912
+ import_node_path30 = require("path");
7913
+ init_paths();
7914
+ init_private_file();
7915
+ hooks = {};
7916
+ }
7917
+ });
7918
+
7919
+ // src/mcp/server.ts
7920
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
7921
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
7922
+ var import_zod2 = require("zod");
7923
+ var import_node_path31 = require("path");
7924
+
7925
+ // src/orchestrator/orchestrator.ts
7926
+ var import_node_events = require("events");
7927
+ var import_node_fs33 = require("fs");
7928
+ init_error_detail();
7929
+
7930
+ // src/agents/spawn.ts
7931
+ var import_node_readline = require("readline");
7932
+ var import_execa2 = require("execa");
7933
+ init_worktree();
7934
+ init_app_settings();
7935
+ init_global_workspace();
7212
7936
  init_brightsy();
7213
7937
  init_agents();
7214
7938
  init_orchestrator_capable();
@@ -7287,9 +8011,9 @@ function toolFilePath(input) {
7287
8011
  if (!input) return void 0;
7288
8012
  return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
7289
8013
  }
7290
- function countLines(text) {
7291
- if (!text) return 0;
7292
- return text.split("\n").length;
8014
+ function countLines(text2) {
8015
+ if (!text2) return 0;
8016
+ return text2.split("\n").length;
7293
8017
  }
7294
8018
  function diffFromInput(input) {
7295
8019
  if (!input) return {};
@@ -7416,9 +8140,9 @@ function applyAgentEvent(parts, event) {
7416
8140
  function partsToAssistantText(parts) {
7417
8141
  return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
7418
8142
  }
7419
- function stripBrightsyNdjsonNoise(text) {
7420
- if (!text || !text.includes('"type"')) return text;
7421
- let out = text;
8143
+ function stripBrightsyNdjsonNoise(text2) {
8144
+ if (!text2 || !text2.includes('"type"')) return text2;
8145
+ let out = text2;
7422
8146
  out = out.replace(
7423
8147
  /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
7424
8148
  ""
@@ -7555,8 +8279,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
7555
8279
  onEvent({ type: "exit", data: exitCode });
7556
8280
  const finalized = finalizeParts(parts);
7557
8281
  const rawText = assistantText.trim() || partsToAssistantText(finalized);
7558
- const text = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
7559
- return { exitCode, sessionId, assistantText: text, parts: finalized, usage };
8282
+ const text2 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
8283
+ return { exitCode, sessionId, assistantText: text2, parts: finalized, usage };
7560
8284
  });
7561
8285
  return {
7562
8286
  pid: child.pid,
@@ -7590,9 +8314,9 @@ function shouldAutoArchiveOnPrMerge(opts) {
7590
8314
  }
7591
8315
 
7592
8316
  // src/hook/conductor.ts
7593
- var import_node_fs16 = require("fs");
8317
+ var import_node_fs18 = require("fs");
7594
8318
  var import_node_net = require("net");
7595
- var import_node_path13 = require("path");
8319
+ var import_node_path15 = require("path");
7596
8320
  var import_execa3 = require("execa");
7597
8321
  var import_node_readline2 = require("readline");
7598
8322
  init_settings();
@@ -7602,9 +8326,9 @@ function matchSimpleGlob(pattern, name) {
7602
8326
  return new RegExp(`^${escaped}$`).test(name);
7603
8327
  }
7604
8328
  function readWorktreeInclude(repoPath) {
7605
- const path = (0, import_node_path13.join)(repoPath, ".worktreeinclude");
7606
- if (!(0, import_node_fs16.existsSync)(path)) return [];
7607
- return (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
8329
+ const path = (0, import_node_path15.join)(repoPath, ".worktreeinclude");
8330
+ if (!(0, import_node_fs18.existsSync)(path)) return [];
8331
+ return (0, import_node_fs18.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
7608
8332
  }
7609
8333
  function resolveFilesToCopy(repoPath) {
7610
8334
  const fromInclude = readWorktreeInclude(repoPath);
@@ -7614,10 +8338,10 @@ function resolveFilesToCopy(repoPath) {
7614
8338
  if (settings?.fileIncludeGlobs?.length) {
7615
8339
  const matched = [];
7616
8340
  try {
7617
- for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
8341
+ for (const entry of (0, import_node_fs18.readdirSync)(repoPath, { withFileTypes: true })) {
7618
8342
  if (!entry.isFile()) continue;
7619
8343
  for (const glob of settings.fileIncludeGlobs) {
7620
- if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path13.basename)(glob), entry.name)) {
8344
+ if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path15.basename)(glob), entry.name)) {
7621
8345
  matched.push(entry.name);
7622
8346
  break;
7623
8347
  }
@@ -7629,7 +8353,7 @@ function resolveFilesToCopy(repoPath) {
7629
8353
  }
7630
8354
  const defaults = [];
7631
8355
  try {
7632
- for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
8356
+ for (const entry of (0, import_node_fs18.readdirSync)(repoPath, { withFileTypes: true })) {
7633
8357
  if (entry.isFile() && entry.name.startsWith(".env")) {
7634
8358
  defaults.push(entry.name);
7635
8359
  }
@@ -7643,11 +8367,11 @@ function copyConfiguredFiles(repoPath, worktreePath) {
7643
8367
  const patterns = resolveFilesToCopy(repoPath);
7644
8368
  const copied = [];
7645
8369
  for (const rel of patterns) {
7646
- const src = (0, import_node_path13.join)(repoPath, rel);
7647
- if (!(0, import_node_fs16.existsSync)(src)) continue;
7648
- const dest = (0, import_node_path13.join)(worktreePath, rel);
7649
- (0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(dest), { recursive: true });
7650
- (0, import_node_fs16.copyFileSync)(src, dest);
8370
+ const src = (0, import_node_path15.join)(repoPath, rel);
8371
+ if (!(0, import_node_fs18.existsSync)(src)) continue;
8372
+ const dest = (0, import_node_path15.join)(worktreePath, rel);
8373
+ (0, import_node_fs18.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
8374
+ (0, import_node_fs18.copyFileSync)(src, dest);
7651
8375
  copied.push(rel);
7652
8376
  }
7653
8377
  return copied;
@@ -7681,7 +8405,7 @@ async function captureLoginEnv() {
7681
8405
  }
7682
8406
  function buildWorkspaceScriptEnv(opts, baseEnv) {
7683
8407
  const env = { ...baseEnv ?? process.env };
7684
- const name = opts.workspaceName ?? (0, import_node_path13.basename)(opts.worktreePath);
8408
+ const name = opts.workspaceName ?? (0, import_node_path15.basename)(opts.worktreePath);
7685
8409
  const ports = opts.ports ?? [];
7686
8410
  const primary = ports[0];
7687
8411
  env.SIDEBOARD_WORKSPACE_NAME = name;
@@ -7900,15 +8624,15 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
7900
8624
  }
7901
8625
 
7902
8626
  // src/hook/cursor-worktrees.ts
7903
- var import_node_fs17 = require("fs");
7904
- var import_node_path14 = require("path");
8627
+ var import_node_fs19 = require("fs");
8628
+ var import_node_path16 = require("path");
7905
8629
  var import_execa4 = require("execa");
7906
8630
  var import_node_readline3 = require("readline");
7907
8631
  function loadCursorWorktreesJson(rootPath) {
7908
- const path = (0, import_node_path14.join)(rootPath, ".cursor", "worktrees.json");
7909
- if (!(0, import_node_fs17.existsSync)(path)) return null;
8632
+ const path = (0, import_node_path16.join)(rootPath, ".cursor", "worktrees.json");
8633
+ if (!(0, import_node_fs19.existsSync)(path)) return null;
7910
8634
  try {
7911
- return JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
8635
+ return JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
7912
8636
  } catch {
7913
8637
  return null;
7914
8638
  }
@@ -7934,7 +8658,7 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
7934
8658
  if (process.platform === "win32") {
7935
8659
  env.ROOT_WORKTREE_PATH = repoPath;
7936
8660
  }
7937
- const commands = Array.isArray(spec) ? spec : [spec.endsWith(".sh") || spec.endsWith(".ps1") ? (0, import_node_path14.join)(
8661
+ const commands = Array.isArray(spec) ? spec : [spec.endsWith(".sh") || spec.endsWith(".ps1") ? (0, import_node_path16.join)(
7938
8662
  fromWorktree ? worktreePath : repoPath,
7939
8663
  ".cursor",
7940
8664
  spec
@@ -7966,8 +8690,8 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
7966
8690
  }
7967
8691
 
7968
8692
  // src/git/orphan-cleanup.ts
7969
- var import_node_fs18 = require("fs");
7970
- var import_node_path15 = require("path");
8693
+ var import_node_fs20 = require("fs");
8694
+ var import_node_path17 = require("path");
7971
8695
  init_worktree();
7972
8696
  init_thread_store();
7973
8697
  init_paths();
@@ -7982,9 +8706,9 @@ async function findOrphanWorktrees(repoPaths) {
7982
8706
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
7983
8707
  );
7984
8708
  const homeRoot = sideboardWorkspacesDir();
7985
- if ((0, import_node_fs18.existsSync)(homeRoot)) {
8709
+ if ((0, import_node_fs20.existsSync)(homeRoot)) {
7986
8710
  try {
7987
- for (const entry of (0, import_node_fs18.readdirSync)(homeRoot, { withFileTypes: true })) {
8711
+ for (const entry of (0, import_node_fs20.readdirSync)(homeRoot, { withFileTypes: true })) {
7988
8712
  if (!entry.isDirectory()) continue;
7989
8713
  void entry;
7990
8714
  }
@@ -7994,7 +8718,7 @@ async function findOrphanWorktrees(repoPaths) {
7994
8718
  const orphans = [];
7995
8719
  const seen = /* @__PURE__ */ new Set();
7996
8720
  for (const repoPath of repos) {
7997
- if (!repoPath || !(0, import_node_fs18.existsSync)(repoPath)) continue;
8721
+ if (!repoPath || !(0, import_node_fs20.existsSync)(repoPath)) continue;
7998
8722
  try {
7999
8723
  const wts = await listWorktrees(repoPath);
8000
8724
  for (const wt of wts) {
@@ -8005,7 +8729,7 @@ async function findOrphanWorktrees(repoPaths) {
8005
8729
  seen.add(path);
8006
8730
  let mtimeMs = 0;
8007
8731
  try {
8008
- mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
8732
+ mtimeMs = (0, import_node_fs20.statSync)(path).mtimeMs;
8009
8733
  } catch {
8010
8734
  mtimeMs = 0;
8011
8735
  }
@@ -8015,16 +8739,16 @@ async function findOrphanWorktrees(repoPaths) {
8015
8739
  }
8016
8740
  try {
8017
8741
  const root = worktreesRoot(repoPath);
8018
- if ((0, import_node_fs18.existsSync)(root)) {
8019
- for (const entry of (0, import_node_fs18.readdirSync)(root, { withFileTypes: true })) {
8742
+ if ((0, import_node_fs20.existsSync)(root)) {
8743
+ for (const entry of (0, import_node_fs20.readdirSync)(root, { withFileTypes: true })) {
8020
8744
  if (!entry.isDirectory()) continue;
8021
- const path = (0, import_node_path15.join)(root, entry.name).replace(/\/$/, "");
8745
+ const path = (0, import_node_path17.join)(root, entry.name).replace(/\/$/, "");
8022
8746
  if (known.has(path) || seen.has(path)) continue;
8023
- if (!(0, import_node_fs18.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
8747
+ if (!(0, import_node_fs20.existsSync)((0, import_node_path17.join)(path, ".git"))) continue;
8024
8748
  seen.add(path);
8025
8749
  let mtimeMs = 0;
8026
8750
  try {
8027
- mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
8751
+ mtimeMs = (0, import_node_fs20.statSync)(path).mtimeMs;
8028
8752
  } catch {
8029
8753
  mtimeMs = Date.now();
8030
8754
  }
@@ -8161,8 +8885,8 @@ async function applyThreadIntoMain(thread, opts) {
8161
8885
  }
8162
8886
 
8163
8887
  // src/git/clone-repo.ts
8164
- var import_node_fs20 = require("fs");
8165
- var import_node_path17 = require("path");
8888
+ var import_node_fs22 = require("fs");
8889
+ var import_node_path19 = require("path");
8166
8890
  var import_execa6 = require("execa");
8167
8891
  init_paths();
8168
8892
  init_workspaces();
@@ -8172,12 +8896,12 @@ async function cloneRepoIntoSideboard(opts) {
8172
8896
  if (!url) throw new Error("Clone URL is required");
8173
8897
  let name = opts.name?.trim();
8174
8898
  if (!name) {
8175
- const leaf = (0, import_node_path17.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
8899
+ const leaf = (0, import_node_path19.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
8176
8900
  name = leaf || "repo";
8177
8901
  }
8178
8902
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
8179
- const dest = (0, import_node_path17.join)(sideboardReposDir(), name);
8180
- if ((0, import_node_fs20.existsSync)(dest)) {
8903
+ const dest = (0, import_node_path19.join)(sideboardReposDir(), name);
8904
+ if ((0, import_node_fs22.existsSync)(dest)) {
8181
8905
  const repoPath2 = await resolveRepoRoot(dest);
8182
8906
  const workspace2 = await ensureWorkspace(repoPath2);
8183
8907
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -8197,7 +8921,7 @@ async function cloneRepoIntoSideboard(opts) {
8197
8921
  init_thread_store();
8198
8922
 
8199
8923
  // src/threads/create.ts
8200
- var import_node_fs21 = require("fs");
8924
+ var import_node_fs23 = require("fs");
8201
8925
 
8202
8926
  // src/detect/detect.ts
8203
8927
  init_agents();
@@ -8242,7 +8966,7 @@ async function createThread(input, _onSetupLine) {
8242
8966
  });
8243
8967
  await requireAgent(resolved.agent);
8244
8968
  const repoPath = await resolveRepoRoot(input.repoPath);
8245
- if (!(0, import_node_fs21.existsSync)(repoPath)) {
8969
+ if (!(0, import_node_fs23.existsSync)(repoPath)) {
8246
8970
  throw new Error(`Repo not found: ${repoPath}`);
8247
8971
  }
8248
8972
  let sourceRef = input.sourceRef;
@@ -8313,7 +9037,7 @@ async function listLinearIssues(agent, repoPath) {
8313
9037
  init_orchestrator_capable();
8314
9038
 
8315
9039
  // src/threads/chat-tabs.ts
8316
- var import_node_crypto2 = require("crypto");
9040
+ var import_node_crypto4 = require("crypto");
8317
9041
 
8318
9042
  // src/composer/summarize.ts
8319
9043
  init_run();
@@ -8361,9 +9085,9 @@ async function tryClaudeSummary(transcript, opts) {
8361
9085
  { cwd: opts?.cwd, reject: false }
8362
9086
  );
8363
9087
  if (exitCode !== 0) return null;
8364
- const text = stdout.trim();
8365
- if (text.length < 40) return null;
8366
- return text;
9088
+ const text2 = stdout.trim();
9089
+ if (text2.length < 40) return null;
9090
+ return text2;
8367
9091
  } catch {
8368
9092
  return null;
8369
9093
  }
@@ -8412,9 +9136,9 @@ function extractiveSummary(transcript) {
8412
9136
  );
8413
9137
  return parts.join("\n");
8414
9138
  }
8415
- function clipSummary(text) {
8416
- if (text.length <= MAX_SUMMARY_CHARS) return text;
8417
- return `${text.slice(0, MAX_SUMMARY_CHARS)}
9139
+ function clipSummary(text2) {
9140
+ if (text2.length <= MAX_SUMMARY_CHARS) return text2;
9141
+ return `${text2.slice(0, MAX_SUMMARY_CHARS)}
8418
9142
 
8419
9143
  [\u2026summary truncated\u2026]`;
8420
9144
  }
@@ -8650,7 +9374,7 @@ function forkMessageSlice(from, throughIndex) {
8650
9374
  function buildForkTranscriptAttachment(baseTitle, messages) {
8651
9375
  const title = baseTitle || "Chat";
8652
9376
  return {
8653
- id: (0, import_node_crypto2.randomUUID)(),
9377
+ id: (0, import_node_crypto4.randomUUID)(),
8654
9378
  name: `Transcript of ${title}.md`,
8655
9379
  kind: "transcript",
8656
9380
  content: formatTranscriptMarkdown(title, messages)
@@ -8704,9 +9428,9 @@ function forkChatTab(input) {
8704
9428
  }
8705
9429
 
8706
9430
  // src/review/request-review.ts
8707
- var import_node_crypto3 = require("crypto");
8708
- var import_node_fs22 = require("fs");
8709
- var import_node_path18 = require("path");
9431
+ var import_node_crypto5 = require("crypto");
9432
+ var import_node_fs24 = require("fs");
9433
+ var import_node_path20 = require("path");
8710
9434
  init_global_workspace();
8711
9435
  init_thread_store();
8712
9436
 
@@ -8850,22 +9574,22 @@ function shouldRefreshReviewRequestTemplate(content) {
8850
9574
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
8851
9575
  }
8852
9576
  function readTextIfPresent(abs) {
8853
- if (!(0, import_node_fs22.existsSync)(abs)) return null;
9577
+ if (!(0, import_node_fs24.existsSync)(abs)) return null;
8854
9578
  try {
8855
- const content = (0, import_node_fs22.readFileSync)(abs, "utf8");
9579
+ const content = (0, import_node_fs24.readFileSync)(abs, "utf8");
8856
9580
  return content.trim() ? content : null;
8857
9581
  } catch {
8858
9582
  return null;
8859
9583
  }
8860
9584
  }
8861
9585
  function ensureAttachmentsGitignore(worktreePath) {
8862
- const gitignoreAbs = (0, import_node_path18.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
8863
- if ((0, import_node_fs22.existsSync)(gitignoreAbs)) return;
8864
- (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
8865
- (0, import_node_fs22.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
9586
+ const gitignoreAbs = (0, import_node_path20.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
9587
+ if ((0, import_node_fs24.existsSync)(gitignoreAbs)) return;
9588
+ (0, import_node_fs24.mkdirSync)((0, import_node_path20.dirname)(gitignoreAbs), { recursive: true });
9589
+ (0, import_node_fs24.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
8866
9590
  }
8867
9591
  function resolveReviewGuidelines(worktreePath) {
8868
- const repoAbs = (0, import_node_path18.join)(worktreePath, REPO_REVIEW_PATH);
9592
+ const repoAbs = (0, import_node_path20.join)(worktreePath, REPO_REVIEW_PATH);
8869
9593
  const repoContent = readTextIfPresent(repoAbs);
8870
9594
  if (repoContent) {
8871
9595
  return {
@@ -8875,7 +9599,7 @@ function resolveReviewGuidelines(worktreePath) {
8875
9599
  source: "repo"
8876
9600
  };
8877
9601
  }
8878
- const localAbs = (0, import_node_path18.join)(worktreePath, REVIEW_REQUEST_PATH);
9602
+ const localAbs = (0, import_node_path20.join)(worktreePath, REVIEW_REQUEST_PATH);
8879
9603
  const localContent = readTextIfPresent(localAbs);
8880
9604
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
8881
9605
  return {
@@ -8885,7 +9609,7 @@ function resolveReviewGuidelines(worktreePath) {
8885
9609
  source: "local"
8886
9610
  };
8887
9611
  }
8888
- const legacyAbs = (0, import_node_path18.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
9612
+ const legacyAbs = (0, import_node_path20.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
8889
9613
  const legacyContent = readTextIfPresent(legacyAbs);
8890
9614
  if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
8891
9615
  return {
@@ -8896,8 +9620,8 @@ function resolveReviewGuidelines(worktreePath) {
8896
9620
  };
8897
9621
  }
8898
9622
  ensureAttachmentsGitignore(worktreePath);
8899
- (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
8900
- (0, import_node_fs22.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
9623
+ (0, import_node_fs24.mkdirSync)((0, import_node_path20.dirname)(localAbs), { recursive: true });
9624
+ (0, import_node_fs24.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
8901
9625
  return {
8902
9626
  path: REVIEW_REQUEST_PATH,
8903
9627
  name: REVIEW_REQUEST_NAME,
@@ -8909,7 +9633,7 @@ function buildReviewRequestAttachment(content, opts) {
8909
9633
  const path = opts?.path ?? REVIEW_REQUEST_PATH;
8910
9634
  const name = opts?.name ?? (path === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
8911
9635
  return {
8912
- id: (0, import_node_crypto3.randomUUID)(),
9636
+ id: (0, import_node_crypto5.randomUUID)(),
8913
9637
  name,
8914
9638
  kind: "file",
8915
9639
  path,
@@ -8987,7 +9711,7 @@ async function forkThreadWorktree(input, onSetupLine) {
8987
9711
  }
8988
9712
 
8989
9713
  // src/orchestrator/quota-failover.ts
8990
- var import_node_crypto4 = require("crypto");
9714
+ var import_node_crypto6 = require("crypto");
8991
9715
  init_session_quota();
8992
9716
  init_app_settings();
8993
9717
  init_global_workspace();
@@ -9042,8 +9766,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
9042
9766
  );
9043
9767
  const recent = from.messages.slice(-8).map((m) => {
9044
9768
  const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
9045
- const text = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
9046
- return text ? `- ${role}: ${text}` : null;
9769
+ const text2 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
9770
+ return text2 ? `- ${role}: ${text2}` : null;
9047
9771
  }).filter(Boolean);
9048
9772
  const body = [
9049
9773
  `# Orchestration handoff`,
@@ -9068,7 +9792,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
9068
9792
  `- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
9069
9793
  ].join("\n");
9070
9794
  return {
9071
- id: (0, import_node_crypto4.randomUUID)(),
9795
+ id: (0, import_node_crypto6.randomUUID)(),
9072
9796
  name: "Orchestration quota handoff.md",
9073
9797
  kind: "transcript",
9074
9798
  content: body
@@ -9096,21 +9820,21 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
9096
9820
  }
9097
9821
 
9098
9822
  // src/threads/adopt.ts
9099
- var import_node_child_process2 = require("child_process");
9100
- var import_node_fs23 = require("fs");
9823
+ var import_node_child_process3 = require("child_process");
9824
+ var import_node_fs25 = require("fs");
9101
9825
  var import_node_os8 = require("os");
9102
- var import_node_path19 = require("path");
9826
+ var import_node_path21 = require("path");
9103
9827
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
9104
9828
  init_worktree();
9105
9829
  init_thread_store();
9106
- var CONDUCTOR_APP_SUPPORT = (0, import_node_path19.join)(
9830
+ var CONDUCTOR_APP_SUPPORT = (0, import_node_path21.join)(
9107
9831
  process.env.HOME ?? "",
9108
9832
  "Library",
9109
9833
  "Application Support",
9110
9834
  "com.conductor.app"
9111
9835
  );
9112
- var CONDUCTOR_DB = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
9113
- var CURSOR_SDK_STORE = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
9836
+ var CONDUCTOR_DB = (0, import_node_path21.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
9837
+ var CURSOR_SDK_STORE = (0, import_node_path21.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
9114
9838
  function mapAgentType(raw) {
9115
9839
  if (!raw) return null;
9116
9840
  const v = raw.toLowerCase();
@@ -9122,25 +9846,25 @@ function mapAgentType(raw) {
9122
9846
  return null;
9123
9847
  }
9124
9848
  function resolveConductorCursorAgentId(workspacePath) {
9125
- if (!workspacePath || !(0, import_node_fs23.existsSync)(CURSOR_SDK_STORE)) return null;
9849
+ if (!workspacePath || !(0, import_node_fs25.existsSync)(CURSOR_SDK_STORE)) return null;
9126
9850
  const normalized = workspacePath.replace(/\/$/, "");
9127
9851
  let best = null;
9128
9852
  let hashes;
9129
9853
  try {
9130
- hashes = (0, import_node_fs23.readdirSync)(CURSOR_SDK_STORE);
9854
+ hashes = (0, import_node_fs25.readdirSync)(CURSOR_SDK_STORE);
9131
9855
  } catch {
9132
9856
  return null;
9133
9857
  }
9134
9858
  for (const hash of hashes) {
9135
- const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
9136
- if (!(0, import_node_fs23.existsSync)(agentsFile)) continue;
9137
- let text;
9859
+ const agentsFile = (0, import_node_path21.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
9860
+ if (!(0, import_node_fs25.existsSync)(agentsFile)) continue;
9861
+ let text2;
9138
9862
  try {
9139
- text = (0, import_node_fs23.readFileSync)(agentsFile, "utf8");
9863
+ text2 = (0, import_node_fs25.readFileSync)(agentsFile, "utf8");
9140
9864
  } catch {
9141
9865
  continue;
9142
9866
  }
9143
- for (const line of text.split("\n")) {
9867
+ for (const line of text2.split("\n")) {
9144
9868
  const trimmed = line.trim();
9145
9869
  if (!trimmed) continue;
9146
9870
  try {
@@ -9160,7 +9884,7 @@ function resolveConductorCursorAgentId(workspacePath) {
9160
9884
  return best?.agentId ?? null;
9161
9885
  }
9162
9886
  async function adoptThread(input) {
9163
- if (!(0, import_node_fs23.existsSync)(input.worktreePath)) {
9887
+ if (!(0, import_node_fs25.existsSync)(input.worktreePath)) {
9164
9888
  throw new Error(`Worktree not found: ${input.worktreePath}`);
9165
9889
  }
9166
9890
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -9184,18 +9908,18 @@ async function adoptThread(input) {
9184
9908
  return thread;
9185
9909
  }
9186
9910
  function listConductorWorkspaces() {
9187
- if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
9911
+ if (!(0, import_node_fs25.existsSync)(CONDUCTOR_DB)) {
9188
9912
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
9189
9913
  }
9190
- const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9191
- const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
9914
+ const tmp = (0, import_node_fs25.mkdtempSync)((0, import_node_path21.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9915
+ const snapshot = (0, import_node_path21.join)(tmp, "conductor.db");
9192
9916
  try {
9193
- (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
9917
+ (0, import_node_fs25.copyFileSync)(CONDUCTOR_DB, snapshot);
9194
9918
  for (const suffix of ["-wal", "-shm"]) {
9195
9919
  const src = `${CONDUCTOR_DB}${suffix}`;
9196
- if ((0, import_node_fs23.existsSync)(src)) {
9920
+ if ((0, import_node_fs25.existsSync)(src)) {
9197
9921
  try {
9198
- (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
9922
+ (0, import_node_fs25.copyFileSync)(src, `${snapshot}${suffix}`);
9199
9923
  } catch {
9200
9924
  }
9201
9925
  }
@@ -9271,22 +9995,22 @@ function listConductorWorkspaces() {
9271
9995
  db.close();
9272
9996
  }
9273
9997
  } finally {
9274
- (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
9998
+ (0, import_node_fs25.rmSync)(tmp, { recursive: true, force: true });
9275
9999
  }
9276
10000
  }
9277
10001
  function importConductorWorkspace(workspaceId) {
9278
- if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
10002
+ if (!(0, import_node_fs25.existsSync)(CONDUCTOR_DB)) {
9279
10003
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
9280
10004
  }
9281
- const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9282
- const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
10005
+ const tmp = (0, import_node_fs25.mkdtempSync)((0, import_node_path21.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
10006
+ const snapshot = (0, import_node_path21.join)(tmp, "conductor.db");
9283
10007
  try {
9284
- (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
10008
+ (0, import_node_fs25.copyFileSync)(CONDUCTOR_DB, snapshot);
9285
10009
  for (const suffix of ["-wal", "-shm"]) {
9286
10010
  const src = `${CONDUCTOR_DB}${suffix}`;
9287
- if ((0, import_node_fs23.existsSync)(src)) {
10011
+ if ((0, import_node_fs25.existsSync)(src)) {
9288
10012
  try {
9289
- (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
10013
+ (0, import_node_fs25.copyFileSync)(src, `${snapshot}${suffix}`);
9290
10014
  } catch {
9291
10015
  }
9292
10016
  }
@@ -9304,7 +10028,7 @@ function importConductorWorkspace(workspaceId) {
9304
10028
  ).get(workspaceId);
9305
10029
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
9306
10030
  const worktreePath = String(row.workspacePath);
9307
- if (!(0, import_node_fs23.existsSync)(worktreePath)) {
10031
+ if (!(0, import_node_fs25.existsSync)(worktreePath)) {
9308
10032
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
9309
10033
  }
9310
10034
  let sessionId = null;
@@ -9344,7 +10068,7 @@ function importConductorWorkspace(workspaceId) {
9344
10068
  }
9345
10069
  let repoPath = worktreePath;
9346
10070
  try {
9347
- repoPath = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
10071
+ repoPath = (0, import_node_child_process3.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
9348
10072
  cwd: worktreePath,
9349
10073
  encoding: "utf8"
9350
10074
  }).trim();
@@ -9367,7 +10091,7 @@ function importConductorWorkspace(workspaceId) {
9367
10091
  db.close();
9368
10092
  }
9369
10093
  } finally {
9370
- (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
10094
+ (0, import_node_fs25.rmSync)(tmp, { recursive: true, force: true });
9371
10095
  }
9372
10096
  }
9373
10097
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -9375,7 +10099,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
9375
10099
  }
9376
10100
 
9377
10101
  // src/threads/stack-layers.ts
9378
- var import_node_fs24 = require("fs");
10102
+ var import_node_fs26 = require("fs");
9379
10103
  init_run();
9380
10104
  init_stack();
9381
10105
  init_worktree();
@@ -9443,7 +10167,7 @@ async function openStackLayer(input, _onSetupLine) {
9443
10167
  let createdWorktree = false;
9444
10168
  const trees = await listWorktrees(repoPath);
9445
10169
  const checkedOut = trees.find((w) => w.branch === branchName);
9446
- if (checkedOut?.path && (0, import_node_fs24.existsSync)(checkedOut.path)) {
10170
+ if (checkedOut?.path && (0, import_node_fs26.existsSync)(checkedOut.path)) {
9447
10171
  if (input.reuseExistingWorktree !== false) {
9448
10172
  worktreePath = checkedOut.path;
9449
10173
  } else {
@@ -9585,7 +10309,7 @@ async function initStackFromThread(input, onSetupLine) {
9585
10309
  async function createPrStack(input, onSetupLine) {
9586
10310
  await requireAgent(input.agent);
9587
10311
  const repoPath = await resolveRepoRoot(input.repoPath);
9588
- if (!(0, import_node_fs24.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
10312
+ if (!(0, import_node_fs26.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9589
10313
  if (!input.branches.length) throw new Error("At least one branch name required");
9590
10314
  const status = await detectGhStack(repoPath);
9591
10315
  if (!status.available) throw new Error(status.reason);
@@ -9652,7 +10376,7 @@ async function createPrStack(input, onSetupLine) {
9652
10376
  }
9653
10377
  }
9654
10378
  const claimed = new Set(threads.map((t) => t.worktreePath));
9655
- if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs24.existsSync)(bootstrap.worktreePath)) {
10379
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs26.existsSync)(bootstrap.worktreePath)) {
9656
10380
  try {
9657
10381
  await removeWorktree(repoPath, bootstrap.worktreePath, {
9658
10382
  deleteBranch: bootstrap.branchName
@@ -9667,12 +10391,12 @@ async function createPrStack(input, onSetupLine) {
9667
10391
  init_worktree();
9668
10392
 
9669
10393
  // src/diff/diff.ts
9670
- var import_node_fs25 = require("fs");
9671
- var import_node_path20 = require("path");
10394
+ var import_node_fs27 = require("fs");
10395
+ var import_node_path22 = require("path");
9672
10396
  init_run();
9673
10397
  init_worktree();
9674
10398
  async function inspectGitWorktree(worktreePath) {
9675
- if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) return "missing_worktree";
10399
+ if (!worktreePath || !(0, import_node_fs27.existsSync)(worktreePath)) return "missing_worktree";
9676
10400
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
9677
10401
  reject: false
9678
10402
  });
@@ -9680,7 +10404,7 @@ async function inspectGitWorktree(worktreePath) {
9680
10404
  return "ok";
9681
10405
  }
9682
10406
  async function initializeGitRepository(worktreePath) {
9683
- if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) {
10407
+ if (!worktreePath || !(0, import_node_fs27.existsSync)(worktreePath)) {
9684
10408
  throw new Error("Worktree not found");
9685
10409
  }
9686
10410
  const status = await inspectGitWorktree(worktreePath);
@@ -9815,11 +10539,11 @@ new file mode 100644
9815
10539
  };
9816
10540
  }
9817
10541
  async function untrackedPatch(worktreePath, path, maxHunk) {
9818
- const abs = (0, import_node_path20.join)(worktreePath, path);
10542
+ const abs = (0, import_node_path22.join)(worktreePath, path);
9819
10543
  try {
9820
- const st = (0, import_node_fs25.statSync)(abs);
10544
+ const st = (0, import_node_fs27.statSync)(abs);
9821
10545
  if (st.isFile() && st.size > maxHunk) {
9822
- const buf = (0, import_node_fs25.readFileSync)(abs).subarray(0, maxHunk);
10546
+ const buf = (0, import_node_fs27.readFileSync)(abs).subarray(0, maxHunk);
9823
10547
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
9824
10548
  }
9825
10549
  } catch {
@@ -9913,13 +10637,13 @@ function formatStat(files) {
9913
10637
  return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
9914
10638
  }
9915
10639
  function emptyScopeStats() {
9916
- const z2 = emptyStat();
10640
+ const z3 = emptyStat();
9917
10641
  return {
9918
- commits: z2,
9919
- uncommitted: z2,
9920
- staged: z2,
9921
- unstaged: z2,
9922
- last_turn: z2
10642
+ commits: z3,
10643
+ uncommitted: z3,
10644
+ staged: z3,
10645
+ unstaged: z3,
10646
+ last_turn: z3
9923
10647
  };
9924
10648
  }
9925
10649
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
@@ -10319,8 +11043,8 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
10319
11043
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
10320
11044
  assertSafeRelativePath(relativePath);
10321
11045
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
10322
- const abs = (0, import_node_path20.join)(worktreePath, relativePath);
10323
- const st = (0, import_node_fs25.statSync)(abs);
11046
+ const abs = (0, import_node_path22.join)(worktreePath, relativePath);
11047
+ const st = (0, import_node_fs27.statSync)(abs);
10324
11048
  if (!st.isFile()) {
10325
11049
  throw new Error(`Not a file: ${relativePath}`);
10326
11050
  }
@@ -10329,7 +11053,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
10329
11053
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
10330
11054
  );
10331
11055
  }
10332
- const buf = (0, import_node_fs25.readFileSync)(abs);
11056
+ const buf = (0, import_node_fs27.readFileSync)(abs);
10333
11057
  return {
10334
11058
  path: relativePath,
10335
11059
  contentBase64: buf.toString("base64"),
@@ -10339,12 +11063,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
10339
11063
  function readWorktreeFile(worktreePath, relativePath, opts) {
10340
11064
  assertSafeRelativePath(relativePath);
10341
11065
  const maxBytes = opts?.maxBytes ?? 2e5;
10342
- const abs = (0, import_node_path20.join)(worktreePath, relativePath);
10343
- const st = (0, import_node_fs25.statSync)(abs);
11066
+ const abs = (0, import_node_path22.join)(worktreePath, relativePath);
11067
+ const st = (0, import_node_fs27.statSync)(abs);
10344
11068
  if (!st.isFile()) {
10345
11069
  throw new Error(`Not a file: ${relativePath}`);
10346
11070
  }
10347
- const buf = (0, import_node_fs25.readFileSync)(abs);
11071
+ const buf = (0, import_node_fs27.readFileSync)(abs);
10348
11072
  if (isImageRelativePath(relativePath)) {
10349
11073
  const maxImageBytes = Math.max(maxBytes, 15e6);
10350
11074
  const truncated2 = buf.length > maxImageBytes;
@@ -10387,9 +11111,9 @@ function assertSafeRelativePath(relativePath) {
10387
11111
  }
10388
11112
  function writeWorktreeFile(worktreePath, relativePath, content) {
10389
11113
  assertSafeRelativePath(relativePath);
10390
- const abs = (0, import_node_path20.join)(worktreePath, relativePath);
10391
- (0, import_node_fs25.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
10392
- (0, import_node_fs25.writeFileSync)(abs, content, "utf8");
11114
+ const abs = (0, import_node_path22.join)(worktreePath, relativePath);
11115
+ (0, import_node_fs27.mkdirSync)((0, import_node_path22.dirname)(abs), { recursive: true });
11116
+ (0, import_node_fs27.writeFileSync)(abs, content, "utf8");
10393
11117
  return { path: relativePath };
10394
11118
  }
10395
11119
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -10568,9 +11292,9 @@ async function confirmLand(thread, opts) {
10568
11292
  }
10569
11293
 
10570
11294
  // src/skills/discover.ts
10571
- var import_node_fs26 = require("fs");
11295
+ var import_node_fs28 = require("fs");
10572
11296
  var import_node_os9 = require("os");
10573
- var import_node_path21 = require("path");
11297
+ var import_node_path23 = require("path");
10574
11298
  function toCommand(name) {
10575
11299
  return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10576
11300
  }
@@ -10602,7 +11326,7 @@ function parseFrontmatter(content) {
10602
11326
  }
10603
11327
  function readSkill(skillMd, source) {
10604
11328
  try {
10605
- const content = (0, import_node_fs26.readFileSync)(skillMd, "utf8");
11329
+ const content = (0, import_node_fs28.readFileSync)(skillMd, "utf8");
10606
11330
  const { name: fmName, description } = parseFrontmatter(content);
10607
11331
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
10608
11332
  const name = fmName || dirName;
@@ -10621,19 +11345,19 @@ function readSkill(skillMd, source) {
10621
11345
  }
10622
11346
  }
10623
11347
  function scanSkillsDir(dir, source, out) {
10624
- if (!(0, import_node_fs26.existsSync)(dir)) return;
11348
+ if (!(0, import_node_fs28.existsSync)(dir)) return;
10625
11349
  let entries;
10626
11350
  try {
10627
- entries = (0, import_node_fs26.readdirSync)(dir);
11351
+ entries = (0, import_node_fs28.readdirSync)(dir);
10628
11352
  } catch {
10629
11353
  return;
10630
11354
  }
10631
11355
  for (const entry of entries) {
10632
11356
  if (entry.startsWith(".")) continue;
10633
- const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
10634
- if (!(0, import_node_fs26.existsSync)(skillMd)) continue;
11357
+ const skillMd = (0, import_node_path23.join)(dir, entry, "SKILL.md");
11358
+ if (!(0, import_node_fs28.existsSync)(skillMd)) continue;
10635
11359
  try {
10636
- if (!(0, import_node_fs26.statSync)(skillMd).isFile()) continue;
11360
+ if (!(0, import_node_fs28.statSync)(skillMd).isFile()) continue;
10637
11361
  } catch {
10638
11362
  continue;
10639
11363
  }
@@ -10642,24 +11366,24 @@ function scanSkillsDir(dir, source, out) {
10642
11366
  }
10643
11367
  }
10644
11368
  function scanClaudePluginSkills(pluginsRoot, out) {
10645
- if (!(0, import_node_fs26.existsSync)(pluginsRoot)) return;
11369
+ if (!(0, import_node_fs28.existsSync)(pluginsRoot)) return;
10646
11370
  const walk = (dir, depth, lookingForSkillsDir) => {
10647
11371
  if (depth > 7) return;
10648
11372
  let entries;
10649
11373
  try {
10650
- entries = (0, import_node_fs26.readdirSync)(dir);
11374
+ entries = (0, import_node_fs28.readdirSync)(dir);
10651
11375
  } catch {
10652
11376
  return;
10653
11377
  }
10654
11378
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
10655
- const skill = readSkill((0, import_node_path21.join)(dir, "SKILL.md"), "cli");
11379
+ const skill = readSkill((0, import_node_path23.join)(dir, "SKILL.md"), "cli");
10656
11380
  if (skill) out.push(skill);
10657
11381
  }
10658
11382
  for (const entry of entries) {
10659
11383
  if (entry === "node_modules" || entry === ".git") continue;
10660
- const full = (0, import_node_path21.join)(dir, entry);
11384
+ const full = (0, import_node_path23.join)(dir, entry);
10661
11385
  try {
10662
- if (!(0, import_node_fs26.statSync)(full).isDirectory()) continue;
11386
+ if (!(0, import_node_fs28.statSync)(full).isDirectory()) continue;
10663
11387
  } catch {
10664
11388
  continue;
10665
11389
  }
@@ -10677,17 +11401,17 @@ function discoverSkills(worktreePath) {
10677
11401
  const home = (0, import_node_os9.homedir)();
10678
11402
  const collected = [];
10679
11403
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
10680
- scanSkillsDir((0, import_node_path21.join)(worktreePath, rel), "workspace", collected);
11404
+ scanSkillsDir((0, import_node_path23.join)(worktreePath, rel), "workspace", collected);
10681
11405
  }
10682
11406
  for (const abs of [
10683
- (0, import_node_path21.join)(home, ".claude/skills"),
10684
- (0, import_node_path21.join)(home, ".cursor/skills"),
10685
- (0, import_node_path21.join)(home, ".sideboard/skills"),
10686
- (0, import_node_path21.join)(home, ".brightsy/skills")
11407
+ (0, import_node_path23.join)(home, ".claude/skills"),
11408
+ (0, import_node_path23.join)(home, ".cursor/skills"),
11409
+ (0, import_node_path23.join)(home, ".sideboard/skills"),
11410
+ (0, import_node_path23.join)(home, ".brightsy/skills")
10687
11411
  ]) {
10688
11412
  scanSkillsDir(abs, "user", collected);
10689
11413
  }
10690
- scanClaudePluginSkills((0, import_node_path21.join)(home, ".claude/plugins"), collected);
11414
+ scanClaudePluginSkills((0, import_node_path23.join)(home, ".claude/plugins"), collected);
10691
11415
  const rank = { workspace: 0, user: 1, cli: 2 };
10692
11416
  const byCommand = /* @__PURE__ */ new Map();
10693
11417
  for (const skill of collected) {
@@ -10699,7 +11423,7 @@ function discoverSkills(worktreePath) {
10699
11423
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
10700
11424
  }
10701
11425
  function readSkillBody(skillPath, maxChars = 12e3) {
10702
- const raw = (0, import_node_fs26.readFileSync)(skillPath, "utf8");
11426
+ const raw = (0, import_node_fs28.readFileSync)(skillPath, "utf8");
10703
11427
  if (raw.startsWith("---")) {
10704
11428
  const end = raw.indexOf("\n---", 3);
10705
11429
  if (end >= 0) {
@@ -10792,9 +11516,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
10792
11516
  }
10793
11517
 
10794
11518
  // src/composer/stage-files.ts
10795
- var import_node_fs27 = require("fs");
10796
- var import_node_path22 = require("path");
10797
- var import_node_crypto5 = require("crypto");
11519
+ var import_node_fs29 = require("fs");
11520
+ var import_node_path24 = require("path");
11521
+ var import_node_crypto7 = require("crypto");
10798
11522
  init_workspace_scratch();
10799
11523
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
10800
11524
  "png",
@@ -10819,7 +11543,7 @@ var IMAGE_MIME_BY_EXT = {
10819
11543
  var MAX_INLINE_BYTES = 4e5;
10820
11544
  var MAX_PREVIEW_BYTES = 5e6;
10821
11545
  function fileExtension(filePath) {
10822
- const base = (0, import_node_path22.basename)(filePath).toLowerCase();
11546
+ const base = (0, import_node_path24.basename)(filePath).toLowerCase();
10823
11547
  return base.includes(".") ? base.split(".").pop() || "" : "";
10824
11548
  }
10825
11549
  function isImageFilePath(filePath) {
@@ -10829,24 +11553,24 @@ function imageMimeType(filePath) {
10829
11553
  return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
10830
11554
  }
10831
11555
  function ensureAttachmentsDir(worktreePath) {
10832
- const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
10833
- (0, import_node_fs27.mkdirSync)(dir, { recursive: true });
10834
- const gi = (0, import_node_path22.join)(dir, ".gitignore");
10835
- if (!(0, import_node_fs27.existsSync)(gi)) {
10836
- (0, import_node_fs27.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
11556
+ const dir = (0, import_node_path24.join)(worktreePath, ATTACHMENTS_DIR);
11557
+ (0, import_node_fs29.mkdirSync)(dir, { recursive: true });
11558
+ const gi = (0, import_node_path24.join)(dir, ".gitignore");
11559
+ if (!(0, import_node_fs29.existsSync)(gi)) {
11560
+ (0, import_node_fs29.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
10837
11561
  }
10838
11562
  return dir;
10839
11563
  }
10840
11564
  function uniqueAttachmentName(dir, originalName) {
10841
11565
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
10842
- if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10843
- const ext = (0, import_node_path22.extname)(safe);
11566
+ if (!(0, import_node_fs29.existsSync)((0, import_node_path24.join)(dir, safe))) return safe;
11567
+ const ext = (0, import_node_path24.extname)(safe);
10844
11568
  const stem = ext ? safe.slice(0, -ext.length) : safe;
10845
11569
  for (let i = 1; i < 1e4; i++) {
10846
11570
  const candidate = `${stem}-${i}${ext}`;
10847
- if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
11571
+ if (!(0, import_node_fs29.existsSync)((0, import_node_path24.join)(dir, candidate))) return candidate;
10848
11572
  }
10849
- return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
11573
+ return `${stem}-${(0, import_node_crypto7.randomUUID)()}${ext}`;
10850
11574
  }
10851
11575
  function previewDataUrlFromBuf(filePath, buf) {
10852
11576
  if (!isImageFilePath(filePath)) return void 0;
@@ -10858,7 +11582,7 @@ function attachmentFromBuffer(name, buf, opts) {
10858
11582
  if (isImageFilePath(name)) {
10859
11583
  const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
10860
11584
  return {
10861
- id: (0, import_node_crypto5.randomUUID)(),
11585
+ id: (0, import_node_crypto7.randomUUID)(),
10862
11586
  name,
10863
11587
  kind: "file",
10864
11588
  path: opts.path,
@@ -10871,7 +11595,7 @@ function attachmentFromBuffer(name, buf, opts) {
10871
11595
  }
10872
11596
  if (buf.length > MAX_INLINE_BYTES) {
10873
11597
  return {
10874
- id: (0, import_node_crypto5.randomUUID)(),
11598
+ id: (0, import_node_crypto7.randomUUID)(),
10875
11599
  name,
10876
11600
  kind: "file",
10877
11601
  path: opts.path,
@@ -10880,7 +11604,7 @@ function attachmentFromBuffer(name, buf, opts) {
10880
11604
  }
10881
11605
  if (buf.includes(0)) {
10882
11606
  return {
10883
- id: (0, import_node_crypto5.randomUUID)(),
11607
+ id: (0, import_node_crypto7.randomUUID)(),
10884
11608
  name,
10885
11609
  kind: "file",
10886
11610
  path: opts.path,
@@ -10888,7 +11612,7 @@ function attachmentFromBuffer(name, buf, opts) {
10888
11612
  };
10889
11613
  }
10890
11614
  return {
10891
- id: (0, import_node_crypto5.randomUUID)(),
11615
+ id: (0, import_node_crypto7.randomUUID)(),
10892
11616
  name,
10893
11617
  kind: "file",
10894
11618
  path: opts.path,
@@ -10900,19 +11624,19 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
10900
11624
  const dir = ensureAttachmentsDir(worktreePath);
10901
11625
  const out = [];
10902
11626
  for (const abs of absolutePaths) {
10903
- const originalName = (0, import_node_path22.basename)(abs);
11627
+ const originalName = (0, import_node_path24.basename)(abs);
10904
11628
  try {
10905
- const st = (0, import_node_fs27.statSync)(abs);
11629
+ const st = (0, import_node_fs29.statSync)(abs);
10906
11630
  if (!st.isFile()) continue;
10907
11631
  const name = uniqueAttachmentName(dir, originalName);
10908
- const destAbs = (0, import_node_path22.join)(dir, name);
10909
- (0, import_node_fs27.copyFileSync)(abs, destAbs);
11632
+ const destAbs = (0, import_node_path24.join)(dir, name);
11633
+ (0, import_node_fs29.copyFileSync)(abs, destAbs);
10910
11634
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10911
- const buf = (0, import_node_fs27.readFileSync)(destAbs);
11635
+ const buf = (0, import_node_fs29.readFileSync)(destAbs);
10912
11636
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10913
11637
  } catch (err) {
10914
11638
  out.push({
10915
- id: (0, import_node_crypto5.randomUUID)(),
11639
+ id: (0, import_node_crypto7.randomUUID)(),
10916
11640
  name: originalName,
10917
11641
  kind: "file",
10918
11642
  content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
@@ -10930,13 +11654,13 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
10930
11654
  try {
10931
11655
  const buf = Buffer.from(item.dataBase64, "base64");
10932
11656
  const name = uniqueAttachmentName(dir, originalName);
10933
- const destAbs = (0, import_node_path22.join)(dir, name);
10934
- (0, import_node_fs27.writeFileSync)(destAbs, buf);
11657
+ const destAbs = (0, import_node_path24.join)(dir, name);
11658
+ (0, import_node_fs29.writeFileSync)(destAbs, buf);
10935
11659
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10936
11660
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
10937
11661
  } catch (err) {
10938
11662
  out.push({
10939
- id: (0, import_node_crypto5.randomUUID)(),
11663
+ id: (0, import_node_crypto7.randomUUID)(),
10940
11664
  name: originalName,
10941
11665
  kind: "file",
10942
11666
  content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
@@ -10950,23 +11674,23 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10950
11674
  for (const rel of relativePaths) {
10951
11675
  if (!rel || rel.includes("..") || rel.startsWith("/")) {
10952
11676
  out.push({
10953
- id: (0, import_node_crypto5.randomUUID)(),
10954
- name: (0, import_node_path22.basename)(rel) || "file",
11677
+ id: (0, import_node_crypto7.randomUUID)(),
11678
+ name: (0, import_node_path24.basename)(rel) || "file",
10955
11679
  kind: "file",
10956
11680
  content: `(invalid path: ${rel})`
10957
11681
  });
10958
11682
  continue;
10959
11683
  }
10960
- const name = (0, import_node_path22.basename)(rel);
11684
+ const name = (0, import_node_path24.basename)(rel);
10961
11685
  try {
10962
- const abs = (0, import_node_path22.join)(worktreePath, rel);
10963
- const st = (0, import_node_fs27.statSync)(abs);
11686
+ const abs = (0, import_node_path24.join)(worktreePath, rel);
11687
+ const st = (0, import_node_fs29.statSync)(abs);
10964
11688
  if (!st.isFile()) continue;
10965
- const buf = (0, import_node_fs27.readFileSync)(abs);
11689
+ const buf = (0, import_node_fs29.readFileSync)(abs);
10966
11690
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10967
11691
  } catch (err) {
10968
11692
  out.push({
10969
- id: (0, import_node_crypto5.randomUUID)(),
11693
+ id: (0, import_node_crypto7.randomUUID)(),
10970
11694
  name,
10971
11695
  kind: "file",
10972
11696
  content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
@@ -10977,8 +11701,8 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10977
11701
  }
10978
11702
 
10979
11703
  // src/agents/instructions.ts
10980
- var import_node_fs28 = require("fs");
10981
- var import_node_path23 = require("path");
11704
+ var import_node_fs30 = require("fs");
11705
+ var import_node_path25 = require("path");
10982
11706
  init_worktree_labels();
10983
11707
  function normPath2(p) {
10984
11708
  return p.replace(/\/+$/, "");
@@ -11224,7 +11948,7 @@ var Orchestrator = class {
11224
11948
  }
11225
11949
  continue;
11226
11950
  }
11227
- if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
11951
+ if (!(0, import_node_fs33.existsSync)(thread.worktreePath)) {
11228
11952
  setStatus(thread.id, "broken", "Worktree missing on disk");
11229
11953
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
11230
11954
  continue;
@@ -11480,11 +12204,11 @@ var Orchestrator = class {
11480
12204
  return results;
11481
12205
  }
11482
12206
  /** Edit the text of a not-yet-started queued message. */
11483
- async editQueuedMessage(threadRef, index, text) {
12207
+ async editQueuedMessage(threadRef, index, text2) {
11484
12208
  const thread = this.requireThread(threadRef);
11485
12209
  return withThreadLock(thread.id, async () => {
11486
12210
  const current = this.requireThread(thread.id);
11487
- const trimmed = text.trim();
12211
+ const trimmed = text2.trim();
11488
12212
  if (!trimmed || index < 0 || index >= current.queue.length) {
11489
12213
  return current;
11490
12214
  }
@@ -11629,10 +12353,19 @@ var Orchestrator = class {
11629
12353
  attachments: sentAttachments ?? thread.attachments
11630
12354
  }
11631
12355
  );
11632
- const orchestrationReminder = isOrchestratorThread(thread) ? coordinatorTurnReminder({
11633
- parentId: threadId,
11634
- goal: thread.sourceRef || thread.title
11635
- }) : null;
12356
+ const slackInbound = /^(Slack DM|Slack @mention)(?:\n|$)/.test(
12357
+ expandedPrompt.trim()
12358
+ );
12359
+ const orchestrationReminder = isOrchestratorThread(thread) ? [
12360
+ coordinatorTurnReminder({
12361
+ parentId: threadId,
12362
+ goal: thread.sourceRef || thread.title
12363
+ }),
12364
+ slackInbound ? [
12365
+ "This turn is a Slack DM or @mention. Your reply is posted back in Slack \u2014 keep it concise. Sideboard signs it with this Mac's destination name; do not prefix the name yourself.",
12366
+ SLACK_REPLY_FORMATTING
12367
+ ].join("\n") : null
12368
+ ].filter(Boolean).join("\n") : null;
11636
12369
  const artifactReminder = thread.agent !== "brightsy" ? [
11637
12370
  "Sideboard side column (important):",
11638
12371
  "There is no claude.ai Artifact tool here \u2014 that is normal.",
@@ -11692,7 +12425,9 @@ var Orchestrator = class {
11692
12425
  goal: fresh.sourceRef || fresh.title || "Orchestration",
11693
12426
  parentId: fresh.id,
11694
12427
  workspaces: inventory,
11695
- audience: "desktop"
12428
+ audience: /^(Slack DM|Slack @mention)(?:\n|$)/.test(
12429
+ expandedPrompt.trim()
12430
+ ) ? "slack" : "desktop"
11696
12431
  });
11697
12432
  }
11698
12433
  }
@@ -12317,7 +13052,8 @@ var Orchestrator = class {
12317
13052
  isDraft: false,
12318
13053
  reviewDecision: null,
12319
13054
  baseRefName: "",
12320
- headRefName: ""
13055
+ headRefName: "",
13056
+ isInMergeQueue: false
12321
13057
  };
12322
13058
  await this.persistPrMetaAndMaybeArchive(thread, metaLike);
12323
13059
  return { url: metaLike.url, state };
@@ -12632,7 +13368,7 @@ var Orchestrator = class {
12632
13368
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
12633
13369
  return restored2;
12634
13370
  }
12635
- if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
13371
+ if (!(0, import_node_fs33.existsSync)(thread.worktreePath)) {
12636
13372
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
12637
13373
  const { execa: execa7 } = await import("execa");
12638
13374
  const slug = thread.worktreePath.split("/").pop();
@@ -12713,8 +13449,105 @@ init_worktree();
12713
13449
  init_run();
12714
13450
  init_app_settings();
12715
13451
 
12716
- // src/integrations/linear.ts
13452
+ // src/integrations/linear-oauth.ts
13453
+ var import_node_crypto8 = require("crypto");
13454
+ var import_node_http = require("http");
12717
13455
  init_app_settings();
13456
+
13457
+ // src/integrations/linear-app.ts
13458
+ var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
13459
+ var BAKED_LINEAR_CLIENT_SECRET = "";
13460
+
13461
+ // src/integrations/linear-oauth.ts
13462
+ var LINEAR_OAUTH_PORT = 19848;
13463
+ var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
13464
+ var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
13465
+ var REFRESH_SKEW_MS = 5 * 6e4;
13466
+ function linearOAuthCredentials() {
13467
+ const settings = loadAppSettings();
13468
+ const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
13469
+ const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || BAKED_LINEAR_CLIENT_SECRET.trim();
13470
+ if (!clientId) {
13471
+ throw new Error(
13472
+ `Linear browser sign-in needs a Linear OAuth app Client ID. Create one at linear.app/settings/api/applications/new with callback ${LINEAR_OAUTH_REDIRECT}, then set SIDEBOARD_LINEAR_CLIENT_ID (PKCE does not require a secret). You can still paste a personal API key.`
13473
+ );
13474
+ }
13475
+ return { clientId, clientSecret };
13476
+ }
13477
+ function linearAuthorizationHeader(token) {
13478
+ const t = token.trim();
13479
+ if (!t) return t;
13480
+ if (/^bearer\s+/i.test(t)) return t;
13481
+ if (t.startsWith("lin_api_")) return t;
13482
+ return `Bearer ${t}`;
13483
+ }
13484
+ async function exchangeLinearToken(body) {
13485
+ const res = await fetch(LINEAR_TOKEN, {
13486
+ method: "POST",
13487
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
13488
+ body
13489
+ });
13490
+ const data = await res.json().catch(() => ({}));
13491
+ if (!res.ok || data.error || !data.access_token?.trim()) {
13492
+ const detail = data.error_description || data.error || `HTTP ${res.status}`;
13493
+ throw new Error(`Linear OAuth token exchange failed: ${detail}`);
13494
+ }
13495
+ return data;
13496
+ }
13497
+ async function persistLinearTokens(data, viewer) {
13498
+ return saveLinearOAuth({
13499
+ accessToken: data.access_token.trim(),
13500
+ refreshToken: data.refresh_token?.trim(),
13501
+ expiresIn: data.expires_in,
13502
+ viewerName: viewer?.name,
13503
+ organizationName: viewer?.organizationName
13504
+ });
13505
+ }
13506
+ var refreshInFlight = null;
13507
+ async function refreshLinearAccessToken(refreshToken) {
13508
+ const { clientId, clientSecret } = linearOAuthCredentials();
13509
+ const body = new URLSearchParams({
13510
+ grant_type: "refresh_token",
13511
+ refresh_token: refreshToken,
13512
+ client_id: clientId
13513
+ });
13514
+ if (clientSecret) body.set("client_secret", clientSecret);
13515
+ try {
13516
+ const data = await exchangeLinearToken(body);
13517
+ await persistLinearTokens(data);
13518
+ return data.access_token.trim();
13519
+ } catch (err) {
13520
+ const message = err instanceof Error ? err.message : String(err);
13521
+ if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
13522
+ disconnectLinearConnection();
13523
+ throw new Error("Linear sign-in expired \u2014 connect again in Account settings");
13524
+ }
13525
+ throw err;
13526
+ }
13527
+ }
13528
+ async function getLinearAuthToken(settings = loadAppSettings()) {
13529
+ const access = settings.integrations.linearAccessToken?.trim() || "";
13530
+ const refresh = settings.integrations.linearRefreshToken?.trim() || "";
13531
+ const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
13532
+ const apiKey = settings.integrations.linearApiKey?.trim() || "";
13533
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS)) {
13534
+ return access;
13535
+ }
13536
+ if (refresh) {
13537
+ if (!refreshInFlight) {
13538
+ refreshInFlight = refreshLinearAccessToken(refresh).finally(() => {
13539
+ refreshInFlight = null;
13540
+ });
13541
+ }
13542
+ return refreshInFlight.catch((err) => {
13543
+ if (apiKey) return apiKey;
13544
+ throw err;
13545
+ });
13546
+ }
13547
+ return apiKey || null;
13548
+ }
13549
+
13550
+ // src/integrations/linear.ts
12718
13551
  var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
12719
13552
  var ASSIGNED_ISSUES_QUERY = `
12720
13553
  query SideboardAssignedIssues($first: Int!) {
@@ -12736,16 +13569,16 @@ query SideboardAssignedIssues($first: Int!) {
12736
13569
  }
12737
13570
  `;
12738
13571
  async function listLinearIssuesDirect(opts) {
12739
- const apiKey = (opts?.apiKey ?? getLinearApiKey())?.trim();
13572
+ const apiKey = (opts?.apiKey ?? await getLinearAuthToken())?.trim();
12740
13573
  if (!apiKey) {
12741
- throw new Error("Linear is not connected \u2014 add an API key in Account settings");
13574
+ throw new Error("Linear is not connected \u2014 sign in from Account settings");
12742
13575
  }
12743
13576
  const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
12744
13577
  const res = await fetch(LINEAR_GRAPHQL, {
12745
13578
  method: "POST",
12746
13579
  headers: {
12747
13580
  "Content-Type": "application/json",
12748
- Authorization: apiKey
13581
+ Authorization: linearAuthorizationHeader(apiKey)
12749
13582
  },
12750
13583
  body: JSON.stringify({
12751
13584
  query: ASSIGNED_ISSUES_QUERY,
@@ -12842,6 +13675,647 @@ function mcpArchiveBlockedReason(thread) {
12842
13675
 
12843
13676
  // src/mcp/server.ts
12844
13677
  init_profile();
13678
+
13679
+ // src/mcp/slack-tools.ts
13680
+ var import_zod = require("zod");
13681
+
13682
+ // src/slack/api.ts
13683
+ var SLACK_API = "https://slack.com/api";
13684
+ var SlackApiError = class extends Error {
13685
+ constructor(method, slackError) {
13686
+ super(`Slack ${method}: ${slackError}`);
13687
+ this.method = method;
13688
+ this.slackError = slackError;
13689
+ this.name = "SlackApiError";
13690
+ }
13691
+ method;
13692
+ slackError;
13693
+ };
13694
+ async function slackApi(token, method, params, fetchImpl) {
13695
+ const doFetch = fetchImpl ?? fetch;
13696
+ const body = new URLSearchParams();
13697
+ if (params) {
13698
+ for (const [key, value] of Object.entries(params)) {
13699
+ if (value === void 0) continue;
13700
+ body.set(key, String(value));
13701
+ }
13702
+ }
13703
+ const res = await doFetch(`${SLACK_API}/${method}`, {
13704
+ method: "POST",
13705
+ headers: {
13706
+ Authorization: `Bearer ${token}`,
13707
+ "Content-Type": "application/x-www-form-urlencoded"
13708
+ },
13709
+ body
13710
+ });
13711
+ const json = await res.json();
13712
+ if (!json.ok) {
13713
+ throw new SlackApiError(method, json.error || `HTTP ${res.status}`);
13714
+ }
13715
+ return json;
13716
+ }
13717
+
13718
+ // src/slack/destination.ts
13719
+ function isChannelId(raw) {
13720
+ return /^[CGD][A-Z0-9]+$/i.test(raw);
13721
+ }
13722
+ function isUserId(raw) {
13723
+ return /^U[A-Z0-9]+$/i.test(raw);
13724
+ }
13725
+ function isEmail(raw) {
13726
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw);
13727
+ }
13728
+ function normalizeSlackHandle(raw) {
13729
+ let s = raw.trim();
13730
+ const mention = s.match(/^<@([UW][A-Z0-9]+)(?:\|[^>]+)?>$/i);
13731
+ if (mention?.[1]) return mention[1];
13732
+ const channelMention = s.match(/^<#([CGD][A-Z0-9]+)(?:\|[^>]+)?>$/i);
13733
+ if (channelMention?.[1]) return channelMention[1];
13734
+ if (s.startsWith("#") || s.startsWith("@")) s = s.slice(1);
13735
+ return s.trim();
13736
+ }
13737
+ async function listSlackUsers(token, opts) {
13738
+ const query = opts?.query?.trim().toLowerCase() ?? "";
13739
+ const limit = Math.min(200, Math.max(1, opts?.limit ?? 50));
13740
+ const members = [];
13741
+ let cursor;
13742
+ do {
13743
+ const data = await slackApi(
13744
+ token,
13745
+ "users.list",
13746
+ {
13747
+ limit: 200,
13748
+ cursor: cursor || void 0
13749
+ },
13750
+ opts?.fetchImpl
13751
+ );
13752
+ for (const m of data.members ?? []) {
13753
+ if (!m.id || m.deleted || m.is_bot) continue;
13754
+ members.push(m);
13755
+ }
13756
+ cursor = data.response_metadata?.next_cursor?.trim() || void 0;
13757
+ } while (cursor && members.length < 2e3);
13758
+ const mapped = members.map((m) => ({
13759
+ id: m.id,
13760
+ name: m.name ?? m.id,
13761
+ real_name: m.profile?.real_name,
13762
+ display_name: m.profile?.display_name,
13763
+ email: m.profile?.email
13764
+ }));
13765
+ const filtered = query ? mapped.filter((u) => {
13766
+ const hay = [u.name, u.real_name, u.display_name, u.email, u.id].filter(Boolean).join(" ").toLowerCase();
13767
+ return hay.includes(query);
13768
+ }) : mapped;
13769
+ return filtered.slice(0, limit);
13770
+ }
13771
+ async function findUserId(token, handle, fetchImpl) {
13772
+ if (isUserId(handle)) {
13773
+ return { id: handle, name: handle };
13774
+ }
13775
+ if (isEmail(handle)) {
13776
+ try {
13777
+ const data = await slackApi(
13778
+ token,
13779
+ "users.lookupByEmail",
13780
+ { email: handle },
13781
+ fetchImpl
13782
+ );
13783
+ if (data.user?.id) {
13784
+ return {
13785
+ id: data.user.id,
13786
+ name: data.user.name ?? data.user.id,
13787
+ real_name: data.user.profile?.real_name,
13788
+ display_name: data.user.profile?.display_name,
13789
+ email: data.user.profile?.email
13790
+ };
13791
+ }
13792
+ } catch {
13793
+ }
13794
+ }
13795
+ const users = await listSlackUsers(token, { query: handle, limit: 20, fetchImpl });
13796
+ const exact = users.find(
13797
+ (u) => u.name.toLowerCase() === handle.toLowerCase() || u.display_name?.toLowerCase() === handle.toLowerCase() || u.real_name?.toLowerCase() === handle.toLowerCase() || u.email?.toLowerCase() === handle.toLowerCase()
13798
+ );
13799
+ if (exact) return exact;
13800
+ if (users.length === 1) return users[0];
13801
+ if (users.length === 0) {
13802
+ throw new Error(`No Slack user matching "${handle}"`);
13803
+ }
13804
+ throw new Error(
13805
+ `Ambiguous Slack user "${handle}". Matches: ${users.slice(0, 5).map((u) => `${u.name} (${u.id})`).join(", ")}. Pass a user id (U\u2026) or use slack_list_users.`
13806
+ );
13807
+ }
13808
+ async function openDmChannel(token, userId, fetchImpl) {
13809
+ const data = await slackApi(
13810
+ token,
13811
+ "conversations.open",
13812
+ { users: userId },
13813
+ fetchImpl
13814
+ );
13815
+ const id = data.channel?.id?.trim();
13816
+ if (!id) throw new Error(`Could not open Slack DM with user ${userId}`);
13817
+ return id;
13818
+ }
13819
+ async function resolveChannelByName(token, name, fetchImpl) {
13820
+ const data = await slackApi(
13821
+ token,
13822
+ "conversations.list",
13823
+ {
13824
+ types: "public_channel,private_channel",
13825
+ exclude_archived: true,
13826
+ limit: 200
13827
+ },
13828
+ fetchImpl
13829
+ );
13830
+ const match = (data.channels ?? []).find((c) => c.name?.toLowerCase() === name.toLowerCase());
13831
+ if (!match?.id) throw new Error(`No Slack channel named #${name}`);
13832
+ return match.id;
13833
+ }
13834
+ async function resolveSlackDestination(token, destination, opts) {
13835
+ const raw = destination.trim();
13836
+ if (!raw) throw new Error("destination is required");
13837
+ const hadAt = raw.startsWith("@") || /^<@/i.test(raw);
13838
+ const hadHash = raw.startsWith("#") || /^<#/i.test(raw);
13839
+ const handle = normalizeSlackHandle(raw);
13840
+ if (isChannelId(handle)) {
13841
+ return {
13842
+ channelId: handle,
13843
+ kind: handle.toUpperCase().startsWith("D") ? "dm" : "channel",
13844
+ label: handle
13845
+ };
13846
+ }
13847
+ if (isUserId(handle) || hadAt || isEmail(handle)) {
13848
+ const user = await findUserId(token, handle, opts?.fetchImpl);
13849
+ if (opts?.forRead) {
13850
+ const channelId2 = await openDmChannel(token, user.id, opts?.fetchImpl);
13851
+ return {
13852
+ channelId: channelId2,
13853
+ kind: "user",
13854
+ userId: user.id,
13855
+ label: user.display_name || user.real_name || user.name
13856
+ };
13857
+ }
13858
+ const channelId = await openDmChannel(token, user.id, opts?.fetchImpl);
13859
+ return {
13860
+ channelId,
13861
+ kind: "user",
13862
+ userId: user.id,
13863
+ label: user.display_name || user.real_name || user.name
13864
+ };
13865
+ }
13866
+ if (hadHash) {
13867
+ const channelId = await resolveChannelByName(token, handle, opts?.fetchImpl);
13868
+ return { channelId, kind: "channel", label: `#${handle}` };
13869
+ }
13870
+ try {
13871
+ const channelId = await resolveChannelByName(token, handle, opts?.fetchImpl);
13872
+ return { channelId, kind: "channel", label: `#${handle}` };
13873
+ } catch (channelErr) {
13874
+ try {
13875
+ const user = await findUserId(token, handle, opts?.fetchImpl);
13876
+ const channelId = await openDmChannel(token, user.id, opts?.fetchImpl);
13877
+ return {
13878
+ channelId,
13879
+ kind: "user",
13880
+ userId: user.id,
13881
+ label: user.display_name || user.real_name || user.name
13882
+ };
13883
+ } catch {
13884
+ throw channelErr;
13885
+ }
13886
+ }
13887
+ }
13888
+
13889
+ // src/slack/github-link.ts
13890
+ function labelGithubUrl(url) {
13891
+ const raw = url.trim();
13892
+ if (!raw) return null;
13893
+ let parsed;
13894
+ try {
13895
+ parsed = new URL(raw);
13896
+ } catch {
13897
+ return null;
13898
+ }
13899
+ if (!/(^|\.)github\.com$/i.test(parsed.hostname)) return null;
13900
+ const path = parsed.pathname;
13901
+ const hash = parsed.hash || "";
13902
+ const pr = path.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/i);
13903
+ if (pr) {
13904
+ const n = pr[3];
13905
+ if (/#discussion_r\d+/i.test(hash) || /#pullrequestreview-\d+/i.test(hash)) {
13906
+ return { kind: "comment", label: `PR #${n} comment`, url: raw };
13907
+ }
13908
+ if (/#issuecomment-\d+/i.test(hash)) {
13909
+ return { kind: "comment", label: `PR #${n} comment`, url: raw };
13910
+ }
13911
+ return { kind: "pr", label: `PR #${n}`, url: raw };
13912
+ }
13913
+ const issueComment = path.match(/\/([^/]+)\/([^/]+)\/issues\/(\d+)/i);
13914
+ if (issueComment && /#issuecomment-\d+/i.test(hash)) {
13915
+ return { kind: "comment", label: `Issue #${issueComment[3]} comment`, url: raw };
13916
+ }
13917
+ const blob = path.match(/\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/i);
13918
+ if (blob) {
13919
+ const filePath = decodeURIComponent(blob[3]);
13920
+ const fileName = filePath.split("/").pop() || filePath;
13921
+ const line = hash.match(/^#L(\d+)(?:-L(\d+))?$/i);
13922
+ if (line) {
13923
+ const start = line[1];
13924
+ const end = line[2];
13925
+ const loc = end && end !== start ? `${start}-${end}` : start;
13926
+ return { kind: "code", label: `${fileName}:${loc}`, url: raw };
13927
+ }
13928
+ return { kind: "code", label: fileName, url: raw };
13929
+ }
13930
+ return { kind: "other", label: "GitHub", url: raw };
13931
+ }
13932
+ function appendGithubLink(text2, githubUrl) {
13933
+ const body = text2.trimEnd();
13934
+ if (!githubUrl?.trim()) return body;
13935
+ const labeled = labelGithubUrl(githubUrl);
13936
+ if (!labeled) {
13937
+ return body ? `${body}
13938
+ ${githubUrl.trim()}` : githubUrl.trim();
13939
+ }
13940
+ const link = `<${labeled.url}|${labeled.label}>`;
13941
+ return body ? `${body}
13942
+ ${link}` : link;
13943
+ }
13944
+
13945
+ // src/slack/outbound-watch.ts
13946
+ var import_node_fs34 = require("fs");
13947
+ var import_node_path29 = require("path");
13948
+ init_paths();
13949
+ init_private_file();
13950
+ init_secure_file();
13951
+
13952
+ // src/slack/workspaces.ts
13953
+ var import_node_path28 = require("path");
13954
+ init_paths();
13955
+ init_secure_file();
13956
+ function storePath2() {
13957
+ return (0, import_node_path28.join)(appDataDir(), "slack-workspaces.json");
13958
+ }
13959
+ function readStore2() {
13960
+ try {
13961
+ const path = storePath2();
13962
+ const wasEncrypted = isSecureFileEncrypted(path);
13963
+ const parsed = readSecureJson(path);
13964
+ const workspaces = Array.isArray(parsed?.workspaces) ? parsed.workspaces : [];
13965
+ if (workspaces.length > 0 && !wasEncrypted && resolveVaultKey()) {
13966
+ writeSecureJson(path, { workspaces });
13967
+ }
13968
+ return workspaces;
13969
+ } catch {
13970
+ return [];
13971
+ }
13972
+ }
13973
+ function toInfo(ws) {
13974
+ return {
13975
+ team_id: ws.team_id,
13976
+ team_name: ws.team_name,
13977
+ user_id: ws.user_id,
13978
+ has_bot_token: Boolean(ws.bot_token),
13979
+ has_user_token: Boolean(ws.user_token),
13980
+ connected_at: ws.connected_at
13981
+ };
13982
+ }
13983
+ function listSlackWorkspaces() {
13984
+ return readStore2().map(toInfo).sort((a, b) => a.team_name.localeCompare(b.team_name));
13985
+ }
13986
+ function getSlackWorkspace(teamId) {
13987
+ const id = teamId.trim();
13988
+ if (!id) return null;
13989
+ return readStore2().find(
13990
+ (ws) => ws.team_id === id || ws.team_name.toLowerCase() === id.toLowerCase()
13991
+ ) ?? null;
13992
+ }
13993
+ function slackTokenFor(ws, kind = "read") {
13994
+ if (kind === "search") {
13995
+ const token2 = ws.user_token?.trim();
13996
+ if (!token2) {
13997
+ throw new Error(
13998
+ `Slack search needs a user token for ${ws.team_name}. Reconnect via Account \u2192 Slack (browser) or paste an xoxp- token.`
13999
+ );
14000
+ }
14001
+ return token2;
14002
+ }
14003
+ const token = (kind === "write" ? ws.bot_token || ws.user_token : ws.user_token || ws.bot_token)?.trim();
14004
+ if (!token) {
14005
+ throw new Error(`Slack workspace ${ws.team_name} has no token`);
14006
+ }
14007
+ return token;
14008
+ }
14009
+ function requireSlackWorkspace(teamId) {
14010
+ const ws = getSlackWorkspace(teamId);
14011
+ if (!ws) {
14012
+ const connected = listSlackWorkspaces();
14013
+ const hint = connected.length === 0 ? "Connect a workspace in Account \u2192 Slack workspaces." : `Connected: ${connected.map((t) => `${t.team_name} (${t.team_id})`).join(", ")}`;
14014
+ throw new Error(`Unknown Slack team_id "${teamId}". ${hint}`);
14015
+ }
14016
+ return ws;
14017
+ }
14018
+
14019
+ // src/slack/outbound-watch.ts
14020
+ var MAX_WATCHES = 40;
14021
+ var WATCH_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
14022
+ function storePath3() {
14023
+ return (0, import_node_path29.join)(appDataDir(), "slack-outbound-watch.json");
14024
+ }
14025
+ function watchId(teamId, channelId, ts) {
14026
+ return `${teamId}:${channelId}:${ts}`;
14027
+ }
14028
+ function slackArchiveUrl(channelId, ts) {
14029
+ return `https://slack.com/archives/${channelId}/p${ts.replace(".", "")}`;
14030
+ }
14031
+ function readStore3() {
14032
+ const path = storePath3();
14033
+ if (!(0, import_node_fs34.existsSync)(path)) return [];
14034
+ try {
14035
+ const parsed = isSecureFileEncrypted(path) ? readSecureJson(path) : JSON.parse((0, import_node_fs34.readFileSync)(path, "utf8"));
14036
+ return Array.isArray(parsed?.watches) ? parsed.watches : [];
14037
+ } catch {
14038
+ return [];
14039
+ }
14040
+ }
14041
+ function writeStore2(watches) {
14042
+ writePrivateFile(storePath3(), `${JSON.stringify({ watches }, null, 2)}
14043
+ `);
14044
+ return watches;
14045
+ }
14046
+ function pruneWatches(watches, nowMs = Date.now()) {
14047
+ const cutoff = nowMs - WATCH_TTL_MS;
14048
+ const kept = watches.filter((w) => {
14049
+ const posted = Date.parse(w.postedAt);
14050
+ return Number.isFinite(posted) ? posted >= cutoff : true;
14051
+ });
14052
+ if (kept.length <= MAX_WATCHES) return kept;
14053
+ return kept.slice().sort((a, b) => b.postedAt.localeCompare(a.postedAt)).slice(0, MAX_WATCHES);
14054
+ }
14055
+ function recordSlackOutboundWatch(input) {
14056
+ const ts = input.ts.trim();
14057
+ const channelId = input.channelId.trim();
14058
+ const teamId = input.teamId.trim();
14059
+ if (!ts || !channelId || !teamId) return null;
14060
+ const owner = input.ownerUserId?.trim();
14061
+ const toUser = input.toUserId?.trim();
14062
+ if (toUser && owner && toUser === owner) return null;
14063
+ const id = watchId(teamId, channelId, ts);
14064
+ const next = {
14065
+ id,
14066
+ teamId,
14067
+ channelId,
14068
+ ts,
14069
+ threadTs: input.threadTs?.trim() || ts,
14070
+ kind: input.kind === "dm" ? "dm" : "channel",
14071
+ toUserId: toUser,
14072
+ toLabel: input.toLabel.trim() || toUser || channelId,
14073
+ ownerUserId: owner,
14074
+ postedAt: (/* @__PURE__ */ new Date()).toISOString(),
14075
+ lastSeenTs: ts,
14076
+ unread: false,
14077
+ permalink: slackArchiveUrl(channelId, ts)
14078
+ };
14079
+ const watches = pruneWatches(readStore3().filter((w) => w.id !== id));
14080
+ watches.unshift(next);
14081
+ writeStore2(pruneWatches(watches));
14082
+ return next;
14083
+ }
14084
+
14085
+ // src/mcp/slack-tools.ts
14086
+ function text(payload, isError = false) {
14087
+ return {
14088
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
14089
+ ...isError ? { isError: true } : {}
14090
+ };
14091
+ }
14092
+ function fail(err) {
14093
+ return text(
14094
+ { error: err instanceof Error ? err.message : String(err) },
14095
+ true
14096
+ );
14097
+ }
14098
+ function registerSlackTools(server) {
14099
+ server.tool(
14100
+ "list_teams",
14101
+ "List Slack workspaces connected in Sideboard Account settings. Each row is team_id + name. Pass team_id to slack_list_channels, slack_list_users, slack_search, slack_read, and slack_post.",
14102
+ {},
14103
+ async () => {
14104
+ const teams = listSlackWorkspaces();
14105
+ if (teams.length === 0) {
14106
+ return text({
14107
+ teams: [],
14108
+ hint: "No Slack workspaces connected. Add one in Account \u2192 Slack workspaces (paste xoxb-/xoxp- or browser sign-in)."
14109
+ });
14110
+ }
14111
+ return text({
14112
+ teams: teams.map((t) => ({
14113
+ team_id: t.team_id,
14114
+ name: t.team_name,
14115
+ search: t.has_user_token
14116
+ }))
14117
+ });
14118
+ }
14119
+ );
14120
+ server.tool(
14121
+ "slack_list_channels",
14122
+ "List channels in a connected Slack workspace. Pass team_id from list_teams.",
14123
+ {
14124
+ team_id: import_zod.z.string(),
14125
+ limit: import_zod.z.number().optional()
14126
+ },
14127
+ async ({ team_id, limit }) => {
14128
+ try {
14129
+ const ws = requireSlackWorkspace(team_id);
14130
+ const token = slackTokenFor(ws, "read");
14131
+ const data = await slackApi(token, "conversations.list", {
14132
+ types: "public_channel,private_channel",
14133
+ exclude_archived: true,
14134
+ limit: Math.min(200, Math.max(1, limit ?? 100))
14135
+ });
14136
+ return text({
14137
+ team_id: ws.team_id,
14138
+ team_name: ws.team_name,
14139
+ channels: (data.channels ?? []).map((c) => ({
14140
+ id: c.id,
14141
+ name: c.name,
14142
+ private: Boolean(c.is_private),
14143
+ member: Boolean(c.is_member)
14144
+ }))
14145
+ });
14146
+ } catch (err) {
14147
+ return fail(err);
14148
+ }
14149
+ }
14150
+ );
14151
+ server.tool(
14152
+ "slack_list_users",
14153
+ "List or search people in a connected Slack workspace (for DMs / @mentions). Pass team_id from list_teams. Optional query matches name, display name, real name, or email.",
14154
+ {
14155
+ team_id: import_zod.z.string(),
14156
+ query: import_zod.z.string().optional(),
14157
+ limit: import_zod.z.number().optional()
14158
+ },
14159
+ async ({ team_id, query, limit }) => {
14160
+ try {
14161
+ const ws = requireSlackWorkspace(team_id);
14162
+ const token = slackTokenFor(ws, "read");
14163
+ const users = await listSlackUsers(token, { query, limit });
14164
+ return text({
14165
+ team_id: ws.team_id,
14166
+ team_name: ws.team_name,
14167
+ query: query?.trim() || void 0,
14168
+ users
14169
+ });
14170
+ } catch (err) {
14171
+ return fail(err);
14172
+ }
14173
+ }
14174
+ );
14175
+ server.tool(
14176
+ "slack_search",
14177
+ "Search messages in a connected Slack workspace (needs a user token). Pass team_id from list_teams.",
14178
+ {
14179
+ team_id: import_zod.z.string(),
14180
+ query: import_zod.z.string(),
14181
+ count: import_zod.z.number().optional()
14182
+ },
14183
+ async ({ team_id, query, count }) => {
14184
+ try {
14185
+ const ws = requireSlackWorkspace(team_id);
14186
+ const token = slackTokenFor(ws, "search");
14187
+ const data = await slackApi(token, "search.messages", {
14188
+ query,
14189
+ count: Math.min(50, Math.max(1, count ?? 10)),
14190
+ sort: "timestamp"
14191
+ });
14192
+ return text({
14193
+ team_id: ws.team_id,
14194
+ team_name: ws.team_name,
14195
+ query,
14196
+ matches: (data.messages?.matches ?? []).map((m) => ({
14197
+ channel: m.channel?.name || m.channel?.id,
14198
+ user: m.username || m.user,
14199
+ ts: m.ts,
14200
+ text: m.text,
14201
+ permalink: m.permalink
14202
+ }))
14203
+ });
14204
+ } catch (err) {
14205
+ return fail(err);
14206
+ }
14207
+ }
14208
+ );
14209
+ server.tool(
14210
+ "slack_read",
14211
+ "Read recent messages in a Slack channel or DM, or a thread when ts is set. Pass team_id from list_teams and #channel, @user, or a C\u2026/D\u2026/U\u2026 id.",
14212
+ {
14213
+ team_id: import_zod.z.string(),
14214
+ channel: import_zod.z.string(),
14215
+ ts: import_zod.z.string().optional(),
14216
+ limit: import_zod.z.number().optional()
14217
+ },
14218
+ async ({ team_id, channel, ts, limit }) => {
14219
+ try {
14220
+ const ws = requireSlackWorkspace(team_id);
14221
+ const token = slackTokenFor(ws, "read");
14222
+ const dest = await resolveSlackDestination(token, channel, { forRead: true });
14223
+ const n = Math.min(100, Math.max(1, limit ?? 20));
14224
+ if (ts?.trim()) {
14225
+ const data2 = await slackApi(token, "conversations.replies", {
14226
+ channel: dest.channelId,
14227
+ ts: ts.trim(),
14228
+ limit: n
14229
+ });
14230
+ return text({
14231
+ team_id: ws.team_id,
14232
+ channel: dest.channelId,
14233
+ label: dest.label,
14234
+ thread_ts: ts.trim(),
14235
+ messages: data2.messages ?? []
14236
+ });
14237
+ }
14238
+ const data = await slackApi(token, "conversations.history", {
14239
+ channel: dest.channelId,
14240
+ limit: n
14241
+ });
14242
+ return text({
14243
+ team_id: ws.team_id,
14244
+ channel: dest.channelId,
14245
+ label: dest.label,
14246
+ messages: data.messages ?? []
14247
+ });
14248
+ } catch (err) {
14249
+ return fail(err);
14250
+ }
14251
+ }
14252
+ );
14253
+ server.tool(
14254
+ "slack_post",
14255
+ "Post a message to a Slack channel or DM (as the Sideboard bot). Pass team_id from list_teams. Use to or channel for #name, @user, or C\u2026/D\u2026/U\u2026 ids. Optional github_url appends a PR / code / comment link. Only notify when the user asks. Thread with thread_ts when set.",
14256
+ {
14257
+ team_id: import_zod.z.string(),
14258
+ channel: import_zod.z.string().optional(),
14259
+ to: import_zod.z.string().optional(),
14260
+ text: import_zod.z.string(),
14261
+ github_url: import_zod.z.string().optional(),
14262
+ thread_ts: import_zod.z.string().optional()
14263
+ },
14264
+ async ({ team_id, channel, to, text: message, github_url, thread_ts }) => {
14265
+ try {
14266
+ const destRaw = (to ?? channel)?.trim();
14267
+ if (!destRaw) {
14268
+ return fail(new Error("slack_post needs `to` or `channel` (#name, @user, or Slack id)"));
14269
+ }
14270
+ const ws = requireSlackWorkspace(team_id);
14271
+ const token = slackTokenFor(ws, "write");
14272
+ const dest = await resolveSlackDestination(token, destRaw);
14273
+ const body = appendGithubLink(message, github_url);
14274
+ const data = await slackApi(
14275
+ token,
14276
+ "chat.postMessage",
14277
+ {
14278
+ channel: dest.channelId,
14279
+ text: body,
14280
+ thread_ts: thread_ts?.trim() || void 0,
14281
+ unfurl_links: true,
14282
+ unfurl_media: true
14283
+ }
14284
+ );
14285
+ const postedTs = data.ts?.trim();
14286
+ const postedChannel = (data.channel || dest.channelId).trim();
14287
+ if (postedTs && postedChannel) {
14288
+ try {
14289
+ recordSlackOutboundWatch({
14290
+ teamId: ws.team_id,
14291
+ channelId: postedChannel,
14292
+ ts: postedTs,
14293
+ threadTs: thread_ts?.trim() || postedTs,
14294
+ kind: dest.kind === "channel" ? "channel" : "dm",
14295
+ toUserId: dest.userId,
14296
+ toLabel: dest.label,
14297
+ ownerUserId: ws.user_id
14298
+ });
14299
+ } catch {
14300
+ }
14301
+ }
14302
+ return text({
14303
+ ok: true,
14304
+ team_id: ws.team_id,
14305
+ channel: postedChannel,
14306
+ label: dest.label,
14307
+ kind: dest.kind,
14308
+ user_id: dest.userId,
14309
+ ts: postedTs
14310
+ });
14311
+ } catch (err) {
14312
+ return fail(err);
14313
+ }
14314
+ }
14315
+ );
14316
+ }
14317
+
14318
+ // src/mcp/server.ts
12845
14319
  var MAX_ORCH_THREADS = 5;
12846
14320
  var CREATE_THREAD_TIMEOUT_MS = 9e4;
12847
14321
  function withTimeout(promise, ms, label) {
@@ -12906,7 +14380,7 @@ async function startMcpServer() {
12906
14380
  async () => {
12907
14381
  const threads = orch.getThreads(true);
12908
14382
  const lines = threads.map((t) => {
12909
- const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path26.basename)(t.repoPath) || t.repoPath;
14383
+ const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path31.basename)(t.repoPath) || t.repoPath;
12910
14384
  return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}`;
12911
14385
  });
12912
14386
  return {
@@ -12917,7 +14391,7 @@ async function startMcpServer() {
12917
14391
  server.tool(
12918
14392
  "get_thread",
12919
14393
  "Get a compact thread summary by id/ref",
12920
- { ref: import_zod.z.string() },
14394
+ { ref: import_zod2.z.string() },
12921
14395
  async ({ ref }) => {
12922
14396
  const t = orch.getThread(ref);
12923
14397
  if (!t) {
@@ -12946,14 +14420,14 @@ async function startMcpServer() {
12946
14420
  "present_artifact",
12947
14421
  "Show an HTML, SVG, markdown, or React document in Sideboard\u2019s Claude-style side column (desktop). Use instead of claude.ai\u2019s artifact tool \u2014 pass the full document content. Prefer type=html for interactive pages. For type=react, pass a single component module that `export default`s a component (JSX/TSX ok) \u2014 Sideboard bootstraps React/ReactDOM/Babel and renders it; only `react`/`react-dom` imports are available, no other npm packages.",
12948
14422
  {
12949
- title: import_zod.z.string().describe("Short title shown in the artifact pane header"),
12950
- type: import_zod.z.enum(["html", "svg", "markdown", "react"]).describe(
14423
+ title: import_zod2.z.string().describe("Short title shown in the artifact pane header"),
14424
+ type: import_zod2.z.enum(["html", "svg", "markdown", "react"]).describe(
12951
14425
  "Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
12952
14426
  ),
12953
- content: import_zod.z.string().describe(
14427
+ content: import_zod2.z.string().describe(
12954
14428
  "Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
12955
14429
  ),
12956
- artifact_id: import_zod.z.string().optional().describe("Stable id when updating the same artifact across turns")
14430
+ artifact_id: import_zod2.z.string().optional().describe("Stable id when updating the same artifact across turns")
12957
14431
  },
12958
14432
  async ({ title, type, artifact_id }) => {
12959
14433
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -12971,15 +14445,15 @@ async function startMcpServer() {
12971
14445
  "ask_user",
12972
14446
  "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
12973
14447
  {
12974
- questions: import_zod.z.array(
12975
- import_zod.z.object({
12976
- question: import_zod.z.string().describe("Full question text ending with ?"),
12977
- header: import_zod.z.string().max(24).optional().describe("Short label shown above the question"),
12978
- multiSelect: import_zod.z.boolean().optional().describe("Allow selecting multiple options"),
12979
- options: import_zod.z.array(
12980
- import_zod.z.object({
12981
- label: import_zod.z.string(),
12982
- description: import_zod.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
14448
+ questions: import_zod2.z.array(
14449
+ import_zod2.z.object({
14450
+ question: import_zod2.z.string().describe("Full question text ending with ?"),
14451
+ header: import_zod2.z.string().max(24).optional().describe("Short label shown above the question"),
14452
+ multiSelect: import_zod2.z.boolean().optional().describe("Allow selecting multiple options"),
14453
+ options: import_zod2.z.array(
14454
+ import_zod2.z.object({
14455
+ label: import_zod2.z.string(),
14456
+ description: import_zod2.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
12983
14457
  })
12984
14458
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
12985
14459
  })
@@ -12998,9 +14472,9 @@ async function startMcpServer() {
12998
14472
  "present_plan",
12999
14473
  "Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
13000
14474
  {
13001
- title: import_zod.z.string().optional().describe("Short plan title (defaults to Plan)"),
13002
- content: import_zod.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
13003
- thread_id: import_zod.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
14475
+ title: import_zod2.z.string().optional().describe("Short plan title (defaults to Plan)"),
14476
+ content: import_zod2.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
14477
+ thread_id: import_zod2.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
13004
14478
  },
13005
14479
  async ({ title, content, thread_id }) => {
13006
14480
  const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
@@ -13021,17 +14495,17 @@ async function startMcpServer() {
13021
14495
  );
13022
14496
  server.tool(
13023
14497
  "present_schema",
13024
- "Open Sideboard\u2019s schema-driven CMS side column (filterable table and/or form). Pass JSON Schema + schemaUi (Brightsy extensions supported). Use datasource=brightsy with resource_id (record type UUID) when logged into Brightsy; use datasource=inline with embedded resource/records for any other source.",
14498
+ "Open Sideboard\u2019s schema-driven side column (filterable table and/or form). Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
13025
14499
  {
13026
- title: import_zod.z.string().describe("Pane title"),
13027
- mode: import_zod.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
13028
- datasource: import_zod.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
13029
- resource_id: import_zod.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
13030
- record_id: import_zod.z.string().optional().describe("Record id when opening form mode"),
13031
- resource: import_zod.z.record(import_zod.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
13032
- record: import_zod.z.record(import_zod.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
13033
- records: import_zod.z.array(import_zod.z.record(import_zod.z.unknown())).optional().describe("Inline records for table mode"),
13034
- pane_id: import_zod.z.string().optional().describe("Stable pane id across updates")
14500
+ title: import_zod2.z.string().describe("Pane title"),
14501
+ mode: import_zod2.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
14502
+ datasource: import_zod2.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
14503
+ resource_id: import_zod2.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
14504
+ record_id: import_zod2.z.string().optional().describe("Record id when opening form mode"),
14505
+ resource: import_zod2.z.record(import_zod2.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
14506
+ record: import_zod2.z.record(import_zod2.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
14507
+ records: import_zod2.z.array(import_zod2.z.record(import_zod2.z.unknown())).optional().describe("Inline records for table mode"),
14508
+ pane_id: import_zod2.z.string().optional().describe("Stable pane id across updates")
13035
14509
  },
13036
14510
  async (args) => {
13037
14511
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -13052,10 +14526,10 @@ async function startMcpServer() {
13052
14526
  "present_files",
13053
14527
  "Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
13054
14528
  {
13055
- title: import_zod.z.string().optional().describe("Pane title (default: Files)"),
13056
- datasource: import_zod.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
13057
- path: import_zod.z.string().optional().describe("Initial folder path (e.g. public)"),
13058
- pane_id: import_zod.z.string().optional().describe("Stable pane id across updates")
14529
+ title: import_zod2.z.string().optional().describe("Pane title (default: Files)"),
14530
+ datasource: import_zod2.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
14531
+ path: import_zod2.z.string().optional().describe("Initial folder path (e.g. public)"),
14532
+ pane_id: import_zod2.z.string().optional().describe("Stable pane id across updates")
13059
14533
  },
13060
14534
  async (args) => {
13061
14535
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -13070,23 +14544,62 @@ async function startMcpServer() {
13070
14544
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
13071
14545
  }
13072
14546
  );
14547
+ registerSlackTools(server);
13073
14548
  if (sideboardMcpProfile() !== "worktree") {
14549
+ const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await Promise.resolve().then(() => (init_caffeinate_hold(), caffeinate_hold_exports));
13074
14550
  const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
13075
14551
  const accountDefaults = resolveThreadDefaults2();
13076
14552
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
14553
+ server.tool(
14554
+ "set_caffeinate",
14555
+ "Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. macOS only.",
14556
+ {
14557
+ enabled: import_zod2.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
14558
+ },
14559
+ async ({ enabled }) => {
14560
+ const state = setCaffeinateHold2(enabled);
14561
+ if (enabled && !state.held) {
14562
+ return {
14563
+ content: [
14564
+ {
14565
+ type: "text",
14566
+ text: JSON.stringify({
14567
+ ...state,
14568
+ ok: false,
14569
+ message: state.platform === "darwin" ? "Could not start caffeinate." : "Caffeinate is macOS only."
14570
+ })
14571
+ }
14572
+ ],
14573
+ isError: true
14574
+ };
14575
+ }
14576
+ return {
14577
+ content: [
14578
+ {
14579
+ type: "text",
14580
+ text: JSON.stringify({
14581
+ ...getCaffeinateHold2(),
14582
+ ok: true,
14583
+ message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false (or the user says they are done)." : "Caffeinate hold released. The Mac can sleep (unless Settings caffeinate toggles are on)."
14584
+ })
14585
+ }
14586
+ ]
14587
+ };
14588
+ }
14589
+ );
13077
14590
  server.tool(
13078
14591
  "create_thread",
13079
14592
  `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Then use send_to_thread to chat.`,
13080
14593
  {
13081
- sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
13082
- sourceRef: import_zod.z.string(),
13083
- agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
13084
- model: import_zod.z.string().nullable().optional().describe(
14594
+ sourceType: import_zod2.z.enum(["branch", "pr", "ticket"]),
14595
+ sourceRef: import_zod2.z.string(),
14596
+ agent: import_zod2.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
14597
+ model: import_zod2.z.string().nullable().optional().describe(
13085
14598
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
13086
14599
  ),
13087
- repoPath: import_zod.z.string(),
13088
- title: import_zod.z.string().optional(),
13089
- parentThreadId: import_zod.z.string().optional().describe(
14600
+ repoPath: import_zod2.z.string(),
14601
+ title: import_zod2.z.string().optional(),
14602
+ parentThreadId: import_zod2.z.string().optional().describe(
13090
14603
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
13091
14604
  )
13092
14605
  },
@@ -13193,9 +14706,9 @@ async function startMcpServer() {
13193
14706
  "send_to_thread",
13194
14707
  "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. 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.",
13195
14708
  {
13196
- ref: import_zod.z.string(),
13197
- prompt: import_zod.z.string(),
13198
- force_stop: import_zod.z.boolean().optional()
14709
+ ref: import_zod2.z.string(),
14710
+ prompt: import_zod2.z.string(),
14711
+ force_stop: import_zod2.z.boolean().optional()
13199
14712
  },
13200
14713
  async ({ ref, prompt, force_stop }) => {
13201
14714
  if (force_stop) {
@@ -13224,8 +14737,8 @@ async function startMcpServer() {
13224
14737
  "wait_for_turn",
13225
14738
  "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
13226
14739
  {
13227
- ref: import_zod.z.string(),
13228
- timeoutMs: import_zod.z.number().optional()
14740
+ ref: import_zod2.z.string(),
14741
+ timeoutMs: import_zod2.z.number().optional()
13229
14742
  },
13230
14743
  async ({ ref, timeoutMs }) => {
13231
14744
  const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
@@ -13247,7 +14760,7 @@ async function startMcpServer() {
13247
14760
  server.tool(
13248
14761
  "get_turn_result",
13249
14762
  "Final assistant message only (not full transcript)",
13250
- { ref: import_zod.z.string() },
14763
+ { ref: import_zod2.z.string() },
13251
14764
  async ({ ref }) => {
13252
14765
  const result = orch.getTurnResult(ref);
13253
14766
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
@@ -13257,8 +14770,8 @@ async function startMcpServer() {
13257
14770
  "stop_thread",
13258
14771
  "Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
13259
14772
  {
13260
- ref: import_zod.z.string(),
13261
- force: import_zod.z.boolean().optional()
14773
+ ref: import_zod2.z.string(),
14774
+ force: import_zod2.z.boolean().optional()
13262
14775
  },
13263
14776
  async ({ ref, force }) => {
13264
14777
  const t = orch.getThread(ref);
@@ -13287,8 +14800,8 @@ async function startMcpServer() {
13287
14800
  );
13288
14801
  server.tool(
13289
14802
  "archive_thread",
13290
- "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Cannot archive the cloud coordinator \u2014 use the Sideboard UI for that.",
13291
- { ref: import_zod.z.string() },
14803
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators open PRs only by asking the worktree agent.",
14804
+ { ref: import_zod2.z.string() },
13292
14805
  async ({ ref }) => {
13293
14806
  const t = orch.getThread(ref);
13294
14807
  if (!t) {
@@ -13321,7 +14834,7 @@ async function startMcpServer() {
13321
14834
  server.tool(
13322
14835
  "restore_thread",
13323
14836
  "Restore an archived thread (recreates worktree from branch when needed)",
13324
- { ref: import_zod.z.string() },
14837
+ { ref: import_zod2.z.string() },
13325
14838
  async ({ ref }) => {
13326
14839
  try {
13327
14840
  const restored = await orch.restore(ref);
@@ -13347,8 +14860,8 @@ async function startMcpServer() {
13347
14860
  "get_diff",
13348
14861
  "Compact diff summary (capped hunks, paginated)",
13349
14862
  {
13350
- ref: import_zod.z.string(),
13351
- maxFiles: import_zod.z.number().optional()
14863
+ ref: import_zod2.z.string(),
14864
+ maxFiles: import_zod2.z.number().optional()
13352
14865
  },
13353
14866
  async ({ ref, maxFiles }) => {
13354
14867
  const summary = await orch.diffSummary(ref);
@@ -13368,7 +14881,7 @@ async function startMcpServer() {
13368
14881
  server.tool(
13369
14882
  "request_review",
13370
14883
  'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
13371
- { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
14884
+ { ref: import_zod2.z.string().describe("Worktree thread id/ref to review") },
13372
14885
  async ({ ref }) => {
13373
14886
  try {
13374
14887
  const tab = await orch.requestReview(ref);
@@ -13393,7 +14906,7 @@ async function startMcpServer() {
13393
14906
  }
13394
14907
  }
13395
14908
  );
13396
- const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
14909
+ const agentEnum = import_zod2.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
13397
14910
  server.tool(
13398
14911
  "list_models",
13399
14912
  "List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
@@ -13416,11 +14929,11 @@ async function startMcpServer() {
13416
14929
  "fork_worktree",
13417
14930
  "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
13418
14931
  {
13419
- ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
13420
- through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
14932
+ ref: import_zod2.z.string().describe("Worktree thread id/ref to fork"),
14933
+ through_index: import_zod2.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13421
14934
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13422
- model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13423
- title: import_zod.z.string().optional()
14935
+ model: import_zod2.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
14936
+ title: import_zod2.z.string().optional()
13424
14937
  },
13425
14938
  async ({ ref, through_index, agent, model, title }) => {
13426
14939
  try {
@@ -13459,13 +14972,13 @@ async function startMcpServer() {
13459
14972
  );
13460
14973
  server.tool(
13461
14974
  "fork_chat",
13462
- "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Remote coordinators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
14975
+ "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
13463
14976
  {
13464
- ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
13465
- through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
14977
+ ref: import_zod2.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
14978
+ through_index: import_zod2.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13466
14979
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13467
- model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13468
- title: import_zod.z.string().optional()
14980
+ model: import_zod2.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
14981
+ title: import_zod2.z.string().optional()
13469
14982
  },
13470
14983
  async ({ ref, through_index, agent, model, title }) => {
13471
14984
  try {
@@ -13511,8 +15024,8 @@ async function startMcpServer() {
13511
15024
  "run_dev_script",
13512
15025
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
13513
15026
  {
13514
- ref: import_zod.z.string(),
13515
- name: import_zod.z.string().optional()
15027
+ ref: import_zod2.z.string(),
15028
+ name: import_zod2.z.string().optional()
13516
15029
  },
13517
15030
  async ({ ref, name }) => {
13518
15031
  const result = await orch.startDev(ref, name);
@@ -13534,7 +15047,7 @@ async function startMcpServer() {
13534
15047
  server.tool(
13535
15048
  "list_run_scripts",
13536
15049
  "List named run scripts available for a thread",
13537
- { ref: import_zod.z.string() },
15050
+ { ref: import_zod2.z.string() },
13538
15051
  async ({ ref }) => {
13539
15052
  const scripts = orch.listThreadRunScripts(ref);
13540
15053
  const active = orch.getActiveRuns(ref);
@@ -13552,8 +15065,8 @@ async function startMcpServer() {
13552
15065
  "stop_dev_script",
13553
15066
  "Stop a running script for a thread (all scripts if name omitted)",
13554
15067
  {
13555
- ref: import_zod.z.string(),
13556
- name: import_zod.z.string().optional()
15068
+ ref: import_zod2.z.string(),
15069
+ name: import_zod2.z.string().optional()
13557
15070
  },
13558
15071
  async ({ ref, name }) => {
13559
15072
  orch.stopDev(ref, name);
@@ -13563,7 +15076,7 @@ async function startMcpServer() {
13563
15076
  server.tool(
13564
15077
  "run_setup",
13565
15078
  "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
13566
- { ref: import_zod.z.string() },
15079
+ { ref: import_zod2.z.string() },
13567
15080
  async ({ ref }) => {
13568
15081
  const result = await orch.runSetup(ref);
13569
15082
  return {
@@ -13574,7 +15087,7 @@ async function startMcpServer() {
13574
15087
  server.tool(
13575
15088
  "add_workspace",
13576
15089
  "Register a git repo as a Sideboard workspace",
13577
- { repoPath: import_zod.z.string() },
15090
+ { repoPath: import_zod2.z.string() },
13578
15091
  async ({ repoPath }) => {
13579
15092
  const ws = await orch.addWorkspace(repoPath);
13580
15093
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -13583,7 +15096,7 @@ async function startMcpServer() {
13583
15096
  server.tool(
13584
15097
  "remove_workspace",
13585
15098
  "Unregister a Sideboard workspace (does not archive threads)",
13586
- { repoPath: import_zod.z.string() },
15099
+ { repoPath: import_zod2.z.string() },
13587
15100
  async ({ repoPath }) => {
13588
15101
  orch.removeWorkspace(repoPath);
13589
15102
  return { content: [{ type: "text", text: "ok" }] };
@@ -13593,12 +15106,12 @@ async function startMcpServer() {
13593
15106
  "fanout",
13594
15107
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
13595
15108
  {
13596
- prompt: import_zod.z.string(),
13597
- agents: import_zod.z.array(import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
13598
- repoPath: import_zod.z.string(),
13599
- sourceType: import_zod.z.enum(["branch", "pr", "ticket"]).optional(),
13600
- sourceRef: import_zod.z.string().optional(),
13601
- title: import_zod.z.string().optional()
15109
+ prompt: import_zod2.z.string(),
15110
+ agents: import_zod2.z.array(import_zod2.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
15111
+ repoPath: import_zod2.z.string(),
15112
+ sourceType: import_zod2.z.enum(["branch", "pr", "ticket"]).optional(),
15113
+ sourceRef: import_zod2.z.string().optional(),
15114
+ title: import_zod2.z.string().optional()
13602
15115
  },
13603
15116
  async (args) => {
13604
15117
  const threads = await orch.bestOfN(args);
@@ -13625,8 +15138,8 @@ async function startMcpServer() {
13625
15138
  "list_branches",
13626
15139
  "List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
13627
15140
  {
13628
- repoPath: import_zod.z.string(),
13629
- unmergedOnly: import_zod.z.boolean().optional()
15141
+ repoPath: import_zod2.z.string(),
15142
+ unmergedOnly: import_zod2.z.boolean().optional()
13630
15143
  },
13631
15144
  async ({ repoPath, unmergedOnly }) => {
13632
15145
  const root = await resolveRepoRoot(repoPath);
@@ -13646,7 +15159,7 @@ async function startMcpServer() {
13646
15159
  server.tool(
13647
15160
  "list_prs",
13648
15161
  "List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
13649
- { repoPath: import_zod.z.string() },
15162
+ { repoPath: import_zod2.z.string() },
13650
15163
  async ({ repoPath }) => {
13651
15164
  const root = await resolveRepoRoot(repoPath);
13652
15165
  const prs = await listPrs(root);
@@ -13665,7 +15178,7 @@ async function startMcpServer() {
13665
15178
  server.tool(
13666
15179
  "get_pr_stack",
13667
15180
  "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 mergePr on stacked PRs.",
13668
- { ref: import_zod.z.string() },
15181
+ { ref: import_zod2.z.string() },
13669
15182
  async ({ ref }) => {
13670
15183
  const stack = await orch.getPrStack(ref);
13671
15184
  return {
@@ -13677,8 +15190,8 @@ async function startMcpServer() {
13677
15190
  "open_pr_stack_layers",
13678
15191
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13679
15192
  {
13680
- ref: import_zod.z.string(),
13681
- layer: import_zod.z.number().int().positive().optional()
15193
+ ref: import_zod2.z.string(),
15194
+ layer: import_zod2.z.number().int().positive().optional()
13682
15195
  },
13683
15196
  async ({ ref, layer }) => {
13684
15197
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -13712,9 +15225,9 @@ async function startMcpServer() {
13712
15225
  "add_stack_layer",
13713
15226
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13714
15227
  {
13715
- ref: import_zod.z.string(),
13716
- branchName: import_zod.z.string(),
13717
- title: import_zod.z.string().optional()
15228
+ ref: import_zod2.z.string(),
15229
+ branchName: import_zod2.z.string(),
15230
+ title: import_zod2.z.string().optional()
13718
15231
  },
13719
15232
  async ({ ref, branchName, title }) => {
13720
15233
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -13743,11 +15256,11 @@ async function startMcpServer() {
13743
15256
  "create_pr_stack",
13744
15257
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13745
15258
  {
13746
- repoPath: import_zod.z.string(),
13747
- branches: import_zod.z.array(import_zod.z.string()).min(1),
13748
- agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13749
- base: import_zod.z.string().optional(),
13750
- title: import_zod.z.string().optional()
15259
+ repoPath: import_zod2.z.string(),
15260
+ branches: import_zod2.z.array(import_zod2.z.string()).min(1),
15261
+ agent: import_zod2.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
15262
+ base: import_zod2.z.string().optional(),
15263
+ title: import_zod2.z.string().optional()
13751
15264
  },
13752
15265
  async (args) => {
13753
15266
  const result = await orch.createPrStack({
@@ -13785,7 +15298,7 @@ async function startMcpServer() {
13785
15298
  server.tool(
13786
15299
  "list_issues",
13787
15300
  "List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
13788
- { repoPath: import_zod.z.string() },
15301
+ { repoPath: import_zod2.z.string() },
13789
15302
  async ({ repoPath }) => {
13790
15303
  const root = await resolveRepoRoot(repoPath);
13791
15304
  const result = await listIssues(root);
@@ -13796,8 +15309,8 @@ async function startMcpServer() {
13796
15309
  "list_linear_issues",
13797
15310
  "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
13798
15311
  {
13799
- agent: import_zod.z.enum(["claude", "codex", "opencode"]),
13800
- repoPath: import_zod.z.string()
15312
+ agent: import_zod2.z.enum(["claude", "codex", "opencode"]),
15313
+ repoPath: import_zod2.z.string()
13801
15314
  },
13802
15315
  async ({ agent, repoPath }) => {
13803
15316
  const issues = await listLinearIssues(agent, repoPath);