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