@sideboard-ai/core 0.1.67 → 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.
- package/dist/{agents-OTBZVTZO.js → agents-GB7XL2LI.js} +8 -7
- package/dist/{agents-TS3EI2YI.js → agents-TCAK3MXN.js} +8 -7
- package/dist/{app-settings-ONKMFSEJ.js → app-settings-3TLA2EJ7.js} +16 -1
- package/dist/{app-settings-GJVZGU3M.js → app-settings-P42Z3VOB.js} +16 -1
- package/dist/caffeinate-hold-BEJIYPJ7.js +107 -0
- package/dist/caffeinate-hold-KLC6SABD.js +14 -0
- package/dist/{chunk-WAFIF22N.js → chunk-3CY6WJ7O.js} +2 -2
- package/dist/{chunk-7ZOQY4A7.js → chunk-57ROTAFD.js} +62 -3
- package/dist/{chunk-57WWNMFP.js → chunk-7NCIHRAU.js} +22 -4
- package/dist/{chunk-FGDJOJ2L.js → chunk-AE5VBOFE.js} +1 -1
- package/dist/{chunk-DB3I75FI.js → chunk-BTL7EMGT.js} +4 -4
- package/dist/{chunk-4UIYN56C.js → chunk-D4DPQ552.js} +1 -1
- package/dist/{chunk-JWZNK3CQ.js → chunk-DGPUAZA4.js} +62 -3
- package/dist/{chunk-ZRFAZVGL.js → chunk-H2IXNFQ2.js} +22 -4
- package/dist/chunk-JAWEDVVA.js +106 -0
- package/dist/chunk-JT7R45JB.js +20 -0
- package/dist/{chunk-4BUCGNZS.js → chunk-MF2YMEGD.js} +83 -29
- package/dist/{chunk-K7LM4CLD.js → chunk-NG7S3OPX.js} +12 -6
- package/dist/chunk-NSTQ6QKD.js +23 -0
- package/dist/{chunk-LW5CMQGZ.js → chunk-OE7YBB5X.js} +83 -29
- package/dist/{chunk-BPOTVI5J.js → chunk-PTJJIQK7.js} +472 -18
- package/dist/{chunk-B4PRRKMA.js → chunk-S25IHL5H.js} +12 -6
- package/dist/{chunk-5HTTCYWA.js → chunk-UH57WVFP.js} +2 -2
- package/dist/{chunk-M5V4QIWF.js → chunk-VROPG6QF.js} +1 -1
- package/dist/{chunk-MHR4OGZ4.js → chunk-W7YOTDVO.js} +493 -18
- package/dist/{connected-teams-RUNMOCG5.js → connected-teams-35EIRJCP.js} +2 -2
- package/dist/{connected-teams-OMDHTL6A.js → connected-teams-ZRS53OAT.js} +2 -2
- package/dist/{coordinator-prompt-6HMTOX4G.js → coordinator-prompt-2J4PIMUF.js} +7 -4
- package/dist/{coordinator-prompt-3BSMKZL4.js → coordinator-prompt-QYYG2IBB.js} +7 -4
- package/dist/{global-workspace-YZ2NJVLL.js → global-workspace-KHIFQUUM.js} +12 -5
- package/dist/{global-workspace-QS7A23CB.js → global-workspace-SVRYNJZA.js} +12 -5
- package/dist/index.cjs +4805 -1262
- package/dist/index.d.cts +570 -22
- package/dist/index.d.ts +570 -22
- package/dist/index.js +3084 -340
- package/dist/mcp/run-stdio.cjs +2339 -617
- package/dist/mcp/run-stdio.js +1289 -290
- package/dist/{run-Y2MI3EVV.js → run-A4RHY7HH.js} +1 -1
- package/dist/{run-JLD2Y52J.js → run-V2WZUMJA.js} +1 -1
- package/dist/{workspaces-EBFW7I7S.js → workspaces-AHFTHVBT.js} +7 -6
- package/dist/{workspaces-AIUYUXB7.js → workspaces-SVQIEVIZ.js} +7 -6
- package/dist/{worktree-WMDJW2BL.js → worktree-KWXHMKRN.js} +2 -2
- package/dist/{worktree-7NBPIVFZ.js → worktree-VOCH3WS3.js} +2 -2
- package/package.json +3 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -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(
|
|
96
|
-
const lower =
|
|
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(
|
|
101
|
-
const lower =
|
|
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(
|
|
1455
|
-
return /API rate limit (already )?exceeded/i.test(
|
|
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(
|
|
1468
|
-
const trimmed =
|
|
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()}`;
|
|
@@ -1649,7 +1649,7 @@ async function git(args, cwd, opts) {
|
|
|
1649
1649
|
prefix.push("-c", `${key}=${value}`);
|
|
1650
1650
|
}
|
|
1651
1651
|
}
|
|
1652
|
-
return run("git", [...prefix, ...args], {
|
|
1652
|
+
return run("git", ["--no-pager", ...prefix, ...args], {
|
|
1653
1653
|
cwd,
|
|
1654
1654
|
reject: opts?.reject,
|
|
1655
1655
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -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
|
-
|
|
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",
|
|
@@ -2124,7 +2141,7 @@ async function originGhRepoEnv(cwd) {
|
|
|
2124
2141
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
2125
2142
|
return slug ? { GH_REPO: slug } : {};
|
|
2126
2143
|
}
|
|
2127
|
-
async function resolveDefaultBranch(repoPath) {
|
|
2144
|
+
async function resolveDefaultBranch(repoPath, opts) {
|
|
2128
2145
|
const viaOrigin = await git(
|
|
2129
2146
|
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
2130
2147
|
repoPath,
|
|
@@ -2133,22 +2150,24 @@ async function resolveDefaultBranch(repoPath) {
|
|
|
2133
2150
|
if (viaOrigin.exitCode === 0 && viaOrigin.stdout.trim()) {
|
|
2134
2151
|
return viaOrigin.stdout.trim().replace(/^refs\/remotes\/origin\//, "");
|
|
2135
2152
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2153
|
+
if (opts?.network !== false) {
|
|
2154
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
2155
|
+
const viaGh = await gh(
|
|
2156
|
+
[
|
|
2157
|
+
"repo",
|
|
2158
|
+
"view",
|
|
2159
|
+
...slug ? ["--repo", slug] : [],
|
|
2160
|
+
"--json",
|
|
2161
|
+
"defaultBranchRef",
|
|
2162
|
+
"--jq",
|
|
2163
|
+
".defaultBranchRef.name"
|
|
2164
|
+
],
|
|
2165
|
+
repoPath,
|
|
2166
|
+
{ reject: false, timeoutMs: 8e3 }
|
|
2167
|
+
);
|
|
2168
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
2169
|
+
return viaGh.stdout.trim();
|
|
2170
|
+
}
|
|
2152
2171
|
}
|
|
2153
2172
|
for (const candidate of ["main", "master"]) {
|
|
2154
2173
|
const check = await git(
|
|
@@ -2289,10 +2308,21 @@ async function fetchPrMergeGate(cwd, selector, slug) {
|
|
|
2289
2308
|
"view",
|
|
2290
2309
|
selector,
|
|
2291
2310
|
"--json",
|
|
2292
|
-
"mergeable,mergeStateStatus,reviewDecision,baseRefName,url"
|
|
2311
|
+
"mergeable,mergeStateStatus,reviewDecision,baseRefName,url,isInMergeQueue"
|
|
2293
2312
|
];
|
|
2294
2313
|
if (slug) args.push("--repo", slug);
|
|
2295
|
-
|
|
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
|
+
}
|
|
2296
2326
|
if (exitCode !== 0 || !stdout.trim()) {
|
|
2297
2327
|
if (/no pull requests found/i.test(stderr)) return null;
|
|
2298
2328
|
return null;
|
|
@@ -2304,12 +2334,22 @@ async function fetchPrMergeGate(cwd, selector, slug) {
|
|
|
2304
2334
|
mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
|
|
2305
2335
|
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
2306
2336
|
baseRefName: typeof view.baseRefName === "string" && view.baseRefName ? view.baseRefName : null,
|
|
2307
|
-
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)
|
|
2308
2339
|
};
|
|
2309
2340
|
} catch {
|
|
2310
2341
|
return null;
|
|
2311
2342
|
}
|
|
2312
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
|
+
}
|
|
2313
2353
|
async function detectLocalMergeConflicts(cwd, baseRefName) {
|
|
2314
2354
|
const baseName = (baseRefName?.trim() || await resolveDefaultBranch(cwd)).replace(
|
|
2315
2355
|
/^origin\//,
|
|
@@ -2370,7 +2410,8 @@ async function getPrChecks(cwd, selector) {
|
|
|
2370
2410
|
const mergeable = (gate?.mergeable ?? "").toUpperCase();
|
|
2371
2411
|
const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
|
|
2372
2412
|
const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
|
|
2373
|
-
const
|
|
2413
|
+
const inQueue = gate ? prIsInMergeQueue(gate) : false;
|
|
2414
|
+
const needsLocalProbe = !alreadyConflicting && !inQueue;
|
|
2374
2415
|
if (needsLocalProbe) {
|
|
2375
2416
|
try {
|
|
2376
2417
|
const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
|
|
@@ -2381,7 +2422,8 @@ async function getPrChecks(cwd, selector) {
|
|
|
2381
2422
|
mergeStateStatus: "DIRTY",
|
|
2382
2423
|
reviewDecision: gate?.reviewDecision ?? null,
|
|
2383
2424
|
baseRefName: local.base,
|
|
2384
|
-
url: gate?.url ?? null
|
|
2425
|
+
url: gate?.url ?? null,
|
|
2426
|
+
isInMergeQueue: false
|
|
2385
2427
|
};
|
|
2386
2428
|
const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
|
|
2387
2429
|
const review2 = (gate.reviewDecision ?? "").toUpperCase();
|
|
@@ -2425,10 +2467,21 @@ async function getPrMeta(cwd, selector) {
|
|
|
2425
2467
|
"view",
|
|
2426
2468
|
selector,
|
|
2427
2469
|
"--json",
|
|
2428
|
-
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName"
|
|
2470
|
+
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
|
|
2429
2471
|
];
|
|
2430
2472
|
if (slug) viewArgs.push("--repo", slug);
|
|
2431
|
-
|
|
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
|
+
}
|
|
2432
2485
|
if (exitCode !== 0 || !stdout.trim()) {
|
|
2433
2486
|
if (/no pull requests found/i.test(stderr)) return null;
|
|
2434
2487
|
return null;
|
|
@@ -2443,7 +2496,8 @@ async function getPrMeta(cwd, selector) {
|
|
|
2443
2496
|
isDraft: Boolean(view.isDraft),
|
|
2444
2497
|
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
2445
2498
|
baseRefName: String(view.baseRefName ?? ""),
|
|
2446
|
-
headRefName: String(view.headRefName ?? "")
|
|
2499
|
+
headRefName: String(view.headRefName ?? ""),
|
|
2500
|
+
isInMergeQueue: parseInMergeQueue(view)
|
|
2447
2501
|
};
|
|
2448
2502
|
} catch {
|
|
2449
2503
|
return null;
|
|
@@ -2800,11 +2854,11 @@ async function listWorktrees(repoPath) {
|
|
|
2800
2854
|
return entries;
|
|
2801
2855
|
}
|
|
2802
2856
|
async function isDirty(worktreePath) {
|
|
2803
|
-
const { stdout } = await git(["status", "--porcelain"
|
|
2857
|
+
const { stdout } = await git(["status", "--porcelain"], worktreePath);
|
|
2804
2858
|
for (const line of stdout.split("\n")) {
|
|
2805
2859
|
if (!line.trim()) continue;
|
|
2806
2860
|
const rel = porcelainStatusPath(line);
|
|
2807
|
-
if (!rel || isSideboardScratchPath(rel)) continue;
|
|
2861
|
+
if (!rel || isSideboardScratchPath(rel) || rel === ".sideboard") continue;
|
|
2808
2862
|
return true;
|
|
2809
2863
|
}
|
|
2810
2864
|
return false;
|
|
@@ -3048,6 +3102,208 @@ var init_worktree = __esm({
|
|
|
3048
3102
|
}
|
|
3049
3103
|
});
|
|
3050
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
|
+
|
|
3051
3307
|
// src/store/app-settings.ts
|
|
3052
3308
|
var app_settings_exports = {};
|
|
3053
3309
|
__export(app_settings_exports, {
|
|
@@ -3063,10 +3319,13 @@ __export(app_settings_exports, {
|
|
|
3063
3319
|
brightsyInjectWorktreeMcpEnabled: () => brightsyInjectWorktreeMcpEnabled,
|
|
3064
3320
|
caffeinateWhileCloudConnectEnabled: () => caffeinateWhileCloudConnectEnabled,
|
|
3065
3321
|
caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
|
|
3322
|
+
caffeinateWhileSlackListenEnabled: () => caffeinateWhileSlackListenEnabled,
|
|
3066
3323
|
childEnvWithAppSettings: () => childEnvWithAppSettings,
|
|
3067
3324
|
claudeChromeEnabled: () => claudeChromeEnabled,
|
|
3068
3325
|
claudeUserSettingsPath: () => claudeUserSettingsPath,
|
|
3069
3326
|
deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
|
|
3327
|
+
disconnectLinearConnection: () => disconnectLinearConnection,
|
|
3328
|
+
ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
|
|
3070
3329
|
getDefaultAgent: () => getDefaultAgent,
|
|
3071
3330
|
getDefaultEffort: () => getDefaultEffort,
|
|
3072
3331
|
getDefaultFast: () => getDefaultFast,
|
|
@@ -3085,6 +3344,10 @@ __export(app_settings_exports, {
|
|
|
3085
3344
|
resolveNewThreadOptions: () => resolveNewThreadOptions,
|
|
3086
3345
|
resolveThreadDefaults: () => resolveThreadDefaults,
|
|
3087
3346
|
saveAppSettings: () => saveAppSettings,
|
|
3347
|
+
saveLinearOAuth: () => saveLinearOAuth,
|
|
3348
|
+
slackAppLevelToken: () => slackAppLevelToken,
|
|
3349
|
+
slackListenEnabled: () => slackListenEnabled,
|
|
3350
|
+
toPublicAppSettings: () => toPublicAppSettings,
|
|
3088
3351
|
updateAdvancedSettings: () => updateAdvancedSettings,
|
|
3089
3352
|
updateAgentExecutable: () => updateAgentExecutable,
|
|
3090
3353
|
updateAppEnvironment: () => updateAppEnvironment,
|
|
@@ -3095,11 +3358,45 @@ __export(app_settings_exports, {
|
|
|
3095
3358
|
updateIntegrationsSettings: () => updateIntegrationsSettings,
|
|
3096
3359
|
updateOpencodeSettings: () => updateOpencodeSettings
|
|
3097
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
|
+
}
|
|
3098
3395
|
function appSettingsPath() {
|
|
3099
|
-
return (0,
|
|
3396
|
+
return (0, import_node_path8.join)(appDataDir(), "settings.json");
|
|
3100
3397
|
}
|
|
3101
3398
|
function claudeUserSettingsPath() {
|
|
3102
|
-
return (0,
|
|
3399
|
+
return (0, import_node_path8.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
|
|
3103
3400
|
}
|
|
3104
3401
|
function normalizeClaude(raw) {
|
|
3105
3402
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -3151,9 +3448,62 @@ function normalizeIntegrations(raw) {
|
|
|
3151
3448
|
const key = source.linearApiKey.trim();
|
|
3152
3449
|
if (key) out.linearApiKey = key;
|
|
3153
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
|
+
}
|
|
3154
3481
|
if (typeof source.issueSource === "string" && ISSUE_SOURCES.has(source.issueSource)) {
|
|
3155
3482
|
out.issueSource = source.issueSource;
|
|
3156
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
|
+
}
|
|
3157
3507
|
return out;
|
|
3158
3508
|
}
|
|
3159
3509
|
function normalizeDefaults(raw) {
|
|
@@ -3188,8 +3538,10 @@ function normalizeAdvanced(raw) {
|
|
|
3188
3538
|
if (typeof source.caffeinateWhileRunning === "boolean") {
|
|
3189
3539
|
out.caffeinateWhileRunning = source.caffeinateWhileRunning;
|
|
3190
3540
|
}
|
|
3191
|
-
if (typeof source.
|
|
3192
|
-
out.
|
|
3541
|
+
if (typeof source.caffeinateWhileSlackListen === "boolean") {
|
|
3542
|
+
out.caffeinateWhileSlackListen = source.caffeinateWhileSlackListen;
|
|
3543
|
+
} else if (typeof source.caffeinateWhileCloudConnect === "boolean") {
|
|
3544
|
+
out.caffeinateWhileSlackListen = source.caffeinateWhileCloudConnect;
|
|
3193
3545
|
}
|
|
3194
3546
|
if (typeof source.deleteBranchOnPurge === "boolean") {
|
|
3195
3547
|
out.deleteBranchOnPurge = source.deleteBranchOnPurge;
|
|
@@ -3278,23 +3630,84 @@ function normalizeSettings(raw) {
|
|
|
3278
3630
|
advanced
|
|
3279
3631
|
};
|
|
3280
3632
|
}
|
|
3281
|
-
function
|
|
3633
|
+
function readSettingsFile() {
|
|
3282
3634
|
const path = appSettingsPath();
|
|
3283
|
-
if (!(0,
|
|
3284
|
-
return { ...EMPTY_SETTINGS };
|
|
3285
|
-
}
|
|
3635
|
+
if (!(0, import_node_fs8.existsSync)(path)) return { ...EMPTY_SETTINGS };
|
|
3286
3636
|
try {
|
|
3287
|
-
|
|
3637
|
+
chmodOwnerOnly(path);
|
|
3638
|
+
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
|
|
3288
3639
|
return normalizeSettings(parsed);
|
|
3289
3640
|
} catch {
|
|
3290
3641
|
return { ...EMPTY_SETTINGS };
|
|
3291
3642
|
}
|
|
3292
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
|
+
}
|
|
3293
3708
|
function saveAppSettings(settings) {
|
|
3294
3709
|
const normalized = normalizeSettings(settings);
|
|
3295
|
-
(
|
|
3296
|
-
(0, import_node_fs6.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
|
|
3297
|
-
`, "utf8");
|
|
3710
|
+
persistSplit(normalized);
|
|
3298
3711
|
return normalized;
|
|
3299
3712
|
}
|
|
3300
3713
|
function updateAppEnvironment(patch) {
|
|
@@ -3385,6 +3798,20 @@ function updateIntegrationsSettings(patch) {
|
|
|
3385
3798
|
integrations.linearApiKey = patch.linearApiKey.trim();
|
|
3386
3799
|
}
|
|
3387
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
|
+
}
|
|
3388
3815
|
if ("issueSource" in patch) {
|
|
3389
3816
|
if (patch.issueSource == null) {
|
|
3390
3817
|
delete integrations.issueSource;
|
|
@@ -3392,6 +3819,48 @@ function updateIntegrationsSettings(patch) {
|
|
|
3392
3819
|
integrations.issueSource = patch.issueSource;
|
|
3393
3820
|
}
|
|
3394
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
|
+
}
|
|
3395
3864
|
return saveAppSettings({ ...current, integrations });
|
|
3396
3865
|
}
|
|
3397
3866
|
function updateDefaultsSettings(patch) {
|
|
@@ -3465,7 +3934,36 @@ function resolveNewThreadOptions(overrides = {}, settings = loadAppSettings()) {
|
|
|
3465
3934
|
};
|
|
3466
3935
|
}
|
|
3467
3936
|
function isLinearConnected(settings = loadAppSettings()) {
|
|
3468
|
-
return
|
|
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 });
|
|
3469
3967
|
}
|
|
3470
3968
|
function getIssueSource(settings = loadAppSettings()) {
|
|
3471
3969
|
return settings.integrations.issueSource ?? "github";
|
|
@@ -3482,6 +3980,33 @@ function getLinearApiKey(settings = loadAppSettings()) {
|
|
|
3482
3980
|
function brightsyCloudConnectEnabled(settings = loadAppSettings()) {
|
|
3483
3981
|
return Boolean(settings.brightsy.cloudConnectEnabled);
|
|
3484
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
|
+
}
|
|
3485
4010
|
function brightsyInjectWorktreeMcpEnabled(settings = loadAppSettings()) {
|
|
3486
4011
|
return Boolean(settings.brightsy.injectWorktreeMcp);
|
|
3487
4012
|
}
|
|
@@ -3505,8 +4030,12 @@ function updateAdvancedSettings(patch) {
|
|
|
3505
4030
|
if (typeof patch.caffeinateWhileRunning === "boolean") {
|
|
3506
4031
|
advanced.caffeinateWhileRunning = patch.caffeinateWhileRunning;
|
|
3507
4032
|
}
|
|
3508
|
-
if (typeof patch.
|
|
3509
|
-
advanced.
|
|
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;
|
|
3510
4039
|
}
|
|
3511
4040
|
if (typeof patch.deleteBranchOnPurge === "boolean") {
|
|
3512
4041
|
advanced.deleteBranchOnPurge = patch.deleteBranchOnPurge;
|
|
@@ -3549,8 +4078,11 @@ function autoRunAfterSetupEnabled(settings = loadAppSettings()) {
|
|
|
3549
4078
|
function caffeinateWhileRunningEnabled(settings = loadAppSettings()) {
|
|
3550
4079
|
return Boolean(settings.advanced.caffeinateWhileRunning);
|
|
3551
4080
|
}
|
|
4081
|
+
function caffeinateWhileSlackListenEnabled(settings = loadAppSettings()) {
|
|
4082
|
+
return Boolean(settings.advanced.caffeinateWhileSlackListen);
|
|
4083
|
+
}
|
|
3552
4084
|
function caffeinateWhileCloudConnectEnabled(settings = loadAppSettings()) {
|
|
3553
|
-
return
|
|
4085
|
+
return caffeinateWhileSlackListenEnabled(settings);
|
|
3554
4086
|
}
|
|
3555
4087
|
function deleteBranchOnPurgeEnabled(settings = loadAppSettings()) {
|
|
3556
4088
|
return Boolean(settings.advanced.deleteBranchOnPurge);
|
|
@@ -3636,15 +4168,18 @@ function childEnvWithAppSettings(extra) {
|
|
|
3636
4168
|
function harnessEnvKey(harness) {
|
|
3637
4169
|
return HARNESS_ENV_KEYS[harness];
|
|
3638
4170
|
}
|
|
3639
|
-
var
|
|
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;
|
|
3640
4172
|
var init_app_settings = __esm({
|
|
3641
4173
|
"src/store/app-settings.ts"() {
|
|
3642
4174
|
"use strict";
|
|
3643
|
-
|
|
4175
|
+
import_node_crypto3 = require("crypto");
|
|
4176
|
+
import_node_fs8 = require("fs");
|
|
3644
4177
|
import_node_os4 = require("os");
|
|
3645
|
-
|
|
4178
|
+
import_node_path8 = require("path");
|
|
3646
4179
|
init_thinking_effort();
|
|
3647
4180
|
init_paths();
|
|
4181
|
+
init_private_file();
|
|
4182
|
+
init_secret_vault();
|
|
3648
4183
|
HARNESS_ENV_KEYS = {
|
|
3649
4184
|
claude: "ANTHROPIC_API_KEY",
|
|
3650
4185
|
codex: "CODEX_API_KEY",
|
|
@@ -3747,6 +4282,7 @@ var init_orchestrator_capable = __esm({
|
|
|
3747
4282
|
var coordinator_prompt_exports = {};
|
|
3748
4283
|
__export(coordinator_prompt_exports, {
|
|
3749
4284
|
COORDINATOR_TOOL_PLAYBOOK: () => COORDINATOR_TOOL_PLAYBOOK,
|
|
4285
|
+
SLACK_REPLY_FORMATTING: () => SLACK_REPLY_FORMATTING,
|
|
3750
4286
|
coordinatorGreenfieldPlaybook: () => coordinatorGreenfieldPlaybook,
|
|
3751
4287
|
coordinatorSystemPrompt: () => coordinatorSystemPrompt,
|
|
3752
4288
|
coordinatorTurnReminder: () => coordinatorTurnReminder,
|
|
@@ -3801,13 +4337,14 @@ function coordinatorTurnReminder(opts) {
|
|
|
3801
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.`,
|
|
3802
4338
|
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.",
|
|
3803
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.",
|
|
3804
|
-
"- 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."
|
|
3805
4342
|
].filter(Boolean).join("\n");
|
|
3806
4343
|
}
|
|
3807
4344
|
function ensureGlobalCoordinatorCwd(opts) {
|
|
3808
4345
|
const dir = globalAgentCwd();
|
|
3809
4346
|
try {
|
|
3810
|
-
(0,
|
|
4347
|
+
(0, import_node_fs9.mkdirSync)(dir, { recursive: true });
|
|
3811
4348
|
} catch {
|
|
3812
4349
|
return dir;
|
|
3813
4350
|
}
|
|
@@ -3815,7 +4352,7 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
3815
4352
|
let orchId = opts?.orchestratorThreadId?.trim() || "";
|
|
3816
4353
|
if (!orchId) {
|
|
3817
4354
|
try {
|
|
3818
|
-
const existing = (0,
|
|
4355
|
+
const existing = (0, import_node_fs9.readFileSync)((0, import_node_path9.join)(dir, "AGENTS.md"), "utf8");
|
|
3819
4356
|
const m = existing.match(
|
|
3820
4357
|
/YOUR orchestration thread id is `([0-9a-f-]{36})`/i
|
|
3821
4358
|
);
|
|
@@ -3857,9 +4394,9 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
3857
4394
|
"Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
|
|
3858
4395
|
].join("\n");
|
|
3859
4396
|
try {
|
|
3860
|
-
(0,
|
|
4397
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(dir, "CLAUDE.md"), `${body}
|
|
3861
4398
|
`, "utf8");
|
|
3862
|
-
(0,
|
|
4399
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(dir, "AGENTS.md"), `${body}
|
|
3863
4400
|
`, "utf8");
|
|
3864
4401
|
} catch {
|
|
3865
4402
|
}
|
|
@@ -3869,8 +4406,14 @@ function coordinatorSystemPrompt(opts) {
|
|
|
3869
4406
|
const audience = opts.audience ?? "cloud";
|
|
3870
4407
|
const reposDir = sideboardReposDir();
|
|
3871
4408
|
const intro = audience === "cloud" ? [
|
|
3872
|
-
"You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (
|
|
4409
|
+
"You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
|
|
3873
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
|
|
3874
4417
|
] : [
|
|
3875
4418
|
"You are a Sideboard orchestration agent: you oversee worktree agents across registered workspaces in the Sideboard app.",
|
|
3876
4419
|
"Stay concise and actionable; prefer Sideboard MCP for fleet status; use Bash for greenfield repo setup and inspecting target worktree paths."
|
|
@@ -3892,12 +4435,12 @@ function coordinatorSystemPrompt(opts) {
|
|
|
3892
4435
|
formatWorkspaceInventory(opts.workspaces)
|
|
3893
4436
|
].join("\n");
|
|
3894
4437
|
}
|
|
3895
|
-
var
|
|
4438
|
+
var import_node_fs9, import_node_path9, COORDINATOR_TOOL_PLAYBOOK, SLACK_REPLY_FORMATTING;
|
|
3896
4439
|
var init_coordinator_prompt = __esm({
|
|
3897
4440
|
"src/orchestrator/coordinator-prompt.ts"() {
|
|
3898
4441
|
"use strict";
|
|
3899
|
-
|
|
3900
|
-
|
|
4442
|
+
import_node_fs9 = require("fs");
|
|
4443
|
+
import_node_path9 = require("path");
|
|
3901
4444
|
init_worktree();
|
|
3902
4445
|
init_app_settings();
|
|
3903
4446
|
init_paths();
|
|
@@ -3907,9 +4450,12 @@ var init_coordinator_prompt = __esm({
|
|
|
3907
4450
|
"Discover:",
|
|
3908
4451
|
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
3909
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.",
|
|
3910
4455
|
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
3911
4456
|
"- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
|
|
3912
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.",
|
|
3913
4459
|
"Workspaces:",
|
|
3914
4460
|
"- add_workspace / remove_workspace \u2014 register or unregister a git repo",
|
|
3915
4461
|
"Worktree threads (chats):",
|
|
@@ -3931,6 +4477,13 @@ var init_coordinator_prompt = __esm({
|
|
|
3931
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.",
|
|
3932
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."
|
|
3933
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");
|
|
3934
4487
|
}
|
|
3935
4488
|
});
|
|
3936
4489
|
|
|
@@ -3940,15 +4493,18 @@ __export(global_workspace_exports, {
|
|
|
3940
4493
|
GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
|
|
3941
4494
|
createGlobalChat: () => createGlobalChat,
|
|
3942
4495
|
ensureCloudCoordinator: () => ensureCloudCoordinator,
|
|
4496
|
+
ensureSlackCoordinator: () => ensureSlackCoordinator,
|
|
3943
4497
|
globalAgentCwd: () => globalAgentCwd,
|
|
3944
4498
|
healOrchestrationSoccerTitles: () => healOrchestrationSoccerTitles,
|
|
3945
4499
|
isCloudCoordinatorThread: () => isCloudCoordinatorThread,
|
|
3946
4500
|
isGlobalRepoPath: () => isGlobalRepoPath,
|
|
3947
4501
|
isGlobalThread: () => isGlobalThread,
|
|
3948
4502
|
isOrchestratorThread: () => isOrchestratorThread,
|
|
4503
|
+
isSlackCoordinatorThread: () => isSlackCoordinatorThread,
|
|
3949
4504
|
listGlobalThreads: () => listGlobalThreads,
|
|
3950
4505
|
orchestrationTitleNeedsSoccerNickname: () => orchestrationTitleNeedsSoccerNickname,
|
|
3951
4506
|
orchestratorSessionPoisonedByBuiltins: () => orchestratorSessionPoisonedByBuiltins,
|
|
4507
|
+
slackCoordinatorSourceRef: () => slackCoordinatorSourceRef,
|
|
3952
4508
|
takenTeamSlugsForOrchestration: () => takenTeamSlugsForOrchestration
|
|
3953
4509
|
});
|
|
3954
4510
|
function isGlobalThread(thread) {
|
|
@@ -4065,6 +4621,62 @@ function findCloudCoordinator() {
|
|
|
4065
4621
|
(t) => t.status !== "archived" && isCloudCoordinatorThread(t)
|
|
4066
4622
|
);
|
|
4067
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
|
+
}
|
|
4068
4680
|
function ensureCloudCoordinator(agent) {
|
|
4069
4681
|
const defaults = resolveThreadDefaults();
|
|
4070
4682
|
const desired = assertOrchestratorCapableAgent(agent);
|
|
@@ -4118,32 +4730,32 @@ var init_global_workspace = __esm({
|
|
|
4118
4730
|
|
|
4119
4731
|
// src/brightsy/config.ts
|
|
4120
4732
|
function brightsyConfigPath() {
|
|
4121
|
-
return (0,
|
|
4733
|
+
return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".brightsy", "config.json");
|
|
4122
4734
|
}
|
|
4123
4735
|
function loadBrightsyConfig() {
|
|
4124
4736
|
const path = brightsyConfigPath();
|
|
4125
|
-
if (!(0,
|
|
4737
|
+
if (!(0, import_node_fs10.existsSync)(path)) {
|
|
4126
4738
|
throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
|
|
4127
4739
|
}
|
|
4128
|
-
const raw = JSON.parse((0,
|
|
4740
|
+
const raw = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
|
|
4129
4741
|
if (!raw.access_token || !raw.account_id) {
|
|
4130
4742
|
throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
|
|
4131
4743
|
}
|
|
4132
4744
|
return raw;
|
|
4133
4745
|
}
|
|
4134
4746
|
function saveBrightsyConfig(cfg) {
|
|
4135
|
-
(0,
|
|
4747
|
+
(0, import_node_fs10.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
|
|
4136
4748
|
`, {
|
|
4137
4749
|
mode: 384
|
|
4138
4750
|
});
|
|
4139
4751
|
}
|
|
4140
|
-
var
|
|
4752
|
+
var import_node_fs10, import_node_os5, import_node_path10;
|
|
4141
4753
|
var init_config = __esm({
|
|
4142
4754
|
"src/brightsy/config.ts"() {
|
|
4143
4755
|
"use strict";
|
|
4144
|
-
|
|
4756
|
+
import_node_fs10 = require("fs");
|
|
4145
4757
|
import_node_os5 = require("os");
|
|
4146
|
-
|
|
4758
|
+
import_node_path10 = require("path");
|
|
4147
4759
|
}
|
|
4148
4760
|
});
|
|
4149
4761
|
|
|
@@ -4158,22 +4770,22 @@ var init_accounts = __esm({
|
|
|
4158
4770
|
|
|
4159
4771
|
// src/brightsy/connected-teams.ts
|
|
4160
4772
|
function storePath() {
|
|
4161
|
-
return (0,
|
|
4773
|
+
return (0, import_node_path11.join)(appDataDir(), "brightsy-teams.json");
|
|
4162
4774
|
}
|
|
4163
4775
|
function readStore() {
|
|
4164
4776
|
const path = storePath();
|
|
4165
|
-
if (!(0,
|
|
4777
|
+
if (!(0, import_node_fs11.existsSync)(path)) return [];
|
|
4166
4778
|
try {
|
|
4167
|
-
const parsed = JSON.parse((0,
|
|
4779
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
4168
4780
|
return Array.isArray(parsed.teams) ? parsed.teams : [];
|
|
4169
4781
|
} catch {
|
|
4170
4782
|
return [];
|
|
4171
4783
|
}
|
|
4172
4784
|
}
|
|
4173
4785
|
function writeStore(teams) {
|
|
4174
|
-
(0,
|
|
4786
|
+
(0, import_node_fs11.mkdirSync)(appDataDir(), { recursive: true });
|
|
4175
4787
|
const path = storePath();
|
|
4176
|
-
(0,
|
|
4788
|
+
(0, import_node_fs11.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
|
|
4177
4789
|
`, {
|
|
4178
4790
|
mode: 384
|
|
4179
4791
|
});
|
|
@@ -4285,12 +4897,12 @@ function brightsyMcpServerName(slug) {
|
|
|
4285
4897
|
const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
|
|
4286
4898
|
return `brightsy_${cleaned || "team"}`;
|
|
4287
4899
|
}
|
|
4288
|
-
var
|
|
4900
|
+
var import_node_fs11, import_node_path11;
|
|
4289
4901
|
var init_connected_teams = __esm({
|
|
4290
4902
|
"src/brightsy/connected-teams.ts"() {
|
|
4291
4903
|
"use strict";
|
|
4292
|
-
|
|
4293
|
-
|
|
4904
|
+
import_node_fs11 = require("fs");
|
|
4905
|
+
import_node_path11 = require("path");
|
|
4294
4906
|
init_paths();
|
|
4295
4907
|
init_accounts();
|
|
4296
4908
|
init_config();
|
|
@@ -4576,11 +5188,11 @@ async function syncCliForTarget(accountId) {
|
|
|
4576
5188
|
}
|
|
4577
5189
|
applyConnectedTeamToCli(team);
|
|
4578
5190
|
}
|
|
4579
|
-
var
|
|
5191
|
+
var import_node_fs12, brightsyAdapter;
|
|
4580
5192
|
var init_brightsy = __esm({
|
|
4581
5193
|
"src/agents/brightsy.ts"() {
|
|
4582
5194
|
"use strict";
|
|
4583
|
-
|
|
5195
|
+
import_node_fs12 = require("fs");
|
|
4584
5196
|
init_run();
|
|
4585
5197
|
init_connected_teams();
|
|
4586
5198
|
init_config();
|
|
@@ -4594,7 +5206,7 @@ var init_brightsy = __esm({
|
|
|
4594
5206
|
async detect() {
|
|
4595
5207
|
const brightsy = resolveAgentExecutable("brightsy");
|
|
4596
5208
|
if (brightsy !== "brightsy") {
|
|
4597
|
-
if (!(0,
|
|
5209
|
+
if (!(0, import_node_fs12.existsSync)(brightsy)) {
|
|
4598
5210
|
return {
|
|
4599
5211
|
agent: "brightsy",
|
|
4600
5212
|
installed: false,
|
|
@@ -4670,10 +5282,10 @@ var init_brightsy = __esm({
|
|
|
4670
5282
|
});
|
|
4671
5283
|
|
|
4672
5284
|
// src/agents/claude-mcp.ts
|
|
4673
|
-
function parseMcpList(
|
|
5285
|
+
function parseMcpList(text2) {
|
|
4674
5286
|
const servers = [];
|
|
4675
5287
|
const seen = /* @__PURE__ */ new Set();
|
|
4676
|
-
for (const raw of
|
|
5288
|
+
for (const raw of text2.split("\n")) {
|
|
4677
5289
|
const line = raw.trim();
|
|
4678
5290
|
if (!line || /^Checking MCP/i.test(line)) continue;
|
|
4679
5291
|
const m = line.match(/^(.+?):\s+\S+/);
|
|
@@ -4767,8 +5379,8 @@ function isBrightsyConnected() {
|
|
|
4767
5379
|
return false;
|
|
4768
5380
|
}
|
|
4769
5381
|
}
|
|
4770
|
-
function promptMentionsBrightsy(
|
|
4771
|
-
return BRIGHTSY_WORD.test(
|
|
5382
|
+
function promptMentionsBrightsy(text2) {
|
|
5383
|
+
return BRIGHTSY_WORD.test(text2 ?? "");
|
|
4772
5384
|
}
|
|
4773
5385
|
function isBrightsyMcpToolName(name) {
|
|
4774
5386
|
const n = name.toLowerCase();
|
|
@@ -4831,35 +5443,35 @@ function corePackageDir() {
|
|
|
4831
5443
|
try {
|
|
4832
5444
|
const url = import_meta.url;
|
|
4833
5445
|
if (typeof url === "string" && url.length > 0) {
|
|
4834
|
-
return (0,
|
|
5446
|
+
return (0, import_node_path12.dirname)((0, import_node_url.fileURLToPath)(url));
|
|
4835
5447
|
}
|
|
4836
5448
|
} catch {
|
|
4837
5449
|
}
|
|
4838
5450
|
try {
|
|
4839
|
-
const req = (0, import_node_module.createRequire)((0,
|
|
4840
|
-
return (0,
|
|
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"));
|
|
4841
5453
|
} catch {
|
|
4842
5454
|
return process.cwd();
|
|
4843
5455
|
}
|
|
4844
5456
|
}
|
|
4845
5457
|
function findSideboardMcpJsEntry() {
|
|
4846
5458
|
const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
|
|
4847
|
-
if (override && (0,
|
|
5459
|
+
if (override && (0, import_node_fs13.existsSync)(override)) return override;
|
|
4848
5460
|
let dir = corePackageDir();
|
|
4849
5461
|
for (let i = 0; i < 10; i++) {
|
|
4850
5462
|
const candidates = [
|
|
4851
|
-
(0,
|
|
4852
|
-
(0,
|
|
4853
|
-
(0,
|
|
4854
|
-
(0,
|
|
4855
|
-
(0,
|
|
4856
|
-
(0,
|
|
4857
|
-
(0,
|
|
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")
|
|
4858
5470
|
];
|
|
4859
5471
|
for (const p of candidates) {
|
|
4860
|
-
if ((0,
|
|
5472
|
+
if ((0, import_node_fs13.existsSync)(p)) return p;
|
|
4861
5473
|
}
|
|
4862
|
-
const parent = (0,
|
|
5474
|
+
const parent = (0, import_node_path12.dirname)(dir);
|
|
4863
5475
|
if (parent === dir) break;
|
|
4864
5476
|
dir = parent;
|
|
4865
5477
|
}
|
|
@@ -4969,19 +5581,19 @@ function writeMcpServersConfig(servers) {
|
|
|
4969
5581
|
...s.env ? { env: s.env } : {}
|
|
4970
5582
|
};
|
|
4971
5583
|
}
|
|
4972
|
-
const dir = (0,
|
|
4973
|
-
const cfgPath = (0,
|
|
4974
|
-
(0,
|
|
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));
|
|
4975
5587
|
return cfgPath;
|
|
4976
5588
|
}
|
|
4977
|
-
var
|
|
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;
|
|
4978
5590
|
var init_injected_mcp = __esm({
|
|
4979
5591
|
"src/agents/injected-mcp.ts"() {
|
|
4980
5592
|
"use strict";
|
|
4981
|
-
|
|
5593
|
+
import_node_fs13 = require("fs");
|
|
4982
5594
|
import_node_module = require("module");
|
|
4983
5595
|
import_node_os6 = require("os");
|
|
4984
|
-
|
|
5596
|
+
import_node_path12 = require("path");
|
|
4985
5597
|
import_node_url = require("url");
|
|
4986
5598
|
init_run();
|
|
4987
5599
|
init_config();
|
|
@@ -5000,7 +5612,13 @@ var init_injected_mcp = __esm({
|
|
|
5000
5612
|
"mcp__sideboard__present_schema",
|
|
5001
5613
|
"mcp__sideboard__present_files",
|
|
5002
5614
|
"mcp__sideboard__ask_user",
|
|
5003
|
-
"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"
|
|
5004
5622
|
];
|
|
5005
5623
|
brightsyMcpCommandCache = null;
|
|
5006
5624
|
BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
|
|
@@ -5134,9 +5752,9 @@ function eventsFromContentBlocks(blocks) {
|
|
|
5134
5752
|
return out;
|
|
5135
5753
|
}
|
|
5136
5754
|
function parseIssuesJson(raw) {
|
|
5137
|
-
const
|
|
5138
|
-
const candidates = [
|
|
5139
|
-
const match =
|
|
5755
|
+
const text2 = raw.trim();
|
|
5756
|
+
const candidates = [text2];
|
|
5757
|
+
const match = text2.match(/\[[\s\S]*\]/);
|
|
5140
5758
|
if (match) candidates.push(match[0]);
|
|
5141
5759
|
for (const c of candidates) {
|
|
5142
5760
|
try {
|
|
@@ -5158,11 +5776,11 @@ function parseIssuesJson(raw) {
|
|
|
5158
5776
|
}
|
|
5159
5777
|
return [];
|
|
5160
5778
|
}
|
|
5161
|
-
var
|
|
5779
|
+
var import_node_fs14, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
|
|
5162
5780
|
var init_claude = __esm({
|
|
5163
5781
|
"src/agents/claude.ts"() {
|
|
5164
5782
|
"use strict";
|
|
5165
|
-
|
|
5783
|
+
import_node_fs14 = require("fs");
|
|
5166
5784
|
init_run();
|
|
5167
5785
|
init_app_settings();
|
|
5168
5786
|
init_claude_mcp();
|
|
@@ -5191,7 +5809,7 @@ var init_claude = __esm({
|
|
|
5191
5809
|
async detect() {
|
|
5192
5810
|
const claude = resolveClaudeExecutable();
|
|
5193
5811
|
if (claude !== "claude") {
|
|
5194
|
-
if (!(0,
|
|
5812
|
+
if (!(0, import_node_fs14.existsSync)(claude)) {
|
|
5195
5813
|
return {
|
|
5196
5814
|
agent: "claude",
|
|
5197
5815
|
installed: false,
|
|
@@ -5365,8 +5983,8 @@ var init_claude = __esm({
|
|
|
5365
5983
|
if (errorDetail) {
|
|
5366
5984
|
events.push({ type: "stderr", data: errorDetail });
|
|
5367
5985
|
} else {
|
|
5368
|
-
const
|
|
5369
|
-
if (typeof
|
|
5986
|
+
const text2 = obj.result;
|
|
5987
|
+
if (typeof text2 === "string" && text2) events.push({ type: "stdout", data: text2 });
|
|
5370
5988
|
}
|
|
5371
5989
|
const usage = usageFromClaude(obj.usage);
|
|
5372
5990
|
if (usage) events.push({ type: "usage", data: usage });
|
|
@@ -5424,7 +6042,7 @@ async function listCodexModels() {
|
|
|
5424
6042
|
if (codex === "codex") {
|
|
5425
6043
|
const which = await run("which", ["codex"], { reject: false });
|
|
5426
6044
|
if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
|
|
5427
|
-
} else if (!(0,
|
|
6045
|
+
} else if (!(0, import_node_fs15.existsSync)(codex)) {
|
|
5428
6046
|
return FALLBACK_CODEX_MODELS;
|
|
5429
6047
|
}
|
|
5430
6048
|
const listed = await run(codex, ["debug", "models"], { reject: false });
|
|
@@ -5462,13 +6080,13 @@ function usageFromCodex(usage) {
|
|
|
5462
6080
|
}
|
|
5463
6081
|
function codexConfigHasNetworkAccess() {
|
|
5464
6082
|
const candidates = [
|
|
5465
|
-
(0,
|
|
5466
|
-
(0,
|
|
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")
|
|
5467
6085
|
];
|
|
5468
6086
|
for (const path of candidates) {
|
|
5469
|
-
if (!(0,
|
|
5470
|
-
const
|
|
5471
|
-
if (/network_access\s*=\s*true/.test(
|
|
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;
|
|
5472
6090
|
}
|
|
5473
6091
|
return false;
|
|
5474
6092
|
}
|
|
@@ -5481,8 +6099,8 @@ function unwrapCodexMcpResult(result) {
|
|
|
5481
6099
|
const texts = [];
|
|
5482
6100
|
for (const item of rec.content) {
|
|
5483
6101
|
if (!item || typeof item !== "object") continue;
|
|
5484
|
-
const
|
|
5485
|
-
if (typeof
|
|
6102
|
+
const text2 = item.text;
|
|
6103
|
+
if (typeof text2 === "string") texts.push(text2);
|
|
5486
6104
|
}
|
|
5487
6105
|
if (texts.length) return texts.join("\n");
|
|
5488
6106
|
}
|
|
@@ -5499,21 +6117,21 @@ function asRecord(value) {
|
|
|
5499
6117
|
return void 0;
|
|
5500
6118
|
}
|
|
5501
6119
|
function codexLooksAuthenticated() {
|
|
5502
|
-
const authPath = (0,
|
|
5503
|
-
if (!(0,
|
|
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;
|
|
5504
6122
|
try {
|
|
5505
|
-
return (0,
|
|
6123
|
+
return (0, import_node_fs15.statSync)(authPath).size > 2;
|
|
5506
6124
|
} catch {
|
|
5507
6125
|
return false;
|
|
5508
6126
|
}
|
|
5509
6127
|
}
|
|
5510
|
-
var
|
|
6128
|
+
var import_node_fs15, import_node_os7, import_node_path13, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
|
|
5511
6129
|
var init_codex = __esm({
|
|
5512
6130
|
"src/agents/codex.ts"() {
|
|
5513
6131
|
"use strict";
|
|
5514
|
-
|
|
6132
|
+
import_node_fs15 = require("fs");
|
|
5515
6133
|
import_node_os7 = require("os");
|
|
5516
|
-
|
|
6134
|
+
import_node_path13 = require("path");
|
|
5517
6135
|
init_run();
|
|
5518
6136
|
init_app_settings();
|
|
5519
6137
|
init_global_workspace();
|
|
@@ -5536,7 +6154,7 @@ var init_codex = __esm({
|
|
|
5536
6154
|
async detect() {
|
|
5537
6155
|
const codex = resolveAgentExecutable("codex");
|
|
5538
6156
|
if (codex !== "codex") {
|
|
5539
|
-
if (!(0,
|
|
6157
|
+
if (!(0, import_node_fs15.existsSync)(codex)) {
|
|
5540
6158
|
return {
|
|
5541
6159
|
agent: "codex",
|
|
5542
6160
|
installed: false,
|
|
@@ -5968,11 +6586,11 @@ function entryDir() {
|
|
|
5968
6586
|
const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
|
|
5969
6587
|
if (cjsDir) return cjsDir;
|
|
5970
6588
|
try {
|
|
5971
|
-
return (0,
|
|
6589
|
+
return (0, import_node_path14.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
|
|
5972
6590
|
} catch {
|
|
5973
6591
|
try {
|
|
5974
6592
|
const req = (0, import_node_module2.createRequire)(process.cwd() + "/");
|
|
5975
|
-
return (0,
|
|
6593
|
+
return (0, import_node_path14.dirname)(req.resolve("@sideboard-ai/core"));
|
|
5976
6594
|
} catch {
|
|
5977
6595
|
return process.cwd();
|
|
5978
6596
|
}
|
|
@@ -5981,27 +6599,27 @@ function entryDir() {
|
|
|
5981
6599
|
function cursorRunnerPath() {
|
|
5982
6600
|
const root = entryDir();
|
|
5983
6601
|
const candidates = [
|
|
5984
|
-
(0,
|
|
5985
|
-
(0,
|
|
6602
|
+
(0, import_node_path14.join)(root, "agents", "cursor-runner.js"),
|
|
6603
|
+
(0, import_node_path14.join)(root, "agents", "cursor-runner.cjs"),
|
|
5986
6604
|
// If somehow resolved from package root instead of dist/
|
|
5987
|
-
(0,
|
|
5988
|
-
(0,
|
|
6605
|
+
(0, import_node_path14.join)(root, "dist", "agents", "cursor-runner.js"),
|
|
6606
|
+
(0, import_node_path14.join)(root, "dist", "agents", "cursor-runner.cjs"),
|
|
5989
6607
|
// Source tree (dev): packages/core/src/agents/cursor-runner.ts
|
|
5990
|
-
(0,
|
|
5991
|
-
(0,
|
|
6608
|
+
(0, import_node_path14.join)(root, "cursor-runner.ts"),
|
|
6609
|
+
(0, import_node_path14.join)(root, "src", "agents", "cursor-runner.ts")
|
|
5992
6610
|
];
|
|
5993
6611
|
for (const candidate of candidates) {
|
|
5994
|
-
if ((0,
|
|
6612
|
+
if ((0, import_node_fs16.existsSync)(candidate)) return candidate;
|
|
5995
6613
|
}
|
|
5996
6614
|
return candidates[0];
|
|
5997
6615
|
}
|
|
5998
|
-
var
|
|
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;
|
|
5999
6617
|
var init_cursor = __esm({
|
|
6000
6618
|
"src/agents/cursor.ts"() {
|
|
6001
6619
|
"use strict";
|
|
6002
|
-
|
|
6620
|
+
import_node_fs16 = require("fs");
|
|
6003
6621
|
import_node_module2 = require("module");
|
|
6004
|
-
|
|
6622
|
+
import_node_path14 = require("path");
|
|
6005
6623
|
import_node_url2 = require("url");
|
|
6006
6624
|
import_sdk = require("@cursor/sdk");
|
|
6007
6625
|
init_run();
|
|
@@ -6127,7 +6745,7 @@ async function listOpencodeModels() {
|
|
|
6127
6745
|
if (opencode === "opencode") {
|
|
6128
6746
|
const which = await run("which", ["opencode"], { reject: false });
|
|
6129
6747
|
if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
|
|
6130
|
-
} else if (!(0,
|
|
6748
|
+
} else if (!(0, import_node_fs17.existsSync)(opencode)) {
|
|
6131
6749
|
return FALLBACK_OPENCODE_MODELS;
|
|
6132
6750
|
}
|
|
6133
6751
|
const listed = await run(opencode, ["models"], { reject: false });
|
|
@@ -6157,11 +6775,11 @@ function usageFromOpencode(tokens) {
|
|
|
6157
6775
|
cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
|
|
6158
6776
|
};
|
|
6159
6777
|
}
|
|
6160
|
-
var
|
|
6778
|
+
var import_node_fs17, FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
|
|
6161
6779
|
var init_opencode = __esm({
|
|
6162
6780
|
"src/agents/opencode.ts"() {
|
|
6163
6781
|
"use strict";
|
|
6164
|
-
|
|
6782
|
+
import_node_fs17 = require("fs");
|
|
6165
6783
|
init_run();
|
|
6166
6784
|
init_app_settings();
|
|
6167
6785
|
init_global_workspace();
|
|
@@ -6187,7 +6805,7 @@ var init_opencode = __esm({
|
|
|
6187
6805
|
async detect() {
|
|
6188
6806
|
const opencode = resolveAgentExecutable("opencode");
|
|
6189
6807
|
if (opencode !== "opencode") {
|
|
6190
|
-
if (!(0,
|
|
6808
|
+
if (!(0, import_node_fs17.existsSync)(opencode)) {
|
|
6191
6809
|
return {
|
|
6192
6810
|
agent: "opencode",
|
|
6193
6811
|
installed: false,
|
|
@@ -6277,8 +6895,8 @@ var init_opencode = __esm({
|
|
|
6277
6895
|
return { type: "session_id", data: sid };
|
|
6278
6896
|
}
|
|
6279
6897
|
if (obj.type === "text") {
|
|
6280
|
-
const
|
|
6281
|
-
if (
|
|
6898
|
+
const text2 = obj.part?.text ?? obj.text;
|
|
6899
|
+
if (text2) return { type: "stdout", data: text2 };
|
|
6282
6900
|
}
|
|
6283
6901
|
if (obj.type === "tool_use") {
|
|
6284
6902
|
const part = obj.part;
|
|
@@ -6491,8 +7109,8 @@ var init_list_models = __esm({
|
|
|
6491
7109
|
});
|
|
6492
7110
|
|
|
6493
7111
|
// src/agents/session-quota.ts
|
|
6494
|
-
function isSessionQuotaLimit(
|
|
6495
|
-
const lower =
|
|
7112
|
+
function isSessionQuotaLimit(text2) {
|
|
7113
|
+
const lower = text2.trim().toLowerCase();
|
|
6496
7114
|
if (!lower) return false;
|
|
6497
7115
|
if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
|
|
6498
7116
|
return false;
|
|
@@ -6500,10 +7118,10 @@ function isSessionQuotaLimit(text) {
|
|
|
6500
7118
|
if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
|
|
6501
7119
|
return false;
|
|
6502
7120
|
}
|
|
6503
|
-
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(
|
|
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);
|
|
6504
7122
|
}
|
|
6505
|
-
function parseSessionQuotaResetAt(
|
|
6506
|
-
const absolute =
|
|
7123
|
+
function parseSessionQuotaResetAt(text2, now = /* @__PURE__ */ new Date()) {
|
|
7124
|
+
const absolute = text2.match(
|
|
6507
7125
|
/resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
|
|
6508
7126
|
);
|
|
6509
7127
|
if (absolute) {
|
|
@@ -6521,7 +7139,7 @@ function parseSessionQuotaResetAt(text, now = /* @__PURE__ */ new Date()) {
|
|
|
6521
7139
|
}
|
|
6522
7140
|
return at;
|
|
6523
7141
|
}
|
|
6524
|
-
const relative =
|
|
7142
|
+
const relative = text2.match(
|
|
6525
7143
|
/resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
|
|
6526
7144
|
);
|
|
6527
7145
|
if (relative) {
|
|
@@ -6924,38 +7542,38 @@ __export(workspaces_exports, {
|
|
|
6924
7542
|
syncWorkspacesFromThreads: () => syncWorkspacesFromThreads
|
|
6925
7543
|
});
|
|
6926
7544
|
function workspacesFile() {
|
|
6927
|
-
return (0,
|
|
7545
|
+
return (0, import_node_path18.join)(appDataDir(), "workspaces.json");
|
|
6928
7546
|
}
|
|
6929
7547
|
function removedWorkspacesFile() {
|
|
6930
|
-
return (0,
|
|
7548
|
+
return (0, import_node_path18.join)(appDataDir(), "removed-workspaces.json");
|
|
6931
7549
|
}
|
|
6932
7550
|
function readAll() {
|
|
6933
7551
|
const path = workspacesFile();
|
|
6934
|
-
if (!(0,
|
|
7552
|
+
if (!(0, import_node_fs21.existsSync)(path)) return [];
|
|
6935
7553
|
try {
|
|
6936
|
-
const raw = JSON.parse((0,
|
|
7554
|
+
const raw = JSON.parse((0, import_node_fs21.readFileSync)(path, "utf8"));
|
|
6937
7555
|
return Array.isArray(raw) ? raw : [];
|
|
6938
7556
|
} catch {
|
|
6939
7557
|
return [];
|
|
6940
7558
|
}
|
|
6941
7559
|
}
|
|
6942
7560
|
function writeAll(list) {
|
|
6943
|
-
(0,
|
|
6944
|
-
(0,
|
|
7561
|
+
(0, import_node_fs21.mkdirSync)(appDataDir(), { recursive: true });
|
|
7562
|
+
(0, import_node_fs21.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
|
|
6945
7563
|
}
|
|
6946
7564
|
function readRemoved() {
|
|
6947
7565
|
const path = removedWorkspacesFile();
|
|
6948
|
-
if (!(0,
|
|
7566
|
+
if (!(0, import_node_fs21.existsSync)(path)) return /* @__PURE__ */ new Set();
|
|
6949
7567
|
try {
|
|
6950
|
-
const raw = JSON.parse((0,
|
|
7568
|
+
const raw = JSON.parse((0, import_node_fs21.readFileSync)(path, "utf8"));
|
|
6951
7569
|
return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
|
|
6952
7570
|
} catch {
|
|
6953
7571
|
return /* @__PURE__ */ new Set();
|
|
6954
7572
|
}
|
|
6955
7573
|
}
|
|
6956
7574
|
function writeRemoved(paths) {
|
|
6957
|
-
(0,
|
|
6958
|
-
(0,
|
|
7575
|
+
(0, import_node_fs21.mkdirSync)(appDataDir(), { recursive: true });
|
|
7576
|
+
(0, import_node_fs21.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
|
|
6959
7577
|
}
|
|
6960
7578
|
function rememberRemoved(repoPath) {
|
|
6961
7579
|
const next = readRemoved();
|
|
@@ -6978,7 +7596,7 @@ function listWorkspaces() {
|
|
|
6978
7596
|
async function addWorkspace(repoPath) {
|
|
6979
7597
|
const root = await resolveRepoRoot(repoPath);
|
|
6980
7598
|
if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
|
|
6981
|
-
if (!(0,
|
|
7599
|
+
if (!(0, import_node_fs21.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
|
|
6982
7600
|
forgetRemoved(root);
|
|
6983
7601
|
await ensureGhPreferOrigin(root);
|
|
6984
7602
|
const current = readAll();
|
|
@@ -6986,7 +7604,7 @@ async function addWorkspace(repoPath) {
|
|
|
6986
7604
|
if (existing) return existing;
|
|
6987
7605
|
const next = {
|
|
6988
7606
|
path: root,
|
|
6989
|
-
name: (0,
|
|
7607
|
+
name: (0, import_node_path18.basename)(root),
|
|
6990
7608
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6991
7609
|
};
|
|
6992
7610
|
writeAll([...current, next]);
|
|
@@ -7008,10 +7626,10 @@ function syncWorkspacesFromThreads(repoPaths) {
|
|
|
7008
7626
|
if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
|
|
7009
7627
|
continue;
|
|
7010
7628
|
}
|
|
7011
|
-
if (!(0,
|
|
7629
|
+
if (!(0, import_node_fs21.existsSync)(path)) continue;
|
|
7012
7630
|
const ws = {
|
|
7013
7631
|
path,
|
|
7014
|
-
name: (0,
|
|
7632
|
+
name: (0, import_node_path18.basename)(path),
|
|
7015
7633
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7016
7634
|
};
|
|
7017
7635
|
byPath.set(path, ws);
|
|
@@ -7021,12 +7639,12 @@ function syncWorkspacesFromThreads(repoPaths) {
|
|
|
7021
7639
|
if (dirty) writeAll(next);
|
|
7022
7640
|
return next.sort((a, b) => a.name.localeCompare(b.name));
|
|
7023
7641
|
}
|
|
7024
|
-
var
|
|
7642
|
+
var import_node_fs21, import_node_path18;
|
|
7025
7643
|
var init_workspaces = __esm({
|
|
7026
7644
|
"src/store/workspaces.ts"() {
|
|
7027
7645
|
"use strict";
|
|
7028
|
-
|
|
7029
|
-
|
|
7646
|
+
import_node_fs21 = require("fs");
|
|
7647
|
+
import_node_path18 = require("path");
|
|
7030
7648
|
init_paths();
|
|
7031
7649
|
init_global_workspace();
|
|
7032
7650
|
init_worktree();
|
|
@@ -7067,11 +7685,11 @@ function resolvePlanMarkdown(opts) {
|
|
|
7067
7685
|
source: "exit_plan"
|
|
7068
7686
|
};
|
|
7069
7687
|
}
|
|
7070
|
-
const
|
|
7071
|
-
if (
|
|
7688
|
+
const text2 = opts.text?.trim();
|
|
7689
|
+
if (text2 && text2.length >= 80) {
|
|
7072
7690
|
return {
|
|
7073
7691
|
title: "Plan",
|
|
7074
|
-
content:
|
|
7692
|
+
content: text2,
|
|
7075
7693
|
path: PLAN_FILE_REL,
|
|
7076
7694
|
source: "text"
|
|
7077
7695
|
};
|
|
@@ -7103,40 +7721,40 @@ __export(plan_file_exports, {
|
|
|
7103
7721
|
writePlanFile: () => writePlanFile
|
|
7104
7722
|
});
|
|
7105
7723
|
function ensureAttachmentsGitignore2(worktreePath) {
|
|
7106
|
-
const gitignoreAbs = (0,
|
|
7107
|
-
if ((0,
|
|
7108
|
-
(0,
|
|
7109
|
-
(0,
|
|
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");
|
|
7110
7728
|
}
|
|
7111
7729
|
function planFileAbs(worktreePath) {
|
|
7112
|
-
return (0,
|
|
7730
|
+
return (0, import_node_path26.join)(worktreePath, PLAN_FILE_REL);
|
|
7113
7731
|
}
|
|
7114
7732
|
function readTextIfPresent2(abs) {
|
|
7115
|
-
if (!(0,
|
|
7733
|
+
if (!(0, import_node_fs31.existsSync)(abs)) return null;
|
|
7116
7734
|
try {
|
|
7117
|
-
const content = (0,
|
|
7735
|
+
const content = (0, import_node_fs31.readFileSync)(abs, "utf8");
|
|
7118
7736
|
return content.trim() ? content : null;
|
|
7119
7737
|
} catch {
|
|
7120
7738
|
return null;
|
|
7121
7739
|
}
|
|
7122
7740
|
}
|
|
7123
7741
|
function readPlanFile(worktreePath) {
|
|
7124
|
-
return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0,
|
|
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));
|
|
7125
7743
|
}
|
|
7126
7744
|
function writePlanFile(worktreePath, content) {
|
|
7127
7745
|
ensureAttachmentsGitignore2(worktreePath);
|
|
7128
7746
|
const abs = planFileAbs(worktreePath);
|
|
7129
|
-
(0,
|
|
7747
|
+
(0, import_node_fs31.mkdirSync)((0, import_node_path26.dirname)(abs), { recursive: true });
|
|
7130
7748
|
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
7131
|
-
(0,
|
|
7749
|
+
(0, import_node_fs31.writeFileSync)(abs, body, "utf8");
|
|
7132
7750
|
return PLAN_FILE_REL;
|
|
7133
7751
|
}
|
|
7134
|
-
var
|
|
7752
|
+
var import_node_fs31, import_node_path26;
|
|
7135
7753
|
var init_plan_file = __esm({
|
|
7136
7754
|
"src/plan/plan-file.ts"() {
|
|
7137
7755
|
"use strict";
|
|
7138
|
-
|
|
7139
|
-
|
|
7756
|
+
import_node_fs31 = require("fs");
|
|
7757
|
+
import_node_path26 = require("path");
|
|
7140
7758
|
init_workspace_scratch();
|
|
7141
7759
|
init_plan_present();
|
|
7142
7760
|
init_plan_present();
|
|
@@ -7151,10 +7769,10 @@ __export(cursor_recover_exports, {
|
|
|
7151
7769
|
function recoverFinishedCursorRun(opts) {
|
|
7152
7770
|
const agentId = opts.agentId.trim();
|
|
7153
7771
|
if (!agentId) return null;
|
|
7154
|
-
const runsPath = (0,
|
|
7155
|
-
if (!(0,
|
|
7772
|
+
const runsPath = (0, import_node_path27.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
|
|
7773
|
+
if (!(0, import_node_fs32.existsSync)(runsPath)) return null;
|
|
7156
7774
|
try {
|
|
7157
|
-
const lines = (0,
|
|
7775
|
+
const lines = (0, import_node_fs32.readFileSync)(runsPath, "utf8").split("\n");
|
|
7158
7776
|
let best = null;
|
|
7159
7777
|
for (const line of lines) {
|
|
7160
7778
|
const trimmed = line.trim();
|
|
@@ -7180,25 +7798,133 @@ function recoverFinishedCursorRun(opts) {
|
|
|
7180
7798
|
return null;
|
|
7181
7799
|
}
|
|
7182
7800
|
}
|
|
7183
|
-
var
|
|
7801
|
+
var import_node_fs32, import_node_path27;
|
|
7184
7802
|
var init_cursor_recover = __esm({
|
|
7185
7803
|
"src/agents/cursor-recover.ts"() {
|
|
7186
7804
|
"use strict";
|
|
7187
|
-
|
|
7188
|
-
|
|
7805
|
+
import_node_fs32 = require("fs");
|
|
7806
|
+
import_node_path27 = require("path");
|
|
7189
7807
|
init_paths();
|
|
7190
7808
|
}
|
|
7191
7809
|
});
|
|
7192
7810
|
|
|
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
|
+
|
|
7193
7919
|
// src/mcp/server.ts
|
|
7194
7920
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
7195
7921
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7196
|
-
var
|
|
7197
|
-
var
|
|
7922
|
+
var import_zod2 = require("zod");
|
|
7923
|
+
var import_node_path31 = require("path");
|
|
7198
7924
|
|
|
7199
7925
|
// src/orchestrator/orchestrator.ts
|
|
7200
7926
|
var import_node_events = require("events");
|
|
7201
|
-
var
|
|
7927
|
+
var import_node_fs33 = require("fs");
|
|
7202
7928
|
init_error_detail();
|
|
7203
7929
|
|
|
7204
7930
|
// src/agents/spawn.ts
|
|
@@ -7285,9 +8011,9 @@ function toolFilePath(input) {
|
|
|
7285
8011
|
if (!input) return void 0;
|
|
7286
8012
|
return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
|
|
7287
8013
|
}
|
|
7288
|
-
function countLines(
|
|
7289
|
-
if (!
|
|
7290
|
-
return
|
|
8014
|
+
function countLines(text2) {
|
|
8015
|
+
if (!text2) return 0;
|
|
8016
|
+
return text2.split("\n").length;
|
|
7291
8017
|
}
|
|
7292
8018
|
function diffFromInput(input) {
|
|
7293
8019
|
if (!input) return {};
|
|
@@ -7414,9 +8140,9 @@ function applyAgentEvent(parts, event) {
|
|
|
7414
8140
|
function partsToAssistantText(parts) {
|
|
7415
8141
|
return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
|
|
7416
8142
|
}
|
|
7417
|
-
function stripBrightsyNdjsonNoise(
|
|
7418
|
-
if (!
|
|
7419
|
-
let out =
|
|
8143
|
+
function stripBrightsyNdjsonNoise(text2) {
|
|
8144
|
+
if (!text2 || !text2.includes('"type"')) return text2;
|
|
8145
|
+
let out = text2;
|
|
7420
8146
|
out = out.replace(
|
|
7421
8147
|
/\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
|
|
7422
8148
|
""
|
|
@@ -7553,8 +8279,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
7553
8279
|
onEvent({ type: "exit", data: exitCode });
|
|
7554
8280
|
const finalized = finalizeParts(parts);
|
|
7555
8281
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
7556
|
-
const
|
|
7557
|
-
return { exitCode, sessionId, assistantText:
|
|
8282
|
+
const text2 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
8283
|
+
return { exitCode, sessionId, assistantText: text2, parts: finalized, usage };
|
|
7558
8284
|
});
|
|
7559
8285
|
return {
|
|
7560
8286
|
pid: child.pid,
|
|
@@ -7588,9 +8314,9 @@ function shouldAutoArchiveOnPrMerge(opts) {
|
|
|
7588
8314
|
}
|
|
7589
8315
|
|
|
7590
8316
|
// src/hook/conductor.ts
|
|
7591
|
-
var
|
|
8317
|
+
var import_node_fs18 = require("fs");
|
|
7592
8318
|
var import_node_net = require("net");
|
|
7593
|
-
var
|
|
8319
|
+
var import_node_path15 = require("path");
|
|
7594
8320
|
var import_execa3 = require("execa");
|
|
7595
8321
|
var import_node_readline2 = require("readline");
|
|
7596
8322
|
init_settings();
|
|
@@ -7600,9 +8326,9 @@ function matchSimpleGlob(pattern, name) {
|
|
|
7600
8326
|
return new RegExp(`^${escaped}$`).test(name);
|
|
7601
8327
|
}
|
|
7602
8328
|
function readWorktreeInclude(repoPath) {
|
|
7603
|
-
const path = (0,
|
|
7604
|
-
if (!(0,
|
|
7605
|
-
return (0,
|
|
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("#"));
|
|
7606
8332
|
}
|
|
7607
8333
|
function resolveFilesToCopy(repoPath) {
|
|
7608
8334
|
const fromInclude = readWorktreeInclude(repoPath);
|
|
@@ -7612,10 +8338,10 @@ function resolveFilesToCopy(repoPath) {
|
|
|
7612
8338
|
if (settings?.fileIncludeGlobs?.length) {
|
|
7613
8339
|
const matched = [];
|
|
7614
8340
|
try {
|
|
7615
|
-
for (const entry of (0,
|
|
8341
|
+
for (const entry of (0, import_node_fs18.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
7616
8342
|
if (!entry.isFile()) continue;
|
|
7617
8343
|
for (const glob of settings.fileIncludeGlobs) {
|
|
7618
|
-
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0,
|
|
8344
|
+
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path15.basename)(glob), entry.name)) {
|
|
7619
8345
|
matched.push(entry.name);
|
|
7620
8346
|
break;
|
|
7621
8347
|
}
|
|
@@ -7627,7 +8353,7 @@ function resolveFilesToCopy(repoPath) {
|
|
|
7627
8353
|
}
|
|
7628
8354
|
const defaults = [];
|
|
7629
8355
|
try {
|
|
7630
|
-
for (const entry of (0,
|
|
8356
|
+
for (const entry of (0, import_node_fs18.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
7631
8357
|
if (entry.isFile() && entry.name.startsWith(".env")) {
|
|
7632
8358
|
defaults.push(entry.name);
|
|
7633
8359
|
}
|
|
@@ -7641,11 +8367,11 @@ function copyConfiguredFiles(repoPath, worktreePath) {
|
|
|
7641
8367
|
const patterns = resolveFilesToCopy(repoPath);
|
|
7642
8368
|
const copied = [];
|
|
7643
8369
|
for (const rel of patterns) {
|
|
7644
|
-
const src = (0,
|
|
7645
|
-
if (!(0,
|
|
7646
|
-
const dest = (0,
|
|
7647
|
-
(0,
|
|
7648
|
-
(0,
|
|
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);
|
|
7649
8375
|
copied.push(rel);
|
|
7650
8376
|
}
|
|
7651
8377
|
return copied;
|
|
@@ -7679,7 +8405,7 @@ async function captureLoginEnv() {
|
|
|
7679
8405
|
}
|
|
7680
8406
|
function buildWorkspaceScriptEnv(opts, baseEnv) {
|
|
7681
8407
|
const env = { ...baseEnv ?? process.env };
|
|
7682
|
-
const name = opts.workspaceName ?? (0,
|
|
8408
|
+
const name = opts.workspaceName ?? (0, import_node_path15.basename)(opts.worktreePath);
|
|
7683
8409
|
const ports = opts.ports ?? [];
|
|
7684
8410
|
const primary = ports[0];
|
|
7685
8411
|
env.SIDEBOARD_WORKSPACE_NAME = name;
|
|
@@ -7898,15 +8624,15 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
|
7898
8624
|
}
|
|
7899
8625
|
|
|
7900
8626
|
// src/hook/cursor-worktrees.ts
|
|
7901
|
-
var
|
|
7902
|
-
var
|
|
8627
|
+
var import_node_fs19 = require("fs");
|
|
8628
|
+
var import_node_path16 = require("path");
|
|
7903
8629
|
var import_execa4 = require("execa");
|
|
7904
8630
|
var import_node_readline3 = require("readline");
|
|
7905
8631
|
function loadCursorWorktreesJson(rootPath) {
|
|
7906
|
-
const path = (0,
|
|
7907
|
-
if (!(0,
|
|
8632
|
+
const path = (0, import_node_path16.join)(rootPath, ".cursor", "worktrees.json");
|
|
8633
|
+
if (!(0, import_node_fs19.existsSync)(path)) return null;
|
|
7908
8634
|
try {
|
|
7909
|
-
return JSON.parse((0,
|
|
8635
|
+
return JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
|
|
7910
8636
|
} catch {
|
|
7911
8637
|
return null;
|
|
7912
8638
|
}
|
|
@@ -7932,7 +8658,7 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
|
|
|
7932
8658
|
if (process.platform === "win32") {
|
|
7933
8659
|
env.ROOT_WORKTREE_PATH = repoPath;
|
|
7934
8660
|
}
|
|
7935
|
-
const commands = Array.isArray(spec) ? spec : [spec.endsWith(".sh") || spec.endsWith(".ps1") ? (0,
|
|
8661
|
+
const commands = Array.isArray(spec) ? spec : [spec.endsWith(".sh") || spec.endsWith(".ps1") ? (0, import_node_path16.join)(
|
|
7936
8662
|
fromWorktree ? worktreePath : repoPath,
|
|
7937
8663
|
".cursor",
|
|
7938
8664
|
spec
|
|
@@ -7964,8 +8690,8 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
|
|
|
7964
8690
|
}
|
|
7965
8691
|
|
|
7966
8692
|
// src/git/orphan-cleanup.ts
|
|
7967
|
-
var
|
|
7968
|
-
var
|
|
8693
|
+
var import_node_fs20 = require("fs");
|
|
8694
|
+
var import_node_path17 = require("path");
|
|
7969
8695
|
init_worktree();
|
|
7970
8696
|
init_thread_store();
|
|
7971
8697
|
init_paths();
|
|
@@ -7980,9 +8706,9 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
7980
8706
|
repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
|
|
7981
8707
|
);
|
|
7982
8708
|
const homeRoot = sideboardWorkspacesDir();
|
|
7983
|
-
if ((0,
|
|
8709
|
+
if ((0, import_node_fs20.existsSync)(homeRoot)) {
|
|
7984
8710
|
try {
|
|
7985
|
-
for (const entry of (0,
|
|
8711
|
+
for (const entry of (0, import_node_fs20.readdirSync)(homeRoot, { withFileTypes: true })) {
|
|
7986
8712
|
if (!entry.isDirectory()) continue;
|
|
7987
8713
|
void entry;
|
|
7988
8714
|
}
|
|
@@ -7992,7 +8718,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
7992
8718
|
const orphans = [];
|
|
7993
8719
|
const seen = /* @__PURE__ */ new Set();
|
|
7994
8720
|
for (const repoPath of repos) {
|
|
7995
|
-
if (!repoPath || !(0,
|
|
8721
|
+
if (!repoPath || !(0, import_node_fs20.existsSync)(repoPath)) continue;
|
|
7996
8722
|
try {
|
|
7997
8723
|
const wts = await listWorktrees(repoPath);
|
|
7998
8724
|
for (const wt of wts) {
|
|
@@ -8003,7 +8729,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
8003
8729
|
seen.add(path);
|
|
8004
8730
|
let mtimeMs = 0;
|
|
8005
8731
|
try {
|
|
8006
|
-
mtimeMs = (0,
|
|
8732
|
+
mtimeMs = (0, import_node_fs20.statSync)(path).mtimeMs;
|
|
8007
8733
|
} catch {
|
|
8008
8734
|
mtimeMs = 0;
|
|
8009
8735
|
}
|
|
@@ -8013,16 +8739,16 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
8013
8739
|
}
|
|
8014
8740
|
try {
|
|
8015
8741
|
const root = worktreesRoot(repoPath);
|
|
8016
|
-
if ((0,
|
|
8017
|
-
for (const entry of (0,
|
|
8742
|
+
if ((0, import_node_fs20.existsSync)(root)) {
|
|
8743
|
+
for (const entry of (0, import_node_fs20.readdirSync)(root, { withFileTypes: true })) {
|
|
8018
8744
|
if (!entry.isDirectory()) continue;
|
|
8019
|
-
const path = (0,
|
|
8745
|
+
const path = (0, import_node_path17.join)(root, entry.name).replace(/\/$/, "");
|
|
8020
8746
|
if (known.has(path) || seen.has(path)) continue;
|
|
8021
|
-
if (!(0,
|
|
8747
|
+
if (!(0, import_node_fs20.existsSync)((0, import_node_path17.join)(path, ".git"))) continue;
|
|
8022
8748
|
seen.add(path);
|
|
8023
8749
|
let mtimeMs = 0;
|
|
8024
8750
|
try {
|
|
8025
|
-
mtimeMs = (0,
|
|
8751
|
+
mtimeMs = (0, import_node_fs20.statSync)(path).mtimeMs;
|
|
8026
8752
|
} catch {
|
|
8027
8753
|
mtimeMs = Date.now();
|
|
8028
8754
|
}
|
|
@@ -8159,8 +8885,8 @@ async function applyThreadIntoMain(thread, opts) {
|
|
|
8159
8885
|
}
|
|
8160
8886
|
|
|
8161
8887
|
// src/git/clone-repo.ts
|
|
8162
|
-
var
|
|
8163
|
-
var
|
|
8888
|
+
var import_node_fs22 = require("fs");
|
|
8889
|
+
var import_node_path19 = require("path");
|
|
8164
8890
|
var import_execa6 = require("execa");
|
|
8165
8891
|
init_paths();
|
|
8166
8892
|
init_workspaces();
|
|
@@ -8170,12 +8896,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
8170
8896
|
if (!url) throw new Error("Clone URL is required");
|
|
8171
8897
|
let name = opts.name?.trim();
|
|
8172
8898
|
if (!name) {
|
|
8173
|
-
const leaf = (0,
|
|
8899
|
+
const leaf = (0, import_node_path19.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
|
|
8174
8900
|
name = leaf || "repo";
|
|
8175
8901
|
}
|
|
8176
8902
|
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
8177
|
-
const dest = (0,
|
|
8178
|
-
if ((0,
|
|
8903
|
+
const dest = (0, import_node_path19.join)(sideboardReposDir(), name);
|
|
8904
|
+
if ((0, import_node_fs22.existsSync)(dest)) {
|
|
8179
8905
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
8180
8906
|
const workspace2 = await ensureWorkspace(repoPath2);
|
|
8181
8907
|
return { repoPath: repoPath2, workspace: workspace2 };
|
|
@@ -8195,7 +8921,7 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
8195
8921
|
init_thread_store();
|
|
8196
8922
|
|
|
8197
8923
|
// src/threads/create.ts
|
|
8198
|
-
var
|
|
8924
|
+
var import_node_fs23 = require("fs");
|
|
8199
8925
|
|
|
8200
8926
|
// src/detect/detect.ts
|
|
8201
8927
|
init_agents();
|
|
@@ -8240,7 +8966,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
8240
8966
|
});
|
|
8241
8967
|
await requireAgent(resolved.agent);
|
|
8242
8968
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
8243
|
-
if (!(0,
|
|
8969
|
+
if (!(0, import_node_fs23.existsSync)(repoPath)) {
|
|
8244
8970
|
throw new Error(`Repo not found: ${repoPath}`);
|
|
8245
8971
|
}
|
|
8246
8972
|
let sourceRef = input.sourceRef;
|
|
@@ -8311,7 +9037,7 @@ async function listLinearIssues(agent, repoPath) {
|
|
|
8311
9037
|
init_orchestrator_capable();
|
|
8312
9038
|
|
|
8313
9039
|
// src/threads/chat-tabs.ts
|
|
8314
|
-
var
|
|
9040
|
+
var import_node_crypto4 = require("crypto");
|
|
8315
9041
|
|
|
8316
9042
|
// src/composer/summarize.ts
|
|
8317
9043
|
init_run();
|
|
@@ -8359,9 +9085,9 @@ async function tryClaudeSummary(transcript, opts) {
|
|
|
8359
9085
|
{ cwd: opts?.cwd, reject: false }
|
|
8360
9086
|
);
|
|
8361
9087
|
if (exitCode !== 0) return null;
|
|
8362
|
-
const
|
|
8363
|
-
if (
|
|
8364
|
-
return
|
|
9088
|
+
const text2 = stdout.trim();
|
|
9089
|
+
if (text2.length < 40) return null;
|
|
9090
|
+
return text2;
|
|
8365
9091
|
} catch {
|
|
8366
9092
|
return null;
|
|
8367
9093
|
}
|
|
@@ -8410,9 +9136,9 @@ function extractiveSummary(transcript) {
|
|
|
8410
9136
|
);
|
|
8411
9137
|
return parts.join("\n");
|
|
8412
9138
|
}
|
|
8413
|
-
function clipSummary(
|
|
8414
|
-
if (
|
|
8415
|
-
return `${
|
|
9139
|
+
function clipSummary(text2) {
|
|
9140
|
+
if (text2.length <= MAX_SUMMARY_CHARS) return text2;
|
|
9141
|
+
return `${text2.slice(0, MAX_SUMMARY_CHARS)}
|
|
8416
9142
|
|
|
8417
9143
|
[\u2026summary truncated\u2026]`;
|
|
8418
9144
|
}
|
|
@@ -8648,7 +9374,7 @@ function forkMessageSlice(from, throughIndex) {
|
|
|
8648
9374
|
function buildForkTranscriptAttachment(baseTitle, messages) {
|
|
8649
9375
|
const title = baseTitle || "Chat";
|
|
8650
9376
|
return {
|
|
8651
|
-
id: (0,
|
|
9377
|
+
id: (0, import_node_crypto4.randomUUID)(),
|
|
8652
9378
|
name: `Transcript of ${title}.md`,
|
|
8653
9379
|
kind: "transcript",
|
|
8654
9380
|
content: formatTranscriptMarkdown(title, messages)
|
|
@@ -8702,9 +9428,9 @@ function forkChatTab(input) {
|
|
|
8702
9428
|
}
|
|
8703
9429
|
|
|
8704
9430
|
// src/review/request-review.ts
|
|
8705
|
-
var
|
|
8706
|
-
var
|
|
8707
|
-
var
|
|
9431
|
+
var import_node_crypto5 = require("crypto");
|
|
9432
|
+
var import_node_fs24 = require("fs");
|
|
9433
|
+
var import_node_path20 = require("path");
|
|
8708
9434
|
init_global_workspace();
|
|
8709
9435
|
init_thread_store();
|
|
8710
9436
|
|
|
@@ -8848,22 +9574,22 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
8848
9574
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
8849
9575
|
}
|
|
8850
9576
|
function readTextIfPresent(abs) {
|
|
8851
|
-
if (!(0,
|
|
9577
|
+
if (!(0, import_node_fs24.existsSync)(abs)) return null;
|
|
8852
9578
|
try {
|
|
8853
|
-
const content = (0,
|
|
9579
|
+
const content = (0, import_node_fs24.readFileSync)(abs, "utf8");
|
|
8854
9580
|
return content.trim() ? content : null;
|
|
8855
9581
|
} catch {
|
|
8856
9582
|
return null;
|
|
8857
9583
|
}
|
|
8858
9584
|
}
|
|
8859
9585
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
8860
|
-
const gitignoreAbs = (0,
|
|
8861
|
-
if ((0,
|
|
8862
|
-
(0,
|
|
8863
|
-
(0,
|
|
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");
|
|
8864
9590
|
}
|
|
8865
9591
|
function resolveReviewGuidelines(worktreePath) {
|
|
8866
|
-
const repoAbs = (0,
|
|
9592
|
+
const repoAbs = (0, import_node_path20.join)(worktreePath, REPO_REVIEW_PATH);
|
|
8867
9593
|
const repoContent = readTextIfPresent(repoAbs);
|
|
8868
9594
|
if (repoContent) {
|
|
8869
9595
|
return {
|
|
@@ -8873,7 +9599,7 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
8873
9599
|
source: "repo"
|
|
8874
9600
|
};
|
|
8875
9601
|
}
|
|
8876
|
-
const localAbs = (0,
|
|
9602
|
+
const localAbs = (0, import_node_path20.join)(worktreePath, REVIEW_REQUEST_PATH);
|
|
8877
9603
|
const localContent = readTextIfPresent(localAbs);
|
|
8878
9604
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
8879
9605
|
return {
|
|
@@ -8883,7 +9609,7 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
8883
9609
|
source: "local"
|
|
8884
9610
|
};
|
|
8885
9611
|
}
|
|
8886
|
-
const legacyAbs = (0,
|
|
9612
|
+
const legacyAbs = (0, import_node_path20.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
|
|
8887
9613
|
const legacyContent = readTextIfPresent(legacyAbs);
|
|
8888
9614
|
if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
|
|
8889
9615
|
return {
|
|
@@ -8894,8 +9620,8 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
8894
9620
|
};
|
|
8895
9621
|
}
|
|
8896
9622
|
ensureAttachmentsGitignore(worktreePath);
|
|
8897
|
-
(0,
|
|
8898
|
-
(0,
|
|
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");
|
|
8899
9625
|
return {
|
|
8900
9626
|
path: REVIEW_REQUEST_PATH,
|
|
8901
9627
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -8907,7 +9633,7 @@ function buildReviewRequestAttachment(content, opts) {
|
|
|
8907
9633
|
const path = opts?.path ?? REVIEW_REQUEST_PATH;
|
|
8908
9634
|
const name = opts?.name ?? (path === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
|
|
8909
9635
|
return {
|
|
8910
|
-
id: (0,
|
|
9636
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8911
9637
|
name,
|
|
8912
9638
|
kind: "file",
|
|
8913
9639
|
path,
|
|
@@ -8985,7 +9711,7 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
8985
9711
|
}
|
|
8986
9712
|
|
|
8987
9713
|
// src/orchestrator/quota-failover.ts
|
|
8988
|
-
var
|
|
9714
|
+
var import_node_crypto6 = require("crypto");
|
|
8989
9715
|
init_session_quota();
|
|
8990
9716
|
init_app_settings();
|
|
8991
9717
|
init_global_workspace();
|
|
@@ -9040,8 +9766,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
9040
9766
|
);
|
|
9041
9767
|
const recent = from.messages.slice(-8).map((m) => {
|
|
9042
9768
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
9043
|
-
const
|
|
9044
|
-
return
|
|
9769
|
+
const text2 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
9770
|
+
return text2 ? `- ${role}: ${text2}` : null;
|
|
9045
9771
|
}).filter(Boolean);
|
|
9046
9772
|
const body = [
|
|
9047
9773
|
`# Orchestration handoff`,
|
|
@@ -9066,7 +9792,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
9066
9792
|
`- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
|
|
9067
9793
|
].join("\n");
|
|
9068
9794
|
return {
|
|
9069
|
-
id: (0,
|
|
9795
|
+
id: (0, import_node_crypto6.randomUUID)(),
|
|
9070
9796
|
name: "Orchestration quota handoff.md",
|
|
9071
9797
|
kind: "transcript",
|
|
9072
9798
|
content: body
|
|
@@ -9094,21 +9820,21 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
9094
9820
|
}
|
|
9095
9821
|
|
|
9096
9822
|
// src/threads/adopt.ts
|
|
9097
|
-
var
|
|
9098
|
-
var
|
|
9823
|
+
var import_node_child_process3 = require("child_process");
|
|
9824
|
+
var import_node_fs25 = require("fs");
|
|
9099
9825
|
var import_node_os8 = require("os");
|
|
9100
|
-
var
|
|
9826
|
+
var import_node_path21 = require("path");
|
|
9101
9827
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
9102
9828
|
init_worktree();
|
|
9103
9829
|
init_thread_store();
|
|
9104
|
-
var CONDUCTOR_APP_SUPPORT = (0,
|
|
9830
|
+
var CONDUCTOR_APP_SUPPORT = (0, import_node_path21.join)(
|
|
9105
9831
|
process.env.HOME ?? "",
|
|
9106
9832
|
"Library",
|
|
9107
9833
|
"Application Support",
|
|
9108
9834
|
"com.conductor.app"
|
|
9109
9835
|
);
|
|
9110
|
-
var CONDUCTOR_DB = (0,
|
|
9111
|
-
var CURSOR_SDK_STORE = (0,
|
|
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");
|
|
9112
9838
|
function mapAgentType(raw) {
|
|
9113
9839
|
if (!raw) return null;
|
|
9114
9840
|
const v = raw.toLowerCase();
|
|
@@ -9120,25 +9846,25 @@ function mapAgentType(raw) {
|
|
|
9120
9846
|
return null;
|
|
9121
9847
|
}
|
|
9122
9848
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
9123
|
-
if (!workspacePath || !(0,
|
|
9849
|
+
if (!workspacePath || !(0, import_node_fs25.existsSync)(CURSOR_SDK_STORE)) return null;
|
|
9124
9850
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
9125
9851
|
let best = null;
|
|
9126
9852
|
let hashes;
|
|
9127
9853
|
try {
|
|
9128
|
-
hashes = (0,
|
|
9854
|
+
hashes = (0, import_node_fs25.readdirSync)(CURSOR_SDK_STORE);
|
|
9129
9855
|
} catch {
|
|
9130
9856
|
return null;
|
|
9131
9857
|
}
|
|
9132
9858
|
for (const hash of hashes) {
|
|
9133
|
-
const agentsFile = (0,
|
|
9134
|
-
if (!(0,
|
|
9135
|
-
let
|
|
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;
|
|
9136
9862
|
try {
|
|
9137
|
-
|
|
9863
|
+
text2 = (0, import_node_fs25.readFileSync)(agentsFile, "utf8");
|
|
9138
9864
|
} catch {
|
|
9139
9865
|
continue;
|
|
9140
9866
|
}
|
|
9141
|
-
for (const line of
|
|
9867
|
+
for (const line of text2.split("\n")) {
|
|
9142
9868
|
const trimmed = line.trim();
|
|
9143
9869
|
if (!trimmed) continue;
|
|
9144
9870
|
try {
|
|
@@ -9158,7 +9884,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
9158
9884
|
return best?.agentId ?? null;
|
|
9159
9885
|
}
|
|
9160
9886
|
async function adoptThread(input) {
|
|
9161
|
-
if (!(0,
|
|
9887
|
+
if (!(0, import_node_fs25.existsSync)(input.worktreePath)) {
|
|
9162
9888
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
9163
9889
|
}
|
|
9164
9890
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -9182,18 +9908,18 @@ async function adoptThread(input) {
|
|
|
9182
9908
|
return thread;
|
|
9183
9909
|
}
|
|
9184
9910
|
function listConductorWorkspaces() {
|
|
9185
|
-
if (!(0,
|
|
9911
|
+
if (!(0, import_node_fs25.existsSync)(CONDUCTOR_DB)) {
|
|
9186
9912
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
9187
9913
|
}
|
|
9188
|
-
const tmp = (0,
|
|
9189
|
-
const snapshot = (0,
|
|
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");
|
|
9190
9916
|
try {
|
|
9191
|
-
(0,
|
|
9917
|
+
(0, import_node_fs25.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
9192
9918
|
for (const suffix of ["-wal", "-shm"]) {
|
|
9193
9919
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
9194
|
-
if ((0,
|
|
9920
|
+
if ((0, import_node_fs25.existsSync)(src)) {
|
|
9195
9921
|
try {
|
|
9196
|
-
(0,
|
|
9922
|
+
(0, import_node_fs25.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
9197
9923
|
} catch {
|
|
9198
9924
|
}
|
|
9199
9925
|
}
|
|
@@ -9269,22 +9995,22 @@ function listConductorWorkspaces() {
|
|
|
9269
9995
|
db.close();
|
|
9270
9996
|
}
|
|
9271
9997
|
} finally {
|
|
9272
|
-
(0,
|
|
9998
|
+
(0, import_node_fs25.rmSync)(tmp, { recursive: true, force: true });
|
|
9273
9999
|
}
|
|
9274
10000
|
}
|
|
9275
10001
|
function importConductorWorkspace(workspaceId) {
|
|
9276
|
-
if (!(0,
|
|
10002
|
+
if (!(0, import_node_fs25.existsSync)(CONDUCTOR_DB)) {
|
|
9277
10003
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
9278
10004
|
}
|
|
9279
|
-
const tmp = (0,
|
|
9280
|
-
const snapshot = (0,
|
|
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");
|
|
9281
10007
|
try {
|
|
9282
|
-
(0,
|
|
10008
|
+
(0, import_node_fs25.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
9283
10009
|
for (const suffix of ["-wal", "-shm"]) {
|
|
9284
10010
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
9285
|
-
if ((0,
|
|
10011
|
+
if ((0, import_node_fs25.existsSync)(src)) {
|
|
9286
10012
|
try {
|
|
9287
|
-
(0,
|
|
10013
|
+
(0, import_node_fs25.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
9288
10014
|
} catch {
|
|
9289
10015
|
}
|
|
9290
10016
|
}
|
|
@@ -9302,7 +10028,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
9302
10028
|
).get(workspaceId);
|
|
9303
10029
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
9304
10030
|
const worktreePath = String(row.workspacePath);
|
|
9305
|
-
if (!(0,
|
|
10031
|
+
if (!(0, import_node_fs25.existsSync)(worktreePath)) {
|
|
9306
10032
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
9307
10033
|
}
|
|
9308
10034
|
let sessionId = null;
|
|
@@ -9342,7 +10068,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
9342
10068
|
}
|
|
9343
10069
|
let repoPath = worktreePath;
|
|
9344
10070
|
try {
|
|
9345
|
-
repoPath = (0,
|
|
10071
|
+
repoPath = (0, import_node_child_process3.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
9346
10072
|
cwd: worktreePath,
|
|
9347
10073
|
encoding: "utf8"
|
|
9348
10074
|
}).trim();
|
|
@@ -9365,7 +10091,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
9365
10091
|
db.close();
|
|
9366
10092
|
}
|
|
9367
10093
|
} finally {
|
|
9368
|
-
(0,
|
|
10094
|
+
(0, import_node_fs25.rmSync)(tmp, { recursive: true, force: true });
|
|
9369
10095
|
}
|
|
9370
10096
|
}
|
|
9371
10097
|
async function importConductorWorkspaceAsync(workspaceId) {
|
|
@@ -9373,7 +10099,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
9373
10099
|
}
|
|
9374
10100
|
|
|
9375
10101
|
// src/threads/stack-layers.ts
|
|
9376
|
-
var
|
|
10102
|
+
var import_node_fs26 = require("fs");
|
|
9377
10103
|
init_run();
|
|
9378
10104
|
init_stack();
|
|
9379
10105
|
init_worktree();
|
|
@@ -9441,7 +10167,7 @@ async function openStackLayer(input, _onSetupLine) {
|
|
|
9441
10167
|
let createdWorktree = false;
|
|
9442
10168
|
const trees = await listWorktrees(repoPath);
|
|
9443
10169
|
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
9444
|
-
if (checkedOut?.path && (0,
|
|
10170
|
+
if (checkedOut?.path && (0, import_node_fs26.existsSync)(checkedOut.path)) {
|
|
9445
10171
|
if (input.reuseExistingWorktree !== false) {
|
|
9446
10172
|
worktreePath = checkedOut.path;
|
|
9447
10173
|
} else {
|
|
@@ -9583,7 +10309,7 @@ async function initStackFromThread(input, onSetupLine) {
|
|
|
9583
10309
|
async function createPrStack(input, onSetupLine) {
|
|
9584
10310
|
await requireAgent(input.agent);
|
|
9585
10311
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
9586
|
-
if (!(0,
|
|
10312
|
+
if (!(0, import_node_fs26.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
9587
10313
|
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
9588
10314
|
const status = await detectGhStack(repoPath);
|
|
9589
10315
|
if (!status.available) throw new Error(status.reason);
|
|
@@ -9650,7 +10376,7 @@ async function createPrStack(input, onSetupLine) {
|
|
|
9650
10376
|
}
|
|
9651
10377
|
}
|
|
9652
10378
|
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
9653
|
-
if (!claimed.has(bootstrap.worktreePath) && (0,
|
|
10379
|
+
if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs26.existsSync)(bootstrap.worktreePath)) {
|
|
9654
10380
|
try {
|
|
9655
10381
|
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
9656
10382
|
deleteBranch: bootstrap.branchName
|
|
@@ -9665,12 +10391,12 @@ async function createPrStack(input, onSetupLine) {
|
|
|
9665
10391
|
init_worktree();
|
|
9666
10392
|
|
|
9667
10393
|
// src/diff/diff.ts
|
|
9668
|
-
var
|
|
9669
|
-
var
|
|
10394
|
+
var import_node_fs27 = require("fs");
|
|
10395
|
+
var import_node_path22 = require("path");
|
|
9670
10396
|
init_run();
|
|
9671
10397
|
init_worktree();
|
|
9672
10398
|
async function inspectGitWorktree(worktreePath) {
|
|
9673
|
-
if (!worktreePath || !(0,
|
|
10399
|
+
if (!worktreePath || !(0, import_node_fs27.existsSync)(worktreePath)) return "missing_worktree";
|
|
9674
10400
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
9675
10401
|
reject: false
|
|
9676
10402
|
});
|
|
@@ -9678,7 +10404,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
9678
10404
|
return "ok";
|
|
9679
10405
|
}
|
|
9680
10406
|
async function initializeGitRepository(worktreePath) {
|
|
9681
|
-
if (!worktreePath || !(0,
|
|
10407
|
+
if (!worktreePath || !(0, import_node_fs27.existsSync)(worktreePath)) {
|
|
9682
10408
|
throw new Error("Worktree not found");
|
|
9683
10409
|
}
|
|
9684
10410
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -9744,24 +10470,25 @@ function capPatch(patch, maxHunk) {
|
|
|
9744
10470
|
|
|
9745
10471
|
\u2026 truncated (${patch.length - maxHunk} more chars)`;
|
|
9746
10472
|
}
|
|
10473
|
+
var mergeBaseCache = /* @__PURE__ */ new Map();
|
|
10474
|
+
var MERGE_BASE_TTL_MS = 45e3;
|
|
9747
10475
|
async function resolveMergeBase(worktreePath, base, repoPath) {
|
|
9748
10476
|
const baseRef = await resolveDiffBaseRef(worktreePath, base, repoPath);
|
|
10477
|
+
const cacheKey = `${worktreePath}\0${baseRef}`;
|
|
10478
|
+
const hit = mergeBaseCache.get(cacheKey);
|
|
10479
|
+
if (hit && Date.now() - hit.at < MERGE_BASE_TTL_MS) return hit.value;
|
|
9749
10480
|
const { stdout } = await git(["merge-base", baseRef, "HEAD"], worktreePath, {
|
|
9750
10481
|
reject: false
|
|
9751
10482
|
});
|
|
9752
|
-
const mb = stdout.trim();
|
|
9753
|
-
|
|
10483
|
+
const mb = stdout.trim() || baseRef;
|
|
10484
|
+
mergeBaseCache.set(cacheKey, { at: Date.now(), value: mb });
|
|
10485
|
+
return mb;
|
|
9754
10486
|
}
|
|
9755
10487
|
async function countUnpushedCommits(worktreePath) {
|
|
9756
10488
|
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
|
|
9757
10489
|
reject: false
|
|
9758
10490
|
});
|
|
9759
10491
|
const branch = head.stdout.trim();
|
|
9760
|
-
if (branch && branch !== "HEAD") {
|
|
9761
|
-
await git(["fetch", "origin", branch, "--quiet"], worktreePath, {
|
|
9762
|
-
reject: false
|
|
9763
|
-
});
|
|
9764
|
-
}
|
|
9765
10492
|
const upstream = await git(
|
|
9766
10493
|
["rev-list", "--count", "@{upstream}..HEAD"],
|
|
9767
10494
|
worktreePath,
|
|
@@ -9794,11 +10521,37 @@ function splitCombinedDiff(stdout) {
|
|
|
9794
10521
|
}
|
|
9795
10522
|
return map;
|
|
9796
10523
|
}
|
|
10524
|
+
function syntheticAddPatch(path, content, maxHunk) {
|
|
10525
|
+
const lines = content.split("\n");
|
|
10526
|
+
const body = lines.map((l) => `+${l}`).join("\n");
|
|
10527
|
+
const header = `diff --git a/${path} b/${path}
|
|
10528
|
+
new file mode 100644
|
|
10529
|
+
--- /dev/null
|
|
10530
|
+
+++ b/${path}
|
|
10531
|
+
@@ -0,0 +1,${lines.length} @@
|
|
10532
|
+
`;
|
|
10533
|
+
return {
|
|
10534
|
+
path,
|
|
10535
|
+
status: "A",
|
|
10536
|
+
patch: capPatch(`${header}${body}`, maxHunk),
|
|
10537
|
+
additions: lines.length,
|
|
10538
|
+
deletions: 0
|
|
10539
|
+
};
|
|
10540
|
+
}
|
|
9797
10541
|
async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
10542
|
+
const abs = (0, import_node_path22.join)(worktreePath, path);
|
|
10543
|
+
try {
|
|
10544
|
+
const st = (0, import_node_fs27.statSync)(abs);
|
|
10545
|
+
if (st.isFile() && st.size > maxHunk) {
|
|
10546
|
+
const buf = (0, import_node_fs27.readFileSync)(abs).subarray(0, maxHunk);
|
|
10547
|
+
return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
|
|
10548
|
+
}
|
|
10549
|
+
} catch {
|
|
10550
|
+
}
|
|
9798
10551
|
const { stdout: patch } = await git(
|
|
9799
|
-
["diff", "--no-index", "--", "/dev/null", path],
|
|
10552
|
+
["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path],
|
|
9800
10553
|
worktreePath,
|
|
9801
|
-
{ reject: false }
|
|
10554
|
+
{ reject: false, timeoutMs: 4e3 }
|
|
9802
10555
|
);
|
|
9803
10556
|
const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path} b/${path}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path}`);
|
|
9804
10557
|
const addLines = normalized.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
|
|
@@ -9810,22 +10563,97 @@ async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
|
9810
10563
|
deletions: 0
|
|
9811
10564
|
};
|
|
9812
10565
|
}
|
|
9813
|
-
async function listUntrackedPaths(worktreePath) {
|
|
9814
|
-
const
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
{ reject: false }
|
|
9818
|
-
);
|
|
10566
|
+
async function listUntrackedPaths(worktreePath, path) {
|
|
10567
|
+
const args = ["ls-files", "-z", "--others", "--exclude-standard"];
|
|
10568
|
+
if (path) args.push("--", path);
|
|
10569
|
+
const { stdout } = await git(args, worktreePath, { reject: false });
|
|
9819
10570
|
return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
|
|
9820
10571
|
}
|
|
9821
|
-
async function listUntrackedFiles(worktreePath, maxHunk) {
|
|
9822
|
-
const
|
|
9823
|
-
if (
|
|
9824
|
-
return Promise.all(
|
|
10572
|
+
async function listUntrackedFiles(worktreePath, maxHunk, paths) {
|
|
10573
|
+
const list = paths ?? await listUntrackedPaths(worktreePath);
|
|
10574
|
+
if (list.length === 0) return [];
|
|
10575
|
+
return Promise.all(list.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
|
|
10576
|
+
}
|
|
10577
|
+
function untrackedStubs(paths) {
|
|
10578
|
+
return paths.map((path) => ({
|
|
10579
|
+
path,
|
|
10580
|
+
status: "A",
|
|
10581
|
+
patch: "",
|
|
10582
|
+
additions: 0,
|
|
10583
|
+
deletions: 0
|
|
10584
|
+
}));
|
|
10585
|
+
}
|
|
10586
|
+
function pathspec(path) {
|
|
10587
|
+
return path ? ["--", path] : [];
|
|
10588
|
+
}
|
|
10589
|
+
function gitDiff(args, cwd, opts) {
|
|
10590
|
+
return git(["diff", "--no-ext-diff", "--no-color", ...args], cwd, {
|
|
10591
|
+
reject: false,
|
|
10592
|
+
timeoutMs: opts?.timeoutMs
|
|
10593
|
+
});
|
|
10594
|
+
}
|
|
10595
|
+
function fileFromPatch(path, patch, maxHunk) {
|
|
10596
|
+
let status = "M";
|
|
10597
|
+
if (/^new file mode /m.test(patch) || /^--- \/dev\/null/m.test(patch)) {
|
|
10598
|
+
status = "A";
|
|
10599
|
+
} else if (/^deleted file mode /m.test(patch) || /^\+\+\+ \/dev\/null/m.test(patch)) {
|
|
10600
|
+
status = "D";
|
|
10601
|
+
} else if (/^rename from /m.test(patch)) {
|
|
10602
|
+
status = "R";
|
|
10603
|
+
}
|
|
10604
|
+
const additions = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
|
|
10605
|
+
const deletions = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
|
|
10606
|
+
return {
|
|
10607
|
+
path,
|
|
10608
|
+
status,
|
|
10609
|
+
patch: capPatch(patch, maxHunk),
|
|
10610
|
+
additions,
|
|
10611
|
+
deletions
|
|
10612
|
+
};
|
|
10613
|
+
}
|
|
10614
|
+
async function gitDiffParts(worktreePath, diffArgs, includePatches, path) {
|
|
10615
|
+
const extra = pathspec(path);
|
|
10616
|
+
if (includePatches) {
|
|
10617
|
+
const diff = await gitDiff([...diffArgs, ...extra], worktreePath, {
|
|
10618
|
+
timeoutMs: 4e3
|
|
10619
|
+
});
|
|
10620
|
+
return { nameStatus: "", numstat: "", combinedDiff: diff.stdout };
|
|
10621
|
+
}
|
|
10622
|
+
const [ns, num2] = await Promise.all([
|
|
10623
|
+
gitDiff(["--name-status", ...diffArgs, ...extra], worktreePath),
|
|
10624
|
+
gitDiff(["--numstat", ...diffArgs, ...extra], worktreePath)
|
|
10625
|
+
]);
|
|
10626
|
+
return {
|
|
10627
|
+
nameStatus: ns.stdout,
|
|
10628
|
+
numstat: num2.stdout,
|
|
10629
|
+
combinedDiff: ""
|
|
10630
|
+
};
|
|
10631
|
+
}
|
|
10632
|
+
function formatStat(files) {
|
|
10633
|
+
if (files.length === 0) return "(no changes)";
|
|
10634
|
+
const additions = files.reduce((n2, f) => n2 + (f.additions ?? 0), 0);
|
|
10635
|
+
const deletions = files.reduce((n2, f) => n2 + (f.deletions ?? 0), 0);
|
|
10636
|
+
const n = files.length;
|
|
10637
|
+
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
10638
|
+
}
|
|
10639
|
+
function emptyScopeStats() {
|
|
10640
|
+
const z3 = emptyStat();
|
|
10641
|
+
return {
|
|
10642
|
+
commits: z3,
|
|
10643
|
+
uncommitted: z3,
|
|
10644
|
+
staged: z3,
|
|
10645
|
+
unstaged: z3,
|
|
10646
|
+
last_turn: z3
|
|
10647
|
+
};
|
|
9825
10648
|
}
|
|
9826
10649
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
9827
|
-
const counts = parseNumstat(numstat);
|
|
9828
10650
|
const patches = splitCombinedDiff(combinedDiff);
|
|
10651
|
+
if (!nameStatus.trim() && patches.size > 0) {
|
|
10652
|
+
return [...patches.entries()].map(
|
|
10653
|
+
([path, patch]) => fileFromPatch(path, patch, maxHunk)
|
|
10654
|
+
);
|
|
10655
|
+
}
|
|
10656
|
+
const counts = parseNumstat(numstat);
|
|
9829
10657
|
const files = [];
|
|
9830
10658
|
for (const { status, path } of parseNameStatus(nameStatus)) {
|
|
9831
10659
|
const n = counts.get(path);
|
|
@@ -9847,11 +10675,11 @@ async function computeScopeStats(worktreePath, mergeBase, lastTurnBase, untracke
|
|
|
9847
10675
|
{ stdout: unstagedNum },
|
|
9848
10676
|
lastTurnNum
|
|
9849
10677
|
] = await Promise.all([
|
|
9850
|
-
mergeBase ?
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
lastTurnBase ?
|
|
10678
|
+
mergeBase ? gitDiff(["--numstat", mergeBase], worktreePath) : Promise.resolve({ stdout: "" }),
|
|
10679
|
+
gitDiff(["--numstat", "HEAD"], worktreePath),
|
|
10680
|
+
gitDiff(["--numstat", "--cached"], worktreePath),
|
|
10681
|
+
gitDiff(["--numstat"], worktreePath),
|
|
10682
|
+
lastTurnBase ? gitDiff(["--numstat", lastTurnBase], worktreePath) : Promise.resolve({ stdout: "" })
|
|
9855
10683
|
]);
|
|
9856
10684
|
return {
|
|
9857
10685
|
commits: mergeBase ? statFromNumstat(commitsNum, untrackedCount) : emptyStat(),
|
|
@@ -9870,7 +10698,7 @@ async function listBranchCommits(worktreePath, repoPath, opts) {
|
|
|
9870
10698
|
if (head.exitCode !== 0) return [];
|
|
9871
10699
|
let base = opts?.base;
|
|
9872
10700
|
try {
|
|
9873
|
-
base = base ?? await resolveDefaultBranch(repoPath);
|
|
10701
|
+
base = base ?? await resolveDefaultBranch(repoPath, { network: false });
|
|
9874
10702
|
} catch {
|
|
9875
10703
|
base = "HEAD";
|
|
9876
10704
|
}
|
|
@@ -9900,7 +10728,121 @@ async function listBranchCommits(worktreePath, repoPath, opts) {
|
|
|
9900
10728
|
}
|
|
9901
10729
|
return commits;
|
|
9902
10730
|
}
|
|
10731
|
+
var SHA_RE = /^[0-9a-f]{7,40}$/i;
|
|
10732
|
+
function emptyPathDiff(scope, base, hasLastTurnBase, commitSha = null) {
|
|
10733
|
+
return {
|
|
10734
|
+
scope,
|
|
10735
|
+
commitSha: scope === "commits" ? commitSha : null,
|
|
10736
|
+
base,
|
|
10737
|
+
files: [],
|
|
10738
|
+
stat: formatStat([]),
|
|
10739
|
+
dirty: false,
|
|
10740
|
+
unpushed: 0,
|
|
10741
|
+
hasLastTurnBase,
|
|
10742
|
+
commits: [],
|
|
10743
|
+
scopeStats: emptyScopeStats()
|
|
10744
|
+
};
|
|
10745
|
+
}
|
|
10746
|
+
async function getPathDiff(worktreePath, repoPath, opts) {
|
|
10747
|
+
const { path, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
|
|
10748
|
+
if (scope === "last_turn" && !lastTurnBase) {
|
|
10749
|
+
return emptyPathDiff(scope, "last turn", false);
|
|
10750
|
+
}
|
|
10751
|
+
let diffArgs;
|
|
10752
|
+
let labelBase;
|
|
10753
|
+
if (scope === "staged") {
|
|
10754
|
+
diffArgs = ["--cached"];
|
|
10755
|
+
labelBase = "staged";
|
|
10756
|
+
} else if (scope === "unstaged") {
|
|
10757
|
+
diffArgs = [];
|
|
10758
|
+
labelBase = "unstaged";
|
|
10759
|
+
} else if (scope === "uncommitted") {
|
|
10760
|
+
diffArgs = ["HEAD"];
|
|
10761
|
+
labelBase = "HEAD";
|
|
10762
|
+
} else if (scope === "last_turn") {
|
|
10763
|
+
diffArgs = [lastTurnBase];
|
|
10764
|
+
labelBase = "last turn";
|
|
10765
|
+
} else if (scope === "commits" && commitSha) {
|
|
10766
|
+
diffArgs = [`${commitSha}^!`];
|
|
10767
|
+
labelBase = commitSha.slice(0, 7);
|
|
10768
|
+
} else if (scope === "commits" && opts.base && SHA_RE.test(opts.base)) {
|
|
10769
|
+
diffArgs = [opts.base];
|
|
10770
|
+
labelBase = opts.base.slice(0, 7);
|
|
10771
|
+
} else if (scope === "commits") {
|
|
10772
|
+
let base = opts.base;
|
|
10773
|
+
try {
|
|
10774
|
+
base = base ?? await resolveDefaultBranch(repoPath, { network: false });
|
|
10775
|
+
} catch {
|
|
10776
|
+
base = "HEAD";
|
|
10777
|
+
}
|
|
10778
|
+
const mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
|
|
10779
|
+
diffArgs = [mergeBase];
|
|
10780
|
+
labelBase = mergeBase;
|
|
10781
|
+
} else {
|
|
10782
|
+
diffArgs = ["HEAD"];
|
|
10783
|
+
labelBase = "HEAD";
|
|
10784
|
+
}
|
|
10785
|
+
const diff = await gitDiff(
|
|
10786
|
+
includePatches ? [...diffArgs, "--", path] : ["--name-status", ...diffArgs, "--", path],
|
|
10787
|
+
worktreePath,
|
|
10788
|
+
{ timeoutMs: 4e3 }
|
|
10789
|
+
);
|
|
10790
|
+
const filesMap = /* @__PURE__ */ new Map();
|
|
10791
|
+
if (includePatches) {
|
|
10792
|
+
const parsed = filesFromDiff("", "", diff.stdout, maxHunk);
|
|
10793
|
+
for (const file of parsed) filesMap.set(file.path, file);
|
|
10794
|
+
if (diff.stdout.trim() && !filesMap.has(path)) {
|
|
10795
|
+
filesMap.set(path, fileFromPatch(path, diff.stdout, maxHunk));
|
|
10796
|
+
}
|
|
10797
|
+
} else {
|
|
10798
|
+
for (const file of filesFromDiff(diff.stdout, "", "", maxHunk)) {
|
|
10799
|
+
filesMap.set(file.path, file);
|
|
10800
|
+
}
|
|
10801
|
+
}
|
|
10802
|
+
if (filesMap.size === 0) {
|
|
10803
|
+
const maybe = await listUntrackedPaths(worktreePath, path);
|
|
10804
|
+
if (maybe.length > 0) {
|
|
10805
|
+
const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
|
|
10806
|
+
for (const file of extra) filesMap.set(file.path, file);
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
10810
|
+
return {
|
|
10811
|
+
scope,
|
|
10812
|
+
commitSha: scope === "commits" ? commitSha : null,
|
|
10813
|
+
base: labelBase,
|
|
10814
|
+
files,
|
|
10815
|
+
stat: formatStat(files),
|
|
10816
|
+
dirty: files.length > 0,
|
|
10817
|
+
unpushed: 0,
|
|
10818
|
+
hasLastTurnBase,
|
|
10819
|
+
commits: [],
|
|
10820
|
+
scopeStats: emptyScopeStats(),
|
|
10821
|
+
mergeBase: SHA_RE.test(diffArgs[0] ?? "") ? diffArgs[0] : null
|
|
10822
|
+
};
|
|
10823
|
+
}
|
|
9903
10824
|
async function getDiff(worktreePath, repoPath, opts) {
|
|
10825
|
+
const maxHunk = opts?.maxHunkChars ?? 8e3;
|
|
10826
|
+
const scope = opts?.scope ?? "commits";
|
|
10827
|
+
const lastTurnBase = opts?.lastTurnBase?.trim() || null;
|
|
10828
|
+
const hasLastTurnBase = Boolean(lastTurnBase);
|
|
10829
|
+
const commitSha = opts?.commitSha?.trim() || null;
|
|
10830
|
+
const includePatches = opts?.includePatches ?? true;
|
|
10831
|
+
const pathFilter = opts?.path?.trim() || null;
|
|
10832
|
+
const fileOnly = Boolean(pathFilter);
|
|
10833
|
+
const includeMeta = (opts?.includeMeta ?? true) && !fileOnly;
|
|
10834
|
+
if (fileOnly && pathFilter && !includeMeta) {
|
|
10835
|
+
return getPathDiff(worktreePath, repoPath, {
|
|
10836
|
+
path: pathFilter,
|
|
10837
|
+
scope,
|
|
10838
|
+
commitSha,
|
|
10839
|
+
lastTurnBase,
|
|
10840
|
+
hasLastTurnBase,
|
|
10841
|
+
base: opts?.base,
|
|
10842
|
+
includePatches,
|
|
10843
|
+
maxHunk
|
|
10844
|
+
});
|
|
10845
|
+
}
|
|
9904
10846
|
const status = await inspectGitWorktree(worktreePath);
|
|
9905
10847
|
if (status === "missing_worktree") {
|
|
9906
10848
|
throw new Error("Worktree not found");
|
|
@@ -9908,18 +10850,19 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
9908
10850
|
if (status === "not_git") {
|
|
9909
10851
|
throw new Error("Not a Git repository");
|
|
9910
10852
|
}
|
|
9911
|
-
const
|
|
9912
|
-
const
|
|
9913
|
-
const
|
|
9914
|
-
const hasLastTurnBase = Boolean(lastTurnBase);
|
|
9915
|
-
const commitSha = opts?.commitSha?.trim() || null;
|
|
9916
|
-
const head = await git(["rev-parse", "--verify", "HEAD"], worktreePath, {
|
|
10853
|
+
const wantUntracked = opts?.includeUntracked !== void 0 ? opts.includeUntracked : !fileOnly && (scope === "commits" && !commitSha || scope === "uncommitted" || scope === "unstaged" || scope === "last_turn");
|
|
10854
|
+
const needsMergeBase = scope === "commits" && !commitSha || includeMeta;
|
|
10855
|
+
const headP = git(["rev-parse", "--verify", "HEAD"], worktreePath, {
|
|
9917
10856
|
reject: false
|
|
9918
10857
|
});
|
|
10858
|
+
const untrackedP = wantUntracked ? listUntrackedPaths(worktreePath, pathFilter) : Promise.resolve([]);
|
|
10859
|
+
const head = await headP;
|
|
9919
10860
|
const hasHead = head.exitCode === 0;
|
|
9920
|
-
const untrackedPaths = await listUntrackedPaths(worktreePath);
|
|
9921
10861
|
if (!hasHead) {
|
|
9922
|
-
const
|
|
10862
|
+
const untrackedPaths2 = await untrackedP;
|
|
10863
|
+
const files2 = scope === "staged" ? [] : includePatches ? (await listUntrackedFiles(worktreePath, maxHunk, untrackedPaths2)).sort(
|
|
10864
|
+
(a, b) => a.path.localeCompare(b.path)
|
|
10865
|
+
) : untrackedStubs(untrackedPaths2).sort(
|
|
9923
10866
|
(a, b) => a.path.localeCompare(b.path)
|
|
9924
10867
|
);
|
|
9925
10868
|
const untrackedStat = {
|
|
@@ -9933,7 +10876,7 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
9933
10876
|
commitSha: null,
|
|
9934
10877
|
base: "(no commits)",
|
|
9935
10878
|
files: files2,
|
|
9936
|
-
stat: files2
|
|
10879
|
+
stat: formatStat(files2),
|
|
9937
10880
|
dirty: files2.length > 0,
|
|
9938
10881
|
unpushed: 0,
|
|
9939
10882
|
hasLastTurnBase,
|
|
@@ -9947,137 +10890,122 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
9947
10890
|
}
|
|
9948
10891
|
};
|
|
9949
10892
|
}
|
|
9950
|
-
let
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
10893
|
+
let mergeBase = "HEAD";
|
|
10894
|
+
let baseLabel = "HEAD";
|
|
10895
|
+
if (needsMergeBase) {
|
|
10896
|
+
let base = opts?.base;
|
|
10897
|
+
try {
|
|
10898
|
+
base = base ?? await resolveDefaultBranch(repoPath, { network: false });
|
|
10899
|
+
} catch {
|
|
10900
|
+
base = "HEAD";
|
|
10901
|
+
}
|
|
10902
|
+
mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
|
|
10903
|
+
baseLabel = await resolveDiffBaseRef(worktreePath, base, repoPath);
|
|
9955
10904
|
}
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
10905
|
+
const includeUntracked = wantUntracked;
|
|
10906
|
+
const metaPromise = includeMeta ? Promise.all([
|
|
10907
|
+
computeScopeStats(
|
|
10908
|
+
worktreePath,
|
|
10909
|
+
mergeBase,
|
|
10910
|
+
lastTurnBase,
|
|
10911
|
+
// Cheap: don't wait on the untracked walk just to pad the badge.
|
|
10912
|
+
0
|
|
10913
|
+
),
|
|
10914
|
+
listBranchCommits(worktreePath, repoPath, { base: baseLabel }),
|
|
10915
|
+
isDirty(worktreePath),
|
|
10916
|
+
countUnpushedCommits(worktreePath)
|
|
10917
|
+
]).then(([scopeStats, commits, dirty, unpushed]) => ({
|
|
10918
|
+
scopeStats,
|
|
10919
|
+
commits,
|
|
10920
|
+
dirty,
|
|
10921
|
+
unpushed
|
|
10922
|
+
})) : Promise.resolve({
|
|
10923
|
+
scopeStats: emptyScopeStats(),
|
|
10924
|
+
commits: [],
|
|
10925
|
+
dirty: false,
|
|
10926
|
+
unpushed: 0
|
|
10927
|
+
});
|
|
10928
|
+
if (scope === "last_turn" && !lastTurnBase) {
|
|
10929
|
+
const meta2 = await metaPromise;
|
|
10930
|
+
return {
|
|
10931
|
+
scope,
|
|
10932
|
+
commitSha: null,
|
|
10933
|
+
base: "last turn",
|
|
10934
|
+
files: [],
|
|
10935
|
+
stat: "(no last agent turn)",
|
|
10936
|
+
dirty: meta2.dirty,
|
|
10937
|
+
unpushed: meta2.unpushed,
|
|
10938
|
+
hasLastTurnBase: false,
|
|
10939
|
+
commits: meta2.commits,
|
|
10940
|
+
scopeStats: meta2.scopeStats
|
|
10941
|
+
};
|
|
9963
10942
|
}
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
worktreePath,
|
|
9967
|
-
mergeBase,
|
|
9968
|
-
lastTurnBase,
|
|
9969
|
-
untrackedPaths.length
|
|
9970
|
-
);
|
|
9971
|
-
const includeUntracked = scope === "commits" && !commitSha || scope === "uncommitted" || scope === "unstaged" || scope === "last_turn";
|
|
9972
|
-
let nameStatus = "";
|
|
9973
|
-
let numstat = "";
|
|
9974
|
-
let combinedDiff = "";
|
|
9975
|
-
let labelBase = base;
|
|
10943
|
+
let diffArgs;
|
|
10944
|
+
let labelBase = baseLabel;
|
|
9976
10945
|
if (scope === "staged") {
|
|
9977
|
-
|
|
9978
|
-
git(["diff", "--name-status", "--cached"], worktreePath, { reject: false }),
|
|
9979
|
-
git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
|
|
9980
|
-
git(["diff", "--cached"], worktreePath, { reject: false })
|
|
9981
|
-
]);
|
|
9982
|
-
nameStatus = ns.stdout;
|
|
9983
|
-
numstat = num2.stdout;
|
|
9984
|
-
combinedDiff = diff.stdout;
|
|
10946
|
+
diffArgs = ["--cached"];
|
|
9985
10947
|
labelBase = "staged";
|
|
9986
10948
|
} else if (scope === "unstaged") {
|
|
9987
|
-
|
|
9988
|
-
git(["diff", "--name-status"], worktreePath, { reject: false }),
|
|
9989
|
-
git(["diff", "--numstat"], worktreePath, { reject: false }),
|
|
9990
|
-
git(["diff"], worktreePath, { reject: false })
|
|
9991
|
-
]);
|
|
9992
|
-
nameStatus = ns.stdout;
|
|
9993
|
-
numstat = num2.stdout;
|
|
9994
|
-
combinedDiff = diff.stdout;
|
|
10949
|
+
diffArgs = [];
|
|
9995
10950
|
labelBase = "unstaged";
|
|
9996
10951
|
} else if (scope === "uncommitted") {
|
|
9997
|
-
|
|
9998
|
-
git(["diff", "--name-status", "HEAD"], worktreePath, { reject: false }),
|
|
9999
|
-
git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
|
|
10000
|
-
git(["diff", "HEAD"], worktreePath, { reject: false })
|
|
10001
|
-
]);
|
|
10002
|
-
nameStatus = ns.stdout;
|
|
10003
|
-
numstat = num2.stdout;
|
|
10004
|
-
combinedDiff = diff.stdout;
|
|
10952
|
+
diffArgs = ["HEAD"];
|
|
10005
10953
|
labelBase = "HEAD";
|
|
10006
10954
|
} else if (scope === "last_turn") {
|
|
10007
|
-
|
|
10008
|
-
const commits2 = await listBranchCommits(worktreePath, repoPath, { base });
|
|
10009
|
-
return {
|
|
10010
|
-
scope,
|
|
10011
|
-
commitSha: null,
|
|
10012
|
-
base: "last turn",
|
|
10013
|
-
files: [],
|
|
10014
|
-
stat: "(no last agent turn)",
|
|
10015
|
-
dirty: await isDirty(worktreePath),
|
|
10016
|
-
unpushed: await countUnpushedCommits(worktreePath),
|
|
10017
|
-
hasLastTurnBase: false,
|
|
10018
|
-
commits: commits2,
|
|
10019
|
-
scopeStats
|
|
10020
|
-
};
|
|
10021
|
-
}
|
|
10022
|
-
const [ns, num2, diff] = await Promise.all([
|
|
10023
|
-
git(["diff", "--name-status", lastTurnBase], worktreePath, { reject: false }),
|
|
10024
|
-
git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }),
|
|
10025
|
-
git(["diff", lastTurnBase], worktreePath, { reject: false })
|
|
10026
|
-
]);
|
|
10027
|
-
nameStatus = ns.stdout;
|
|
10028
|
-
numstat = num2.stdout;
|
|
10029
|
-
combinedDiff = diff.stdout;
|
|
10955
|
+
diffArgs = [lastTurnBase];
|
|
10030
10956
|
labelBase = "last turn";
|
|
10031
10957
|
} else if (scope === "commits" && commitSha) {
|
|
10032
|
-
|
|
10033
|
-
const [ns, num2, diff] = await Promise.all([
|
|
10034
|
-
git(["diff", "--name-status", range], worktreePath, { reject: false }),
|
|
10035
|
-
git(["diff", "--numstat", range], worktreePath, { reject: false }),
|
|
10036
|
-
git(["diff", range], worktreePath, { reject: false })
|
|
10037
|
-
]);
|
|
10038
|
-
nameStatus = ns.stdout;
|
|
10039
|
-
numstat = num2.stdout;
|
|
10040
|
-
combinedDiff = diff.stdout;
|
|
10958
|
+
diffArgs = [`${commitSha}^!`];
|
|
10041
10959
|
labelBase = commitSha.slice(0, 7);
|
|
10042
10960
|
} else {
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }),
|
|
10046
|
-
git(["diff", mergeBase], worktreePath, { reject: false })
|
|
10047
|
-
]);
|
|
10048
|
-
nameStatus = ns.stdout;
|
|
10049
|
-
numstat = num2.stdout;
|
|
10050
|
-
combinedDiff = diff.stdout;
|
|
10051
|
-
labelBase = base;
|
|
10961
|
+
diffArgs = [mergeBase];
|
|
10962
|
+
labelBase = baseLabel;
|
|
10052
10963
|
}
|
|
10053
|
-
const
|
|
10964
|
+
const partsPromise = gitDiffParts(
|
|
10965
|
+
worktreePath,
|
|
10966
|
+
diffArgs,
|
|
10967
|
+
includePatches,
|
|
10968
|
+
pathFilter
|
|
10969
|
+
);
|
|
10970
|
+
const [parts, meta, untrackedPaths] = await Promise.all([
|
|
10971
|
+
partsPromise,
|
|
10972
|
+
metaPromise,
|
|
10973
|
+
untrackedP
|
|
10974
|
+
]);
|
|
10054
10975
|
const filesMap = /* @__PURE__ */ new Map();
|
|
10055
|
-
for (const file of filesFromDiff(
|
|
10976
|
+
for (const file of filesFromDiff(
|
|
10977
|
+
parts.nameStatus,
|
|
10978
|
+
parts.numstat,
|
|
10979
|
+
parts.combinedDiff,
|
|
10980
|
+
maxHunk
|
|
10981
|
+
)) {
|
|
10056
10982
|
filesMap.set(file.path, file);
|
|
10057
10983
|
}
|
|
10058
10984
|
if (includeUntracked) {
|
|
10059
|
-
const
|
|
10060
|
-
for (const file of
|
|
10985
|
+
const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, untrackedPaths) : untrackedStubs(untrackedPaths);
|
|
10986
|
+
for (const file of extra) {
|
|
10061
10987
|
if (!filesMap.has(file.path)) filesMap.set(file.path, file);
|
|
10062
10988
|
}
|
|
10989
|
+
} else if (fileOnly && pathFilter && !filesMap.has(pathFilter)) {
|
|
10990
|
+
const maybe = await listUntrackedPaths(worktreePath, pathFilter);
|
|
10991
|
+
if (maybe.length > 0) {
|
|
10992
|
+
const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
|
|
10993
|
+
for (const file of extra) filesMap.set(file.path, file);
|
|
10994
|
+
}
|
|
10063
10995
|
}
|
|
10064
10996
|
const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
10065
|
-
const { stdout: statOut } = await git(
|
|
10066
|
-
scope === "staged" ? ["diff", "--stat", "--cached"] : scope === "unstaged" ? ["diff", "--stat"] : scope === "uncommitted" ? ["diff", "--stat", "HEAD"] : scope === "last_turn" && lastTurnBase ? ["diff", "--stat", lastTurnBase] : scope === "commits" && commitSha ? ["diff", "--stat", `${commitSha}^!`] : ["diff", "--stat", mergeBase],
|
|
10067
|
-
worktreePath,
|
|
10068
|
-
{ reject: false }
|
|
10069
|
-
);
|
|
10070
10997
|
return {
|
|
10071
10998
|
scope,
|
|
10072
10999
|
commitSha: scope === "commits" ? commitSha : null,
|
|
10073
11000
|
base: labelBase,
|
|
10074
11001
|
files,
|
|
10075
|
-
stat:
|
|
10076
|
-
dirty:
|
|
10077
|
-
unpushed:
|
|
11002
|
+
stat: formatStat(files),
|
|
11003
|
+
dirty: fileOnly || !includeMeta ? files.length > 0 : meta.dirty,
|
|
11004
|
+
unpushed: meta.unpushed,
|
|
10078
11005
|
hasLastTurnBase,
|
|
10079
|
-
commits,
|
|
10080
|
-
scopeStats
|
|
11006
|
+
commits: meta.commits,
|
|
11007
|
+
scopeStats: meta.scopeStats,
|
|
11008
|
+
mergeBase: needsMergeBase ? mergeBase : null
|
|
10081
11009
|
};
|
|
10082
11010
|
}
|
|
10083
11011
|
async function listWorktreeFiles(worktreePath, opts) {
|
|
@@ -10115,8 +11043,8 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
|
|
|
10115
11043
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
10116
11044
|
assertSafeRelativePath(relativePath);
|
|
10117
11045
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
10118
|
-
const abs = (0,
|
|
10119
|
-
const st = (0,
|
|
11046
|
+
const abs = (0, import_node_path22.join)(worktreePath, relativePath);
|
|
11047
|
+
const st = (0, import_node_fs27.statSync)(abs);
|
|
10120
11048
|
if (!st.isFile()) {
|
|
10121
11049
|
throw new Error(`Not a file: ${relativePath}`);
|
|
10122
11050
|
}
|
|
@@ -10125,7 +11053,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
10125
11053
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
10126
11054
|
);
|
|
10127
11055
|
}
|
|
10128
|
-
const buf = (0,
|
|
11056
|
+
const buf = (0, import_node_fs27.readFileSync)(abs);
|
|
10129
11057
|
return {
|
|
10130
11058
|
path: relativePath,
|
|
10131
11059
|
contentBase64: buf.toString("base64"),
|
|
@@ -10135,12 +11063,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
10135
11063
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
10136
11064
|
assertSafeRelativePath(relativePath);
|
|
10137
11065
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
10138
|
-
const abs = (0,
|
|
10139
|
-
const st = (0,
|
|
11066
|
+
const abs = (0, import_node_path22.join)(worktreePath, relativePath);
|
|
11067
|
+
const st = (0, import_node_fs27.statSync)(abs);
|
|
10140
11068
|
if (!st.isFile()) {
|
|
10141
11069
|
throw new Error(`Not a file: ${relativePath}`);
|
|
10142
11070
|
}
|
|
10143
|
-
const buf = (0,
|
|
11071
|
+
const buf = (0, import_node_fs27.readFileSync)(abs);
|
|
10144
11072
|
if (isImageRelativePath(relativePath)) {
|
|
10145
11073
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
10146
11074
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -10183,9 +11111,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
10183
11111
|
}
|
|
10184
11112
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
10185
11113
|
assertSafeRelativePath(relativePath);
|
|
10186
|
-
const abs = (0,
|
|
10187
|
-
(0,
|
|
10188
|
-
(0,
|
|
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");
|
|
10189
11117
|
return { path: relativePath };
|
|
10190
11118
|
}
|
|
10191
11119
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -10364,9 +11292,9 @@ async function confirmLand(thread, opts) {
|
|
|
10364
11292
|
}
|
|
10365
11293
|
|
|
10366
11294
|
// src/skills/discover.ts
|
|
10367
|
-
var
|
|
11295
|
+
var import_node_fs28 = require("fs");
|
|
10368
11296
|
var import_node_os9 = require("os");
|
|
10369
|
-
var
|
|
11297
|
+
var import_node_path23 = require("path");
|
|
10370
11298
|
function toCommand(name) {
|
|
10371
11299
|
return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10372
11300
|
}
|
|
@@ -10398,7 +11326,7 @@ function parseFrontmatter(content) {
|
|
|
10398
11326
|
}
|
|
10399
11327
|
function readSkill(skillMd, source) {
|
|
10400
11328
|
try {
|
|
10401
|
-
const content = (0,
|
|
11329
|
+
const content = (0, import_node_fs28.readFileSync)(skillMd, "utf8");
|
|
10402
11330
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
10403
11331
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
10404
11332
|
const name = fmName || dirName;
|
|
@@ -10417,19 +11345,19 @@ function readSkill(skillMd, source) {
|
|
|
10417
11345
|
}
|
|
10418
11346
|
}
|
|
10419
11347
|
function scanSkillsDir(dir, source, out) {
|
|
10420
|
-
if (!(0,
|
|
11348
|
+
if (!(0, import_node_fs28.existsSync)(dir)) return;
|
|
10421
11349
|
let entries;
|
|
10422
11350
|
try {
|
|
10423
|
-
entries = (0,
|
|
11351
|
+
entries = (0, import_node_fs28.readdirSync)(dir);
|
|
10424
11352
|
} catch {
|
|
10425
11353
|
return;
|
|
10426
11354
|
}
|
|
10427
11355
|
for (const entry of entries) {
|
|
10428
11356
|
if (entry.startsWith(".")) continue;
|
|
10429
|
-
const skillMd = (0,
|
|
10430
|
-
if (!(0,
|
|
11357
|
+
const skillMd = (0, import_node_path23.join)(dir, entry, "SKILL.md");
|
|
11358
|
+
if (!(0, import_node_fs28.existsSync)(skillMd)) continue;
|
|
10431
11359
|
try {
|
|
10432
|
-
if (!(0,
|
|
11360
|
+
if (!(0, import_node_fs28.statSync)(skillMd).isFile()) continue;
|
|
10433
11361
|
} catch {
|
|
10434
11362
|
continue;
|
|
10435
11363
|
}
|
|
@@ -10438,24 +11366,24 @@ function scanSkillsDir(dir, source, out) {
|
|
|
10438
11366
|
}
|
|
10439
11367
|
}
|
|
10440
11368
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
10441
|
-
if (!(0,
|
|
11369
|
+
if (!(0, import_node_fs28.existsSync)(pluginsRoot)) return;
|
|
10442
11370
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
10443
11371
|
if (depth > 7) return;
|
|
10444
11372
|
let entries;
|
|
10445
11373
|
try {
|
|
10446
|
-
entries = (0,
|
|
11374
|
+
entries = (0, import_node_fs28.readdirSync)(dir);
|
|
10447
11375
|
} catch {
|
|
10448
11376
|
return;
|
|
10449
11377
|
}
|
|
10450
11378
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
10451
|
-
const skill = readSkill((0,
|
|
11379
|
+
const skill = readSkill((0, import_node_path23.join)(dir, "SKILL.md"), "cli");
|
|
10452
11380
|
if (skill) out.push(skill);
|
|
10453
11381
|
}
|
|
10454
11382
|
for (const entry of entries) {
|
|
10455
11383
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
10456
|
-
const full = (0,
|
|
11384
|
+
const full = (0, import_node_path23.join)(dir, entry);
|
|
10457
11385
|
try {
|
|
10458
|
-
if (!(0,
|
|
11386
|
+
if (!(0, import_node_fs28.statSync)(full).isDirectory()) continue;
|
|
10459
11387
|
} catch {
|
|
10460
11388
|
continue;
|
|
10461
11389
|
}
|
|
@@ -10473,17 +11401,17 @@ function discoverSkills(worktreePath) {
|
|
|
10473
11401
|
const home = (0, import_node_os9.homedir)();
|
|
10474
11402
|
const collected = [];
|
|
10475
11403
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
10476
|
-
scanSkillsDir((0,
|
|
11404
|
+
scanSkillsDir((0, import_node_path23.join)(worktreePath, rel), "workspace", collected);
|
|
10477
11405
|
}
|
|
10478
11406
|
for (const abs of [
|
|
10479
|
-
(0,
|
|
10480
|
-
(0,
|
|
10481
|
-
(0,
|
|
10482
|
-
(0,
|
|
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")
|
|
10483
11411
|
]) {
|
|
10484
11412
|
scanSkillsDir(abs, "user", collected);
|
|
10485
11413
|
}
|
|
10486
|
-
scanClaudePluginSkills((0,
|
|
11414
|
+
scanClaudePluginSkills((0, import_node_path23.join)(home, ".claude/plugins"), collected);
|
|
10487
11415
|
const rank = { workspace: 0, user: 1, cli: 2 };
|
|
10488
11416
|
const byCommand = /* @__PURE__ */ new Map();
|
|
10489
11417
|
for (const skill of collected) {
|
|
@@ -10495,7 +11423,7 @@ function discoverSkills(worktreePath) {
|
|
|
10495
11423
|
return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
|
|
10496
11424
|
}
|
|
10497
11425
|
function readSkillBody(skillPath, maxChars = 12e3) {
|
|
10498
|
-
const raw = (0,
|
|
11426
|
+
const raw = (0, import_node_fs28.readFileSync)(skillPath, "utf8");
|
|
10499
11427
|
if (raw.startsWith("---")) {
|
|
10500
11428
|
const end = raw.indexOf("\n---", 3);
|
|
10501
11429
|
if (end >= 0) {
|
|
@@ -10588,9 +11516,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
10588
11516
|
}
|
|
10589
11517
|
|
|
10590
11518
|
// src/composer/stage-files.ts
|
|
10591
|
-
var
|
|
10592
|
-
var
|
|
10593
|
-
var
|
|
11519
|
+
var import_node_fs29 = require("fs");
|
|
11520
|
+
var import_node_path24 = require("path");
|
|
11521
|
+
var import_node_crypto7 = require("crypto");
|
|
10594
11522
|
init_workspace_scratch();
|
|
10595
11523
|
var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
10596
11524
|
"png",
|
|
@@ -10615,7 +11543,7 @@ var IMAGE_MIME_BY_EXT = {
|
|
|
10615
11543
|
var MAX_INLINE_BYTES = 4e5;
|
|
10616
11544
|
var MAX_PREVIEW_BYTES = 5e6;
|
|
10617
11545
|
function fileExtension(filePath) {
|
|
10618
|
-
const base = (0,
|
|
11546
|
+
const base = (0, import_node_path24.basename)(filePath).toLowerCase();
|
|
10619
11547
|
return base.includes(".") ? base.split(".").pop() || "" : "";
|
|
10620
11548
|
}
|
|
10621
11549
|
function isImageFilePath(filePath) {
|
|
@@ -10625,24 +11553,24 @@ function imageMimeType(filePath) {
|
|
|
10625
11553
|
return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
|
|
10626
11554
|
}
|
|
10627
11555
|
function ensureAttachmentsDir(worktreePath) {
|
|
10628
|
-
const dir = (0,
|
|
10629
|
-
(0,
|
|
10630
|
-
const gi = (0,
|
|
10631
|
-
if (!(0,
|
|
10632
|
-
(0,
|
|
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");
|
|
10633
11561
|
}
|
|
10634
11562
|
return dir;
|
|
10635
11563
|
}
|
|
10636
11564
|
function uniqueAttachmentName(dir, originalName) {
|
|
10637
11565
|
const safe = originalName.replace(/[/\\]/g, "_") || "file";
|
|
10638
|
-
if (!(0,
|
|
10639
|
-
const ext = (0,
|
|
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);
|
|
10640
11568
|
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
10641
11569
|
for (let i = 1; i < 1e4; i++) {
|
|
10642
11570
|
const candidate = `${stem}-${i}${ext}`;
|
|
10643
|
-
if (!(0,
|
|
11571
|
+
if (!(0, import_node_fs29.existsSync)((0, import_node_path24.join)(dir, candidate))) return candidate;
|
|
10644
11572
|
}
|
|
10645
|
-
return `${stem}-${(0,
|
|
11573
|
+
return `${stem}-${(0, import_node_crypto7.randomUUID)()}${ext}`;
|
|
10646
11574
|
}
|
|
10647
11575
|
function previewDataUrlFromBuf(filePath, buf) {
|
|
10648
11576
|
if (!isImageFilePath(filePath)) return void 0;
|
|
@@ -10654,7 +11582,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
10654
11582
|
if (isImageFilePath(name)) {
|
|
10655
11583
|
const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
|
|
10656
11584
|
return {
|
|
10657
|
-
id: (0,
|
|
11585
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10658
11586
|
name,
|
|
10659
11587
|
kind: "file",
|
|
10660
11588
|
path: opts.path,
|
|
@@ -10667,7 +11595,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
10667
11595
|
}
|
|
10668
11596
|
if (buf.length > MAX_INLINE_BYTES) {
|
|
10669
11597
|
return {
|
|
10670
|
-
id: (0,
|
|
11598
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10671
11599
|
name,
|
|
10672
11600
|
kind: "file",
|
|
10673
11601
|
path: opts.path,
|
|
@@ -10676,7 +11604,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
10676
11604
|
}
|
|
10677
11605
|
if (buf.includes(0)) {
|
|
10678
11606
|
return {
|
|
10679
|
-
id: (0,
|
|
11607
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10680
11608
|
name,
|
|
10681
11609
|
kind: "file",
|
|
10682
11610
|
path: opts.path,
|
|
@@ -10684,7 +11612,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
10684
11612
|
};
|
|
10685
11613
|
}
|
|
10686
11614
|
return {
|
|
10687
|
-
id: (0,
|
|
11615
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10688
11616
|
name,
|
|
10689
11617
|
kind: "file",
|
|
10690
11618
|
path: opts.path,
|
|
@@ -10696,19 +11624,19 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
10696
11624
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
10697
11625
|
const out = [];
|
|
10698
11626
|
for (const abs of absolutePaths) {
|
|
10699
|
-
const originalName = (0,
|
|
11627
|
+
const originalName = (0, import_node_path24.basename)(abs);
|
|
10700
11628
|
try {
|
|
10701
|
-
const st = (0,
|
|
11629
|
+
const st = (0, import_node_fs29.statSync)(abs);
|
|
10702
11630
|
if (!st.isFile()) continue;
|
|
10703
11631
|
const name = uniqueAttachmentName(dir, originalName);
|
|
10704
|
-
const destAbs = (0,
|
|
10705
|
-
(0,
|
|
11632
|
+
const destAbs = (0, import_node_path24.join)(dir, name);
|
|
11633
|
+
(0, import_node_fs29.copyFileSync)(abs, destAbs);
|
|
10706
11634
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
10707
|
-
const buf = (0,
|
|
11635
|
+
const buf = (0, import_node_fs29.readFileSync)(destAbs);
|
|
10708
11636
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
10709
11637
|
} catch (err) {
|
|
10710
11638
|
out.push({
|
|
10711
|
-
id: (0,
|
|
11639
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10712
11640
|
name: originalName,
|
|
10713
11641
|
kind: "file",
|
|
10714
11642
|
content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -10726,13 +11654,13 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
10726
11654
|
try {
|
|
10727
11655
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
10728
11656
|
const name = uniqueAttachmentName(dir, originalName);
|
|
10729
|
-
const destAbs = (0,
|
|
10730
|
-
(0,
|
|
11657
|
+
const destAbs = (0, import_node_path24.join)(dir, name);
|
|
11658
|
+
(0, import_node_fs29.writeFileSync)(destAbs, buf);
|
|
10731
11659
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
10732
11660
|
out.push(attachmentFromBuffer(name, buf, { path: rel }));
|
|
10733
11661
|
} catch (err) {
|
|
10734
11662
|
out.push({
|
|
10735
|
-
id: (0,
|
|
11663
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10736
11664
|
name: originalName,
|
|
10737
11665
|
kind: "file",
|
|
10738
11666
|
content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -10746,23 +11674,23 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
10746
11674
|
for (const rel of relativePaths) {
|
|
10747
11675
|
if (!rel || rel.includes("..") || rel.startsWith("/")) {
|
|
10748
11676
|
out.push({
|
|
10749
|
-
id: (0,
|
|
10750
|
-
name: (0,
|
|
11677
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
11678
|
+
name: (0, import_node_path24.basename)(rel) || "file",
|
|
10751
11679
|
kind: "file",
|
|
10752
11680
|
content: `(invalid path: ${rel})`
|
|
10753
11681
|
});
|
|
10754
11682
|
continue;
|
|
10755
11683
|
}
|
|
10756
|
-
const name = (0,
|
|
11684
|
+
const name = (0, import_node_path24.basename)(rel);
|
|
10757
11685
|
try {
|
|
10758
|
-
const abs = (0,
|
|
10759
|
-
const st = (0,
|
|
11686
|
+
const abs = (0, import_node_path24.join)(worktreePath, rel);
|
|
11687
|
+
const st = (0, import_node_fs29.statSync)(abs);
|
|
10760
11688
|
if (!st.isFile()) continue;
|
|
10761
|
-
const buf = (0,
|
|
11689
|
+
const buf = (0, import_node_fs29.readFileSync)(abs);
|
|
10762
11690
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
10763
11691
|
} catch (err) {
|
|
10764
11692
|
out.push({
|
|
10765
|
-
id: (0,
|
|
11693
|
+
id: (0, import_node_crypto7.randomUUID)(),
|
|
10766
11694
|
name,
|
|
10767
11695
|
kind: "file",
|
|
10768
11696
|
content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -10773,8 +11701,8 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
10773
11701
|
}
|
|
10774
11702
|
|
|
10775
11703
|
// src/agents/instructions.ts
|
|
10776
|
-
var
|
|
10777
|
-
var
|
|
11704
|
+
var import_node_fs30 = require("fs");
|
|
11705
|
+
var import_node_path25 = require("path");
|
|
10778
11706
|
init_worktree_labels();
|
|
10779
11707
|
function normPath2(p) {
|
|
10780
11708
|
return p.replace(/\/+$/, "");
|
|
@@ -11020,7 +11948,7 @@ var Orchestrator = class {
|
|
|
11020
11948
|
}
|
|
11021
11949
|
continue;
|
|
11022
11950
|
}
|
|
11023
|
-
if (!(0,
|
|
11951
|
+
if (!(0, import_node_fs33.existsSync)(thread.worktreePath)) {
|
|
11024
11952
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
11025
11953
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
11026
11954
|
continue;
|
|
@@ -11276,11 +12204,11 @@ var Orchestrator = class {
|
|
|
11276
12204
|
return results;
|
|
11277
12205
|
}
|
|
11278
12206
|
/** Edit the text of a not-yet-started queued message. */
|
|
11279
|
-
async editQueuedMessage(threadRef, index,
|
|
12207
|
+
async editQueuedMessage(threadRef, index, text2) {
|
|
11280
12208
|
const thread = this.requireThread(threadRef);
|
|
11281
12209
|
return withThreadLock(thread.id, async () => {
|
|
11282
12210
|
const current = this.requireThread(thread.id);
|
|
11283
|
-
const trimmed =
|
|
12211
|
+
const trimmed = text2.trim();
|
|
11284
12212
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
11285
12213
|
return current;
|
|
11286
12214
|
}
|
|
@@ -11425,10 +12353,19 @@ var Orchestrator = class {
|
|
|
11425
12353
|
attachments: sentAttachments ?? thread.attachments
|
|
11426
12354
|
}
|
|
11427
12355
|
);
|
|
11428
|
-
const
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
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;
|
|
11432
12369
|
const artifactReminder = thread.agent !== "brightsy" ? [
|
|
11433
12370
|
"Sideboard side column (important):",
|
|
11434
12371
|
"There is no claude.ai Artifact tool here \u2014 that is normal.",
|
|
@@ -11488,7 +12425,9 @@ var Orchestrator = class {
|
|
|
11488
12425
|
goal: fresh.sourceRef || fresh.title || "Orchestration",
|
|
11489
12426
|
parentId: fresh.id,
|
|
11490
12427
|
workspaces: inventory,
|
|
11491
|
-
audience:
|
|
12428
|
+
audience: /^(Slack DM|Slack @mention)(?:\n|$)/.test(
|
|
12429
|
+
expandedPrompt.trim()
|
|
12430
|
+
) ? "slack" : "desktop"
|
|
11492
12431
|
});
|
|
11493
12432
|
}
|
|
11494
12433
|
}
|
|
@@ -12031,6 +12970,11 @@ var Orchestrator = class {
|
|
|
12031
12970
|
return getDiff(thread.worktreePath, thread.repoPath, {
|
|
12032
12971
|
scope: opts?.scope,
|
|
12033
12972
|
commitSha: opts?.commitSha,
|
|
12973
|
+
base: opts?.base,
|
|
12974
|
+
includePatches: opts?.includePatches,
|
|
12975
|
+
includeMeta: opts?.includeMeta,
|
|
12976
|
+
includeUntracked: opts?.includeUntracked,
|
|
12977
|
+
path: opts?.path,
|
|
12034
12978
|
lastTurnBase: this.turnBaselines.get(thread.id) ?? null
|
|
12035
12979
|
});
|
|
12036
12980
|
}
|
|
@@ -12108,7 +13052,8 @@ var Orchestrator = class {
|
|
|
12108
13052
|
isDraft: false,
|
|
12109
13053
|
reviewDecision: null,
|
|
12110
13054
|
baseRefName: "",
|
|
12111
|
-
headRefName: ""
|
|
13055
|
+
headRefName: "",
|
|
13056
|
+
isInMergeQueue: false
|
|
12112
13057
|
};
|
|
12113
13058
|
await this.persistPrMetaAndMaybeArchive(thread, metaLike);
|
|
12114
13059
|
return { url: metaLike.url, state };
|
|
@@ -12423,7 +13368,7 @@ var Orchestrator = class {
|
|
|
12423
13368
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
12424
13369
|
return restored2;
|
|
12425
13370
|
}
|
|
12426
|
-
if (!(0,
|
|
13371
|
+
if (!(0, import_node_fs33.existsSync)(thread.worktreePath)) {
|
|
12427
13372
|
const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
12428
13373
|
const { execa: execa7 } = await import("execa");
|
|
12429
13374
|
const slug = thread.worktreePath.split("/").pop();
|
|
@@ -12504,8 +13449,105 @@ init_worktree();
|
|
|
12504
13449
|
init_run();
|
|
12505
13450
|
init_app_settings();
|
|
12506
13451
|
|
|
12507
|
-
// src/integrations/linear.ts
|
|
13452
|
+
// src/integrations/linear-oauth.ts
|
|
13453
|
+
var import_node_crypto8 = require("crypto");
|
|
13454
|
+
var import_node_http = require("http");
|
|
12508
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
|
|
12509
13551
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
12510
13552
|
var ASSIGNED_ISSUES_QUERY = `
|
|
12511
13553
|
query SideboardAssignedIssues($first: Int!) {
|
|
@@ -12527,16 +13569,16 @@ query SideboardAssignedIssues($first: Int!) {
|
|
|
12527
13569
|
}
|
|
12528
13570
|
`;
|
|
12529
13571
|
async function listLinearIssuesDirect(opts) {
|
|
12530
|
-
const apiKey = (opts?.apiKey ??
|
|
13572
|
+
const apiKey = (opts?.apiKey ?? await getLinearAuthToken())?.trim();
|
|
12531
13573
|
if (!apiKey) {
|
|
12532
|
-
throw new Error("Linear is not connected \u2014
|
|
13574
|
+
throw new Error("Linear is not connected \u2014 sign in from Account settings");
|
|
12533
13575
|
}
|
|
12534
13576
|
const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
|
|
12535
13577
|
const res = await fetch(LINEAR_GRAPHQL, {
|
|
12536
13578
|
method: "POST",
|
|
12537
13579
|
headers: {
|
|
12538
13580
|
"Content-Type": "application/json",
|
|
12539
|
-
Authorization: apiKey
|
|
13581
|
+
Authorization: linearAuthorizationHeader(apiKey)
|
|
12540
13582
|
},
|
|
12541
13583
|
body: JSON.stringify({
|
|
12542
13584
|
query: ASSIGNED_ISSUES_QUERY,
|
|
@@ -12633,6 +13675,647 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
12633
13675
|
|
|
12634
13676
|
// src/mcp/server.ts
|
|
12635
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
|
|
12636
14319
|
var MAX_ORCH_THREADS = 5;
|
|
12637
14320
|
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
12638
14321
|
function withTimeout(promise, ms, label) {
|
|
@@ -12697,7 +14380,7 @@ async function startMcpServer() {
|
|
|
12697
14380
|
async () => {
|
|
12698
14381
|
const threads = orch.getThreads(true);
|
|
12699
14382
|
const lines = threads.map((t) => {
|
|
12700
|
-
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0,
|
|
14383
|
+
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path31.basename)(t.repoPath) || t.repoPath;
|
|
12701
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}` : ""}`;
|
|
12702
14385
|
});
|
|
12703
14386
|
return {
|
|
@@ -12708,7 +14391,7 @@ async function startMcpServer() {
|
|
|
12708
14391
|
server.tool(
|
|
12709
14392
|
"get_thread",
|
|
12710
14393
|
"Get a compact thread summary by id/ref",
|
|
12711
|
-
{ ref:
|
|
14394
|
+
{ ref: import_zod2.z.string() },
|
|
12712
14395
|
async ({ ref }) => {
|
|
12713
14396
|
const t = orch.getThread(ref);
|
|
12714
14397
|
if (!t) {
|
|
@@ -12737,14 +14420,14 @@ async function startMcpServer() {
|
|
|
12737
14420
|
"present_artifact",
|
|
12738
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.",
|
|
12739
14422
|
{
|
|
12740
|
-
title:
|
|
12741
|
-
type:
|
|
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(
|
|
12742
14425
|
"Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
|
|
12743
14426
|
),
|
|
12744
|
-
content:
|
|
14427
|
+
content: import_zod2.z.string().describe(
|
|
12745
14428
|
"Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
|
|
12746
14429
|
),
|
|
12747
|
-
artifact_id:
|
|
14430
|
+
artifact_id: import_zod2.z.string().optional().describe("Stable id when updating the same artifact across turns")
|
|
12748
14431
|
},
|
|
12749
14432
|
async ({ title, type, artifact_id }) => {
|
|
12750
14433
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -12762,15 +14445,15 @@ async function startMcpServer() {
|
|
|
12762
14445
|
"ask_user",
|
|
12763
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.",
|
|
12764
14447
|
{
|
|
12765
|
-
questions:
|
|
12766
|
-
|
|
12767
|
-
question:
|
|
12768
|
-
header:
|
|
12769
|
-
multiSelect:
|
|
12770
|
-
options:
|
|
12771
|
-
|
|
12772
|
-
label:
|
|
12773
|
-
description:
|
|
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)")
|
|
12774
14457
|
})
|
|
12775
14458
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
12776
14459
|
})
|
|
@@ -12789,9 +14472,9 @@ async function startMcpServer() {
|
|
|
12789
14472
|
"present_plan",
|
|
12790
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.",
|
|
12791
14474
|
{
|
|
12792
|
-
title:
|
|
12793
|
-
content:
|
|
12794
|
-
thread_id:
|
|
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")
|
|
12795
14478
|
},
|
|
12796
14479
|
async ({ title, content, thread_id }) => {
|
|
12797
14480
|
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
@@ -12812,17 +14495,17 @@ async function startMcpServer() {
|
|
|
12812
14495
|
);
|
|
12813
14496
|
server.tool(
|
|
12814
14497
|
"present_schema",
|
|
12815
|
-
"Open Sideboard\u2019s schema-driven
|
|
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.",
|
|
12816
14499
|
{
|
|
12817
|
-
title:
|
|
12818
|
-
mode:
|
|
12819
|
-
datasource:
|
|
12820
|
-
resource_id:
|
|
12821
|
-
record_id:
|
|
12822
|
-
resource:
|
|
12823
|
-
record:
|
|
12824
|
-
records:
|
|
12825
|
-
pane_id:
|
|
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")
|
|
12826
14509
|
},
|
|
12827
14510
|
async (args) => {
|
|
12828
14511
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -12843,10 +14526,10 @@ async function startMcpServer() {
|
|
|
12843
14526
|
"present_files",
|
|
12844
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.",
|
|
12845
14528
|
{
|
|
12846
|
-
title:
|
|
12847
|
-
datasource:
|
|
12848
|
-
path:
|
|
12849
|
-
pane_id:
|
|
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")
|
|
12850
14533
|
},
|
|
12851
14534
|
async (args) => {
|
|
12852
14535
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -12861,23 +14544,62 @@ async function startMcpServer() {
|
|
|
12861
14544
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
12862
14545
|
}
|
|
12863
14546
|
);
|
|
14547
|
+
registerSlackTools(server);
|
|
12864
14548
|
if (sideboardMcpProfile() !== "worktree") {
|
|
14549
|
+
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await Promise.resolve().then(() => (init_caffeinate_hold(), caffeinate_hold_exports));
|
|
12865
14550
|
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
|
|
12866
14551
|
const accountDefaults = resolveThreadDefaults2();
|
|
12867
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
|
+
);
|
|
12868
14590
|
server.tool(
|
|
12869
14591
|
"create_thread",
|
|
12870
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.`,
|
|
12871
14593
|
{
|
|
12872
|
-
sourceType:
|
|
12873
|
-
sourceRef:
|
|
12874
|
-
agent:
|
|
12875
|
-
model:
|
|
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(
|
|
12876
14598
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
12877
14599
|
),
|
|
12878
|
-
repoPath:
|
|
12879
|
-
title:
|
|
12880
|
-
parentThreadId:
|
|
14600
|
+
repoPath: import_zod2.z.string(),
|
|
14601
|
+
title: import_zod2.z.string().optional(),
|
|
14602
|
+
parentThreadId: import_zod2.z.string().optional().describe(
|
|
12881
14603
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
12882
14604
|
)
|
|
12883
14605
|
},
|
|
@@ -12984,9 +14706,9 @@ async function startMcpServer() {
|
|
|
12984
14706
|
"send_to_thread",
|
|
12985
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.",
|
|
12986
14708
|
{
|
|
12987
|
-
ref:
|
|
12988
|
-
prompt:
|
|
12989
|
-
force_stop:
|
|
14709
|
+
ref: import_zod2.z.string(),
|
|
14710
|
+
prompt: import_zod2.z.string(),
|
|
14711
|
+
force_stop: import_zod2.z.boolean().optional()
|
|
12990
14712
|
},
|
|
12991
14713
|
async ({ ref, prompt, force_stop }) => {
|
|
12992
14714
|
if (force_stop) {
|
|
@@ -13015,8 +14737,8 @@ async function startMcpServer() {
|
|
|
13015
14737
|
"wait_for_turn",
|
|
13016
14738
|
"Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
|
|
13017
14739
|
{
|
|
13018
|
-
ref:
|
|
13019
|
-
timeoutMs:
|
|
14740
|
+
ref: import_zod2.z.string(),
|
|
14741
|
+
timeoutMs: import_zod2.z.number().optional()
|
|
13020
14742
|
},
|
|
13021
14743
|
async ({ ref, timeoutMs }) => {
|
|
13022
14744
|
const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
|
|
@@ -13038,7 +14760,7 @@ async function startMcpServer() {
|
|
|
13038
14760
|
server.tool(
|
|
13039
14761
|
"get_turn_result",
|
|
13040
14762
|
"Final assistant message only (not full transcript)",
|
|
13041
|
-
{ ref:
|
|
14763
|
+
{ ref: import_zod2.z.string() },
|
|
13042
14764
|
async ({ ref }) => {
|
|
13043
14765
|
const result = orch.getTurnResult(ref);
|
|
13044
14766
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
@@ -13048,8 +14770,8 @@ async function startMcpServer() {
|
|
|
13048
14770
|
"stop_thread",
|
|
13049
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.",
|
|
13050
14772
|
{
|
|
13051
|
-
ref:
|
|
13052
|
-
force:
|
|
14773
|
+
ref: import_zod2.z.string(),
|
|
14774
|
+
force: import_zod2.z.boolean().optional()
|
|
13053
14775
|
},
|
|
13054
14776
|
async ({ ref, force }) => {
|
|
13055
14777
|
const t = orch.getThread(ref);
|
|
@@ -13078,8 +14800,8 @@ async function startMcpServer() {
|
|
|
13078
14800
|
);
|
|
13079
14801
|
server.tool(
|
|
13080
14802
|
"archive_thread",
|
|
13081
|
-
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab).
|
|
13082
|
-
{ ref:
|
|
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() },
|
|
13083
14805
|
async ({ ref }) => {
|
|
13084
14806
|
const t = orch.getThread(ref);
|
|
13085
14807
|
if (!t) {
|
|
@@ -13112,7 +14834,7 @@ async function startMcpServer() {
|
|
|
13112
14834
|
server.tool(
|
|
13113
14835
|
"restore_thread",
|
|
13114
14836
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
13115
|
-
{ ref:
|
|
14837
|
+
{ ref: import_zod2.z.string() },
|
|
13116
14838
|
async ({ ref }) => {
|
|
13117
14839
|
try {
|
|
13118
14840
|
const restored = await orch.restore(ref);
|
|
@@ -13138,8 +14860,8 @@ async function startMcpServer() {
|
|
|
13138
14860
|
"get_diff",
|
|
13139
14861
|
"Compact diff summary (capped hunks, paginated)",
|
|
13140
14862
|
{
|
|
13141
|
-
ref:
|
|
13142
|
-
maxFiles:
|
|
14863
|
+
ref: import_zod2.z.string(),
|
|
14864
|
+
maxFiles: import_zod2.z.number().optional()
|
|
13143
14865
|
},
|
|
13144
14866
|
async ({ ref, maxFiles }) => {
|
|
13145
14867
|
const summary = await orch.diffSummary(ref);
|
|
@@ -13159,7 +14881,7 @@ async function startMcpServer() {
|
|
|
13159
14881
|
server.tool(
|
|
13160
14882
|
"request_review",
|
|
13161
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.',
|
|
13162
|
-
{ ref:
|
|
14884
|
+
{ ref: import_zod2.z.string().describe("Worktree thread id/ref to review") },
|
|
13163
14885
|
async ({ ref }) => {
|
|
13164
14886
|
try {
|
|
13165
14887
|
const tab = await orch.requestReview(ref);
|
|
@@ -13184,7 +14906,7 @@ async function startMcpServer() {
|
|
|
13184
14906
|
}
|
|
13185
14907
|
}
|
|
13186
14908
|
);
|
|
13187
|
-
const agentEnum =
|
|
14909
|
+
const agentEnum = import_zod2.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
13188
14910
|
server.tool(
|
|
13189
14911
|
"list_models",
|
|
13190
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.",
|
|
@@ -13207,11 +14929,11 @@ async function startMcpServer() {
|
|
|
13207
14929
|
"fork_worktree",
|
|
13208
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.",
|
|
13209
14931
|
{
|
|
13210
|
-
ref:
|
|
13211
|
-
through_index:
|
|
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)"),
|
|
13212
14934
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
13213
|
-
model:
|
|
13214
|
-
title:
|
|
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()
|
|
13215
14937
|
},
|
|
13216
14938
|
async ({ ref, through_index, agent, model, title }) => {
|
|
13217
14939
|
try {
|
|
@@ -13250,13 +14972,13 @@ async function startMcpServer() {
|
|
|
13250
14972
|
);
|
|
13251
14973
|
server.tool(
|
|
13252
14974
|
"fork_chat",
|
|
13253
|
-
"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).
|
|
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.",
|
|
13254
14976
|
{
|
|
13255
|
-
ref:
|
|
13256
|
-
through_index:
|
|
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)"),
|
|
13257
14979
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
13258
|
-
model:
|
|
13259
|
-
title:
|
|
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()
|
|
13260
14982
|
},
|
|
13261
14983
|
async ({ ref, through_index, agent, model, title }) => {
|
|
13262
14984
|
try {
|
|
@@ -13302,8 +15024,8 @@ async function startMcpServer() {
|
|
|
13302
15024
|
"run_dev_script",
|
|
13303
15025
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
13304
15026
|
{
|
|
13305
|
-
ref:
|
|
13306
|
-
name:
|
|
15027
|
+
ref: import_zod2.z.string(),
|
|
15028
|
+
name: import_zod2.z.string().optional()
|
|
13307
15029
|
},
|
|
13308
15030
|
async ({ ref, name }) => {
|
|
13309
15031
|
const result = await orch.startDev(ref, name);
|
|
@@ -13325,7 +15047,7 @@ async function startMcpServer() {
|
|
|
13325
15047
|
server.tool(
|
|
13326
15048
|
"list_run_scripts",
|
|
13327
15049
|
"List named run scripts available for a thread",
|
|
13328
|
-
{ ref:
|
|
15050
|
+
{ ref: import_zod2.z.string() },
|
|
13329
15051
|
async ({ ref }) => {
|
|
13330
15052
|
const scripts = orch.listThreadRunScripts(ref);
|
|
13331
15053
|
const active = orch.getActiveRuns(ref);
|
|
@@ -13343,8 +15065,8 @@ async function startMcpServer() {
|
|
|
13343
15065
|
"stop_dev_script",
|
|
13344
15066
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
13345
15067
|
{
|
|
13346
|
-
ref:
|
|
13347
|
-
name:
|
|
15068
|
+
ref: import_zod2.z.string(),
|
|
15069
|
+
name: import_zod2.z.string().optional()
|
|
13348
15070
|
},
|
|
13349
15071
|
async ({ ref, name }) => {
|
|
13350
15072
|
orch.stopDev(ref, name);
|
|
@@ -13354,7 +15076,7 @@ async function startMcpServer() {
|
|
|
13354
15076
|
server.tool(
|
|
13355
15077
|
"run_setup",
|
|
13356
15078
|
"Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
|
|
13357
|
-
{ ref:
|
|
15079
|
+
{ ref: import_zod2.z.string() },
|
|
13358
15080
|
async ({ ref }) => {
|
|
13359
15081
|
const result = await orch.runSetup(ref);
|
|
13360
15082
|
return {
|
|
@@ -13365,7 +15087,7 @@ async function startMcpServer() {
|
|
|
13365
15087
|
server.tool(
|
|
13366
15088
|
"add_workspace",
|
|
13367
15089
|
"Register a git repo as a Sideboard workspace",
|
|
13368
|
-
{ repoPath:
|
|
15090
|
+
{ repoPath: import_zod2.z.string() },
|
|
13369
15091
|
async ({ repoPath }) => {
|
|
13370
15092
|
const ws = await orch.addWorkspace(repoPath);
|
|
13371
15093
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -13374,7 +15096,7 @@ async function startMcpServer() {
|
|
|
13374
15096
|
server.tool(
|
|
13375
15097
|
"remove_workspace",
|
|
13376
15098
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
13377
|
-
{ repoPath:
|
|
15099
|
+
{ repoPath: import_zod2.z.string() },
|
|
13378
15100
|
async ({ repoPath }) => {
|
|
13379
15101
|
orch.removeWorkspace(repoPath);
|
|
13380
15102
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -13384,12 +15106,12 @@ async function startMcpServer() {
|
|
|
13384
15106
|
"fanout",
|
|
13385
15107
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
13386
15108
|
{
|
|
13387
|
-
prompt:
|
|
13388
|
-
agents:
|
|
13389
|
-
repoPath:
|
|
13390
|
-
sourceType:
|
|
13391
|
-
sourceRef:
|
|
13392
|
-
title:
|
|
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()
|
|
13393
15115
|
},
|
|
13394
15116
|
async (args) => {
|
|
13395
15117
|
const threads = await orch.bestOfN(args);
|
|
@@ -13416,8 +15138,8 @@ async function startMcpServer() {
|
|
|
13416
15138
|
"list_branches",
|
|
13417
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).",
|
|
13418
15140
|
{
|
|
13419
|
-
repoPath:
|
|
13420
|
-
unmergedOnly:
|
|
15141
|
+
repoPath: import_zod2.z.string(),
|
|
15142
|
+
unmergedOnly: import_zod2.z.boolean().optional()
|
|
13421
15143
|
},
|
|
13422
15144
|
async ({ repoPath, unmergedOnly }) => {
|
|
13423
15145
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -13437,7 +15159,7 @@ async function startMcpServer() {
|
|
|
13437
15159
|
server.tool(
|
|
13438
15160
|
"list_prs",
|
|
13439
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.",
|
|
13440
|
-
{ repoPath:
|
|
15162
|
+
{ repoPath: import_zod2.z.string() },
|
|
13441
15163
|
async ({ repoPath }) => {
|
|
13442
15164
|
const root = await resolveRepoRoot(repoPath);
|
|
13443
15165
|
const prs = await listPrs(root);
|
|
@@ -13456,7 +15178,7 @@ async function startMcpServer() {
|
|
|
13456
15178
|
server.tool(
|
|
13457
15179
|
"get_pr_stack",
|
|
13458
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.",
|
|
13459
|
-
{ ref:
|
|
15181
|
+
{ ref: import_zod2.z.string() },
|
|
13460
15182
|
async ({ ref }) => {
|
|
13461
15183
|
const stack = await orch.getPrStack(ref);
|
|
13462
15184
|
return {
|
|
@@ -13468,8 +15190,8 @@ async function startMcpServer() {
|
|
|
13468
15190
|
"open_pr_stack_layers",
|
|
13469
15191
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
13470
15192
|
{
|
|
13471
|
-
ref:
|
|
13472
|
-
layer:
|
|
15193
|
+
ref: import_zod2.z.string(),
|
|
15194
|
+
layer: import_zod2.z.number().int().positive().optional()
|
|
13473
15195
|
},
|
|
13474
15196
|
async ({ ref, layer }) => {
|
|
13475
15197
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -13503,9 +15225,9 @@ async function startMcpServer() {
|
|
|
13503
15225
|
"add_stack_layer",
|
|
13504
15226
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
13505
15227
|
{
|
|
13506
|
-
ref:
|
|
13507
|
-
branchName:
|
|
13508
|
-
title:
|
|
15228
|
+
ref: import_zod2.z.string(),
|
|
15229
|
+
branchName: import_zod2.z.string(),
|
|
15230
|
+
title: import_zod2.z.string().optional()
|
|
13509
15231
|
},
|
|
13510
15232
|
async ({ ref, branchName, title }) => {
|
|
13511
15233
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -13534,11 +15256,11 @@ async function startMcpServer() {
|
|
|
13534
15256
|
"create_pr_stack",
|
|
13535
15257
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
13536
15258
|
{
|
|
13537
|
-
repoPath:
|
|
13538
|
-
branches:
|
|
13539
|
-
agent:
|
|
13540
|
-
base:
|
|
13541
|
-
title:
|
|
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()
|
|
13542
15264
|
},
|
|
13543
15265
|
async (args) => {
|
|
13544
15266
|
const result = await orch.createPrStack({
|
|
@@ -13576,7 +15298,7 @@ async function startMcpServer() {
|
|
|
13576
15298
|
server.tool(
|
|
13577
15299
|
"list_issues",
|
|
13578
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.",
|
|
13579
|
-
{ repoPath:
|
|
15301
|
+
{ repoPath: import_zod2.z.string() },
|
|
13580
15302
|
async ({ repoPath }) => {
|
|
13581
15303
|
const root = await resolveRepoRoot(repoPath);
|
|
13582
15304
|
const result = await listIssues(root);
|
|
@@ -13587,8 +15309,8 @@ async function startMcpServer() {
|
|
|
13587
15309
|
"list_linear_issues",
|
|
13588
15310
|
"Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
|
|
13589
15311
|
{
|
|
13590
|
-
agent:
|
|
13591
|
-
repoPath:
|
|
15312
|
+
agent: import_zod2.z.enum(["claude", "codex", "opencode"]),
|
|
15313
|
+
repoPath: import_zod2.z.string()
|
|
13592
15314
|
},
|
|
13593
15315
|
async ({ agent, repoPath }) => {
|
|
13594
15316
|
const issues = await listLinearIssues(agent, repoPath);
|