@sideboard-ai/core 0.1.155 → 0.1.157
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/{abletime-JSTJLXAW.js → abletime-BEIDJG7W.js} +5 -1
- package/dist/{abletime-H7LPGIP2.js → abletime-GWZE6OYM.js} +5 -1
- package/dist/agents/cursor-runner.cjs +6 -1
- package/dist/agents/cursor-runner.js +6 -1
- package/dist/{agents-V24DYZI4.js → agents-C55LAEUR.js} +4 -2
- package/dist/{agents-YVW75KBO.js → agents-LCDXN2B2.js} +4 -2
- package/dist/{chunk-RNC3GR6L.js → chunk-4I4VKAPZ.js} +5 -4
- package/dist/{chunk-2F63IMCL.js → chunk-CVR4RJ6G.js} +5 -4
- package/dist/{chunk-JOK4XWBG.js → chunk-DVJBO3M7.js} +87 -3
- package/dist/{chunk-J2OKVOBM.js → chunk-GWEEGE6L.js} +119 -10
- package/dist/{chunk-4THE3EY7.js → chunk-KQ4JOGUH.js} +214 -39
- package/dist/{chunk-FDCFXMDG.js → chunk-N27GVFZY.js} +87 -3
- package/dist/{chunk-46ZTBUVJ.js → chunk-SUCJAWV4.js} +90 -9
- package/dist/{chunk-QZEZXYVV.js → chunk-TIJ6QVX5.js} +305 -33
- package/dist/{coordinator-prompt-7FHKCWXB.js → coordinator-prompt-26IJU2AV.js} +1 -1
- package/dist/{coordinator-prompt-KX2FVVYT.js → coordinator-prompt-AM47SATF.js} +1 -1
- package/dist/{global-workspace-2BRRKSDN.js → global-workspace-5BIW26YR.js} +1 -1
- package/dist/{global-workspace-4LHCZ2DC.js → global-workspace-QV7CC7SK.js} +1 -1
- package/dist/index.cjs +1438 -695
- package/dist/index.d.cts +150 -3
- package/dist/index.d.ts +150 -3
- package/dist/index.js +600 -322
- package/dist/mcp/run-stdio.cjs +1223 -573
- package/dist/mcp/run-stdio.js +501 -199
- package/dist/{orchestrator-HC7XVGJB.js → orchestrator-BUH3LJLL.js} +3 -3
- package/dist/{orchestrator-3FWEKDAQ.js → orchestrator-KQO4MXBI.js} +3 -3
- package/dist/{workspaces-5UNDTN4G.js → workspaces-NYSM2EPX.js} +1 -1
- package/dist/{workspaces-MQYNZXUK.js → workspaces-Q2VCHIAS.js} +1 -1
- package/package.json +1 -1
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -31,15 +31,16 @@ import {
|
|
|
31
31
|
slackTokenFor,
|
|
32
32
|
syncBoardPins,
|
|
33
33
|
updateSchedule
|
|
34
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-SUCJAWV4.js";
|
|
35
35
|
import {
|
|
36
36
|
isInternalAgentStatusText,
|
|
37
37
|
listModelsForAgent,
|
|
38
38
|
sideboardMcpProfile
|
|
39
|
-
} from "../chunk-
|
|
39
|
+
} from "../chunk-KQ4JOGUH.js";
|
|
40
40
|
import "../chunk-LZ6QMEQC.js";
|
|
41
41
|
import "../chunk-S42XV45P.js";
|
|
42
42
|
import {
|
|
43
|
+
commentAbleTimeTask,
|
|
43
44
|
createAbleTimeTask,
|
|
44
45
|
ensureAbleTimeTask,
|
|
45
46
|
getAbleTimeOrientation,
|
|
@@ -48,8 +49,9 @@ import {
|
|
|
48
49
|
listAbleTimeProjects,
|
|
49
50
|
listAbleTimeTasks,
|
|
50
51
|
searchAbleTimeTasks,
|
|
51
|
-
toAbleTimeIssueInfo
|
|
52
|
-
|
|
52
|
+
toAbleTimeIssueInfo,
|
|
53
|
+
updateAbleTimeTask
|
|
54
|
+
} from "../chunk-N27GVFZY.js";
|
|
53
55
|
import {
|
|
54
56
|
httpFetch
|
|
55
57
|
} from "../chunk-6GN5WPYZ.js";
|
|
@@ -58,10 +60,11 @@ import "../chunk-OCQPTUR7.js";
|
|
|
58
60
|
import {
|
|
59
61
|
GLOBAL_WORKSPACE_ID,
|
|
60
62
|
isCloudCoordinatorThread
|
|
61
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-4I4VKAPZ.js";
|
|
62
64
|
import {
|
|
63
65
|
canonicalizeRepoPath,
|
|
64
66
|
formatGhLandError,
|
|
67
|
+
ghRepoSelectArgs,
|
|
65
68
|
listBranches,
|
|
66
69
|
listPrs,
|
|
67
70
|
resolveGithubRepoSlug,
|
|
@@ -96,7 +99,7 @@ import {
|
|
|
96
99
|
// src/mcp/server.ts
|
|
97
100
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
98
101
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
99
|
-
import { z as
|
|
102
|
+
import { z as z6 } from "zod";
|
|
100
103
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
101
104
|
import { basename } from "path";
|
|
102
105
|
|
|
@@ -680,6 +683,11 @@ async function createLinearIssue(input, opts) {
|
|
|
680
683
|
if (input.state?.trim()) {
|
|
681
684
|
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
682
685
|
}
|
|
686
|
+
const parentRef = input.parent?.trim();
|
|
687
|
+
if (parentRef) {
|
|
688
|
+
const parent = await getLinearIssue(parentRef, opts);
|
|
689
|
+
mutationInput.parentId = parent.id;
|
|
690
|
+
}
|
|
683
691
|
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
684
692
|
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
685
693
|
else if (assignee) mutationInput.assigneeId = assignee;
|
|
@@ -948,9 +956,9 @@ function mcpWaitFinishedHint(status) {
|
|
|
948
956
|
function lastMessagePreview(messages, max = 160) {
|
|
949
957
|
if (!messages?.length) return null;
|
|
950
958
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
951
|
-
const
|
|
952
|
-
if (!
|
|
953
|
-
const flat =
|
|
959
|
+
const text6 = messages[i]?.text?.trim();
|
|
960
|
+
if (!text6 || isInternalAgentStatusText(text6)) continue;
|
|
961
|
+
const flat = text6.replace(/\s+/g, " ");
|
|
954
962
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
955
963
|
}
|
|
956
964
|
return null;
|
|
@@ -1182,8 +1190,8 @@ function labelGithubUrl(url) {
|
|
|
1182
1190
|
}
|
|
1183
1191
|
return { kind: "other", label: "GitHub", url: raw };
|
|
1184
1192
|
}
|
|
1185
|
-
function appendGithubLink(
|
|
1186
|
-
const body =
|
|
1193
|
+
function appendGithubLink(text6, githubUrl) {
|
|
1194
|
+
const body = text6.trimEnd();
|
|
1187
1195
|
if (!githubUrl?.trim()) return body;
|
|
1188
1196
|
const labeled = labelGithubUrl(githubUrl);
|
|
1189
1197
|
if (!labeled) {
|
|
@@ -1579,11 +1587,51 @@ function registerAbleTimeTools(server) {
|
|
|
1579
1587
|
);
|
|
1580
1588
|
server.tool(
|
|
1581
1589
|
"abletime_get_task",
|
|
1582
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232).",
|
|
1590
|
+
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments.",
|
|
1583
1591
|
{ id: z2.string() },
|
|
1584
1592
|
async ({ id }) => {
|
|
1585
1593
|
try {
|
|
1586
|
-
|
|
1594
|
+
const task = await getAbleTimeTask(id);
|
|
1595
|
+
return text2({
|
|
1596
|
+
...toAbleTimeIssueInfo(task),
|
|
1597
|
+
description: task.description,
|
|
1598
|
+
state: task.state,
|
|
1599
|
+
comments: task.comments
|
|
1600
|
+
});
|
|
1601
|
+
} catch (err) {
|
|
1602
|
+
return fail2(err);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1606
|
+
server.tool(
|
|
1607
|
+
"abletime_comment",
|
|
1608
|
+
"Add a markdown comment on an AbleTime task (id or CRM-232).",
|
|
1609
|
+
{ id: z2.string(), body: z2.string() },
|
|
1610
|
+
async (args) => {
|
|
1611
|
+
try {
|
|
1612
|
+
return text2(await commentAbleTimeTask(args));
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
return fail2(err);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
);
|
|
1618
|
+
server.tool(
|
|
1619
|
+
"abletime_update_task",
|
|
1620
|
+
"Update an AbleTime task (id or CRM-232). Pass title, description, and/or state.",
|
|
1621
|
+
{
|
|
1622
|
+
id: z2.string(),
|
|
1623
|
+
title: z2.string().optional(),
|
|
1624
|
+
description: z2.string().optional(),
|
|
1625
|
+
state: z2.string().optional()
|
|
1626
|
+
},
|
|
1627
|
+
async (args) => {
|
|
1628
|
+
try {
|
|
1629
|
+
const task = await updateAbleTimeTask(args);
|
|
1630
|
+
return text2({
|
|
1631
|
+
...toAbleTimeIssueInfo(task),
|
|
1632
|
+
description: task.description,
|
|
1633
|
+
state: task.state
|
|
1634
|
+
});
|
|
1587
1635
|
} catch (err) {
|
|
1588
1636
|
return fail2(err);
|
|
1589
1637
|
}
|
|
@@ -1591,13 +1639,14 @@ function registerAbleTimeTools(server) {
|
|
|
1591
1639
|
);
|
|
1592
1640
|
server.tool(
|
|
1593
1641
|
"abletime_create_task",
|
|
1594
|
-
"Create an AbleTime task in a project. Call abletime_list_projects if you do not have a project id. New tasks start in todo (or backlog).",
|
|
1642
|
+
"Create an AbleTime task in a project. Call abletime_list_projects if you do not have a project id. Pass parent (CRM-232) for a spin-off. New tasks start in todo (or backlog).",
|
|
1595
1643
|
{
|
|
1596
1644
|
title: z2.string(),
|
|
1597
1645
|
description: z2.string().optional(),
|
|
1598
1646
|
projectId: z2.string().optional(),
|
|
1599
1647
|
categoryId: z2.string().optional(),
|
|
1600
|
-
state: z2.enum(["backlog", "todo"]).optional()
|
|
1648
|
+
state: z2.enum(["backlog", "todo"]).optional(),
|
|
1649
|
+
parent: z2.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
1601
1650
|
},
|
|
1602
1651
|
async (args) => {
|
|
1603
1652
|
try {
|
|
@@ -1627,8 +1676,182 @@ function registerAbleTimeTools(server) {
|
|
|
1627
1676
|
);
|
|
1628
1677
|
}
|
|
1629
1678
|
|
|
1630
|
-
// src/mcp/
|
|
1679
|
+
// src/mcp/github-tools.ts
|
|
1631
1680
|
import { z as z3 } from "zod";
|
|
1681
|
+
|
|
1682
|
+
// src/integrations/github-issues.ts
|
|
1683
|
+
function requireGhOk(result, label) {
|
|
1684
|
+
if (result.exitCode !== 0) {
|
|
1685
|
+
const detail = (result.stderr || result.stdout).trim() || "gh failed";
|
|
1686
|
+
throw new Error(`${label}: ${detail}`);
|
|
1687
|
+
}
|
|
1688
|
+
return result.stdout;
|
|
1689
|
+
}
|
|
1690
|
+
function parseGitHubIssueNumber(id) {
|
|
1691
|
+
const trimmed = id.trim();
|
|
1692
|
+
if (!trimmed) throw new Error("GitHub issue id is required (#123 or a URL)");
|
|
1693
|
+
const url = trimmed.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/i);
|
|
1694
|
+
if (url) return Number(url[1]);
|
|
1695
|
+
const prefixed = trimmed.match(/^gh-(\d+)$/i);
|
|
1696
|
+
if (prefixed) return Number(prefixed[1]);
|
|
1697
|
+
const bare = trimmed.match(/^#?(\d+)$/);
|
|
1698
|
+
if (bare) return Number(bare[1]);
|
|
1699
|
+
throw new Error(`GitHub issue id must be #123, a number, or an issue URL (got ${trimmed})`);
|
|
1700
|
+
}
|
|
1701
|
+
async function resolveGitHubIssueRepo(repoPath) {
|
|
1702
|
+
const cwd = await resolveRepoRoot((repoPath ?? "").trim() || process.cwd());
|
|
1703
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1704
|
+
return { cwd, slug, repoArgs: slug ? ghRepoSelectArgs(slug) : [] };
|
|
1705
|
+
}
|
|
1706
|
+
function mapLabels(raw) {
|
|
1707
|
+
if (!Array.isArray(raw)) return [];
|
|
1708
|
+
return raw.map((item) => typeof item === "string" ? item : String(item?.name ?? "")).map((name) => name.trim()).filter(Boolean);
|
|
1709
|
+
}
|
|
1710
|
+
function mapAssignees(raw) {
|
|
1711
|
+
if (!Array.isArray(raw)) return [];
|
|
1712
|
+
return raw.map(
|
|
1713
|
+
(item) => typeof item === "string" ? item : String(item?.login ?? "")
|
|
1714
|
+
).map((login) => login.trim()).filter(Boolean);
|
|
1715
|
+
}
|
|
1716
|
+
function mapComments2(raw) {
|
|
1717
|
+
if (!Array.isArray(raw)) return [];
|
|
1718
|
+
return raw.map((item) => {
|
|
1719
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
1720
|
+
if (!rec) return null;
|
|
1721
|
+
const body = typeof rec.body === "string" ? rec.body : "";
|
|
1722
|
+
const authorRec = rec.author && typeof rec.author === "object" ? rec.author : null;
|
|
1723
|
+
const comment = {
|
|
1724
|
+
body,
|
|
1725
|
+
id: rec.id != null ? String(rec.id) : void 0,
|
|
1726
|
+
url: typeof rec.url === "string" ? rec.url : void 0,
|
|
1727
|
+
createdAt: typeof rec.createdAt === "string" ? rec.createdAt : void 0,
|
|
1728
|
+
author: authorRec?.login?.trim() || void 0
|
|
1729
|
+
};
|
|
1730
|
+
return comment;
|
|
1731
|
+
}).filter((item) => Boolean(item));
|
|
1732
|
+
}
|
|
1733
|
+
function toGitHubIssue(raw) {
|
|
1734
|
+
const number = Number(raw.number);
|
|
1735
|
+
if (!Number.isFinite(number) || number <= 0) {
|
|
1736
|
+
throw new Error("GitHub issue response was missing a number");
|
|
1737
|
+
}
|
|
1738
|
+
const assignees = mapAssignees(raw.assignees);
|
|
1739
|
+
return {
|
|
1740
|
+
id: `gh-${number}`,
|
|
1741
|
+
identifier: `#${number}`,
|
|
1742
|
+
number,
|
|
1743
|
+
title: String(raw.title ?? ""),
|
|
1744
|
+
url: String(raw.url ?? ""),
|
|
1745
|
+
body: typeof raw.body === "string" && raw.body.trim() ? raw.body : void 0,
|
|
1746
|
+
state: typeof raw.state === "string" ? raw.state : void 0,
|
|
1747
|
+
labels: mapLabels(raw.labels),
|
|
1748
|
+
assignees,
|
|
1749
|
+
comments: mapComments2(raw.comments)
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
async function getGitHubIssue(id, opts) {
|
|
1753
|
+
const number = parseGitHubIssueNumber(id);
|
|
1754
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1755
|
+
const stdout = requireGhOk(
|
|
1756
|
+
await gh(
|
|
1757
|
+
[
|
|
1758
|
+
"issue",
|
|
1759
|
+
"view",
|
|
1760
|
+
String(number),
|
|
1761
|
+
...repoArgs,
|
|
1762
|
+
"--json",
|
|
1763
|
+
"number,title,body,url,state,labels,assignees,comments,author"
|
|
1764
|
+
],
|
|
1765
|
+
cwd,
|
|
1766
|
+
{ reject: false }
|
|
1767
|
+
),
|
|
1768
|
+
`GitHub issue ${number}`
|
|
1769
|
+
);
|
|
1770
|
+
let parsed;
|
|
1771
|
+
try {
|
|
1772
|
+
parsed = JSON.parse(stdout);
|
|
1773
|
+
} catch {
|
|
1774
|
+
throw new Error(`GitHub issue ${number}: gh returned non-JSON`);
|
|
1775
|
+
}
|
|
1776
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1777
|
+
throw new Error(`GitHub issue not found: #${number}`);
|
|
1778
|
+
}
|
|
1779
|
+
return toGitHubIssue(parsed);
|
|
1780
|
+
}
|
|
1781
|
+
async function commentGitHubIssue(input, opts) {
|
|
1782
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1783
|
+
const body = input.body.trim();
|
|
1784
|
+
if (!body) throw new Error("GitHub comment body is required");
|
|
1785
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1786
|
+
const stdout = requireGhOk(
|
|
1787
|
+
await gh(
|
|
1788
|
+
["issue", "comment", String(number), ...repoArgs, "--body", body],
|
|
1789
|
+
cwd,
|
|
1790
|
+
{ reject: false }
|
|
1791
|
+
),
|
|
1792
|
+
`GitHub comment on #${number}`
|
|
1793
|
+
);
|
|
1794
|
+
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
1795
|
+
return { body, url };
|
|
1796
|
+
}
|
|
1797
|
+
async function updateGitHubIssue(input, opts) {
|
|
1798
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1799
|
+
const title = input.title?.trim();
|
|
1800
|
+
const body = input.body;
|
|
1801
|
+
const state = input.state?.trim().toLowerCase();
|
|
1802
|
+
if (!title && body === void 0 && !state) {
|
|
1803
|
+
throw new Error("github_update_issue needs at least one of title, body, state");
|
|
1804
|
+
}
|
|
1805
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1806
|
+
if (state === "closed" || state === "close") {
|
|
1807
|
+
requireGhOk(
|
|
1808
|
+
await gh(["issue", "close", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1809
|
+
`GitHub close #${number}`
|
|
1810
|
+
);
|
|
1811
|
+
} else if (state === "open" || state === "reopen") {
|
|
1812
|
+
requireGhOk(
|
|
1813
|
+
await gh(["issue", "reopen", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1814
|
+
`GitHub reopen #${number}`
|
|
1815
|
+
);
|
|
1816
|
+
} else if (state) {
|
|
1817
|
+
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
1818
|
+
}
|
|
1819
|
+
if (title || body !== void 0) {
|
|
1820
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
1821
|
+
if (title) args.push("--title", title);
|
|
1822
|
+
if (body !== void 0) args.push("--body", body);
|
|
1823
|
+
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
1824
|
+
}
|
|
1825
|
+
return getGitHubIssue(String(number), opts);
|
|
1826
|
+
}
|
|
1827
|
+
async function createGitHubIssue(input, opts) {
|
|
1828
|
+
const title = input.title.trim();
|
|
1829
|
+
if (!title) throw new Error("GitHub issue title is required");
|
|
1830
|
+
const parent = input.parent?.trim();
|
|
1831
|
+
const parentNumber = parent ? parseGitHubIssueNumber(parent) : null;
|
|
1832
|
+
const bodyParts = [
|
|
1833
|
+
parentNumber ? `Spin-off of #${parentNumber}.` : null,
|
|
1834
|
+
input.body?.trim() || null
|
|
1835
|
+
].filter(Boolean);
|
|
1836
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1837
|
+
const args = ["issue", "create", ...repoArgs, "--title", title];
|
|
1838
|
+
if (bodyParts.length) args.push("--body", bodyParts.join("\n\n"));
|
|
1839
|
+
const created = await gh([...args, "--json", "number,url,title"], cwd, { reject: false });
|
|
1840
|
+
if (created.exitCode === 0 && created.stdout.trim()) {
|
|
1841
|
+
try {
|
|
1842
|
+
const parsed = JSON.parse(created.stdout);
|
|
1843
|
+
if (parsed.number) return getGitHubIssue(String(parsed.number), opts);
|
|
1844
|
+
} catch {
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
const fallback = created.exitCode === 0 ? created : await gh(args, cwd, { reject: false });
|
|
1848
|
+
const stdout = requireGhOk(fallback, "GitHub create issue");
|
|
1849
|
+
const url = stdout.trim().match(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/i);
|
|
1850
|
+
if (url?.[1]) return getGitHubIssue(url[1], opts);
|
|
1851
|
+
throw new Error(`GitHub create issue: could not parse issue from ${stdout.trim() || "empty output"}`);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/mcp/github-tools.ts
|
|
1632
1855
|
function text3(payload, isError = false) {
|
|
1633
1856
|
return mcpJson(payload, isError);
|
|
1634
1857
|
}
|
|
@@ -1638,7 +1861,81 @@ function fail3(err) {
|
|
|
1638
1861
|
true
|
|
1639
1862
|
);
|
|
1640
1863
|
}
|
|
1641
|
-
var
|
|
1864
|
+
var repoPathSchema = z3.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
1865
|
+
function registerGithubIssueTools(server) {
|
|
1866
|
+
server.tool(
|
|
1867
|
+
"github_get_issue",
|
|
1868
|
+
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Uses Account gh.",
|
|
1869
|
+
{ id: z3.string(), repoPath: repoPathSchema },
|
|
1870
|
+
async ({ id, repoPath }) => {
|
|
1871
|
+
try {
|
|
1872
|
+
return text3(await getGitHubIssue(id, { repoPath }));
|
|
1873
|
+
} catch (err) {
|
|
1874
|
+
return fail3(err);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
);
|
|
1878
|
+
server.tool(
|
|
1879
|
+
"github_comment",
|
|
1880
|
+
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh.",
|
|
1881
|
+
{ id: z3.string(), body: z3.string(), repoPath: repoPathSchema },
|
|
1882
|
+
async ({ id, body, repoPath }) => {
|
|
1883
|
+
try {
|
|
1884
|
+
return text3(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
1885
|
+
} catch (err) {
|
|
1886
|
+
return fail3(err);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
);
|
|
1890
|
+
server.tool(
|
|
1891
|
+
"github_update_issue",
|
|
1892
|
+
"Update a GitHub issue (#123). Pass title, body, and/or state (open|closed).",
|
|
1893
|
+
{
|
|
1894
|
+
id: z3.string(),
|
|
1895
|
+
title: z3.string().optional(),
|
|
1896
|
+
body: z3.string().optional(),
|
|
1897
|
+
state: z3.string().optional().describe("open or closed"),
|
|
1898
|
+
repoPath: repoPathSchema
|
|
1899
|
+
},
|
|
1900
|
+
async (args) => {
|
|
1901
|
+
try {
|
|
1902
|
+
return text3(await updateGitHubIssue(args, { repoPath: args.repoPath }));
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
return fail3(err);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
);
|
|
1908
|
+
server.tool(
|
|
1909
|
+
"github_create_issue",
|
|
1910
|
+
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
1911
|
+
{
|
|
1912
|
+
title: z3.string(),
|
|
1913
|
+
body: z3.string().optional(),
|
|
1914
|
+
parent: z3.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
1915
|
+
repoPath: repoPathSchema
|
|
1916
|
+
},
|
|
1917
|
+
async (args) => {
|
|
1918
|
+
try {
|
|
1919
|
+
return text3(await createGitHubIssue(args, { repoPath: args.repoPath }));
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
return fail3(err);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// src/mcp/linear-tools.ts
|
|
1928
|
+
import { z as z4 } from "zod";
|
|
1929
|
+
function text4(payload, isError = false) {
|
|
1930
|
+
return mcpJson(payload, isError);
|
|
1931
|
+
}
|
|
1932
|
+
function fail4(err) {
|
|
1933
|
+
return text4(
|
|
1934
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
1935
|
+
true
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
var prioritySchema = z4.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
1642
1939
|
function registerLinearTools(server) {
|
|
1643
1940
|
server.tool(
|
|
1644
1941
|
"linear_list_teams",
|
|
@@ -1646,9 +1943,9 @@ function registerLinearTools(server) {
|
|
|
1646
1943
|
{},
|
|
1647
1944
|
async () => {
|
|
1648
1945
|
try {
|
|
1649
|
-
return
|
|
1946
|
+
return text4(await listLinearTeams());
|
|
1650
1947
|
} catch (err) {
|
|
1651
|
-
return
|
|
1948
|
+
return fail4(err);
|
|
1652
1949
|
}
|
|
1653
1950
|
}
|
|
1654
1951
|
);
|
|
@@ -1656,9 +1953,9 @@ function registerLinearTools(server) {
|
|
|
1656
1953
|
"linear_search_issues",
|
|
1657
1954
|
"Search or list open Linear issues. Default 40; pass query and/or limit (max 250) when truncated. assignee: me, unassigned, all (default with query), or a user id/name.",
|
|
1658
1955
|
{
|
|
1659
|
-
query:
|
|
1660
|
-
assignee:
|
|
1661
|
-
limit:
|
|
1956
|
+
query: z4.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
1957
|
+
assignee: z4.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
1958
|
+
limit: z4.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
1662
1959
|
},
|
|
1663
1960
|
async ({ query, assignee, limit }) => {
|
|
1664
1961
|
try {
|
|
@@ -1679,38 +1976,39 @@ function registerLinearTools(server) {
|
|
|
1679
1976
|
})
|
|
1680
1977
|
);
|
|
1681
1978
|
} catch (err) {
|
|
1682
|
-
return
|
|
1979
|
+
return fail4(err);
|
|
1683
1980
|
}
|
|
1684
1981
|
}
|
|
1685
1982
|
);
|
|
1686
1983
|
server.tool(
|
|
1687
1984
|
"linear_get_issue",
|
|
1688
1985
|
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
1689
|
-
{ id:
|
|
1986
|
+
{ id: z4.string() },
|
|
1690
1987
|
async ({ id }) => {
|
|
1691
1988
|
try {
|
|
1692
|
-
return
|
|
1989
|
+
return text4(await getLinearIssue(id));
|
|
1693
1990
|
} catch (err) {
|
|
1694
|
-
return
|
|
1991
|
+
return fail4(err);
|
|
1695
1992
|
}
|
|
1696
1993
|
}
|
|
1697
1994
|
);
|
|
1698
1995
|
server.tool(
|
|
1699
1996
|
"linear_create_issue",
|
|
1700
|
-
'Create a Linear issue. Call linear_list_teams first. team is id/key/name; state is name/type/id; assignee is "me" or a user id.',
|
|
1997
|
+
'Create a Linear issue. Call linear_list_teams first. team is id/key/name; state is name/type/id; assignee is "me" or a user id. Pass parent (ENG-123 or uuid) to nest a spin-off under the current ticket.',
|
|
1701
1998
|
{
|
|
1702
|
-
team:
|
|
1703
|
-
title:
|
|
1704
|
-
description:
|
|
1705
|
-
state:
|
|
1706
|
-
assignee:
|
|
1707
|
-
priority: prioritySchema
|
|
1999
|
+
team: z4.string(),
|
|
2000
|
+
title: z4.string(),
|
|
2001
|
+
description: z4.string().optional(),
|
|
2002
|
+
state: z4.string().optional(),
|
|
2003
|
+
assignee: z4.string().nullable().optional(),
|
|
2004
|
+
priority: prioritySchema,
|
|
2005
|
+
parent: z4.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
1708
2006
|
},
|
|
1709
2007
|
async (args) => {
|
|
1710
2008
|
try {
|
|
1711
|
-
return
|
|
2009
|
+
return text4(await createLinearIssue(args));
|
|
1712
2010
|
} catch (err) {
|
|
1713
|
-
return
|
|
2011
|
+
return fail4(err);
|
|
1714
2012
|
}
|
|
1715
2013
|
}
|
|
1716
2014
|
);
|
|
@@ -1718,18 +2016,18 @@ function registerLinearTools(server) {
|
|
|
1718
2016
|
"linear_update_issue",
|
|
1719
2017
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
1720
2018
|
{
|
|
1721
|
-
id:
|
|
1722
|
-
title:
|
|
1723
|
-
description:
|
|
1724
|
-
state:
|
|
1725
|
-
assignee:
|
|
2019
|
+
id: z4.string(),
|
|
2020
|
+
title: z4.string().optional(),
|
|
2021
|
+
description: z4.string().optional(),
|
|
2022
|
+
state: z4.string().optional(),
|
|
2023
|
+
assignee: z4.string().nullable().optional(),
|
|
1726
2024
|
priority: prioritySchema
|
|
1727
2025
|
},
|
|
1728
2026
|
async (args) => {
|
|
1729
2027
|
try {
|
|
1730
|
-
return
|
|
2028
|
+
return text4(await updateLinearIssue(args));
|
|
1731
2029
|
} catch (err) {
|
|
1732
|
-
return
|
|
2030
|
+
return fail4(err);
|
|
1733
2031
|
}
|
|
1734
2032
|
}
|
|
1735
2033
|
);
|
|
@@ -1737,14 +2035,14 @@ function registerLinearTools(server) {
|
|
|
1737
2035
|
"linear_comment",
|
|
1738
2036
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
1739
2037
|
{
|
|
1740
|
-
id:
|
|
1741
|
-
body:
|
|
2038
|
+
id: z4.string(),
|
|
2039
|
+
body: z4.string()
|
|
1742
2040
|
},
|
|
1743
2041
|
async (args) => {
|
|
1744
2042
|
try {
|
|
1745
|
-
return
|
|
2043
|
+
return text4(await commentLinearIssue(args));
|
|
1746
2044
|
} catch (err) {
|
|
1747
|
-
return
|
|
2045
|
+
return fail4(err);
|
|
1748
2046
|
}
|
|
1749
2047
|
}
|
|
1750
2048
|
);
|
|
@@ -1752,6 +2050,7 @@ function registerLinearTools(server) {
|
|
|
1752
2050
|
|
|
1753
2051
|
// src/mcp/issue-vendor-tools.ts
|
|
1754
2052
|
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
2053
|
+
registerGithubIssueTools(server);
|
|
1755
2054
|
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
1756
2055
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
1757
2056
|
}
|
|
@@ -1791,15 +2090,15 @@ function formatMcpPrList(input) {
|
|
|
1791
2090
|
}
|
|
1792
2091
|
|
|
1793
2092
|
// src/mcp/schedule-tools.ts
|
|
1794
|
-
import { z as
|
|
1795
|
-
function
|
|
2093
|
+
import { z as z5 } from "zod";
|
|
2094
|
+
function text5(payload, isError = false) {
|
|
1796
2095
|
return {
|
|
1797
2096
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
1798
2097
|
...isError ? { isError: true } : {}
|
|
1799
2098
|
};
|
|
1800
2099
|
}
|
|
1801
|
-
function
|
|
1802
|
-
return
|
|
2100
|
+
function fail5(err) {
|
|
2101
|
+
return text5(
|
|
1803
2102
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
1804
2103
|
true
|
|
1805
2104
|
);
|
|
@@ -1824,9 +2123,9 @@ function registerScheduleTools(server) {
|
|
|
1824
2123
|
{},
|
|
1825
2124
|
async () => {
|
|
1826
2125
|
try {
|
|
1827
|
-
return
|
|
2126
|
+
return text5({ schedules: listSchedules() });
|
|
1828
2127
|
} catch (err) {
|
|
1829
|
-
return
|
|
2128
|
+
return fail5(err);
|
|
1830
2129
|
}
|
|
1831
2130
|
}
|
|
1832
2131
|
);
|
|
@@ -1834,24 +2133,24 @@ function registerScheduleTools(server) {
|
|
|
1834
2133
|
"create_schedule",
|
|
1835
2134
|
"Create a local schedule that, when due, sends a prompt to an orchestration chat (threadId) or starts a new Global orchestration chat (omit threadId). Pass threadId=self to continue this coordinator. Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without threadId open a new chat each run. Overnight/unattended runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate. Sideboard.app must be running for the job to fire.",
|
|
1836
2135
|
{
|
|
1837
|
-
prompt:
|
|
1838
|
-
name:
|
|
1839
|
-
at:
|
|
1840
|
-
every:
|
|
1841
|
-
cron:
|
|
1842
|
-
tz:
|
|
1843
|
-
threadId:
|
|
2136
|
+
prompt: z5.string().describe("User message / goal queued when the schedule fires"),
|
|
2137
|
+
name: z5.string().optional(),
|
|
2138
|
+
at: z5.string().optional().describe("ISO datetime for a one-shot"),
|
|
2139
|
+
every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
2140
|
+
cron: z5.string().optional().describe("5-field cron expression"),
|
|
2141
|
+
tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
2142
|
+
threadId: z5.string().optional().describe(
|
|
1844
2143
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
1845
2144
|
),
|
|
1846
|
-
agent:
|
|
1847
|
-
model:
|
|
2145
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
2146
|
+
model: z5.string().optional()
|
|
1848
2147
|
},
|
|
1849
2148
|
async (args) => {
|
|
1850
2149
|
try {
|
|
1851
2150
|
const when = parseWhen(args);
|
|
1852
2151
|
const threadId = resolveScheduleThreadId(args.threadId);
|
|
1853
2152
|
if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
|
|
1854
|
-
return
|
|
2153
|
+
return fail5(
|
|
1855
2154
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
1856
2155
|
);
|
|
1857
2156
|
}
|
|
@@ -1864,12 +2163,12 @@ function registerScheduleTools(server) {
|
|
|
1864
2163
|
model: args.model,
|
|
1865
2164
|
createdBy: "mcp"
|
|
1866
2165
|
});
|
|
1867
|
-
return
|
|
2166
|
+
return text5({
|
|
1868
2167
|
schedule,
|
|
1869
2168
|
hint: "Fires while Sideboard.app is running. Recurring jobs without threadId create a new orchestration chat each run. Overnight runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate."
|
|
1870
2169
|
});
|
|
1871
2170
|
} catch (err) {
|
|
1872
|
-
return
|
|
2171
|
+
return fail5(err);
|
|
1873
2172
|
}
|
|
1874
2173
|
}
|
|
1875
2174
|
);
|
|
@@ -1877,17 +2176,17 @@ function registerScheduleTools(server) {
|
|
|
1877
2176
|
"update_schedule",
|
|
1878
2177
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
1879
2178
|
{
|
|
1880
|
-
id:
|
|
1881
|
-
prompt:
|
|
1882
|
-
name:
|
|
1883
|
-
at:
|
|
1884
|
-
every:
|
|
1885
|
-
cron:
|
|
1886
|
-
tz:
|
|
1887
|
-
threadId:
|
|
1888
|
-
agent:
|
|
1889
|
-
model:
|
|
1890
|
-
enabled:
|
|
2179
|
+
id: z5.string(),
|
|
2180
|
+
prompt: z5.string().optional(),
|
|
2181
|
+
name: z5.string().optional(),
|
|
2182
|
+
at: z5.string().optional(),
|
|
2183
|
+
every: z5.string().optional(),
|
|
2184
|
+
cron: z5.string().optional(),
|
|
2185
|
+
tz: z5.string().optional(),
|
|
2186
|
+
threadId: z5.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
2187
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
2188
|
+
model: z5.string().optional(),
|
|
2189
|
+
enabled: z5.boolean().optional()
|
|
1891
2190
|
},
|
|
1892
2191
|
async (args) => {
|
|
1893
2192
|
try {
|
|
@@ -1902,7 +2201,7 @@ function registerScheduleTools(server) {
|
|
|
1902
2201
|
if (args.threadId !== void 0) {
|
|
1903
2202
|
threadId = resolveScheduleThreadId(args.threadId);
|
|
1904
2203
|
if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
|
|
1905
|
-
return
|
|
2204
|
+
return fail5(
|
|
1906
2205
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
1907
2206
|
);
|
|
1908
2207
|
}
|
|
@@ -1916,38 +2215,38 @@ function registerScheduleTools(server) {
|
|
|
1916
2215
|
enabled: args.enabled,
|
|
1917
2216
|
model: args.model
|
|
1918
2217
|
});
|
|
1919
|
-
return
|
|
2218
|
+
return text5({ schedule });
|
|
1920
2219
|
} catch (err) {
|
|
1921
|
-
return
|
|
2220
|
+
return fail5(err);
|
|
1922
2221
|
}
|
|
1923
2222
|
}
|
|
1924
2223
|
);
|
|
1925
2224
|
server.tool(
|
|
1926
2225
|
"delete_schedule",
|
|
1927
2226
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
1928
|
-
{ id:
|
|
2227
|
+
{ id: z5.string() },
|
|
1929
2228
|
async ({ id }) => {
|
|
1930
2229
|
try {
|
|
1931
2230
|
deleteSchedule(id);
|
|
1932
|
-
return
|
|
2231
|
+
return text5({ ok: true, id });
|
|
1933
2232
|
} catch (err) {
|
|
1934
|
-
return
|
|
2233
|
+
return fail5(err);
|
|
1935
2234
|
}
|
|
1936
2235
|
}
|
|
1937
2236
|
);
|
|
1938
2237
|
server.tool(
|
|
1939
2238
|
"run_schedule",
|
|
1940
2239
|
"Fire a schedule now (does not wait for nextRunAt). Queues the prompt or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
|
|
1941
|
-
{ id:
|
|
2240
|
+
{ id: z5.string() },
|
|
1942
2241
|
async ({ id }) => {
|
|
1943
2242
|
try {
|
|
1944
2243
|
if (!getSchedule(id)) {
|
|
1945
|
-
return
|
|
2244
|
+
return fail5(new Error(`Schedule not found: ${id}`));
|
|
1946
2245
|
}
|
|
1947
2246
|
const schedule = await fireSchedule(id);
|
|
1948
|
-
return
|
|
2247
|
+
return text5({ schedule });
|
|
1949
2248
|
} catch (err) {
|
|
1950
|
-
return
|
|
2249
|
+
return fail5(err);
|
|
1951
2250
|
}
|
|
1952
2251
|
}
|
|
1953
2252
|
);
|
|
@@ -2303,6 +2602,9 @@ async function startMcpServer() {
|
|
|
2303
2602
|
version: "0.1.0"
|
|
2304
2603
|
});
|
|
2305
2604
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
2605
|
+
if (worktreeProfile) {
|
|
2606
|
+
registerConnectedIssueVendorTools(server);
|
|
2607
|
+
}
|
|
2306
2608
|
if (!worktreeProfile) {
|
|
2307
2609
|
server.tool(
|
|
2308
2610
|
"list_workspaces",
|
|
@@ -2355,13 +2657,13 @@ async function startMcpServer() {
|
|
|
2355
2657
|
"list_board",
|
|
2356
2658
|
"Home Kanban of worktrees (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chat tabs nest as inner cards. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), column, limit (default 40). create_thread adds a worktree (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
|
|
2357
2659
|
{
|
|
2358
|
-
query:
|
|
2359
|
-
repoPath:
|
|
2360
|
-
kind:
|
|
2361
|
-
column:
|
|
2660
|
+
query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
2661
|
+
repoPath: z6.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
2662
|
+
kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
2663
|
+
column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
2362
2664
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
2363
2665
|
),
|
|
2364
|
-
limit:
|
|
2666
|
+
limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
2365
2667
|
},
|
|
2366
2668
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
2367
2669
|
const workspaces = orch.listWorkspaces();
|
|
@@ -2399,7 +2701,7 @@ async function startMcpServer() {
|
|
|
2399
2701
|
server.tool(
|
|
2400
2702
|
"get_thread",
|
|
2401
2703
|
"Get a compact thread summary by id/ref. Includes last message preview, parentThreadId, and child worktree threads (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
|
|
2402
|
-
{ ref:
|
|
2704
|
+
{ ref: z6.string() },
|
|
2403
2705
|
async ({ ref }) => {
|
|
2404
2706
|
const t = orch.getThread(ref);
|
|
2405
2707
|
if (!t) {
|
|
@@ -2441,17 +2743,17 @@ async function startMcpServer() {
|
|
|
2441
2743
|
"present_artifact",
|
|
2442
2744
|
"Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
|
|
2443
2745
|
{
|
|
2444
|
-
title:
|
|
2445
|
-
type:
|
|
2746
|
+
title: z6.string().describe("Short title shown in the artifact pane header"),
|
|
2747
|
+
type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
2446
2748
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
2447
2749
|
),
|
|
2448
|
-
content:
|
|
2750
|
+
content: z6.string().describe(
|
|
2449
2751
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
2450
2752
|
),
|
|
2451
|
-
artifact_id:
|
|
2452
|
-
status:
|
|
2453
|
-
phase:
|
|
2454
|
-
mode:
|
|
2753
|
+
artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
2754
|
+
status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
2755
|
+
phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
2756
|
+
mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
2455
2757
|
},
|
|
2456
2758
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
2457
2759
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -2472,15 +2774,15 @@ async function startMcpServer() {
|
|
|
2472
2774
|
"ask_user",
|
|
2473
2775
|
"Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
|
|
2474
2776
|
{
|
|
2475
|
-
questions:
|
|
2476
|
-
|
|
2477
|
-
question:
|
|
2478
|
-
header:
|
|
2479
|
-
multiSelect:
|
|
2480
|
-
options:
|
|
2481
|
-
|
|
2482
|
-
label:
|
|
2483
|
-
description:
|
|
2777
|
+
questions: z6.array(
|
|
2778
|
+
z6.object({
|
|
2779
|
+
question: z6.string().describe("Full question text ending with ?"),
|
|
2780
|
+
header: z6.string().max(24).optional().describe("Short label shown above the question"),
|
|
2781
|
+
multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
|
|
2782
|
+
options: z6.array(
|
|
2783
|
+
z6.object({
|
|
2784
|
+
label: z6.string(),
|
|
2785
|
+
description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
2484
2786
|
})
|
|
2485
2787
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
2486
2788
|
})
|
|
@@ -2499,9 +2801,9 @@ async function startMcpServer() {
|
|
|
2499
2801
|
"present_plan",
|
|
2500
2802
|
"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.",
|
|
2501
2803
|
{
|
|
2502
|
-
title:
|
|
2503
|
-
content:
|
|
2504
|
-
thread_id:
|
|
2804
|
+
title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
2805
|
+
content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
2806
|
+
thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
2505
2807
|
},
|
|
2506
2808
|
async ({ title, content, thread_id }) => {
|
|
2507
2809
|
const { writePlanFile } = await import("../plan-file-OZEBFSGQ.js");
|
|
@@ -2524,15 +2826,15 @@ async function startMcpServer() {
|
|
|
2524
2826
|
"present_schema",
|
|
2525
2827
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. 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.",
|
|
2526
2828
|
{
|
|
2527
|
-
title:
|
|
2528
|
-
mode:
|
|
2529
|
-
datasource:
|
|
2530
|
-
resource_id:
|
|
2531
|
-
record_id:
|
|
2532
|
-
resource:
|
|
2533
|
-
record:
|
|
2534
|
-
records:
|
|
2535
|
-
pane_id:
|
|
2829
|
+
title: z6.string().describe("Pane title"),
|
|
2830
|
+
mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
2831
|
+
datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
2832
|
+
resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
2833
|
+
record_id: z6.string().optional().describe("Record id when opening form mode"),
|
|
2834
|
+
resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
2835
|
+
record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
2836
|
+
records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
|
|
2837
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
2536
2838
|
},
|
|
2537
2839
|
async (args) => {
|
|
2538
2840
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -2553,10 +2855,10 @@ async function startMcpServer() {
|
|
|
2553
2855
|
"present_files",
|
|
2554
2856
|
"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.",
|
|
2555
2857
|
{
|
|
2556
|
-
title:
|
|
2557
|
-
datasource:
|
|
2558
|
-
path:
|
|
2559
|
-
pane_id:
|
|
2858
|
+
title: z6.string().optional().describe("Pane title (default: Files)"),
|
|
2859
|
+
datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
2860
|
+
path: z6.string().optional().describe("Initial folder path (e.g. public)"),
|
|
2861
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
2560
2862
|
},
|
|
2561
2863
|
async (args) => {
|
|
2562
2864
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -2583,7 +2885,7 @@ async function startMcpServer() {
|
|
|
2583
2885
|
"set_caffeinate",
|
|
2584
2886
|
"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.",
|
|
2585
2887
|
{
|
|
2586
|
-
enabled:
|
|
2888
|
+
enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
2587
2889
|
},
|
|
2588
2890
|
async ({ enabled }) => {
|
|
2589
2891
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -2621,18 +2923,18 @@ async function startMcpServer() {
|
|
|
2621
2923
|
"create_thread",
|
|
2622
2924
|
`Create a worktree thread (chat) from branch, pr, or ticket. A ticket, PR, or named branch may have only one live worktree \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. Creating from the default branch still opens a new isolated worktree. Pass repoPath from list_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). 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}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_thread to chat.`,
|
|
2623
2925
|
{
|
|
2624
|
-
sourceType:
|
|
2625
|
-
sourceRef:
|
|
2626
|
-
agent:
|
|
2627
|
-
model:
|
|
2926
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]),
|
|
2927
|
+
sourceRef: z6.string(),
|
|
2928
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
2929
|
+
model: z6.string().nullable().optional().describe(
|
|
2628
2930
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
2629
2931
|
),
|
|
2630
|
-
repoPath:
|
|
2631
|
-
title:
|
|
2632
|
-
cowboy:
|
|
2932
|
+
repoPath: z6.string(),
|
|
2933
|
+
title: z6.string().optional(),
|
|
2934
|
+
cowboy: z6.boolean().optional().describe(
|
|
2633
2935
|
"If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
|
|
2634
2936
|
),
|
|
2635
|
-
parentThreadId:
|
|
2937
|
+
parentThreadId: z6.string().optional().describe(
|
|
2636
2938
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
2637
2939
|
)
|
|
2638
2940
|
},
|
|
@@ -2648,10 +2950,10 @@ async function startMcpServer() {
|
|
|
2648
2950
|
"start_board_card",
|
|
2649
2951
|
"Same as create_thread for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second worktree when one already matches \u2014 returns that thread (alreadyStarted). Then send_to_thread.",
|
|
2650
2952
|
{
|
|
2651
|
-
kind:
|
|
2652
|
-
ref:
|
|
2653
|
-
repoPath:
|
|
2654
|
-
title:
|
|
2953
|
+
kind: z6.enum(["ticket", "pr", "branch"]),
|
|
2954
|
+
ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
2955
|
+
repoPath: z6.string().describe("Workspace path from list_workspaces / list_board"),
|
|
2956
|
+
title: z6.string().optional()
|
|
2655
2957
|
},
|
|
2656
2958
|
async ({ kind, ref, repoPath, title }) => {
|
|
2657
2959
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -2755,9 +3057,9 @@ async function startMcpServer() {
|
|
|
2755
3057
|
"send_to_thread",
|
|
2756
3058
|
'Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not force_stop to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that stops the child mid-thought. Call wait_for_turn again instead.',
|
|
2757
3059
|
{
|
|
2758
|
-
ref:
|
|
2759
|
-
prompt:
|
|
2760
|
-
force_stop:
|
|
3060
|
+
ref: z6.string(),
|
|
3061
|
+
prompt: z6.string(),
|
|
3062
|
+
force_stop: z6.boolean().optional()
|
|
2761
3063
|
},
|
|
2762
3064
|
async ({ ref, prompt, force_stop }) => {
|
|
2763
3065
|
if (force_stop) {
|
|
@@ -2786,8 +3088,8 @@ async function startMcpServer() {
|
|
|
2786
3088
|
"wait_for_turn",
|
|
2787
3089
|
"Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. stillRunning is the source of truth \u2014 if false, the child is not working (do not say it is waiting for a gate). If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume a hang. On status error, lastError/text is the failure. On status stopped or broken, the child did not finish \u2014 resume with send_to_thread or tell the user; do not treat that as success. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
|
|
2788
3090
|
{
|
|
2789
|
-
ref:
|
|
2790
|
-
timeoutMs:
|
|
3091
|
+
ref: z6.string(),
|
|
3092
|
+
timeoutMs: z6.number().optional()
|
|
2791
3093
|
},
|
|
2792
3094
|
async ({ ref, timeoutMs }) => {
|
|
2793
3095
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -2817,7 +3119,7 @@ async function startMcpServer() {
|
|
|
2817
3119
|
server.tool(
|
|
2818
3120
|
"get_turn_result",
|
|
2819
3121
|
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript. Includes usage for the last agent turn (tokens + costUsd when reported).",
|
|
2820
|
-
{ ref:
|
|
3122
|
+
{ ref: z6.string() },
|
|
2821
3123
|
async ({ ref }) => {
|
|
2822
3124
|
const result = orch.getTurnResult(ref);
|
|
2823
3125
|
return {
|
|
@@ -2838,8 +3140,8 @@ async function startMcpServer() {
|
|
|
2838
3140
|
"stop_thread",
|
|
2839
3141
|
"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.",
|
|
2840
3142
|
{
|
|
2841
|
-
ref:
|
|
2842
|
-
force:
|
|
3143
|
+
ref: z6.string(),
|
|
3144
|
+
force: z6.boolean().optional()
|
|
2843
3145
|
},
|
|
2844
3146
|
async ({ ref, force }) => {
|
|
2845
3147
|
const t = orch.getThread(ref);
|
|
@@ -2869,7 +3171,7 @@ async function startMcpServer() {
|
|
|
2869
3171
|
server.tool(
|
|
2870
3172
|
"archive_thread",
|
|
2871
3173
|
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
|
|
2872
|
-
{ ref:
|
|
3174
|
+
{ ref: z6.string() },
|
|
2873
3175
|
async ({ ref }) => {
|
|
2874
3176
|
const t = orch.getThread(ref);
|
|
2875
3177
|
if (!t) {
|
|
@@ -2902,7 +3204,7 @@ async function startMcpServer() {
|
|
|
2902
3204
|
server.tool(
|
|
2903
3205
|
"restore_thread",
|
|
2904
3206
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
2905
|
-
{ ref:
|
|
3207
|
+
{ ref: z6.string() },
|
|
2906
3208
|
async ({ ref }) => {
|
|
2907
3209
|
try {
|
|
2908
3210
|
const restored = await orch.restore(ref);
|
|
@@ -2928,8 +3230,8 @@ async function startMcpServer() {
|
|
|
2928
3230
|
"get_diff",
|
|
2929
3231
|
"Compact diff summary (capped hunks, paginated)",
|
|
2930
3232
|
{
|
|
2931
|
-
ref:
|
|
2932
|
-
maxFiles:
|
|
3233
|
+
ref: z6.string(),
|
|
3234
|
+
maxFiles: z6.number().optional()
|
|
2933
3235
|
},
|
|
2934
3236
|
async ({ ref, maxFiles }) => {
|
|
2935
3237
|
const summary = await orch.diffSummary(ref);
|
|
@@ -2949,7 +3251,7 @@ async function startMcpServer() {
|
|
|
2949
3251
|
server.tool(
|
|
2950
3252
|
"get_pr_checks",
|
|
2951
3253
|
"Snapshot of GitHub PR checks for a worktree thread (`gh pr checks` plus merge/review gates). null = no PR. If the user gave a goal (Greptile 5/5, CI green), the worktree agent watches with `gh pr checks --watch` via /long-running \u2014 this tool is a snapshot, not a waiter. Coordinators: do not run gh from the orchestration cwd.",
|
|
2952
|
-
{ ref:
|
|
3254
|
+
{ ref: z6.string().describe("Worktree thread id/ref") },
|
|
2953
3255
|
async ({ ref }) => {
|
|
2954
3256
|
try {
|
|
2955
3257
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -2965,7 +3267,7 @@ async function startMcpServer() {
|
|
|
2965
3267
|
server.tool(
|
|
2966
3268
|
"request_review",
|
|
2967
3269
|
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the 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 (loop while stillRunning) / get_turn_result on the returned review tab id.',
|
|
2968
|
-
{ ref:
|
|
3270
|
+
{ ref: z6.string().describe("Worktree thread id/ref to review") },
|
|
2969
3271
|
async ({ ref }) => {
|
|
2970
3272
|
try {
|
|
2971
3273
|
const tab = await orch.requestReview(ref);
|
|
@@ -2994,8 +3296,8 @@ async function startMcpServer() {
|
|
|
2994
3296
|
"ask_git",
|
|
2995
3297
|
"Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` even when origin is SSH / Settings \u2192 Git is SSH). When dirty, queues the worktree agent to commit; then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd. If this tool errors that the GraphQL/PR body is too long, the branch is already pushed \u2014 have the worktree agent retry `gh pr create --body-file` with a short description (GitHub limit 65,536 characters). Do not invent SSH/auth failures from that error.",
|
|
2996
3298
|
{
|
|
2997
|
-
ref:
|
|
2998
|
-
action:
|
|
3299
|
+
ref: z6.string().describe("Worktree thread id/ref"),
|
|
3300
|
+
action: z6.enum(AGENT_GIT_ACTIONS).describe(
|
|
2999
3301
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
3000
3302
|
)
|
|
3001
3303
|
},
|
|
@@ -3040,7 +3342,7 @@ async function startMcpServer() {
|
|
|
3040
3342
|
}
|
|
3041
3343
|
}
|
|
3042
3344
|
);
|
|
3043
|
-
const agentEnum =
|
|
3345
|
+
const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
3044
3346
|
server.tool(
|
|
3045
3347
|
"list_models",
|
|
3046
3348
|
"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.",
|
|
@@ -3063,11 +3365,11 @@ async function startMcpServer() {
|
|
|
3063
3365
|
"fork_worktree",
|
|
3064
3366
|
"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 (loop while stillRunning) on the returned id.",
|
|
3065
3367
|
{
|
|
3066
|
-
ref:
|
|
3067
|
-
through_index:
|
|
3368
|
+
ref: z6.string().describe("Worktree thread id/ref to fork"),
|
|
3369
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
3068
3370
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
3069
|
-
model:
|
|
3070
|
-
title:
|
|
3371
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
3372
|
+
title: z6.string().optional()
|
|
3071
3373
|
},
|
|
3072
3374
|
async ({ ref, through_index, agent, model, title }) => {
|
|
3073
3375
|
try {
|
|
@@ -3108,11 +3410,11 @@ async function startMcpServer() {
|
|
|
3108
3410
|
"fork_chat",
|
|
3109
3411
|
"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 (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
3110
3412
|
{
|
|
3111
|
-
ref:
|
|
3112
|
-
through_index:
|
|
3413
|
+
ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
3414
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
3113
3415
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
3114
|
-
model:
|
|
3115
|
-
title:
|
|
3416
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
3417
|
+
title: z6.string().optional()
|
|
3116
3418
|
},
|
|
3117
3419
|
async ({ ref, through_index, agent, model, title }) => {
|
|
3118
3420
|
try {
|
|
@@ -3158,8 +3460,8 @@ async function startMcpServer() {
|
|
|
3158
3460
|
"run_dev_script",
|
|
3159
3461
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
3160
3462
|
{
|
|
3161
|
-
ref:
|
|
3162
|
-
name:
|
|
3463
|
+
ref: z6.string(),
|
|
3464
|
+
name: z6.string().optional()
|
|
3163
3465
|
},
|
|
3164
3466
|
async ({ ref, name }) => {
|
|
3165
3467
|
const result = await orch.startDev(ref, name);
|
|
@@ -3181,7 +3483,7 @@ async function startMcpServer() {
|
|
|
3181
3483
|
server.tool(
|
|
3182
3484
|
"list_run_scripts",
|
|
3183
3485
|
"List named run scripts available for a thread",
|
|
3184
|
-
{ ref:
|
|
3486
|
+
{ ref: z6.string() },
|
|
3185
3487
|
async ({ ref }) => {
|
|
3186
3488
|
const scripts = orch.listThreadRunScripts(ref);
|
|
3187
3489
|
const active = orch.getActiveRuns(ref);
|
|
@@ -3199,8 +3501,8 @@ async function startMcpServer() {
|
|
|
3199
3501
|
"stop_dev_script",
|
|
3200
3502
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
3201
3503
|
{
|
|
3202
|
-
ref:
|
|
3203
|
-
name:
|
|
3504
|
+
ref: z6.string(),
|
|
3505
|
+
name: z6.string().optional()
|
|
3204
3506
|
},
|
|
3205
3507
|
async ({ ref, name }) => {
|
|
3206
3508
|
orch.stopDev(ref, name);
|
|
@@ -3210,7 +3512,7 @@ async function startMcpServer() {
|
|
|
3210
3512
|
server.tool(
|
|
3211
3513
|
"run_setup",
|
|
3212
3514
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
3213
|
-
{ ref:
|
|
3515
|
+
{ ref: z6.string() },
|
|
3214
3516
|
async ({ ref }) => {
|
|
3215
3517
|
const result = await orch.runSetup(ref);
|
|
3216
3518
|
return {
|
|
@@ -3221,7 +3523,7 @@ async function startMcpServer() {
|
|
|
3221
3523
|
server.tool(
|
|
3222
3524
|
"add_workspace",
|
|
3223
3525
|
"Register a git repo as a Sideboard workspace",
|
|
3224
|
-
{ repoPath:
|
|
3526
|
+
{ repoPath: z6.string() },
|
|
3225
3527
|
async ({ repoPath }) => {
|
|
3226
3528
|
const ws = await orch.addWorkspace(repoPath);
|
|
3227
3529
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -3230,7 +3532,7 @@ async function startMcpServer() {
|
|
|
3230
3532
|
server.tool(
|
|
3231
3533
|
"remove_workspace",
|
|
3232
3534
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
3233
|
-
{ repoPath:
|
|
3535
|
+
{ repoPath: z6.string() },
|
|
3234
3536
|
async ({ repoPath }) => {
|
|
3235
3537
|
orch.removeWorkspace(repoPath);
|
|
3236
3538
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -3240,12 +3542,12 @@ async function startMcpServer() {
|
|
|
3240
3542
|
"fanout",
|
|
3241
3543
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
3242
3544
|
{
|
|
3243
|
-
prompt:
|
|
3244
|
-
agents:
|
|
3245
|
-
repoPath:
|
|
3246
|
-
sourceType:
|
|
3247
|
-
sourceRef:
|
|
3248
|
-
title:
|
|
3545
|
+
prompt: z6.string(),
|
|
3546
|
+
agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
3547
|
+
repoPath: z6.string(),
|
|
3548
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
|
|
3549
|
+
sourceRef: z6.string().optional(),
|
|
3550
|
+
title: z6.string().optional()
|
|
3249
3551
|
},
|
|
3250
3552
|
async (args) => {
|
|
3251
3553
|
const threads = await orch.bestOfN(args);
|
|
@@ -3272,8 +3574,8 @@ async function startMcpServer() {
|
|
|
3272
3574
|
"list_branches",
|
|
3273
3575
|
"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).",
|
|
3274
3576
|
{
|
|
3275
|
-
repoPath:
|
|
3276
|
-
unmergedOnly:
|
|
3577
|
+
repoPath: z6.string(),
|
|
3578
|
+
unmergedOnly: z6.boolean().optional()
|
|
3277
3579
|
},
|
|
3278
3580
|
async ({ repoPath, unmergedOnly }) => {
|
|
3279
3581
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3294,19 +3596,19 @@ async function startMcpServer() {
|
|
|
3294
3596
|
"list_prs",
|
|
3295
3597
|
'List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_workspaces. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled eng-review with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_thread with sourceType=pr.',
|
|
3296
3598
|
{
|
|
3297
|
-
repoPath:
|
|
3298
|
-
query:
|
|
3299
|
-
queue:
|
|
3599
|
+
repoPath: z6.string(),
|
|
3600
|
+
query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
3601
|
+
queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
3300
3602
|
'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
|
|
3301
3603
|
),
|
|
3302
|
-
state:
|
|
3303
|
-
label:
|
|
3604
|
+
state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
3605
|
+
label: z6.string().optional().describe(
|
|
3304
3606
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
3305
3607
|
),
|
|
3306
|
-
reviewer:
|
|
3608
|
+
reviewer: z6.string().optional().describe(
|
|
3307
3609
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
3308
3610
|
),
|
|
3309
|
-
limit:
|
|
3611
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
3310
3612
|
},
|
|
3311
3613
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
3312
3614
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3338,7 +3640,7 @@ async function startMcpServer() {
|
|
|
3338
3640
|
server.tool(
|
|
3339
3641
|
"get_pr_stack",
|
|
3340
3642
|
"Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
|
|
3341
|
-
{ ref:
|
|
3643
|
+
{ ref: z6.string() },
|
|
3342
3644
|
async ({ ref }) => {
|
|
3343
3645
|
const stack = await orch.getPrStack(ref);
|
|
3344
3646
|
return {
|
|
@@ -3350,8 +3652,8 @@ async function startMcpServer() {
|
|
|
3350
3652
|
"open_pr_stack_layers",
|
|
3351
3653
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
3352
3654
|
{
|
|
3353
|
-
ref:
|
|
3354
|
-
layer:
|
|
3655
|
+
ref: z6.string(),
|
|
3656
|
+
layer: z6.number().int().positive().optional()
|
|
3355
3657
|
},
|
|
3356
3658
|
async ({ ref, layer }) => {
|
|
3357
3659
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -3385,9 +3687,9 @@ async function startMcpServer() {
|
|
|
3385
3687
|
"add_stack_layer",
|
|
3386
3688
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
3387
3689
|
{
|
|
3388
|
-
ref:
|
|
3389
|
-
branchName:
|
|
3390
|
-
title:
|
|
3690
|
+
ref: z6.string(),
|
|
3691
|
+
branchName: z6.string(),
|
|
3692
|
+
title: z6.string().optional()
|
|
3391
3693
|
},
|
|
3392
3694
|
async ({ ref, branchName, title }) => {
|
|
3393
3695
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -3416,11 +3718,11 @@ async function startMcpServer() {
|
|
|
3416
3718
|
"create_pr_stack",
|
|
3417
3719
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
3418
3720
|
{
|
|
3419
|
-
repoPath:
|
|
3420
|
-
branches:
|
|
3421
|
-
agent:
|
|
3422
|
-
base:
|
|
3423
|
-
title:
|
|
3721
|
+
repoPath: z6.string(),
|
|
3722
|
+
branches: z6.array(z6.string()).min(1),
|
|
3723
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
3724
|
+
base: z6.string().optional(),
|
|
3725
|
+
title: z6.string().optional()
|
|
3424
3726
|
},
|
|
3425
3727
|
async (args) => {
|
|
3426
3728
|
const result = await orch.createPrStack({
|
|
@@ -3459,10 +3761,10 @@ async function startMcpServer() {
|
|
|
3459
3761
|
"list_issues",
|
|
3460
3762
|
"List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Then create_thread with sourceType=ticket.",
|
|
3461
3763
|
{
|
|
3462
|
-
repoPath:
|
|
3463
|
-
query:
|
|
3464
|
-
assignee:
|
|
3465
|
-
limit:
|
|
3764
|
+
repoPath: z6.string(),
|
|
3765
|
+
query: z6.string().optional().describe("Search title, identifier, or description"),
|
|
3766
|
+
assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
3767
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
3466
3768
|
},
|
|
3467
3769
|
async ({ repoPath, query, assignee, limit }) => {
|
|
3468
3770
|
const root = await resolveRepoRoot(repoPath);
|