@sideboard-ai/core 0.1.74 → 0.1.77
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-2S2JVLZ6.js → agents-LKUHPPEQ.js} +3 -3
- package/dist/{agents-H7M5CQY2.js → agents-XJV6J3K2.js} +3 -3
- package/dist/{caffeinate-hold-KLC6SABD.js → caffeinate-hold-EAZNWJBE.js} +5 -1
- package/dist/caffeinate-hold-OHAUWOA6.js +20 -0
- package/dist/{chunk-KBKPVKYZ.js → chunk-2FJI5JXX.js} +2 -2
- package/dist/{chunk-GJ2LZQJI.js → chunk-2ZIPJRJE.js} +1 -1
- package/dist/{chunk-DG3S2UXP.js → chunk-75U65MBE.js} +1 -1
- package/dist/{chunk-HCWAIEBU.js → chunk-EEKSZTMU.js} +3 -2
- package/dist/{chunk-2NKSHIRI.js → chunk-GWTLKXXP.js} +1 -1
- package/dist/chunk-MFF2RE3I.js +168 -0
- package/dist/{chunk-5KJMNNCB.js → chunk-QD37IILI.js} +2 -2
- package/dist/chunk-RV6DBEDQ.js +170 -0
- package/dist/{chunk-77W62MTX.js → chunk-VERLUKN2.js} +1 -1
- package/dist/{chunk-XA2FQJTN.js → chunk-XJNCWYFR.js} +3 -2
- package/dist/{coordinator-prompt-3XXSG7M2.js → coordinator-prompt-LLFJUO2R.js} +1 -1
- package/dist/{coordinator-prompt-RTUCCPCI.js → coordinator-prompt-MRCMMRSF.js} +1 -1
- package/dist/{global-workspace-PJU6HISJ.js → global-workspace-TWHMYLTZ.js} +2 -2
- package/dist/{global-workspace-CSIS62Z4.js → global-workspace-YNMNO4CL.js} +2 -2
- package/dist/index.cjs +1050 -438
- package/dist/index.d.cts +144 -21
- package/dist/index.d.ts +144 -21
- package/dist/index.js +926 -389
- package/dist/mcp/run-stdio.cjs +709 -276
- package/dist/mcp/run-stdio.js +543 -171
- package/dist/{workspaces-L4TXMUNM.js → workspaces-GQ4XKBD3.js} +3 -3
- package/dist/{workspaces-W72KZL4B.js → workspaces-O3U5BENH.js} +3 -3
- package/package.json +1 -1
- package/dist/caffeinate-hold-BEJIYPJ7.js +0 -107
- package/dist/chunk-JAWEDVVA.js +0 -106
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -16,19 +16,22 @@ import {
|
|
|
16
16
|
resolveQuotaFallbackAgent,
|
|
17
17
|
sideboardMcpProfile,
|
|
18
18
|
summarizeTurnStderr
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-2FJI5JXX.js";
|
|
20
20
|
import "../chunk-VROPG6QF.js";
|
|
21
21
|
import {
|
|
22
22
|
addWorkspace,
|
|
23
23
|
ensureWorkspace,
|
|
24
24
|
removeWorkspace,
|
|
25
25
|
syncWorkspacesFromThreads
|
|
26
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-2ZIPJRJE.js";
|
|
27
27
|
import {
|
|
28
28
|
extractPresentedPlan,
|
|
29
29
|
readPlanFile,
|
|
30
30
|
writePlanFile
|
|
31
31
|
} from "../chunk-N5PM7HGQ.js";
|
|
32
|
+
import {
|
|
33
|
+
releaseCaffeinateHoldForThread
|
|
34
|
+
} from "../chunk-RV6DBEDQ.js";
|
|
32
35
|
import {
|
|
33
36
|
GLOBAL_WORKSPACE_ID,
|
|
34
37
|
assertOrchestratorCapableAgent,
|
|
@@ -38,14 +41,14 @@ import {
|
|
|
38
41
|
isGlobalThread,
|
|
39
42
|
isOrchestratorThread,
|
|
40
43
|
orchestratorSessionPoisonedByBuiltins
|
|
41
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-GWTLKXXP.js";
|
|
42
45
|
import {
|
|
43
46
|
SLACK_REPLY_FORMATTING,
|
|
44
47
|
coordinatorSystemPrompt,
|
|
45
48
|
coordinatorTurnReminder,
|
|
46
49
|
enrichWorkspacesWithGithub,
|
|
47
50
|
ensureGlobalCoordinatorCwd
|
|
48
|
-
} from "../chunk-
|
|
51
|
+
} from "../chunk-XJNCWYFR.js";
|
|
49
52
|
import {
|
|
50
53
|
addPrStackLayer,
|
|
51
54
|
allocateTeamName,
|
|
@@ -140,7 +143,7 @@ import {
|
|
|
140
143
|
// src/mcp/server.ts
|
|
141
144
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
142
145
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
143
|
-
import { z as
|
|
146
|
+
import { z as z3 } from "zod";
|
|
144
147
|
import { basename as basename4 } from "path";
|
|
145
148
|
|
|
146
149
|
// src/orchestrator/orchestrator.ts
|
|
@@ -341,8 +344,8 @@ ${input.permalink}` : "";
|
|
|
341
344
|
|
|
342
345
|
${body}${link}`;
|
|
343
346
|
}
|
|
344
|
-
function isSlackExternalReplyPrompt(
|
|
345
|
-
return
|
|
347
|
+
function isSlackExternalReplyPrompt(text3) {
|
|
348
|
+
return text3.startsWith("Slack reply from ") && text3.includes("not a command");
|
|
346
349
|
}
|
|
347
350
|
function pendingSlackExternalReplies(messages) {
|
|
348
351
|
let i = messages.length - 1;
|
|
@@ -366,9 +369,9 @@ function formatSlackRepliesForTurn(replies) {
|
|
|
366
369
|
function listSlackOutboundWatches() {
|
|
367
370
|
return pruneWatches(readStore3());
|
|
368
371
|
}
|
|
369
|
-
function formatOwnerSlackFyi(userName,
|
|
372
|
+
function formatOwnerSlackFyi(userName, text3) {
|
|
370
373
|
const who = userName.trim() || "Someone";
|
|
371
|
-
const body =
|
|
374
|
+
const body = text3.trim() || "(no text)";
|
|
372
375
|
return `${who} replied in Slack:
|
|
373
376
|
${body}`;
|
|
374
377
|
}
|
|
@@ -383,7 +386,7 @@ async function relayExternalReply(opts) {
|
|
|
383
386
|
const thread = readThread(threadId);
|
|
384
387
|
if (!thread || thread.status === "archived") return true;
|
|
385
388
|
try {
|
|
386
|
-
const
|
|
389
|
+
const text3 = formatSlackExternalReplyPrompt({
|
|
387
390
|
userName: opts.reply.userName,
|
|
388
391
|
kind: opts.watch.kind,
|
|
389
392
|
toLabel: opts.watch.toLabel,
|
|
@@ -392,7 +395,7 @@ async function relayExternalReply(opts) {
|
|
|
392
395
|
});
|
|
393
396
|
appendMessage(threadId, {
|
|
394
397
|
role: "agent",
|
|
395
|
-
text:
|
|
398
|
+
text: text3,
|
|
396
399
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
397
400
|
});
|
|
398
401
|
} catch {
|
|
@@ -576,7 +579,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
576
579
|
const messages = await fetchMessages(token, watch, opts?.fetchImpl);
|
|
577
580
|
const replies = messages.filter((m) => isHumanReply(m, watch)).sort((a, b) => Number(a.ts) - Number(b.ts));
|
|
578
581
|
if (replies.length === 0) continue;
|
|
579
|
-
const
|
|
582
|
+
const injected2 = new Set(watch.injectedReplyTs ?? []);
|
|
580
583
|
const collected = [...watch.replies ?? []];
|
|
581
584
|
let lastSeenTs = watch.lastSeenTs;
|
|
582
585
|
let latestUser;
|
|
@@ -610,7 +613,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
610
613
|
latestName = replyUserName;
|
|
611
614
|
latestText = reply.text;
|
|
612
615
|
latestPermalink = permalink;
|
|
613
|
-
if (
|
|
616
|
+
if (injected2.has(ts)) {
|
|
614
617
|
lastSeenTs = ts;
|
|
615
618
|
continue;
|
|
616
619
|
}
|
|
@@ -621,7 +624,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
621
624
|
fetchImpl: opts?.fetchImpl
|
|
622
625
|
});
|
|
623
626
|
if (!delivered) break;
|
|
624
|
-
|
|
627
|
+
injected2.add(ts);
|
|
625
628
|
lastSeenTs = ts;
|
|
626
629
|
}
|
|
627
630
|
watches[i] = {
|
|
@@ -633,7 +636,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
633
636
|
replyTs: lastSeenTs,
|
|
634
637
|
replyPreview: latestText.slice(0, 140),
|
|
635
638
|
permalink: latestPermalink,
|
|
636
|
-
injectedReplyTs: [...
|
|
639
|
+
injectedReplyTs: [...injected2],
|
|
637
640
|
replies: collected.slice(-MAX_REPLIES_PER_WATCH)
|
|
638
641
|
};
|
|
639
642
|
changed = true;
|
|
@@ -723,9 +726,9 @@ function toolFilePath(input) {
|
|
|
723
726
|
if (!input) return void 0;
|
|
724
727
|
return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
|
|
725
728
|
}
|
|
726
|
-
function countLines(
|
|
727
|
-
if (!
|
|
728
|
-
return
|
|
729
|
+
function countLines(text3) {
|
|
730
|
+
if (!text3) return 0;
|
|
731
|
+
return text3.split("\n").length;
|
|
729
732
|
}
|
|
730
733
|
function diffFromInput(input) {
|
|
731
734
|
if (!input) return {};
|
|
@@ -852,9 +855,9 @@ function applyAgentEvent(parts, event) {
|
|
|
852
855
|
function partsToAssistantText(parts) {
|
|
853
856
|
return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
|
|
854
857
|
}
|
|
855
|
-
function stripBrightsyNdjsonNoise(
|
|
856
|
-
if (!
|
|
857
|
-
let out =
|
|
858
|
+
function stripBrightsyNdjsonNoise(text3) {
|
|
859
|
+
if (!text3 || !text3.includes('"type"')) return text3;
|
|
860
|
+
let out = text3;
|
|
858
861
|
out = out.replace(
|
|
859
862
|
/\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
|
|
860
863
|
""
|
|
@@ -913,9 +916,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
913
916
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
914
917
|
);
|
|
915
918
|
}
|
|
916
|
-
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-
|
|
919
|
+
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-TWHMYLTZ.js");
|
|
917
920
|
if (isGlobalThread2(thread)) {
|
|
918
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-
|
|
921
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-MRCMMRSF.js");
|
|
919
922
|
ensureGlobalCoordinatorCwd2(
|
|
920
923
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
921
924
|
);
|
|
@@ -1002,8 +1005,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1002
1005
|
onEvent({ type: "exit", data: exitCode });
|
|
1003
1006
|
const finalized = finalizeParts(parts);
|
|
1004
1007
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
1005
|
-
const
|
|
1006
|
-
return { exitCode, sessionId, assistantText:
|
|
1008
|
+
const text3 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
1009
|
+
return { exitCode, sessionId, assistantText: text3, parts: finalized, usage };
|
|
1007
1010
|
});
|
|
1008
1011
|
return {
|
|
1009
1012
|
pid: child.pid,
|
|
@@ -1756,7 +1759,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
1756
1759
|
return readThread(thread.id) ?? thread;
|
|
1757
1760
|
}
|
|
1758
1761
|
async function listLinearIssues(agent, repoPath) {
|
|
1759
|
-
const { getAdapter: getAdapter2 } = await import("../agents-
|
|
1762
|
+
const { getAdapter: getAdapter2 } = await import("../agents-LKUHPPEQ.js");
|
|
1760
1763
|
await requireAgent(agent, { requireLinear: true });
|
|
1761
1764
|
const adapter = getAdapter2(agent);
|
|
1762
1765
|
if (!adapter.listLinearIssues) {
|
|
@@ -1812,9 +1815,9 @@ async function tryClaudeSummary(transcript, opts) {
|
|
|
1812
1815
|
{ cwd: opts?.cwd, reject: false }
|
|
1813
1816
|
);
|
|
1814
1817
|
if (exitCode !== 0) return null;
|
|
1815
|
-
const
|
|
1816
|
-
if (
|
|
1817
|
-
return
|
|
1818
|
+
const text3 = stdout.trim();
|
|
1819
|
+
if (text3.length < 40) return null;
|
|
1820
|
+
return text3;
|
|
1818
1821
|
} catch {
|
|
1819
1822
|
return null;
|
|
1820
1823
|
}
|
|
@@ -1863,9 +1866,9 @@ function extractiveSummary(transcript) {
|
|
|
1863
1866
|
);
|
|
1864
1867
|
return parts.join("\n");
|
|
1865
1868
|
}
|
|
1866
|
-
function clipSummary(
|
|
1867
|
-
if (
|
|
1868
|
-
return `${
|
|
1869
|
+
function clipSummary(text3) {
|
|
1870
|
+
if (text3.length <= MAX_SUMMARY_CHARS) return text3;
|
|
1871
|
+
return `${text3.slice(0, MAX_SUMMARY_CHARS)}
|
|
1869
1872
|
|
|
1870
1873
|
[\u2026summary truncated\u2026]`;
|
|
1871
1874
|
}
|
|
@@ -2478,8 +2481,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
2478
2481
|
);
|
|
2479
2482
|
const recent = from.messages.slice(-8).map((m) => {
|
|
2480
2483
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
2481
|
-
const
|
|
2482
|
-
return
|
|
2484
|
+
const text3 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
2485
|
+
return text3 ? `- ${role}: ${text3}` : null;
|
|
2483
2486
|
}).filter(Boolean);
|
|
2484
2487
|
const body = [
|
|
2485
2488
|
`# Orchestration handoff`,
|
|
@@ -2575,13 +2578,13 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
2575
2578
|
for (const hash of hashes) {
|
|
2576
2579
|
const agentsFile = join9(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
2577
2580
|
if (!existsSync9(agentsFile)) continue;
|
|
2578
|
-
let
|
|
2581
|
+
let text3;
|
|
2579
2582
|
try {
|
|
2580
|
-
|
|
2583
|
+
text3 = readFileSync6(agentsFile, "utf8");
|
|
2581
2584
|
} catch {
|
|
2582
2585
|
continue;
|
|
2583
2586
|
}
|
|
2584
|
-
for (const line of
|
|
2587
|
+
for (const line of text3.split("\n")) {
|
|
2585
2588
|
const trimmed = line.trim();
|
|
2586
2589
|
if (!trimmed) continue;
|
|
2587
2590
|
try {
|
|
@@ -2620,7 +2623,7 @@ async function adoptThread(input) {
|
|
|
2620
2623
|
messages: input.messages ?? []
|
|
2621
2624
|
});
|
|
2622
2625
|
writeThread(thread);
|
|
2623
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
2626
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-GQ4XKBD3.js");
|
|
2624
2627
|
await ensureWorkspace2(repoPath);
|
|
2625
2628
|
return thread;
|
|
2626
2629
|
}
|
|
@@ -3344,13 +3347,13 @@ function formatStat(files) {
|
|
|
3344
3347
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
3345
3348
|
}
|
|
3346
3349
|
function emptyScopeStats() {
|
|
3347
|
-
const
|
|
3350
|
+
const z4 = emptyStat();
|
|
3348
3351
|
return {
|
|
3349
|
-
commits:
|
|
3350
|
-
uncommitted:
|
|
3351
|
-
staged:
|
|
3352
|
-
unstaged:
|
|
3353
|
-
last_turn:
|
|
3352
|
+
commits: z4,
|
|
3353
|
+
uncommitted: z4,
|
|
3354
|
+
staged: z4,
|
|
3355
|
+
unstaged: z4,
|
|
3356
|
+
last_turn: z4
|
|
3354
3357
|
};
|
|
3355
3358
|
}
|
|
3356
3359
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -4897,11 +4900,11 @@ var Orchestrator = class {
|
|
|
4897
4900
|
return results;
|
|
4898
4901
|
}
|
|
4899
4902
|
/** Edit the text of a not-yet-started queued message. */
|
|
4900
|
-
async editQueuedMessage(threadRef, index,
|
|
4903
|
+
async editQueuedMessage(threadRef, index, text3) {
|
|
4901
4904
|
const thread = this.requireThread(threadRef);
|
|
4902
4905
|
return withThreadLock(thread.id, async () => {
|
|
4903
4906
|
const current = this.requireThread(thread.id);
|
|
4904
|
-
const trimmed =
|
|
4907
|
+
const trimmed = text3.trim();
|
|
4905
4908
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
4906
4909
|
return current;
|
|
4907
4910
|
}
|
|
@@ -5661,6 +5664,14 @@ var Orchestrator = class {
|
|
|
5661
5664
|
throw new Error(`${action} is not available on the global coordinator`);
|
|
5662
5665
|
}
|
|
5663
5666
|
}
|
|
5667
|
+
/** MCP set_caffeinate is a detached hold — closing the chat must not leave the Mac awake. */
|
|
5668
|
+
releaseOrchestratorCaffeinate(thread) {
|
|
5669
|
+
if (!isOrchestratorThread(thread)) return;
|
|
5670
|
+
try {
|
|
5671
|
+
releaseCaffeinateHoldForThread(thread.id);
|
|
5672
|
+
} catch {
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5664
5675
|
async diff(threadRef, opts) {
|
|
5665
5676
|
const thread = this.requireThread(threadRef);
|
|
5666
5677
|
this.assertNotGlobal(thread, "Diff");
|
|
@@ -6030,6 +6041,7 @@ var Orchestrator = class {
|
|
|
6030
6041
|
async archive(threadRef) {
|
|
6031
6042
|
const thread = this.requireThread(threadRef);
|
|
6032
6043
|
this.stop(thread.id);
|
|
6044
|
+
this.releaseOrchestratorCaffeinate(thread);
|
|
6033
6045
|
if (isGlobalThread(thread)) {
|
|
6034
6046
|
const archived2 = setStatus(thread.id, "archived");
|
|
6035
6047
|
this.emit({ type: "status_changed", threadId: archived2.id, status: "archived" });
|
|
@@ -6054,7 +6066,7 @@ var Orchestrator = class {
|
|
|
6054
6066
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
6055
6067
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
6056
6068
|
try {
|
|
6057
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
6069
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-GQ4XKBD3.js");
|
|
6058
6070
|
await ensureWorkspace2(thread.repoPath);
|
|
6059
6071
|
} catch {
|
|
6060
6072
|
}
|
|
@@ -6064,6 +6076,7 @@ var Orchestrator = class {
|
|
|
6064
6076
|
async purge(threadRef, opts) {
|
|
6065
6077
|
const thread = this.requireThread(threadRef);
|
|
6066
6078
|
this.stop(thread.id);
|
|
6079
|
+
this.releaseOrchestratorCaffeinate(thread);
|
|
6067
6080
|
if (isGlobalThread(thread)) {
|
|
6068
6081
|
deleteThreadRecord(thread.id);
|
|
6069
6082
|
return;
|
|
@@ -6168,13 +6181,36 @@ function getOrchestrator() {
|
|
|
6168
6181
|
return singleton;
|
|
6169
6182
|
}
|
|
6170
6183
|
|
|
6184
|
+
// src/http/fetch.ts
|
|
6185
|
+
var injected = null;
|
|
6186
|
+
function formatFetchError(err, url) {
|
|
6187
|
+
if (!(err instanceof Error)) return `${String(err)} (${url})`;
|
|
6188
|
+
const cause = err.cause;
|
|
6189
|
+
let detail = "";
|
|
6190
|
+
if (cause instanceof Error) {
|
|
6191
|
+
const code = typeof cause.code === "string" ? cause.code : void 0;
|
|
6192
|
+
detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
|
|
6193
|
+
} else if (cause != null) {
|
|
6194
|
+
detail = ` [${String(cause)}]`;
|
|
6195
|
+
}
|
|
6196
|
+
return `${err.message}${detail} (${url})`;
|
|
6197
|
+
}
|
|
6198
|
+
async function httpFetch(input, init) {
|
|
6199
|
+
const url = typeof input === "string" ? input : input.href;
|
|
6200
|
+
const fn = injected ?? globalThis.fetch.bind(globalThis);
|
|
6201
|
+
try {
|
|
6202
|
+
return await fn(url, init);
|
|
6203
|
+
} catch (err) {
|
|
6204
|
+
throw new Error(formatFetchError(err, url));
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6171
6208
|
// src/integrations/linear-oauth.ts
|
|
6172
6209
|
import { createHash, randomBytes } from "crypto";
|
|
6173
6210
|
import { createServer as createServer2 } from "http";
|
|
6174
6211
|
|
|
6175
6212
|
// src/integrations/linear-app.ts
|
|
6176
6213
|
var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
|
|
6177
|
-
var BAKED_LINEAR_CLIENT_SECRET = "";
|
|
6178
6214
|
|
|
6179
6215
|
// src/integrations/linear-oauth.ts
|
|
6180
6216
|
var LINEAR_OAUTH_PORT = 19848;
|
|
@@ -6184,7 +6220,7 @@ var REFRESH_SKEW_MS = 5 * 6e4;
|
|
|
6184
6220
|
function linearOAuthCredentials() {
|
|
6185
6221
|
const settings = loadAppSettings();
|
|
6186
6222
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
6187
|
-
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() ||
|
|
6223
|
+
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || "";
|
|
6188
6224
|
if (!clientId) {
|
|
6189
6225
|
throw new Error(
|
|
6190
6226
|
`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.`
|
|
@@ -6200,7 +6236,7 @@ function linearAuthorizationHeader(token) {
|
|
|
6200
6236
|
return `Bearer ${t}`;
|
|
6201
6237
|
}
|
|
6202
6238
|
async function exchangeLinearToken(body) {
|
|
6203
|
-
const res = await
|
|
6239
|
+
const res = await httpFetch(LINEAR_TOKEN, {
|
|
6204
6240
|
method: "POST",
|
|
6205
6241
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6206
6242
|
body
|
|
@@ -6267,6 +6303,18 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
6267
6303
|
|
|
6268
6304
|
// src/integrations/linear.ts
|
|
6269
6305
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
6306
|
+
var ISSUE_FIELDS = `
|
|
6307
|
+
id
|
|
6308
|
+
identifier
|
|
6309
|
+
title
|
|
6310
|
+
description
|
|
6311
|
+
url
|
|
6312
|
+
priority
|
|
6313
|
+
state { id name type }
|
|
6314
|
+
assignee { id name }
|
|
6315
|
+
team { id key name states { nodes { id name type } } }
|
|
6316
|
+
labels { nodes { name } }
|
|
6317
|
+
`;
|
|
6270
6318
|
var ASSIGNED_ISSUES_QUERY = `
|
|
6271
6319
|
query SideboardAssignedIssues($first: Int!) {
|
|
6272
6320
|
viewer {
|
|
@@ -6275,53 +6323,280 @@ query SideboardAssignedIssues($first: Int!) {
|
|
|
6275
6323
|
orderBy: updatedAt
|
|
6276
6324
|
filter: { state: { type: { nin: ["completed", "canceled"] } } }
|
|
6277
6325
|
) {
|
|
6278
|
-
nodes {
|
|
6279
|
-
id
|
|
6280
|
-
identifier
|
|
6281
|
-
title
|
|
6282
|
-
url
|
|
6283
|
-
labels { nodes { name } }
|
|
6284
|
-
}
|
|
6326
|
+
nodes { ${ISSUE_FIELDS} }
|
|
6285
6327
|
}
|
|
6286
6328
|
}
|
|
6287
6329
|
}
|
|
6288
6330
|
`;
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6331
|
+
var TEAMS_QUERY = `
|
|
6332
|
+
query SideboardTeams {
|
|
6333
|
+
viewer { id name }
|
|
6334
|
+
teams(first: 50) {
|
|
6335
|
+
nodes {
|
|
6336
|
+
id
|
|
6337
|
+
key
|
|
6338
|
+
name
|
|
6339
|
+
states { nodes { id name type } }
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
`;
|
|
6344
|
+
var ISSUE_QUERY = `
|
|
6345
|
+
query SideboardIssue($id: String!) {
|
|
6346
|
+
issue(id: $id) { ${ISSUE_FIELDS} }
|
|
6347
|
+
}
|
|
6348
|
+
`;
|
|
6349
|
+
var ISSUE_CREATE = `
|
|
6350
|
+
mutation SideboardIssueCreate($input: IssueCreateInput!) {
|
|
6351
|
+
issueCreate(input: $input) {
|
|
6352
|
+
success
|
|
6353
|
+
issue { ${ISSUE_FIELDS} }
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
`;
|
|
6357
|
+
var ISSUE_UPDATE = `
|
|
6358
|
+
mutation SideboardIssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
6359
|
+
issueUpdate(id: $id, input: $input) {
|
|
6360
|
+
success
|
|
6361
|
+
issue { ${ISSUE_FIELDS} }
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
`;
|
|
6365
|
+
var COMMENT_CREATE = `
|
|
6366
|
+
mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
6367
|
+
commentCreate(input: $input) {
|
|
6368
|
+
success
|
|
6369
|
+
comment { id body url }
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
`;
|
|
6373
|
+
function rewriteLinearError(message) {
|
|
6374
|
+
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
6375
|
+
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
6376
|
+
}
|
|
6377
|
+
return message;
|
|
6378
|
+
}
|
|
6379
|
+
async function requireLinearToken(apiKey) {
|
|
6380
|
+
const token = (apiKey ?? await getLinearAuthToken())?.trim();
|
|
6381
|
+
if (!token) {
|
|
6292
6382
|
throw new Error("Linear is not connected \u2014 sign in from Account settings");
|
|
6293
6383
|
}
|
|
6294
|
-
|
|
6295
|
-
|
|
6384
|
+
return token;
|
|
6385
|
+
}
|
|
6386
|
+
async function linearGraphql(query, variables, opts) {
|
|
6387
|
+
const apiKey = await requireLinearToken(opts?.apiKey);
|
|
6388
|
+
const res = await httpFetch(LINEAR_GRAPHQL, {
|
|
6296
6389
|
method: "POST",
|
|
6297
6390
|
headers: {
|
|
6298
6391
|
"Content-Type": "application/json",
|
|
6299
6392
|
Authorization: linearAuthorizationHeader(apiKey)
|
|
6300
6393
|
},
|
|
6301
|
-
body: JSON.stringify({
|
|
6302
|
-
query: ASSIGNED_ISSUES_QUERY,
|
|
6303
|
-
variables: { first }
|
|
6304
|
-
})
|
|
6394
|
+
body: JSON.stringify({ query, variables })
|
|
6305
6395
|
});
|
|
6306
6396
|
if (!res.ok) {
|
|
6307
6397
|
const body = await res.text().catch(() => "");
|
|
6308
6398
|
throw new Error(
|
|
6309
|
-
|
|
6399
|
+
rewriteLinearError(
|
|
6400
|
+
`Linear API error ${res.status}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
6401
|
+
)
|
|
6310
6402
|
);
|
|
6311
6403
|
}
|
|
6312
6404
|
const json = await res.json();
|
|
6313
6405
|
if (json.errors?.length) {
|
|
6314
|
-
throw new Error(
|
|
6406
|
+
throw new Error(
|
|
6407
|
+
rewriteLinearError(json.errors.map((e) => e.message ?? "Linear error").join("; "))
|
|
6408
|
+
);
|
|
6315
6409
|
}
|
|
6316
|
-
|
|
6317
|
-
|
|
6410
|
+
if (json.data == null) {
|
|
6411
|
+
throw new Error("Linear API returned no data");
|
|
6412
|
+
}
|
|
6413
|
+
return json.data;
|
|
6414
|
+
}
|
|
6415
|
+
function mapState(node) {
|
|
6416
|
+
if (!node?.id) return void 0;
|
|
6417
|
+
return {
|
|
6418
|
+
id: String(node.id),
|
|
6419
|
+
name: String(node.name ?? ""),
|
|
6420
|
+
type: String(node.type ?? "")
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
function mapIssue(node) {
|
|
6424
|
+
const team = node.team;
|
|
6425
|
+
return {
|
|
6318
6426
|
id: String(node.id ?? node.identifier ?? ""),
|
|
6319
6427
|
identifier: String(node.identifier ?? node.id ?? ""),
|
|
6320
6428
|
title: String(node.title ?? ""),
|
|
6321
6429
|
url: String(node.url ?? ""),
|
|
6322
|
-
|
|
6430
|
+
description: node.description?.trim() || void 0,
|
|
6431
|
+
priority: typeof node.priority === "number" ? node.priority : void 0,
|
|
6432
|
+
state: mapState(node.state),
|
|
6433
|
+
assignee: node.assignee?.id ? { id: String(node.assignee.id), name: String(node.assignee.name ?? "") } : void 0,
|
|
6434
|
+
team: team?.id ? {
|
|
6435
|
+
id: String(team.id),
|
|
6436
|
+
key: String(team.key ?? ""),
|
|
6437
|
+
name: String(team.name ?? ""),
|
|
6438
|
+
states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
6439
|
+
} : void 0,
|
|
6440
|
+
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
|
|
6441
|
+
};
|
|
6442
|
+
}
|
|
6443
|
+
function toIssueInfo(issue) {
|
|
6444
|
+
return {
|
|
6445
|
+
id: issue.id,
|
|
6446
|
+
identifier: issue.identifier,
|
|
6447
|
+
title: issue.title,
|
|
6448
|
+
url: issue.url,
|
|
6449
|
+
labels: issue.labels,
|
|
6323
6450
|
provider: "linear"
|
|
6324
|
-
}
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6453
|
+
function mapTeam(node) {
|
|
6454
|
+
return {
|
|
6455
|
+
id: String(node.id ?? ""),
|
|
6456
|
+
key: String(node.key ?? ""),
|
|
6457
|
+
name: String(node.name ?? ""),
|
|
6458
|
+
states: (node.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
6459
|
+
};
|
|
6460
|
+
}
|
|
6461
|
+
function resolveLinearTeam(teams, team) {
|
|
6462
|
+
const t = team.trim();
|
|
6463
|
+
if (!t) throw new Error("Linear team is required (id, key, or name from linear_list_teams)");
|
|
6464
|
+
const lower = t.toLowerCase();
|
|
6465
|
+
const found = teams.find(
|
|
6466
|
+
(x) => x.id === t || x.key.toLowerCase() === lower || x.name.toLowerCase() === lower
|
|
6467
|
+
);
|
|
6468
|
+
if (!found) {
|
|
6469
|
+
const keys = teams.map((x) => x.key).filter(Boolean).join(", ") || "(none)";
|
|
6470
|
+
throw new Error(`Linear team not found: ${team}. Known keys: ${keys}`);
|
|
6471
|
+
}
|
|
6472
|
+
return found;
|
|
6473
|
+
}
|
|
6474
|
+
function resolveLinearState(team, state) {
|
|
6475
|
+
const s = state.trim();
|
|
6476
|
+
if (!s) throw new Error("Linear state is empty");
|
|
6477
|
+
const lower = s.toLowerCase();
|
|
6478
|
+
const found = team.states.find((x) => x.id === s) || team.states.find((x) => x.name.toLowerCase() === lower) || team.states.find((x) => x.type.toLowerCase() === lower);
|
|
6479
|
+
if (!found) {
|
|
6480
|
+
const available = team.states.map((x) => `${x.name} (${x.type})`).join(", ") || "(none)";
|
|
6481
|
+
throw new Error(`Linear state not found on ${team.key}: ${state}. Available: ${available}`);
|
|
6482
|
+
}
|
|
6483
|
+
return found;
|
|
6484
|
+
}
|
|
6485
|
+
function normalizePriority(priority) {
|
|
6486
|
+
if (priority == null) return void 0;
|
|
6487
|
+
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
6488
|
+
throw new Error("Linear priority must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low)");
|
|
6489
|
+
}
|
|
6490
|
+
return priority;
|
|
6491
|
+
}
|
|
6492
|
+
async function listLinearIssuesDirect(opts) {
|
|
6493
|
+
const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
|
|
6494
|
+
const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
|
|
6495
|
+
return (json.viewer?.assignedIssues?.nodes ?? []).map((node) => toIssueInfo(mapIssue(node)));
|
|
6496
|
+
}
|
|
6497
|
+
async function listLinearTeams(opts) {
|
|
6498
|
+
const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
|
|
6499
|
+
return {
|
|
6500
|
+
viewer: {
|
|
6501
|
+
id: String(json.viewer?.id ?? ""),
|
|
6502
|
+
name: String(json.viewer?.name ?? "")
|
|
6503
|
+
},
|
|
6504
|
+
teams: (json.teams?.nodes ?? []).map(mapTeam)
|
|
6505
|
+
};
|
|
6506
|
+
}
|
|
6507
|
+
async function getLinearIssue(id, opts) {
|
|
6508
|
+
const issueId = id.trim();
|
|
6509
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
6510
|
+
const json = await linearGraphql(
|
|
6511
|
+
ISSUE_QUERY,
|
|
6512
|
+
{ id: issueId },
|
|
6513
|
+
opts
|
|
6514
|
+
);
|
|
6515
|
+
if (!json.issue) {
|
|
6516
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
6517
|
+
}
|
|
6518
|
+
return mapIssue(json.issue);
|
|
6519
|
+
}
|
|
6520
|
+
async function createLinearIssue(input, opts) {
|
|
6521
|
+
const title = input.title.trim();
|
|
6522
|
+
if (!title) throw new Error("Linear issue title is required");
|
|
6523
|
+
const { viewer, teams } = await listLinearTeams(opts);
|
|
6524
|
+
const team = resolveLinearTeam(teams, input.team);
|
|
6525
|
+
const mutationInput = {
|
|
6526
|
+
teamId: team.id,
|
|
6527
|
+
title
|
|
6528
|
+
};
|
|
6529
|
+
const description = input.description?.trim();
|
|
6530
|
+
if (description) mutationInput.description = description;
|
|
6531
|
+
if (input.state?.trim()) {
|
|
6532
|
+
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
6533
|
+
}
|
|
6534
|
+
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
6535
|
+
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
6536
|
+
else if (assignee) mutationInput.assigneeId = assignee;
|
|
6537
|
+
const priority = normalizePriority(input.priority);
|
|
6538
|
+
if (priority != null) mutationInput.priority = priority;
|
|
6539
|
+
const json = await linearGraphql(ISSUE_CREATE, { input: mutationInput }, opts);
|
|
6540
|
+
if (!json.issueCreate?.success || !json.issueCreate.issue) {
|
|
6541
|
+
throw new Error("Linear issueCreate failed");
|
|
6542
|
+
}
|
|
6543
|
+
return mapIssue(json.issueCreate.issue);
|
|
6544
|
+
}
|
|
6545
|
+
async function updateLinearIssue(input, opts) {
|
|
6546
|
+
const issueId = input.id.trim();
|
|
6547
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
6548
|
+
const mutationInput = {};
|
|
6549
|
+
if (input.title !== void 0) {
|
|
6550
|
+
const title = input.title.trim();
|
|
6551
|
+
if (!title) throw new Error("Linear issue title cannot be empty");
|
|
6552
|
+
mutationInput.title = title;
|
|
6553
|
+
}
|
|
6554
|
+
if (input.description !== void 0) {
|
|
6555
|
+
mutationInput.description = input.description;
|
|
6556
|
+
}
|
|
6557
|
+
if (input.state?.trim()) {
|
|
6558
|
+
const existing = await getLinearIssue(issueId, opts);
|
|
6559
|
+
const team = existing.team;
|
|
6560
|
+
if (!team?.states.length) {
|
|
6561
|
+
throw new Error(`Linear issue ${existing.identifier} has no workflow states to resolve "${input.state}"`);
|
|
6562
|
+
}
|
|
6563
|
+
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
6564
|
+
}
|
|
6565
|
+
if (input.assignee !== void 0) {
|
|
6566
|
+
const assignee = input.assignee?.trim() || null;
|
|
6567
|
+
if (assignee === "me") {
|
|
6568
|
+
const { viewer } = await listLinearTeams(opts);
|
|
6569
|
+
mutationInput.assigneeId = viewer.id;
|
|
6570
|
+
} else {
|
|
6571
|
+
mutationInput.assigneeId = assignee;
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
if (input.priority !== void 0) {
|
|
6575
|
+
mutationInput.priority = normalizePriority(input.priority);
|
|
6576
|
+
}
|
|
6577
|
+
if (Object.keys(mutationInput).length === 0) {
|
|
6578
|
+
throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority");
|
|
6579
|
+
}
|
|
6580
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
6581
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
6582
|
+
throw new Error("Linear issueUpdate failed");
|
|
6583
|
+
}
|
|
6584
|
+
return mapIssue(json.issueUpdate.issue);
|
|
6585
|
+
}
|
|
6586
|
+
async function commentLinearIssue(input, opts) {
|
|
6587
|
+
const issueId = input.id.trim();
|
|
6588
|
+
const body = input.body.trim();
|
|
6589
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
6590
|
+
if (!body) throw new Error("Linear comment body is required");
|
|
6591
|
+
const json = await linearGraphql(COMMENT_CREATE, { input: { issueId, body } }, opts);
|
|
6592
|
+
if (!json.commentCreate?.success || !json.commentCreate.comment?.id) {
|
|
6593
|
+
throw new Error("Linear commentCreate failed");
|
|
6594
|
+
}
|
|
6595
|
+
return {
|
|
6596
|
+
id: String(json.commentCreate.comment.id),
|
|
6597
|
+
body: String(json.commentCreate.comment.body ?? body),
|
|
6598
|
+
url: json.commentCreate.comment.url?.trim() || void 0
|
|
6599
|
+
};
|
|
6325
6600
|
}
|
|
6326
6601
|
|
|
6327
6602
|
// src/integrations/issues.ts
|
|
@@ -6603,8 +6878,8 @@ function labelGithubUrl(url) {
|
|
|
6603
6878
|
}
|
|
6604
6879
|
return { kind: "other", label: "GitHub", url: raw };
|
|
6605
6880
|
}
|
|
6606
|
-
function appendGithubLink(
|
|
6607
|
-
const body =
|
|
6881
|
+
function appendGithubLink(text3, githubUrl) {
|
|
6882
|
+
const body = text3.trimEnd();
|
|
6608
6883
|
if (!githubUrl?.trim()) return body;
|
|
6609
6884
|
const labeled = labelGithubUrl(githubUrl);
|
|
6610
6885
|
if (!labeled) {
|
|
@@ -6887,6 +7162,101 @@ function registerSlackTools(server) {
|
|
|
6887
7162
|
);
|
|
6888
7163
|
}
|
|
6889
7164
|
|
|
7165
|
+
// src/mcp/linear-tools.ts
|
|
7166
|
+
import { z as z2 } from "zod";
|
|
7167
|
+
function text2(payload, isError = false) {
|
|
7168
|
+
return {
|
|
7169
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
7170
|
+
...isError ? { isError: true } : {}
|
|
7171
|
+
};
|
|
7172
|
+
}
|
|
7173
|
+
function fail2(err) {
|
|
7174
|
+
return text2(
|
|
7175
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
7176
|
+
true
|
|
7177
|
+
);
|
|
7178
|
+
}
|
|
7179
|
+
var prioritySchema = z2.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
7180
|
+
function registerLinearTools(server) {
|
|
7181
|
+
server.tool(
|
|
7182
|
+
"linear_list_teams",
|
|
7183
|
+
"List Linear teams (id, key, name) and workflow states for the connected Account. Use team key (e.g. ENG) on linear_create_issue; use state name or type (started, completed) on create/update. Viewer id is for assignee=me.",
|
|
7184
|
+
{},
|
|
7185
|
+
async () => {
|
|
7186
|
+
try {
|
|
7187
|
+
return text2(await listLinearTeams());
|
|
7188
|
+
} catch (err) {
|
|
7189
|
+
return fail2(err);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
);
|
|
7193
|
+
server.tool(
|
|
7194
|
+
"linear_get_issue",
|
|
7195
|
+
"Get a Linear issue by uuid or identifier (ENG-123).",
|
|
7196
|
+
{ id: z2.string() },
|
|
7197
|
+
async ({ id }) => {
|
|
7198
|
+
try {
|
|
7199
|
+
return text2(await getLinearIssue(id));
|
|
7200
|
+
} catch (err) {
|
|
7201
|
+
return fail2(err);
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
7204
|
+
);
|
|
7205
|
+
server.tool(
|
|
7206
|
+
"linear_create_issue",
|
|
7207
|
+
'Create a Linear issue. Call linear_list_teams first. team is id, key, or name. state is name, type (unstarted/started/completed/canceled/backlog), or id. assignee is "me" or a user id.',
|
|
7208
|
+
{
|
|
7209
|
+
team: z2.string(),
|
|
7210
|
+
title: z2.string(),
|
|
7211
|
+
description: z2.string().optional(),
|
|
7212
|
+
state: z2.string().optional(),
|
|
7213
|
+
assignee: z2.string().nullable().optional(),
|
|
7214
|
+
priority: prioritySchema
|
|
7215
|
+
},
|
|
7216
|
+
async (args) => {
|
|
7217
|
+
try {
|
|
7218
|
+
return text2(await createLinearIssue(args));
|
|
7219
|
+
} catch (err) {
|
|
7220
|
+
return fail2(err);
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
);
|
|
7224
|
+
server.tool(
|
|
7225
|
+
"linear_update_issue",
|
|
7226
|
+
'Update a Linear issue (uuid or ENG-123). Pass at least one of title, description, state, assignee, priority. state is name, type, or id. assignee is "me", a user id, or null to unassign.',
|
|
7227
|
+
{
|
|
7228
|
+
id: z2.string(),
|
|
7229
|
+
title: z2.string().optional(),
|
|
7230
|
+
description: z2.string().optional(),
|
|
7231
|
+
state: z2.string().optional(),
|
|
7232
|
+
assignee: z2.string().nullable().optional(),
|
|
7233
|
+
priority: prioritySchema
|
|
7234
|
+
},
|
|
7235
|
+
async (args) => {
|
|
7236
|
+
try {
|
|
7237
|
+
return text2(await updateLinearIssue(args));
|
|
7238
|
+
} catch (err) {
|
|
7239
|
+
return fail2(err);
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
);
|
|
7243
|
+
server.tool(
|
|
7244
|
+
"linear_comment",
|
|
7245
|
+
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
7246
|
+
{
|
|
7247
|
+
id: z2.string(),
|
|
7248
|
+
body: z2.string()
|
|
7249
|
+
},
|
|
7250
|
+
async (args) => {
|
|
7251
|
+
try {
|
|
7252
|
+
return text2(await commentLinearIssue(args));
|
|
7253
|
+
} catch (err) {
|
|
7254
|
+
return fail2(err);
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
);
|
|
7258
|
+
}
|
|
7259
|
+
|
|
6890
7260
|
// src/mcp/server.ts
|
|
6891
7261
|
var MAX_ORCH_THREADS = 5;
|
|
6892
7262
|
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
@@ -6963,7 +7333,7 @@ async function startMcpServer() {
|
|
|
6963
7333
|
server.tool(
|
|
6964
7334
|
"get_thread",
|
|
6965
7335
|
"Get a compact thread summary by id/ref",
|
|
6966
|
-
{ ref:
|
|
7336
|
+
{ ref: z3.string() },
|
|
6967
7337
|
async ({ ref }) => {
|
|
6968
7338
|
const t = orch.getThread(ref);
|
|
6969
7339
|
if (!t) {
|
|
@@ -6992,14 +7362,14 @@ async function startMcpServer() {
|
|
|
6992
7362
|
"present_artifact",
|
|
6993
7363
|
"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.",
|
|
6994
7364
|
{
|
|
6995
|
-
title:
|
|
6996
|
-
type:
|
|
7365
|
+
title: z3.string().describe("Short title shown in the artifact pane header"),
|
|
7366
|
+
type: z3.enum(["html", "svg", "markdown", "react"]).describe(
|
|
6997
7367
|
"Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
|
|
6998
7368
|
),
|
|
6999
|
-
content:
|
|
7369
|
+
content: z3.string().describe(
|
|
7000
7370
|
"Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
|
|
7001
7371
|
),
|
|
7002
|
-
artifact_id:
|
|
7372
|
+
artifact_id: z3.string().optional().describe("Stable id when updating the same artifact across turns")
|
|
7003
7373
|
},
|
|
7004
7374
|
async ({ title, type, artifact_id }) => {
|
|
7005
7375
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7017,15 +7387,15 @@ async function startMcpServer() {
|
|
|
7017
7387
|
"ask_user",
|
|
7018
7388
|
"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.",
|
|
7019
7389
|
{
|
|
7020
|
-
questions:
|
|
7021
|
-
|
|
7022
|
-
question:
|
|
7023
|
-
header:
|
|
7024
|
-
multiSelect:
|
|
7025
|
-
options:
|
|
7026
|
-
|
|
7027
|
-
label:
|
|
7028
|
-
description:
|
|
7390
|
+
questions: z3.array(
|
|
7391
|
+
z3.object({
|
|
7392
|
+
question: z3.string().describe("Full question text ending with ?"),
|
|
7393
|
+
header: z3.string().max(24).optional().describe("Short label shown above the question"),
|
|
7394
|
+
multiSelect: z3.boolean().optional().describe("Allow selecting multiple options"),
|
|
7395
|
+
options: z3.array(
|
|
7396
|
+
z3.object({
|
|
7397
|
+
label: z3.string(),
|
|
7398
|
+
description: z3.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
7029
7399
|
})
|
|
7030
7400
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
7031
7401
|
})
|
|
@@ -7044,9 +7414,9 @@ async function startMcpServer() {
|
|
|
7044
7414
|
"present_plan",
|
|
7045
7415
|
"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.",
|
|
7046
7416
|
{
|
|
7047
|
-
title:
|
|
7048
|
-
content:
|
|
7049
|
-
thread_id:
|
|
7417
|
+
title: z3.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
7418
|
+
content: z3.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
7419
|
+
thread_id: z3.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
7050
7420
|
},
|
|
7051
7421
|
async ({ title, content, thread_id }) => {
|
|
7052
7422
|
const { writePlanFile: writePlanFile2 } = await import("../plan-file-PHVKUAEE.js");
|
|
@@ -7069,15 +7439,15 @@ async function startMcpServer() {
|
|
|
7069
7439
|
"present_schema",
|
|
7070
7440
|
"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.",
|
|
7071
7441
|
{
|
|
7072
|
-
title:
|
|
7073
|
-
mode:
|
|
7074
|
-
datasource:
|
|
7075
|
-
resource_id:
|
|
7076
|
-
record_id:
|
|
7077
|
-
resource:
|
|
7078
|
-
record:
|
|
7079
|
-
records:
|
|
7080
|
-
pane_id:
|
|
7442
|
+
title: z3.string().describe("Pane title"),
|
|
7443
|
+
mode: z3.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
7444
|
+
datasource: z3.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
7445
|
+
resource_id: z3.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
7446
|
+
record_id: z3.string().optional().describe("Record id when opening form mode"),
|
|
7447
|
+
resource: z3.record(z3.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
7448
|
+
record: z3.record(z3.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
7449
|
+
records: z3.array(z3.record(z3.unknown())).optional().describe("Inline records for table mode"),
|
|
7450
|
+
pane_id: z3.string().optional().describe("Stable pane id across updates")
|
|
7081
7451
|
},
|
|
7082
7452
|
async (args) => {
|
|
7083
7453
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7098,10 +7468,10 @@ async function startMcpServer() {
|
|
|
7098
7468
|
"present_files",
|
|
7099
7469
|
"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.",
|
|
7100
7470
|
{
|
|
7101
|
-
title:
|
|
7102
|
-
datasource:
|
|
7103
|
-
path:
|
|
7104
|
-
pane_id:
|
|
7471
|
+
title: z3.string().optional().describe("Pane title (default: Files)"),
|
|
7472
|
+
datasource: z3.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
7473
|
+
path: z3.string().optional().describe("Initial folder path (e.g. public)"),
|
|
7474
|
+
pane_id: z3.string().optional().describe("Stable pane id across updates")
|
|
7105
7475
|
},
|
|
7106
7476
|
async (args) => {
|
|
7107
7477
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7117,19 +7487,21 @@ async function startMcpServer() {
|
|
|
7117
7487
|
}
|
|
7118
7488
|
);
|
|
7119
7489
|
registerSlackTools(server);
|
|
7490
|
+
registerLinearTools(server);
|
|
7120
7491
|
if (sideboardMcpProfile() !== "worktree") {
|
|
7121
|
-
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-
|
|
7492
|
+
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-OHAUWOA6.js");
|
|
7122
7493
|
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults } = await import("../app-settings-P42Z3VOB.js");
|
|
7123
7494
|
const accountDefaults = resolveThreadDefaults();
|
|
7124
7495
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
7125
7496
|
server.tool(
|
|
7126
7497
|
"set_caffeinate",
|
|
7127
|
-
"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.",
|
|
7498
|
+
"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. Closing or archiving this orchestration chat also releases the hold. macOS only.",
|
|
7128
7499
|
{
|
|
7129
|
-
enabled:
|
|
7500
|
+
enabled: z3.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
7130
7501
|
},
|
|
7131
7502
|
async ({ enabled }) => {
|
|
7132
|
-
const
|
|
7503
|
+
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
7504
|
+
const state = setCaffeinateHold(enabled, { threadId });
|
|
7133
7505
|
if (enabled && !state.held) {
|
|
7134
7506
|
return {
|
|
7135
7507
|
content: [
|
|
@@ -7152,7 +7524,7 @@ async function startMcpServer() {
|
|
|
7152
7524
|
text: JSON.stringify({
|
|
7153
7525
|
...getCaffeinateHold(),
|
|
7154
7526
|
ok: true,
|
|
7155
|
-
message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false
|
|
7527
|
+
message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false, the user says they are done, or this orchestration chat is closed." : "Caffeinate hold released. The Mac can sleep (unless Settings caffeinate toggles are on)."
|
|
7156
7528
|
})
|
|
7157
7529
|
}
|
|
7158
7530
|
]
|
|
@@ -7163,15 +7535,15 @@ async function startMcpServer() {
|
|
|
7163
7535
|
"create_thread",
|
|
7164
7536
|
`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.`,
|
|
7165
7537
|
{
|
|
7166
|
-
sourceType:
|
|
7167
|
-
sourceRef:
|
|
7168
|
-
agent:
|
|
7169
|
-
model:
|
|
7538
|
+
sourceType: z3.enum(["branch", "pr", "ticket"]),
|
|
7539
|
+
sourceRef: z3.string(),
|
|
7540
|
+
agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
7541
|
+
model: z3.string().nullable().optional().describe(
|
|
7170
7542
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
7171
7543
|
),
|
|
7172
|
-
repoPath:
|
|
7173
|
-
title:
|
|
7174
|
-
parentThreadId:
|
|
7544
|
+
repoPath: z3.string(),
|
|
7545
|
+
title: z3.string().optional(),
|
|
7546
|
+
parentThreadId: z3.string().optional().describe(
|
|
7175
7547
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
7176
7548
|
)
|
|
7177
7549
|
},
|
|
@@ -7278,9 +7650,9 @@ async function startMcpServer() {
|
|
|
7278
7650
|
"send_to_thread",
|
|
7279
7651
|
"Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
|
|
7280
7652
|
{
|
|
7281
|
-
ref:
|
|
7282
|
-
prompt:
|
|
7283
|
-
force_stop:
|
|
7653
|
+
ref: z3.string(),
|
|
7654
|
+
prompt: z3.string(),
|
|
7655
|
+
force_stop: z3.boolean().optional()
|
|
7284
7656
|
},
|
|
7285
7657
|
async ({ ref, prompt, force_stop }) => {
|
|
7286
7658
|
if (force_stop) {
|
|
@@ -7309,8 +7681,8 @@ async function startMcpServer() {
|
|
|
7309
7681
|
"wait_for_turn",
|
|
7310
7682
|
"Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
|
|
7311
7683
|
{
|
|
7312
|
-
ref:
|
|
7313
|
-
timeoutMs:
|
|
7684
|
+
ref: z3.string(),
|
|
7685
|
+
timeoutMs: z3.number().optional()
|
|
7314
7686
|
},
|
|
7315
7687
|
async ({ ref, timeoutMs }) => {
|
|
7316
7688
|
const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
|
|
@@ -7332,7 +7704,7 @@ async function startMcpServer() {
|
|
|
7332
7704
|
server.tool(
|
|
7333
7705
|
"get_turn_result",
|
|
7334
7706
|
"Final assistant message only (not full transcript)",
|
|
7335
|
-
{ ref:
|
|
7707
|
+
{ ref: z3.string() },
|
|
7336
7708
|
async ({ ref }) => {
|
|
7337
7709
|
const result = orch.getTurnResult(ref);
|
|
7338
7710
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
@@ -7342,8 +7714,8 @@ async function startMcpServer() {
|
|
|
7342
7714
|
"stop_thread",
|
|
7343
7715
|
"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.",
|
|
7344
7716
|
{
|
|
7345
|
-
ref:
|
|
7346
|
-
force:
|
|
7717
|
+
ref: z3.string(),
|
|
7718
|
+
force: z3.boolean().optional()
|
|
7347
7719
|
},
|
|
7348
7720
|
async ({ ref, force }) => {
|
|
7349
7721
|
const t = orch.getThread(ref);
|
|
@@ -7373,7 +7745,7 @@ async function startMcpServer() {
|
|
|
7373
7745
|
server.tool(
|
|
7374
7746
|
"archive_thread",
|
|
7375
7747
|
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
|
|
7376
|
-
{ ref:
|
|
7748
|
+
{ ref: z3.string() },
|
|
7377
7749
|
async ({ ref }) => {
|
|
7378
7750
|
const t = orch.getThread(ref);
|
|
7379
7751
|
if (!t) {
|
|
@@ -7406,7 +7778,7 @@ async function startMcpServer() {
|
|
|
7406
7778
|
server.tool(
|
|
7407
7779
|
"restore_thread",
|
|
7408
7780
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
7409
|
-
{ ref:
|
|
7781
|
+
{ ref: z3.string() },
|
|
7410
7782
|
async ({ ref }) => {
|
|
7411
7783
|
try {
|
|
7412
7784
|
const restored = await orch.restore(ref);
|
|
@@ -7432,8 +7804,8 @@ async function startMcpServer() {
|
|
|
7432
7804
|
"get_diff",
|
|
7433
7805
|
"Compact diff summary (capped hunks, paginated)",
|
|
7434
7806
|
{
|
|
7435
|
-
ref:
|
|
7436
|
-
maxFiles:
|
|
7807
|
+
ref: z3.string(),
|
|
7808
|
+
maxFiles: z3.number().optional()
|
|
7437
7809
|
},
|
|
7438
7810
|
async ({ ref, maxFiles }) => {
|
|
7439
7811
|
const summary = await orch.diffSummary(ref);
|
|
@@ -7453,7 +7825,7 @@ async function startMcpServer() {
|
|
|
7453
7825
|
server.tool(
|
|
7454
7826
|
"request_review",
|
|
7455
7827
|
'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.',
|
|
7456
|
-
{ ref:
|
|
7828
|
+
{ ref: z3.string().describe("Worktree thread id/ref to review") },
|
|
7457
7829
|
async ({ ref }) => {
|
|
7458
7830
|
try {
|
|
7459
7831
|
const tab = await orch.requestReview(ref);
|
|
@@ -7482,8 +7854,8 @@ async function startMcpServer() {
|
|
|
7482
7854
|
"ask_git",
|
|
7483
7855
|
"Tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge the linked PR \u2014 same short prompts as the desktop git buttons. The worktree agent runs git/gh (`gh pr merge`); this only queues the prompt. Pass a worktree thread ref (not the orchestrator). Then wait_for_turn / get_turn_result. Do not run git or gh from the orchestration cwd.",
|
|
7484
7856
|
{
|
|
7485
|
-
ref:
|
|
7486
|
-
action:
|
|
7857
|
+
ref: z3.string().describe("Worktree thread id/ref"),
|
|
7858
|
+
action: z3.enum(AGENT_GIT_ACTIONS).describe(
|
|
7487
7859
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
7488
7860
|
)
|
|
7489
7861
|
},
|
|
@@ -7510,7 +7882,7 @@ async function startMcpServer() {
|
|
|
7510
7882
|
}
|
|
7511
7883
|
}
|
|
7512
7884
|
);
|
|
7513
|
-
const agentEnum =
|
|
7885
|
+
const agentEnum = z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
7514
7886
|
server.tool(
|
|
7515
7887
|
"list_models",
|
|
7516
7888
|
"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.",
|
|
@@ -7533,11 +7905,11 @@ async function startMcpServer() {
|
|
|
7533
7905
|
"fork_worktree",
|
|
7534
7906
|
"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.",
|
|
7535
7907
|
{
|
|
7536
|
-
ref:
|
|
7537
|
-
through_index:
|
|
7908
|
+
ref: z3.string().describe("Worktree thread id/ref to fork"),
|
|
7909
|
+
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
7538
7910
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
7539
|
-
model:
|
|
7540
|
-
title:
|
|
7911
|
+
model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
7912
|
+
title: z3.string().optional()
|
|
7541
7913
|
},
|
|
7542
7914
|
async ({ ref, through_index, agent, model, title }) => {
|
|
7543
7915
|
try {
|
|
@@ -7578,11 +7950,11 @@ async function startMcpServer() {
|
|
|
7578
7950
|
"fork_chat",
|
|
7579
7951
|
"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.",
|
|
7580
7952
|
{
|
|
7581
|
-
ref:
|
|
7582
|
-
through_index:
|
|
7953
|
+
ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
7954
|
+
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
7583
7955
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
7584
|
-
model:
|
|
7585
|
-
title:
|
|
7956
|
+
model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
7957
|
+
title: z3.string().optional()
|
|
7586
7958
|
},
|
|
7587
7959
|
async ({ ref, through_index, agent, model, title }) => {
|
|
7588
7960
|
try {
|
|
@@ -7628,8 +8000,8 @@ async function startMcpServer() {
|
|
|
7628
8000
|
"run_dev_script",
|
|
7629
8001
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
7630
8002
|
{
|
|
7631
|
-
ref:
|
|
7632
|
-
name:
|
|
8003
|
+
ref: z3.string(),
|
|
8004
|
+
name: z3.string().optional()
|
|
7633
8005
|
},
|
|
7634
8006
|
async ({ ref, name }) => {
|
|
7635
8007
|
const result = await orch.startDev(ref, name);
|
|
@@ -7651,7 +8023,7 @@ async function startMcpServer() {
|
|
|
7651
8023
|
server.tool(
|
|
7652
8024
|
"list_run_scripts",
|
|
7653
8025
|
"List named run scripts available for a thread",
|
|
7654
|
-
{ ref:
|
|
8026
|
+
{ ref: z3.string() },
|
|
7655
8027
|
async ({ ref }) => {
|
|
7656
8028
|
const scripts = orch.listThreadRunScripts(ref);
|
|
7657
8029
|
const active = orch.getActiveRuns(ref);
|
|
@@ -7669,8 +8041,8 @@ async function startMcpServer() {
|
|
|
7669
8041
|
"stop_dev_script",
|
|
7670
8042
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
7671
8043
|
{
|
|
7672
|
-
ref:
|
|
7673
|
-
name:
|
|
8044
|
+
ref: z3.string(),
|
|
8045
|
+
name: z3.string().optional()
|
|
7674
8046
|
},
|
|
7675
8047
|
async ({ ref, name }) => {
|
|
7676
8048
|
orch.stopDev(ref, name);
|
|
@@ -7680,7 +8052,7 @@ async function startMcpServer() {
|
|
|
7680
8052
|
server.tool(
|
|
7681
8053
|
"run_setup",
|
|
7682
8054
|
"Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
|
|
7683
|
-
{ ref:
|
|
8055
|
+
{ ref: z3.string() },
|
|
7684
8056
|
async ({ ref }) => {
|
|
7685
8057
|
const result = await orch.runSetup(ref);
|
|
7686
8058
|
return {
|
|
@@ -7691,7 +8063,7 @@ async function startMcpServer() {
|
|
|
7691
8063
|
server.tool(
|
|
7692
8064
|
"add_workspace",
|
|
7693
8065
|
"Register a git repo as a Sideboard workspace",
|
|
7694
|
-
{ repoPath:
|
|
8066
|
+
{ repoPath: z3.string() },
|
|
7695
8067
|
async ({ repoPath }) => {
|
|
7696
8068
|
const ws = await orch.addWorkspace(repoPath);
|
|
7697
8069
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -7700,7 +8072,7 @@ async function startMcpServer() {
|
|
|
7700
8072
|
server.tool(
|
|
7701
8073
|
"remove_workspace",
|
|
7702
8074
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
7703
|
-
{ repoPath:
|
|
8075
|
+
{ repoPath: z3.string() },
|
|
7704
8076
|
async ({ repoPath }) => {
|
|
7705
8077
|
orch.removeWorkspace(repoPath);
|
|
7706
8078
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -7710,12 +8082,12 @@ async function startMcpServer() {
|
|
|
7710
8082
|
"fanout",
|
|
7711
8083
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
7712
8084
|
{
|
|
7713
|
-
prompt:
|
|
7714
|
-
agents:
|
|
7715
|
-
repoPath:
|
|
7716
|
-
sourceType:
|
|
7717
|
-
sourceRef:
|
|
7718
|
-
title:
|
|
8085
|
+
prompt: z3.string(),
|
|
8086
|
+
agents: z3.array(z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
8087
|
+
repoPath: z3.string(),
|
|
8088
|
+
sourceType: z3.enum(["branch", "pr", "ticket"]).optional(),
|
|
8089
|
+
sourceRef: z3.string().optional(),
|
|
8090
|
+
title: z3.string().optional()
|
|
7719
8091
|
},
|
|
7720
8092
|
async (args) => {
|
|
7721
8093
|
const threads = await orch.bestOfN(args);
|
|
@@ -7742,8 +8114,8 @@ async function startMcpServer() {
|
|
|
7742
8114
|
"list_branches",
|
|
7743
8115
|
"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).",
|
|
7744
8116
|
{
|
|
7745
|
-
repoPath:
|
|
7746
|
-
unmergedOnly:
|
|
8117
|
+
repoPath: z3.string(),
|
|
8118
|
+
unmergedOnly: z3.boolean().optional()
|
|
7747
8119
|
},
|
|
7748
8120
|
async ({ repoPath, unmergedOnly }) => {
|
|
7749
8121
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -7763,7 +8135,7 @@ async function startMcpServer() {
|
|
|
7763
8135
|
server.tool(
|
|
7764
8136
|
"list_prs",
|
|
7765
8137
|
"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.",
|
|
7766
|
-
{ repoPath:
|
|
8138
|
+
{ repoPath: z3.string() },
|
|
7767
8139
|
async ({ repoPath }) => {
|
|
7768
8140
|
const root = await resolveRepoRoot(repoPath);
|
|
7769
8141
|
const prs = await listPrs(root);
|
|
@@ -7782,7 +8154,7 @@ async function startMcpServer() {
|
|
|
7782
8154
|
server.tool(
|
|
7783
8155
|
"get_pr_stack",
|
|
7784
8156
|
"Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs.",
|
|
7785
|
-
{ ref:
|
|
8157
|
+
{ ref: z3.string() },
|
|
7786
8158
|
async ({ ref }) => {
|
|
7787
8159
|
const stack = await orch.getPrStack(ref);
|
|
7788
8160
|
return {
|
|
@@ -7794,8 +8166,8 @@ async function startMcpServer() {
|
|
|
7794
8166
|
"open_pr_stack_layers",
|
|
7795
8167
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
7796
8168
|
{
|
|
7797
|
-
ref:
|
|
7798
|
-
layer:
|
|
8169
|
+
ref: z3.string(),
|
|
8170
|
+
layer: z3.number().int().positive().optional()
|
|
7799
8171
|
},
|
|
7800
8172
|
async ({ ref, layer }) => {
|
|
7801
8173
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -7829,9 +8201,9 @@ async function startMcpServer() {
|
|
|
7829
8201
|
"add_stack_layer",
|
|
7830
8202
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
7831
8203
|
{
|
|
7832
|
-
ref:
|
|
7833
|
-
branchName:
|
|
7834
|
-
title:
|
|
8204
|
+
ref: z3.string(),
|
|
8205
|
+
branchName: z3.string(),
|
|
8206
|
+
title: z3.string().optional()
|
|
7835
8207
|
},
|
|
7836
8208
|
async ({ ref, branchName, title }) => {
|
|
7837
8209
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -7860,11 +8232,11 @@ async function startMcpServer() {
|
|
|
7860
8232
|
"create_pr_stack",
|
|
7861
8233
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
7862
8234
|
{
|
|
7863
|
-
repoPath:
|
|
7864
|
-
branches:
|
|
7865
|
-
agent:
|
|
7866
|
-
base:
|
|
7867
|
-
title:
|
|
8235
|
+
repoPath: z3.string(),
|
|
8236
|
+
branches: z3.array(z3.string()).min(1),
|
|
8237
|
+
agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
8238
|
+
base: z3.string().optional(),
|
|
8239
|
+
title: z3.string().optional()
|
|
7868
8240
|
},
|
|
7869
8241
|
async (args) => {
|
|
7870
8242
|
const result = await orch.createPrStack({
|
|
@@ -7902,7 +8274,7 @@ async function startMcpServer() {
|
|
|
7902
8274
|
server.tool(
|
|
7903
8275
|
"list_issues",
|
|
7904
8276
|
"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.",
|
|
7905
|
-
{ repoPath:
|
|
8277
|
+
{ repoPath: z3.string() },
|
|
7906
8278
|
async ({ repoPath }) => {
|
|
7907
8279
|
const root = await resolveRepoRoot(repoPath);
|
|
7908
8280
|
const result = await listIssues(root);
|
|
@@ -7913,8 +8285,8 @@ async function startMcpServer() {
|
|
|
7913
8285
|
"list_linear_issues",
|
|
7914
8286
|
"Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
|
|
7915
8287
|
{
|
|
7916
|
-
agent:
|
|
7917
|
-
repoPath:
|
|
8288
|
+
agent: z3.enum(["claude", "codex", "opencode"]),
|
|
8289
|
+
repoPath: z3.string()
|
|
7918
8290
|
},
|
|
7919
8291
|
async ({ agent, repoPath }) => {
|
|
7920
8292
|
const issues = await listLinearIssues(agent, repoPath);
|