@sideboard-ai/core 0.1.156 → 0.1.158
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-IY4CSPWV.js → agents-E3ZPWZRS.js} +2 -2
- package/dist/{agents-2FTZAXO3.js → agents-RGE7PSUY.js} +4 -4
- package/dist/{chunk-TI6UMRWK.js → chunk-3SUZMPGL.js} +5 -4
- package/dist/{chunk-NVXVEGGM.js → chunk-5U4NZLBH.js} +4 -0
- package/dist/{chunk-CJQLPPEJ.js → chunk-A7QXLYBN.js} +1 -1
- package/dist/{chunk-XLU7DXHH.js → chunk-BLRHO7XQ.js} +54 -5
- package/dist/{chunk-MSFPM5VK.js → chunk-CVR4RJ6G.js} +3 -2
- package/dist/{chunk-JOK4XWBG.js → chunk-DVJBO3M7.js} +87 -3
- package/dist/{chunk-UA3YZKRX.js → chunk-EPQIOAAY.js} +25 -5
- package/dist/{chunk-OLE2BLIX.js → chunk-FBFIOVDH.js} +457 -106
- package/dist/{chunk-2SDMENAL.js → chunk-JV4VNFMS.js} +1 -1
- package/dist/{chunk-7EIPEPAH.js → chunk-KYKNFJK5.js} +484 -105
- package/dist/{chunk-FDCFXMDG.js → chunk-N27GVFZY.js} +87 -3
- package/dist/{coordinator-prompt-D2FHU5IZ.js → coordinator-prompt-26IJU2AV.js} +1 -1
- package/dist/{coordinator-prompt-UZDNIVP7.js → coordinator-prompt-GZESIQNH.js} +3 -3
- package/dist/{global-workspace-5WZVSWNT.js → global-workspace-5BIW26YR.js} +1 -1
- package/dist/{global-workspace-MR75BDBY.js → global-workspace-REI55FZ6.js} +3 -3
- package/dist/index.cjs +1405 -560
- package/dist/index.d.cts +138 -4
- package/dist/index.d.ts +138 -4
- package/dist/index.js +609 -274
- package/dist/mcp/run-stdio.cjs +1253 -500
- package/dist/mcp/run-stdio.js +525 -226
- package/dist/{orchestrator-LXO2CDOG.js → orchestrator-J727WIJY.js} +6 -6
- package/dist/{orchestrator-G4WKJG5Y.js → orchestrator-ZVW2AC6B.js} +3 -3
- package/dist/{plan-file-OZEBFSGQ.js → plan-file-I24VJVP2.js} +2 -2
- package/dist/{workspaces-RP32AW6X.js → workspaces-NYSM2EPX.js} +1 -1
- package/dist/{workspaces-HO4EQNNM.js → workspaces-OCEPZXFD.js} +3 -3
- package/dist/{worktree-PEWDAX5O.js → worktree-VTMBZKY6.js} +2 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -22,6 +22,10 @@ import {
|
|
|
22
22
|
listSchedules,
|
|
23
23
|
listSlackOutboundWatches,
|
|
24
24
|
listSlackWorkspaces,
|
|
25
|
+
mcpWaitFinishedHint,
|
|
26
|
+
mcpWaitForJobTimeoutMs,
|
|
27
|
+
mcpWaitForTurnTimeoutMs,
|
|
28
|
+
mcpWaitStillRunningHint,
|
|
25
29
|
pollSlackOutboundWatches,
|
|
26
30
|
readTurnLive,
|
|
27
31
|
recordSlackOutboundWatch,
|
|
@@ -30,16 +34,18 @@ import {
|
|
|
30
34
|
slackApi,
|
|
31
35
|
slackTokenFor,
|
|
32
36
|
syncBoardPins,
|
|
33
|
-
updateSchedule
|
|
34
|
-
|
|
37
|
+
updateSchedule,
|
|
38
|
+
waitForDetachedJob
|
|
39
|
+
} from "../chunk-FBFIOVDH.js";
|
|
35
40
|
import {
|
|
36
41
|
isInternalAgentStatusText,
|
|
37
42
|
listModelsForAgent,
|
|
38
43
|
sideboardMcpProfile
|
|
39
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-EPQIOAAY.js";
|
|
40
45
|
import "../chunk-LZ6QMEQC.js";
|
|
41
46
|
import "../chunk-S42XV45P.js";
|
|
42
47
|
import {
|
|
48
|
+
commentAbleTimeTask,
|
|
43
49
|
createAbleTimeTask,
|
|
44
50
|
ensureAbleTimeTask,
|
|
45
51
|
getAbleTimeOrientation,
|
|
@@ -48,28 +54,30 @@ import {
|
|
|
48
54
|
listAbleTimeProjects,
|
|
49
55
|
listAbleTimeTasks,
|
|
50
56
|
searchAbleTimeTasks,
|
|
51
|
-
toAbleTimeIssueInfo
|
|
52
|
-
|
|
57
|
+
toAbleTimeIssueInfo,
|
|
58
|
+
updateAbleTimeTask
|
|
59
|
+
} from "../chunk-N27GVFZY.js";
|
|
53
60
|
import {
|
|
54
61
|
httpFetch
|
|
55
62
|
} from "../chunk-6GN5WPYZ.js";
|
|
56
|
-
import "../chunk-
|
|
63
|
+
import "../chunk-JV4VNFMS.js";
|
|
57
64
|
import "../chunk-OCQPTUR7.js";
|
|
58
65
|
import {
|
|
59
66
|
GLOBAL_WORKSPACE_ID,
|
|
60
67
|
isCloudCoordinatorThread
|
|
61
|
-
} from "../chunk-
|
|
68
|
+
} from "../chunk-3SUZMPGL.js";
|
|
62
69
|
import {
|
|
63
70
|
canonicalizeRepoPath,
|
|
64
71
|
formatGhLandError,
|
|
72
|
+
ghRepoSelectArgs,
|
|
65
73
|
listBranches,
|
|
66
74
|
listPrs,
|
|
67
75
|
resolveGithubRepoSlug,
|
|
68
76
|
resolveListPrsOptions,
|
|
69
77
|
resolveRepoRoot,
|
|
70
78
|
warmGithubAgentAuth
|
|
71
|
-
} from "../chunk-
|
|
72
|
-
import "../chunk-
|
|
79
|
+
} from "../chunk-A7QXLYBN.js";
|
|
80
|
+
import "../chunk-5U4NZLBH.js";
|
|
73
81
|
import "../chunk-LZQULFIT.js";
|
|
74
82
|
import "../chunk-SYFVUF2C.js";
|
|
75
83
|
import {
|
|
@@ -96,7 +104,7 @@ import {
|
|
|
96
104
|
// src/mcp/server.ts
|
|
97
105
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
98
106
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
99
|
-
import { z as
|
|
107
|
+
import { z as z6 } from "zod";
|
|
100
108
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
101
109
|
import { basename } from "path";
|
|
102
110
|
|
|
@@ -680,6 +688,11 @@ async function createLinearIssue(input, opts) {
|
|
|
680
688
|
if (input.state?.trim()) {
|
|
681
689
|
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
682
690
|
}
|
|
691
|
+
const parentRef = input.parent?.trim();
|
|
692
|
+
if (parentRef) {
|
|
693
|
+
const parent = await getLinearIssue(parentRef, opts);
|
|
694
|
+
mutationInput.parentId = parent.id;
|
|
695
|
+
}
|
|
683
696
|
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
684
697
|
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
685
698
|
else if (assignee) mutationInput.assigneeId = assignee;
|
|
@@ -922,35 +935,13 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
922
935
|
return null;
|
|
923
936
|
}
|
|
924
937
|
|
|
925
|
-
// src/mcp/wait-for-turn.ts
|
|
926
|
-
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
927
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
928
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
929
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
930
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
931
|
-
}
|
|
932
|
-
var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
|
|
933
|
-
var MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume it failed to start.";
|
|
934
|
-
function mcpWaitStillRunningHint(status) {
|
|
935
|
-
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
936
|
-
}
|
|
937
|
-
var MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
938
|
-
var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
939
|
-
var MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
940
|
-
function mcpWaitFinishedHint(status) {
|
|
941
|
-
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
942
|
-
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
943
|
-
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
944
|
-
return void 0;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
938
|
// src/mcp/thread-visibility.ts
|
|
948
939
|
function lastMessagePreview(messages, max = 160) {
|
|
949
940
|
if (!messages?.length) return null;
|
|
950
941
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
951
|
-
const
|
|
952
|
-
if (!
|
|
953
|
-
const flat =
|
|
942
|
+
const text6 = messages[i]?.text?.trim();
|
|
943
|
+
if (!text6 || isInternalAgentStatusText(text6)) continue;
|
|
944
|
+
const flat = text6.replace(/\s+/g, " ");
|
|
954
945
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
955
946
|
}
|
|
956
947
|
return null;
|
|
@@ -1182,8 +1173,8 @@ function labelGithubUrl(url) {
|
|
|
1182
1173
|
}
|
|
1183
1174
|
return { kind: "other", label: "GitHub", url: raw };
|
|
1184
1175
|
}
|
|
1185
|
-
function appendGithubLink(
|
|
1186
|
-
const body =
|
|
1176
|
+
function appendGithubLink(text6, githubUrl) {
|
|
1177
|
+
const body = text6.trimEnd();
|
|
1187
1178
|
if (!githubUrl?.trim()) return body;
|
|
1188
1179
|
const labeled = labelGithubUrl(githubUrl);
|
|
1189
1180
|
if (!labeled) {
|
|
@@ -1579,11 +1570,51 @@ function registerAbleTimeTools(server) {
|
|
|
1579
1570
|
);
|
|
1580
1571
|
server.tool(
|
|
1581
1572
|
"abletime_get_task",
|
|
1582
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232).",
|
|
1573
|
+
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments.",
|
|
1583
1574
|
{ id: z2.string() },
|
|
1584
1575
|
async ({ id }) => {
|
|
1585
1576
|
try {
|
|
1586
|
-
|
|
1577
|
+
const task = await getAbleTimeTask(id);
|
|
1578
|
+
return text2({
|
|
1579
|
+
...toAbleTimeIssueInfo(task),
|
|
1580
|
+
description: task.description,
|
|
1581
|
+
state: task.state,
|
|
1582
|
+
comments: task.comments
|
|
1583
|
+
});
|
|
1584
|
+
} catch (err) {
|
|
1585
|
+
return fail2(err);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
);
|
|
1589
|
+
server.tool(
|
|
1590
|
+
"abletime_comment",
|
|
1591
|
+
"Add a markdown comment on an AbleTime task (id or CRM-232).",
|
|
1592
|
+
{ id: z2.string(), body: z2.string() },
|
|
1593
|
+
async (args) => {
|
|
1594
|
+
try {
|
|
1595
|
+
return text2(await commentAbleTimeTask(args));
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
return fail2(err);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
);
|
|
1601
|
+
server.tool(
|
|
1602
|
+
"abletime_update_task",
|
|
1603
|
+
"Update an AbleTime task (id or CRM-232). Pass title, description, and/or state.",
|
|
1604
|
+
{
|
|
1605
|
+
id: z2.string(),
|
|
1606
|
+
title: z2.string().optional(),
|
|
1607
|
+
description: z2.string().optional(),
|
|
1608
|
+
state: z2.string().optional()
|
|
1609
|
+
},
|
|
1610
|
+
async (args) => {
|
|
1611
|
+
try {
|
|
1612
|
+
const task = await updateAbleTimeTask(args);
|
|
1613
|
+
return text2({
|
|
1614
|
+
...toAbleTimeIssueInfo(task),
|
|
1615
|
+
description: task.description,
|
|
1616
|
+
state: task.state
|
|
1617
|
+
});
|
|
1587
1618
|
} catch (err) {
|
|
1588
1619
|
return fail2(err);
|
|
1589
1620
|
}
|
|
@@ -1591,13 +1622,14 @@ function registerAbleTimeTools(server) {
|
|
|
1591
1622
|
);
|
|
1592
1623
|
server.tool(
|
|
1593
1624
|
"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).",
|
|
1625
|
+
"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
1626
|
{
|
|
1596
1627
|
title: z2.string(),
|
|
1597
1628
|
description: z2.string().optional(),
|
|
1598
1629
|
projectId: z2.string().optional(),
|
|
1599
1630
|
categoryId: z2.string().optional(),
|
|
1600
|
-
state: z2.enum(["backlog", "todo"]).optional()
|
|
1631
|
+
state: z2.enum(["backlog", "todo"]).optional(),
|
|
1632
|
+
parent: z2.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
1601
1633
|
},
|
|
1602
1634
|
async (args) => {
|
|
1603
1635
|
try {
|
|
@@ -1627,8 +1659,182 @@ function registerAbleTimeTools(server) {
|
|
|
1627
1659
|
);
|
|
1628
1660
|
}
|
|
1629
1661
|
|
|
1630
|
-
// src/mcp/
|
|
1662
|
+
// src/mcp/github-tools.ts
|
|
1631
1663
|
import { z as z3 } from "zod";
|
|
1664
|
+
|
|
1665
|
+
// src/integrations/github-issues.ts
|
|
1666
|
+
function requireGhOk(result, label) {
|
|
1667
|
+
if (result.exitCode !== 0) {
|
|
1668
|
+
const detail = (result.stderr || result.stdout).trim() || "gh failed";
|
|
1669
|
+
throw new Error(`${label}: ${detail}`);
|
|
1670
|
+
}
|
|
1671
|
+
return result.stdout;
|
|
1672
|
+
}
|
|
1673
|
+
function parseGitHubIssueNumber(id) {
|
|
1674
|
+
const trimmed = id.trim();
|
|
1675
|
+
if (!trimmed) throw new Error("GitHub issue id is required (#123 or a URL)");
|
|
1676
|
+
const url = trimmed.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/i);
|
|
1677
|
+
if (url) return Number(url[1]);
|
|
1678
|
+
const prefixed = trimmed.match(/^gh-(\d+)$/i);
|
|
1679
|
+
if (prefixed) return Number(prefixed[1]);
|
|
1680
|
+
const bare = trimmed.match(/^#?(\d+)$/);
|
|
1681
|
+
if (bare) return Number(bare[1]);
|
|
1682
|
+
throw new Error(`GitHub issue id must be #123, a number, or an issue URL (got ${trimmed})`);
|
|
1683
|
+
}
|
|
1684
|
+
async function resolveGitHubIssueRepo(repoPath) {
|
|
1685
|
+
const cwd = await resolveRepoRoot((repoPath ?? "").trim() || process.cwd());
|
|
1686
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1687
|
+
return { cwd, slug, repoArgs: slug ? ghRepoSelectArgs(slug) : [] };
|
|
1688
|
+
}
|
|
1689
|
+
function mapLabels(raw) {
|
|
1690
|
+
if (!Array.isArray(raw)) return [];
|
|
1691
|
+
return raw.map((item) => typeof item === "string" ? item : String(item?.name ?? "")).map((name) => name.trim()).filter(Boolean);
|
|
1692
|
+
}
|
|
1693
|
+
function mapAssignees(raw) {
|
|
1694
|
+
if (!Array.isArray(raw)) return [];
|
|
1695
|
+
return raw.map(
|
|
1696
|
+
(item) => typeof item === "string" ? item : String(item?.login ?? "")
|
|
1697
|
+
).map((login) => login.trim()).filter(Boolean);
|
|
1698
|
+
}
|
|
1699
|
+
function mapComments2(raw) {
|
|
1700
|
+
if (!Array.isArray(raw)) return [];
|
|
1701
|
+
return raw.map((item) => {
|
|
1702
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
1703
|
+
if (!rec) return null;
|
|
1704
|
+
const body = typeof rec.body === "string" ? rec.body : "";
|
|
1705
|
+
const authorRec = rec.author && typeof rec.author === "object" ? rec.author : null;
|
|
1706
|
+
const comment = {
|
|
1707
|
+
body,
|
|
1708
|
+
id: rec.id != null ? String(rec.id) : void 0,
|
|
1709
|
+
url: typeof rec.url === "string" ? rec.url : void 0,
|
|
1710
|
+
createdAt: typeof rec.createdAt === "string" ? rec.createdAt : void 0,
|
|
1711
|
+
author: authorRec?.login?.trim() || void 0
|
|
1712
|
+
};
|
|
1713
|
+
return comment;
|
|
1714
|
+
}).filter((item) => Boolean(item));
|
|
1715
|
+
}
|
|
1716
|
+
function toGitHubIssue(raw) {
|
|
1717
|
+
const number = Number(raw.number);
|
|
1718
|
+
if (!Number.isFinite(number) || number <= 0) {
|
|
1719
|
+
throw new Error("GitHub issue response was missing a number");
|
|
1720
|
+
}
|
|
1721
|
+
const assignees = mapAssignees(raw.assignees);
|
|
1722
|
+
return {
|
|
1723
|
+
id: `gh-${number}`,
|
|
1724
|
+
identifier: `#${number}`,
|
|
1725
|
+
number,
|
|
1726
|
+
title: String(raw.title ?? ""),
|
|
1727
|
+
url: String(raw.url ?? ""),
|
|
1728
|
+
body: typeof raw.body === "string" && raw.body.trim() ? raw.body : void 0,
|
|
1729
|
+
state: typeof raw.state === "string" ? raw.state : void 0,
|
|
1730
|
+
labels: mapLabels(raw.labels),
|
|
1731
|
+
assignees,
|
|
1732
|
+
comments: mapComments2(raw.comments)
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
async function getGitHubIssue(id, opts) {
|
|
1736
|
+
const number = parseGitHubIssueNumber(id);
|
|
1737
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1738
|
+
const stdout = requireGhOk(
|
|
1739
|
+
await gh(
|
|
1740
|
+
[
|
|
1741
|
+
"issue",
|
|
1742
|
+
"view",
|
|
1743
|
+
String(number),
|
|
1744
|
+
...repoArgs,
|
|
1745
|
+
"--json",
|
|
1746
|
+
"number,title,body,url,state,labels,assignees,comments,author"
|
|
1747
|
+
],
|
|
1748
|
+
cwd,
|
|
1749
|
+
{ reject: false }
|
|
1750
|
+
),
|
|
1751
|
+
`GitHub issue ${number}`
|
|
1752
|
+
);
|
|
1753
|
+
let parsed;
|
|
1754
|
+
try {
|
|
1755
|
+
parsed = JSON.parse(stdout);
|
|
1756
|
+
} catch {
|
|
1757
|
+
throw new Error(`GitHub issue ${number}: gh returned non-JSON`);
|
|
1758
|
+
}
|
|
1759
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1760
|
+
throw new Error(`GitHub issue not found: #${number}`);
|
|
1761
|
+
}
|
|
1762
|
+
return toGitHubIssue(parsed);
|
|
1763
|
+
}
|
|
1764
|
+
async function commentGitHubIssue(input, opts) {
|
|
1765
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1766
|
+
const body = input.body.trim();
|
|
1767
|
+
if (!body) throw new Error("GitHub comment body is required");
|
|
1768
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1769
|
+
const stdout = requireGhOk(
|
|
1770
|
+
await gh(
|
|
1771
|
+
["issue", "comment", String(number), ...repoArgs, "--body", body],
|
|
1772
|
+
cwd,
|
|
1773
|
+
{ reject: false }
|
|
1774
|
+
),
|
|
1775
|
+
`GitHub comment on #${number}`
|
|
1776
|
+
);
|
|
1777
|
+
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
1778
|
+
return { body, url };
|
|
1779
|
+
}
|
|
1780
|
+
async function updateGitHubIssue(input, opts) {
|
|
1781
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1782
|
+
const title = input.title?.trim();
|
|
1783
|
+
const body = input.body;
|
|
1784
|
+
const state = input.state?.trim().toLowerCase();
|
|
1785
|
+
if (!title && body === void 0 && !state) {
|
|
1786
|
+
throw new Error("github_update_issue needs at least one of title, body, state");
|
|
1787
|
+
}
|
|
1788
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1789
|
+
if (state === "closed" || state === "close") {
|
|
1790
|
+
requireGhOk(
|
|
1791
|
+
await gh(["issue", "close", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1792
|
+
`GitHub close #${number}`
|
|
1793
|
+
);
|
|
1794
|
+
} else if (state === "open" || state === "reopen") {
|
|
1795
|
+
requireGhOk(
|
|
1796
|
+
await gh(["issue", "reopen", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1797
|
+
`GitHub reopen #${number}`
|
|
1798
|
+
);
|
|
1799
|
+
} else if (state) {
|
|
1800
|
+
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
1801
|
+
}
|
|
1802
|
+
if (title || body !== void 0) {
|
|
1803
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
1804
|
+
if (title) args.push("--title", title);
|
|
1805
|
+
if (body !== void 0) args.push("--body", body);
|
|
1806
|
+
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
1807
|
+
}
|
|
1808
|
+
return getGitHubIssue(String(number), opts);
|
|
1809
|
+
}
|
|
1810
|
+
async function createGitHubIssue(input, opts) {
|
|
1811
|
+
const title = input.title.trim();
|
|
1812
|
+
if (!title) throw new Error("GitHub issue title is required");
|
|
1813
|
+
const parent = input.parent?.trim();
|
|
1814
|
+
const parentNumber = parent ? parseGitHubIssueNumber(parent) : null;
|
|
1815
|
+
const bodyParts = [
|
|
1816
|
+
parentNumber ? `Spin-off of #${parentNumber}.` : null,
|
|
1817
|
+
input.body?.trim() || null
|
|
1818
|
+
].filter(Boolean);
|
|
1819
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1820
|
+
const args = ["issue", "create", ...repoArgs, "--title", title];
|
|
1821
|
+
if (bodyParts.length) args.push("--body", bodyParts.join("\n\n"));
|
|
1822
|
+
const created = await gh([...args, "--json", "number,url,title"], cwd, { reject: false });
|
|
1823
|
+
if (created.exitCode === 0 && created.stdout.trim()) {
|
|
1824
|
+
try {
|
|
1825
|
+
const parsed = JSON.parse(created.stdout);
|
|
1826
|
+
if (parsed.number) return getGitHubIssue(String(parsed.number), opts);
|
|
1827
|
+
} catch {
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
const fallback = created.exitCode === 0 ? created : await gh(args, cwd, { reject: false });
|
|
1831
|
+
const stdout = requireGhOk(fallback, "GitHub create issue");
|
|
1832
|
+
const url = stdout.trim().match(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/i);
|
|
1833
|
+
if (url?.[1]) return getGitHubIssue(url[1], opts);
|
|
1834
|
+
throw new Error(`GitHub create issue: could not parse issue from ${stdout.trim() || "empty output"}`);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// src/mcp/github-tools.ts
|
|
1632
1838
|
function text3(payload, isError = false) {
|
|
1633
1839
|
return mcpJson(payload, isError);
|
|
1634
1840
|
}
|
|
@@ -1638,7 +1844,81 @@ function fail3(err) {
|
|
|
1638
1844
|
true
|
|
1639
1845
|
);
|
|
1640
1846
|
}
|
|
1641
|
-
var
|
|
1847
|
+
var repoPathSchema = z3.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
1848
|
+
function registerGithubIssueTools(server) {
|
|
1849
|
+
server.tool(
|
|
1850
|
+
"github_get_issue",
|
|
1851
|
+
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Uses Account gh.",
|
|
1852
|
+
{ id: z3.string(), repoPath: repoPathSchema },
|
|
1853
|
+
async ({ id, repoPath }) => {
|
|
1854
|
+
try {
|
|
1855
|
+
return text3(await getGitHubIssue(id, { repoPath }));
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
return fail3(err);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
);
|
|
1861
|
+
server.tool(
|
|
1862
|
+
"github_comment",
|
|
1863
|
+
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh.",
|
|
1864
|
+
{ id: z3.string(), body: z3.string(), repoPath: repoPathSchema },
|
|
1865
|
+
async ({ id, body, repoPath }) => {
|
|
1866
|
+
try {
|
|
1867
|
+
return text3(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
return fail3(err);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
);
|
|
1873
|
+
server.tool(
|
|
1874
|
+
"github_update_issue",
|
|
1875
|
+
"Update a GitHub issue (#123). Pass title, body, and/or state (open|closed).",
|
|
1876
|
+
{
|
|
1877
|
+
id: z3.string(),
|
|
1878
|
+
title: z3.string().optional(),
|
|
1879
|
+
body: z3.string().optional(),
|
|
1880
|
+
state: z3.string().optional().describe("open or closed"),
|
|
1881
|
+
repoPath: repoPathSchema
|
|
1882
|
+
},
|
|
1883
|
+
async (args) => {
|
|
1884
|
+
try {
|
|
1885
|
+
return text3(await updateGitHubIssue(args, { repoPath: args.repoPath }));
|
|
1886
|
+
} catch (err) {
|
|
1887
|
+
return fail3(err);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
);
|
|
1891
|
+
server.tool(
|
|
1892
|
+
"github_create_issue",
|
|
1893
|
+
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
1894
|
+
{
|
|
1895
|
+
title: z3.string(),
|
|
1896
|
+
body: z3.string().optional(),
|
|
1897
|
+
parent: z3.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
1898
|
+
repoPath: repoPathSchema
|
|
1899
|
+
},
|
|
1900
|
+
async (args) => {
|
|
1901
|
+
try {
|
|
1902
|
+
return text3(await createGitHubIssue(args, { repoPath: args.repoPath }));
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
return fail3(err);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/mcp/linear-tools.ts
|
|
1911
|
+
import { z as z4 } from "zod";
|
|
1912
|
+
function text4(payload, isError = false) {
|
|
1913
|
+
return mcpJson(payload, isError);
|
|
1914
|
+
}
|
|
1915
|
+
function fail4(err) {
|
|
1916
|
+
return text4(
|
|
1917
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
1918
|
+
true
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
var prioritySchema = z4.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
1642
1922
|
function registerLinearTools(server) {
|
|
1643
1923
|
server.tool(
|
|
1644
1924
|
"linear_list_teams",
|
|
@@ -1646,9 +1926,9 @@ function registerLinearTools(server) {
|
|
|
1646
1926
|
{},
|
|
1647
1927
|
async () => {
|
|
1648
1928
|
try {
|
|
1649
|
-
return
|
|
1929
|
+
return text4(await listLinearTeams());
|
|
1650
1930
|
} catch (err) {
|
|
1651
|
-
return
|
|
1931
|
+
return fail4(err);
|
|
1652
1932
|
}
|
|
1653
1933
|
}
|
|
1654
1934
|
);
|
|
@@ -1656,9 +1936,9 @@ function registerLinearTools(server) {
|
|
|
1656
1936
|
"linear_search_issues",
|
|
1657
1937
|
"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
1938
|
{
|
|
1659
|
-
query:
|
|
1660
|
-
assignee:
|
|
1661
|
-
limit:
|
|
1939
|
+
query: z4.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
1940
|
+
assignee: z4.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
1941
|
+
limit: z4.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
1662
1942
|
},
|
|
1663
1943
|
async ({ query, assignee, limit }) => {
|
|
1664
1944
|
try {
|
|
@@ -1679,38 +1959,39 @@ function registerLinearTools(server) {
|
|
|
1679
1959
|
})
|
|
1680
1960
|
);
|
|
1681
1961
|
} catch (err) {
|
|
1682
|
-
return
|
|
1962
|
+
return fail4(err);
|
|
1683
1963
|
}
|
|
1684
1964
|
}
|
|
1685
1965
|
);
|
|
1686
1966
|
server.tool(
|
|
1687
1967
|
"linear_get_issue",
|
|
1688
1968
|
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
1689
|
-
{ id:
|
|
1969
|
+
{ id: z4.string() },
|
|
1690
1970
|
async ({ id }) => {
|
|
1691
1971
|
try {
|
|
1692
|
-
return
|
|
1972
|
+
return text4(await getLinearIssue(id));
|
|
1693
1973
|
} catch (err) {
|
|
1694
|
-
return
|
|
1974
|
+
return fail4(err);
|
|
1695
1975
|
}
|
|
1696
1976
|
}
|
|
1697
1977
|
);
|
|
1698
1978
|
server.tool(
|
|
1699
1979
|
"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.',
|
|
1980
|
+
'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
1981
|
{
|
|
1702
|
-
team:
|
|
1703
|
-
title:
|
|
1704
|
-
description:
|
|
1705
|
-
state:
|
|
1706
|
-
assignee:
|
|
1707
|
-
priority: prioritySchema
|
|
1982
|
+
team: z4.string(),
|
|
1983
|
+
title: z4.string(),
|
|
1984
|
+
description: z4.string().optional(),
|
|
1985
|
+
state: z4.string().optional(),
|
|
1986
|
+
assignee: z4.string().nullable().optional(),
|
|
1987
|
+
priority: prioritySchema,
|
|
1988
|
+
parent: z4.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
1708
1989
|
},
|
|
1709
1990
|
async (args) => {
|
|
1710
1991
|
try {
|
|
1711
|
-
return
|
|
1992
|
+
return text4(await createLinearIssue(args));
|
|
1712
1993
|
} catch (err) {
|
|
1713
|
-
return
|
|
1994
|
+
return fail4(err);
|
|
1714
1995
|
}
|
|
1715
1996
|
}
|
|
1716
1997
|
);
|
|
@@ -1718,18 +1999,18 @@ function registerLinearTools(server) {
|
|
|
1718
1999
|
"linear_update_issue",
|
|
1719
2000
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
1720
2001
|
{
|
|
1721
|
-
id:
|
|
1722
|
-
title:
|
|
1723
|
-
description:
|
|
1724
|
-
state:
|
|
1725
|
-
assignee:
|
|
2002
|
+
id: z4.string(),
|
|
2003
|
+
title: z4.string().optional(),
|
|
2004
|
+
description: z4.string().optional(),
|
|
2005
|
+
state: z4.string().optional(),
|
|
2006
|
+
assignee: z4.string().nullable().optional(),
|
|
1726
2007
|
priority: prioritySchema
|
|
1727
2008
|
},
|
|
1728
2009
|
async (args) => {
|
|
1729
2010
|
try {
|
|
1730
|
-
return
|
|
2011
|
+
return text4(await updateLinearIssue(args));
|
|
1731
2012
|
} catch (err) {
|
|
1732
|
-
return
|
|
2013
|
+
return fail4(err);
|
|
1733
2014
|
}
|
|
1734
2015
|
}
|
|
1735
2016
|
);
|
|
@@ -1737,14 +2018,14 @@ function registerLinearTools(server) {
|
|
|
1737
2018
|
"linear_comment",
|
|
1738
2019
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
1739
2020
|
{
|
|
1740
|
-
id:
|
|
1741
|
-
body:
|
|
2021
|
+
id: z4.string(),
|
|
2022
|
+
body: z4.string()
|
|
1742
2023
|
},
|
|
1743
2024
|
async (args) => {
|
|
1744
2025
|
try {
|
|
1745
|
-
return
|
|
2026
|
+
return text4(await commentLinearIssue(args));
|
|
1746
2027
|
} catch (err) {
|
|
1747
|
-
return
|
|
2028
|
+
return fail4(err);
|
|
1748
2029
|
}
|
|
1749
2030
|
}
|
|
1750
2031
|
);
|
|
@@ -1752,6 +2033,7 @@ function registerLinearTools(server) {
|
|
|
1752
2033
|
|
|
1753
2034
|
// src/mcp/issue-vendor-tools.ts
|
|
1754
2035
|
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
2036
|
+
registerGithubIssueTools(server);
|
|
1755
2037
|
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
1756
2038
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
1757
2039
|
}
|
|
@@ -1791,15 +2073,15 @@ function formatMcpPrList(input) {
|
|
|
1791
2073
|
}
|
|
1792
2074
|
|
|
1793
2075
|
// src/mcp/schedule-tools.ts
|
|
1794
|
-
import { z as
|
|
1795
|
-
function
|
|
2076
|
+
import { z as z5 } from "zod";
|
|
2077
|
+
function text5(payload, isError = false) {
|
|
1796
2078
|
return {
|
|
1797
2079
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
1798
2080
|
...isError ? { isError: true } : {}
|
|
1799
2081
|
};
|
|
1800
2082
|
}
|
|
1801
|
-
function
|
|
1802
|
-
return
|
|
2083
|
+
function fail5(err) {
|
|
2084
|
+
return text5(
|
|
1803
2085
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
1804
2086
|
true
|
|
1805
2087
|
);
|
|
@@ -1824,9 +2106,9 @@ function registerScheduleTools(server) {
|
|
|
1824
2106
|
{},
|
|
1825
2107
|
async () => {
|
|
1826
2108
|
try {
|
|
1827
|
-
return
|
|
2109
|
+
return text5({ schedules: listSchedules() });
|
|
1828
2110
|
} catch (err) {
|
|
1829
|
-
return
|
|
2111
|
+
return fail5(err);
|
|
1830
2112
|
}
|
|
1831
2113
|
}
|
|
1832
2114
|
);
|
|
@@ -1834,24 +2116,24 @@ function registerScheduleTools(server) {
|
|
|
1834
2116
|
"create_schedule",
|
|
1835
2117
|
"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
2118
|
{
|
|
1837
|
-
prompt:
|
|
1838
|
-
name:
|
|
1839
|
-
at:
|
|
1840
|
-
every:
|
|
1841
|
-
cron:
|
|
1842
|
-
tz:
|
|
1843
|
-
threadId:
|
|
2119
|
+
prompt: z5.string().describe("User message / goal queued when the schedule fires"),
|
|
2120
|
+
name: z5.string().optional(),
|
|
2121
|
+
at: z5.string().optional().describe("ISO datetime for a one-shot"),
|
|
2122
|
+
every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
2123
|
+
cron: z5.string().optional().describe("5-field cron expression"),
|
|
2124
|
+
tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
2125
|
+
threadId: z5.string().optional().describe(
|
|
1844
2126
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
1845
2127
|
),
|
|
1846
|
-
agent:
|
|
1847
|
-
model:
|
|
2128
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
2129
|
+
model: z5.string().optional()
|
|
1848
2130
|
},
|
|
1849
2131
|
async (args) => {
|
|
1850
2132
|
try {
|
|
1851
2133
|
const when = parseWhen(args);
|
|
1852
2134
|
const threadId = resolveScheduleThreadId(args.threadId);
|
|
1853
2135
|
if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
|
|
1854
|
-
return
|
|
2136
|
+
return fail5(
|
|
1855
2137
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
1856
2138
|
);
|
|
1857
2139
|
}
|
|
@@ -1864,12 +2146,12 @@ function registerScheduleTools(server) {
|
|
|
1864
2146
|
model: args.model,
|
|
1865
2147
|
createdBy: "mcp"
|
|
1866
2148
|
});
|
|
1867
|
-
return
|
|
2149
|
+
return text5({
|
|
1868
2150
|
schedule,
|
|
1869
2151
|
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
2152
|
});
|
|
1871
2153
|
} catch (err) {
|
|
1872
|
-
return
|
|
2154
|
+
return fail5(err);
|
|
1873
2155
|
}
|
|
1874
2156
|
}
|
|
1875
2157
|
);
|
|
@@ -1877,17 +2159,17 @@ function registerScheduleTools(server) {
|
|
|
1877
2159
|
"update_schedule",
|
|
1878
2160
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
1879
2161
|
{
|
|
1880
|
-
id:
|
|
1881
|
-
prompt:
|
|
1882
|
-
name:
|
|
1883
|
-
at:
|
|
1884
|
-
every:
|
|
1885
|
-
cron:
|
|
1886
|
-
tz:
|
|
1887
|
-
threadId:
|
|
1888
|
-
agent:
|
|
1889
|
-
model:
|
|
1890
|
-
enabled:
|
|
2162
|
+
id: z5.string(),
|
|
2163
|
+
prompt: z5.string().optional(),
|
|
2164
|
+
name: z5.string().optional(),
|
|
2165
|
+
at: z5.string().optional(),
|
|
2166
|
+
every: z5.string().optional(),
|
|
2167
|
+
cron: z5.string().optional(),
|
|
2168
|
+
tz: z5.string().optional(),
|
|
2169
|
+
threadId: z5.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
2170
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
2171
|
+
model: z5.string().optional(),
|
|
2172
|
+
enabled: z5.boolean().optional()
|
|
1891
2173
|
},
|
|
1892
2174
|
async (args) => {
|
|
1893
2175
|
try {
|
|
@@ -1902,7 +2184,7 @@ function registerScheduleTools(server) {
|
|
|
1902
2184
|
if (args.threadId !== void 0) {
|
|
1903
2185
|
threadId = resolveScheduleThreadId(args.threadId);
|
|
1904
2186
|
if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
|
|
1905
|
-
return
|
|
2187
|
+
return fail5(
|
|
1906
2188
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
1907
2189
|
);
|
|
1908
2190
|
}
|
|
@@ -1916,38 +2198,38 @@ function registerScheduleTools(server) {
|
|
|
1916
2198
|
enabled: args.enabled,
|
|
1917
2199
|
model: args.model
|
|
1918
2200
|
});
|
|
1919
|
-
return
|
|
2201
|
+
return text5({ schedule });
|
|
1920
2202
|
} catch (err) {
|
|
1921
|
-
return
|
|
2203
|
+
return fail5(err);
|
|
1922
2204
|
}
|
|
1923
2205
|
}
|
|
1924
2206
|
);
|
|
1925
2207
|
server.tool(
|
|
1926
2208
|
"delete_schedule",
|
|
1927
2209
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
1928
|
-
{ id:
|
|
2210
|
+
{ id: z5.string() },
|
|
1929
2211
|
async ({ id }) => {
|
|
1930
2212
|
try {
|
|
1931
2213
|
deleteSchedule(id);
|
|
1932
|
-
return
|
|
2214
|
+
return text5({ ok: true, id });
|
|
1933
2215
|
} catch (err) {
|
|
1934
|
-
return
|
|
2216
|
+
return fail5(err);
|
|
1935
2217
|
}
|
|
1936
2218
|
}
|
|
1937
2219
|
);
|
|
1938
2220
|
server.tool(
|
|
1939
2221
|
"run_schedule",
|
|
1940
2222
|
"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:
|
|
2223
|
+
{ id: z5.string() },
|
|
1942
2224
|
async ({ id }) => {
|
|
1943
2225
|
try {
|
|
1944
2226
|
if (!getSchedule(id)) {
|
|
1945
|
-
return
|
|
2227
|
+
return fail5(new Error(`Schedule not found: ${id}`));
|
|
1946
2228
|
}
|
|
1947
2229
|
const schedule = await fireSchedule(id);
|
|
1948
|
-
return
|
|
2230
|
+
return text5({ schedule });
|
|
1949
2231
|
} catch (err) {
|
|
1950
|
-
return
|
|
2232
|
+
return fail5(err);
|
|
1951
2233
|
}
|
|
1952
2234
|
}
|
|
1953
2235
|
);
|
|
@@ -2303,6 +2585,9 @@ async function startMcpServer() {
|
|
|
2303
2585
|
version: "0.1.0"
|
|
2304
2586
|
});
|
|
2305
2587
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
2588
|
+
if (worktreeProfile) {
|
|
2589
|
+
registerConnectedIssueVendorTools(server);
|
|
2590
|
+
}
|
|
2306
2591
|
if (!worktreeProfile) {
|
|
2307
2592
|
server.tool(
|
|
2308
2593
|
"list_workspaces",
|
|
@@ -2355,13 +2640,13 @@ async function startMcpServer() {
|
|
|
2355
2640
|
"list_board",
|
|
2356
2641
|
"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
2642
|
{
|
|
2358
|
-
query:
|
|
2359
|
-
repoPath:
|
|
2360
|
-
kind:
|
|
2361
|
-
column:
|
|
2643
|
+
query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
2644
|
+
repoPath: z6.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
2645
|
+
kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
2646
|
+
column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
2362
2647
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
2363
2648
|
),
|
|
2364
|
-
limit:
|
|
2649
|
+
limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
2365
2650
|
},
|
|
2366
2651
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
2367
2652
|
const workspaces = orch.listWorkspaces();
|
|
@@ -2399,7 +2684,7 @@ async function startMcpServer() {
|
|
|
2399
2684
|
server.tool(
|
|
2400
2685
|
"get_thread",
|
|
2401
2686
|
"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:
|
|
2687
|
+
{ ref: z6.string() },
|
|
2403
2688
|
async ({ ref }) => {
|
|
2404
2689
|
const t = orch.getThread(ref);
|
|
2405
2690
|
if (!t) {
|
|
@@ -2441,17 +2726,17 @@ async function startMcpServer() {
|
|
|
2441
2726
|
"present_artifact",
|
|
2442
2727
|
"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
2728
|
{
|
|
2444
|
-
title:
|
|
2445
|
-
type:
|
|
2729
|
+
title: z6.string().describe("Short title shown in the artifact pane header"),
|
|
2730
|
+
type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
2446
2731
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
2447
2732
|
),
|
|
2448
|
-
content:
|
|
2733
|
+
content: z6.string().describe(
|
|
2449
2734
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
2450
2735
|
),
|
|
2451
|
-
artifact_id:
|
|
2452
|
-
status:
|
|
2453
|
-
phase:
|
|
2454
|
-
mode:
|
|
2736
|
+
artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
2737
|
+
status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
2738
|
+
phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
2739
|
+
mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
2455
2740
|
},
|
|
2456
2741
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
2457
2742
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -2472,15 +2757,15 @@ async function startMcpServer() {
|
|
|
2472
2757
|
"ask_user",
|
|
2473
2758
|
"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
2759
|
{
|
|
2475
|
-
questions:
|
|
2476
|
-
|
|
2477
|
-
question:
|
|
2478
|
-
header:
|
|
2479
|
-
multiSelect:
|
|
2480
|
-
options:
|
|
2481
|
-
|
|
2482
|
-
label:
|
|
2483
|
-
description:
|
|
2760
|
+
questions: z6.array(
|
|
2761
|
+
z6.object({
|
|
2762
|
+
question: z6.string().describe("Full question text ending with ?"),
|
|
2763
|
+
header: z6.string().max(24).optional().describe("Short label shown above the question"),
|
|
2764
|
+
multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
|
|
2765
|
+
options: z6.array(
|
|
2766
|
+
z6.object({
|
|
2767
|
+
label: z6.string(),
|
|
2768
|
+
description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
2484
2769
|
})
|
|
2485
2770
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
2486
2771
|
})
|
|
@@ -2499,12 +2784,12 @@ async function startMcpServer() {
|
|
|
2499
2784
|
"present_plan",
|
|
2500
2785
|
"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
2786
|
{
|
|
2502
|
-
title:
|
|
2503
|
-
content:
|
|
2504
|
-
thread_id:
|
|
2787
|
+
title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
2788
|
+
content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
2789
|
+
thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
2505
2790
|
},
|
|
2506
2791
|
async ({ title, content, thread_id }) => {
|
|
2507
|
-
const { writePlanFile } = await import("../plan-file-
|
|
2792
|
+
const { writePlanFile } = await import("../plan-file-I24VJVP2.js");
|
|
2508
2793
|
let root = process.cwd();
|
|
2509
2794
|
if (thread_id?.trim()) {
|
|
2510
2795
|
const t = orch.getThread(thread_id.trim());
|
|
@@ -2524,15 +2809,15 @@ async function startMcpServer() {
|
|
|
2524
2809
|
"present_schema",
|
|
2525
2810
|
"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
2811
|
{
|
|
2527
|
-
title:
|
|
2528
|
-
mode:
|
|
2529
|
-
datasource:
|
|
2530
|
-
resource_id:
|
|
2531
|
-
record_id:
|
|
2532
|
-
resource:
|
|
2533
|
-
record:
|
|
2534
|
-
records:
|
|
2535
|
-
pane_id:
|
|
2812
|
+
title: z6.string().describe("Pane title"),
|
|
2813
|
+
mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
2814
|
+
datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
2815
|
+
resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
2816
|
+
record_id: z6.string().optional().describe("Record id when opening form mode"),
|
|
2817
|
+
resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
2818
|
+
record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
2819
|
+
records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
|
|
2820
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
2536
2821
|
},
|
|
2537
2822
|
async (args) => {
|
|
2538
2823
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -2553,10 +2838,10 @@ async function startMcpServer() {
|
|
|
2553
2838
|
"present_files",
|
|
2554
2839
|
"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
2840
|
{
|
|
2556
|
-
title:
|
|
2557
|
-
datasource:
|
|
2558
|
-
path:
|
|
2559
|
-
pane_id:
|
|
2841
|
+
title: z6.string().optional().describe("Pane title (default: Files)"),
|
|
2842
|
+
datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
2843
|
+
path: z6.string().optional().describe("Initial folder path (e.g. public)"),
|
|
2844
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
2560
2845
|
},
|
|
2561
2846
|
async (args) => {
|
|
2562
2847
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -2571,6 +2856,20 @@ async function startMcpServer() {
|
|
|
2571
2856
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
2572
2857
|
}
|
|
2573
2858
|
);
|
|
2859
|
+
server.tool(
|
|
2860
|
+
"wait_for_job",
|
|
2861
|
+
"Wait on a detached long job (tests, pack, deploy) started with detached-job.js. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
2862
|
+
{
|
|
2863
|
+
id: z6.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
|
|
2864
|
+
timeoutMs: z6.number().optional()
|
|
2865
|
+
},
|
|
2866
|
+
async ({ id, timeoutMs }) => {
|
|
2867
|
+
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
2868
|
+
timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
|
|
2869
|
+
});
|
|
2870
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
2871
|
+
}
|
|
2872
|
+
);
|
|
2574
2873
|
if (!worktreeProfile) {
|
|
2575
2874
|
registerSlackTools(server);
|
|
2576
2875
|
registerConnectedIssueVendorTools(server);
|
|
@@ -2583,7 +2882,7 @@ async function startMcpServer() {
|
|
|
2583
2882
|
"set_caffeinate",
|
|
2584
2883
|
"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
2884
|
{
|
|
2586
|
-
enabled:
|
|
2885
|
+
enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
2587
2886
|
},
|
|
2588
2887
|
async ({ enabled }) => {
|
|
2589
2888
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -2621,18 +2920,18 @@ async function startMcpServer() {
|
|
|
2621
2920
|
"create_thread",
|
|
2622
2921
|
`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
2922
|
{
|
|
2624
|
-
sourceType:
|
|
2625
|
-
sourceRef:
|
|
2626
|
-
agent:
|
|
2627
|
-
model:
|
|
2923
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]),
|
|
2924
|
+
sourceRef: z6.string(),
|
|
2925
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
2926
|
+
model: z6.string().nullable().optional().describe(
|
|
2628
2927
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
2629
2928
|
),
|
|
2630
|
-
repoPath:
|
|
2631
|
-
title:
|
|
2632
|
-
cowboy:
|
|
2929
|
+
repoPath: z6.string(),
|
|
2930
|
+
title: z6.string().optional(),
|
|
2931
|
+
cowboy: z6.boolean().optional().describe(
|
|
2633
2932
|
"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
2933
|
),
|
|
2635
|
-
parentThreadId:
|
|
2934
|
+
parentThreadId: z6.string().optional().describe(
|
|
2636
2935
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
2637
2936
|
)
|
|
2638
2937
|
},
|
|
@@ -2648,10 +2947,10 @@ async function startMcpServer() {
|
|
|
2648
2947
|
"start_board_card",
|
|
2649
2948
|
"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
2949
|
{
|
|
2651
|
-
kind:
|
|
2652
|
-
ref:
|
|
2653
|
-
repoPath:
|
|
2654
|
-
title:
|
|
2950
|
+
kind: z6.enum(["ticket", "pr", "branch"]),
|
|
2951
|
+
ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
2952
|
+
repoPath: z6.string().describe("Workspace path from list_workspaces / list_board"),
|
|
2953
|
+
title: z6.string().optional()
|
|
2655
2954
|
},
|
|
2656
2955
|
async ({ kind, ref, repoPath, title }) => {
|
|
2657
2956
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -2755,9 +3054,9 @@ async function startMcpServer() {
|
|
|
2755
3054
|
"send_to_thread",
|
|
2756
3055
|
'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
3056
|
{
|
|
2758
|
-
ref:
|
|
2759
|
-
prompt:
|
|
2760
|
-
force_stop:
|
|
3057
|
+
ref: z6.string(),
|
|
3058
|
+
prompt: z6.string(),
|
|
3059
|
+
force_stop: z6.boolean().optional()
|
|
2761
3060
|
},
|
|
2762
3061
|
async ({ ref, prompt, force_stop }) => {
|
|
2763
3062
|
if (force_stop) {
|
|
@@ -2786,8 +3085,8 @@ async function startMcpServer() {
|
|
|
2786
3085
|
"wait_for_turn",
|
|
2787
3086
|
"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
3087
|
{
|
|
2789
|
-
ref:
|
|
2790
|
-
timeoutMs:
|
|
3088
|
+
ref: z6.string(),
|
|
3089
|
+
timeoutMs: z6.number().optional()
|
|
2791
3090
|
},
|
|
2792
3091
|
async ({ ref, timeoutMs }) => {
|
|
2793
3092
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -2817,7 +3116,7 @@ async function startMcpServer() {
|
|
|
2817
3116
|
server.tool(
|
|
2818
3117
|
"get_turn_result",
|
|
2819
3118
|
"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:
|
|
3119
|
+
{ ref: z6.string() },
|
|
2821
3120
|
async ({ ref }) => {
|
|
2822
3121
|
const result = orch.getTurnResult(ref);
|
|
2823
3122
|
return {
|
|
@@ -2838,8 +3137,8 @@ async function startMcpServer() {
|
|
|
2838
3137
|
"stop_thread",
|
|
2839
3138
|
"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
3139
|
{
|
|
2841
|
-
ref:
|
|
2842
|
-
force:
|
|
3140
|
+
ref: z6.string(),
|
|
3141
|
+
force: z6.boolean().optional()
|
|
2843
3142
|
},
|
|
2844
3143
|
async ({ ref, force }) => {
|
|
2845
3144
|
const t = orch.getThread(ref);
|
|
@@ -2869,7 +3168,7 @@ async function startMcpServer() {
|
|
|
2869
3168
|
server.tool(
|
|
2870
3169
|
"archive_thread",
|
|
2871
3170
|
"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:
|
|
3171
|
+
{ ref: z6.string() },
|
|
2873
3172
|
async ({ ref }) => {
|
|
2874
3173
|
const t = orch.getThread(ref);
|
|
2875
3174
|
if (!t) {
|
|
@@ -2902,7 +3201,7 @@ async function startMcpServer() {
|
|
|
2902
3201
|
server.tool(
|
|
2903
3202
|
"restore_thread",
|
|
2904
3203
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
2905
|
-
{ ref:
|
|
3204
|
+
{ ref: z6.string() },
|
|
2906
3205
|
async ({ ref }) => {
|
|
2907
3206
|
try {
|
|
2908
3207
|
const restored = await orch.restore(ref);
|
|
@@ -2928,8 +3227,8 @@ async function startMcpServer() {
|
|
|
2928
3227
|
"get_diff",
|
|
2929
3228
|
"Compact diff summary (capped hunks, paginated)",
|
|
2930
3229
|
{
|
|
2931
|
-
ref:
|
|
2932
|
-
maxFiles:
|
|
3230
|
+
ref: z6.string(),
|
|
3231
|
+
maxFiles: z6.number().optional()
|
|
2933
3232
|
},
|
|
2934
3233
|
async ({ ref, maxFiles }) => {
|
|
2935
3234
|
const summary = await orch.diffSummary(ref);
|
|
@@ -2949,7 +3248,7 @@ async function startMcpServer() {
|
|
|
2949
3248
|
server.tool(
|
|
2950
3249
|
"get_pr_checks",
|
|
2951
3250
|
"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:
|
|
3251
|
+
{ ref: z6.string().describe("Worktree thread id/ref") },
|
|
2953
3252
|
async ({ ref }) => {
|
|
2954
3253
|
try {
|
|
2955
3254
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -2965,7 +3264,7 @@ async function startMcpServer() {
|
|
|
2965
3264
|
server.tool(
|
|
2966
3265
|
"request_review",
|
|
2967
3266
|
'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:
|
|
3267
|
+
{ ref: z6.string().describe("Worktree thread id/ref to review") },
|
|
2969
3268
|
async ({ ref }) => {
|
|
2970
3269
|
try {
|
|
2971
3270
|
const tab = await orch.requestReview(ref);
|
|
@@ -2994,8 +3293,8 @@ async function startMcpServer() {
|
|
|
2994
3293
|
"ask_git",
|
|
2995
3294
|
"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
3295
|
{
|
|
2997
|
-
ref:
|
|
2998
|
-
action:
|
|
3296
|
+
ref: z6.string().describe("Worktree thread id/ref"),
|
|
3297
|
+
action: z6.enum(AGENT_GIT_ACTIONS).describe(
|
|
2999
3298
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
3000
3299
|
)
|
|
3001
3300
|
},
|
|
@@ -3040,7 +3339,7 @@ async function startMcpServer() {
|
|
|
3040
3339
|
}
|
|
3041
3340
|
}
|
|
3042
3341
|
);
|
|
3043
|
-
const agentEnum =
|
|
3342
|
+
const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
3044
3343
|
server.tool(
|
|
3045
3344
|
"list_models",
|
|
3046
3345
|
"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 +3362,11 @@ async function startMcpServer() {
|
|
|
3063
3362
|
"fork_worktree",
|
|
3064
3363
|
"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
3364
|
{
|
|
3066
|
-
ref:
|
|
3067
|
-
through_index:
|
|
3365
|
+
ref: z6.string().describe("Worktree thread id/ref to fork"),
|
|
3366
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
3068
3367
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
3069
|
-
model:
|
|
3070
|
-
title:
|
|
3368
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
3369
|
+
title: z6.string().optional()
|
|
3071
3370
|
},
|
|
3072
3371
|
async ({ ref, through_index, agent, model, title }) => {
|
|
3073
3372
|
try {
|
|
@@ -3108,11 +3407,11 @@ async function startMcpServer() {
|
|
|
3108
3407
|
"fork_chat",
|
|
3109
3408
|
"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
3409
|
{
|
|
3111
|
-
ref:
|
|
3112
|
-
through_index:
|
|
3410
|
+
ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
3411
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
3113
3412
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
3114
|
-
model:
|
|
3115
|
-
title:
|
|
3413
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
3414
|
+
title: z6.string().optional()
|
|
3116
3415
|
},
|
|
3117
3416
|
async ({ ref, through_index, agent, model, title }) => {
|
|
3118
3417
|
try {
|
|
@@ -3158,8 +3457,8 @@ async function startMcpServer() {
|
|
|
3158
3457
|
"run_dev_script",
|
|
3159
3458
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
3160
3459
|
{
|
|
3161
|
-
ref:
|
|
3162
|
-
name:
|
|
3460
|
+
ref: z6.string(),
|
|
3461
|
+
name: z6.string().optional()
|
|
3163
3462
|
},
|
|
3164
3463
|
async ({ ref, name }) => {
|
|
3165
3464
|
const result = await orch.startDev(ref, name);
|
|
@@ -3181,7 +3480,7 @@ async function startMcpServer() {
|
|
|
3181
3480
|
server.tool(
|
|
3182
3481
|
"list_run_scripts",
|
|
3183
3482
|
"List named run scripts available for a thread",
|
|
3184
|
-
{ ref:
|
|
3483
|
+
{ ref: z6.string() },
|
|
3185
3484
|
async ({ ref }) => {
|
|
3186
3485
|
const scripts = orch.listThreadRunScripts(ref);
|
|
3187
3486
|
const active = orch.getActiveRuns(ref);
|
|
@@ -3199,8 +3498,8 @@ async function startMcpServer() {
|
|
|
3199
3498
|
"stop_dev_script",
|
|
3200
3499
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
3201
3500
|
{
|
|
3202
|
-
ref:
|
|
3203
|
-
name:
|
|
3501
|
+
ref: z6.string(),
|
|
3502
|
+
name: z6.string().optional()
|
|
3204
3503
|
},
|
|
3205
3504
|
async ({ ref, name }) => {
|
|
3206
3505
|
orch.stopDev(ref, name);
|
|
@@ -3210,7 +3509,7 @@ async function startMcpServer() {
|
|
|
3210
3509
|
server.tool(
|
|
3211
3510
|
"run_setup",
|
|
3212
3511
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
3213
|
-
{ ref:
|
|
3512
|
+
{ ref: z6.string() },
|
|
3214
3513
|
async ({ ref }) => {
|
|
3215
3514
|
const result = await orch.runSetup(ref);
|
|
3216
3515
|
return {
|
|
@@ -3221,7 +3520,7 @@ async function startMcpServer() {
|
|
|
3221
3520
|
server.tool(
|
|
3222
3521
|
"add_workspace",
|
|
3223
3522
|
"Register a git repo as a Sideboard workspace",
|
|
3224
|
-
{ repoPath:
|
|
3523
|
+
{ repoPath: z6.string() },
|
|
3225
3524
|
async ({ repoPath }) => {
|
|
3226
3525
|
const ws = await orch.addWorkspace(repoPath);
|
|
3227
3526
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -3230,7 +3529,7 @@ async function startMcpServer() {
|
|
|
3230
3529
|
server.tool(
|
|
3231
3530
|
"remove_workspace",
|
|
3232
3531
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
3233
|
-
{ repoPath:
|
|
3532
|
+
{ repoPath: z6.string() },
|
|
3234
3533
|
async ({ repoPath }) => {
|
|
3235
3534
|
orch.removeWorkspace(repoPath);
|
|
3236
3535
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -3240,12 +3539,12 @@ async function startMcpServer() {
|
|
|
3240
3539
|
"fanout",
|
|
3241
3540
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
3242
3541
|
{
|
|
3243
|
-
prompt:
|
|
3244
|
-
agents:
|
|
3245
|
-
repoPath:
|
|
3246
|
-
sourceType:
|
|
3247
|
-
sourceRef:
|
|
3248
|
-
title:
|
|
3542
|
+
prompt: z6.string(),
|
|
3543
|
+
agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
3544
|
+
repoPath: z6.string(),
|
|
3545
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
|
|
3546
|
+
sourceRef: z6.string().optional(),
|
|
3547
|
+
title: z6.string().optional()
|
|
3249
3548
|
},
|
|
3250
3549
|
async (args) => {
|
|
3251
3550
|
const threads = await orch.bestOfN(args);
|
|
@@ -3272,8 +3571,8 @@ async function startMcpServer() {
|
|
|
3272
3571
|
"list_branches",
|
|
3273
3572
|
"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
3573
|
{
|
|
3275
|
-
repoPath:
|
|
3276
|
-
unmergedOnly:
|
|
3574
|
+
repoPath: z6.string(),
|
|
3575
|
+
unmergedOnly: z6.boolean().optional()
|
|
3277
3576
|
},
|
|
3278
3577
|
async ({ repoPath, unmergedOnly }) => {
|
|
3279
3578
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3294,19 +3593,19 @@ async function startMcpServer() {
|
|
|
3294
3593
|
"list_prs",
|
|
3295
3594
|
'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
3595
|
{
|
|
3297
|
-
repoPath:
|
|
3298
|
-
query:
|
|
3299
|
-
queue:
|
|
3596
|
+
repoPath: z6.string(),
|
|
3597
|
+
query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
3598
|
+
queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
3300
3599
|
'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
3600
|
),
|
|
3302
|
-
state:
|
|
3303
|
-
label:
|
|
3601
|
+
state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
3602
|
+
label: z6.string().optional().describe(
|
|
3304
3603
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
3305
3604
|
),
|
|
3306
|
-
reviewer:
|
|
3605
|
+
reviewer: z6.string().optional().describe(
|
|
3307
3606
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
3308
3607
|
),
|
|
3309
|
-
limit:
|
|
3608
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
3310
3609
|
},
|
|
3311
3610
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
3312
3611
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3338,7 +3637,7 @@ async function startMcpServer() {
|
|
|
3338
3637
|
server.tool(
|
|
3339
3638
|
"get_pr_stack",
|
|
3340
3639
|
"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:
|
|
3640
|
+
{ ref: z6.string() },
|
|
3342
3641
|
async ({ ref }) => {
|
|
3343
3642
|
const stack = await orch.getPrStack(ref);
|
|
3344
3643
|
return {
|
|
@@ -3350,8 +3649,8 @@ async function startMcpServer() {
|
|
|
3350
3649
|
"open_pr_stack_layers",
|
|
3351
3650
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
3352
3651
|
{
|
|
3353
|
-
ref:
|
|
3354
|
-
layer:
|
|
3652
|
+
ref: z6.string(),
|
|
3653
|
+
layer: z6.number().int().positive().optional()
|
|
3355
3654
|
},
|
|
3356
3655
|
async ({ ref, layer }) => {
|
|
3357
3656
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -3385,9 +3684,9 @@ async function startMcpServer() {
|
|
|
3385
3684
|
"add_stack_layer",
|
|
3386
3685
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
3387
3686
|
{
|
|
3388
|
-
ref:
|
|
3389
|
-
branchName:
|
|
3390
|
-
title:
|
|
3687
|
+
ref: z6.string(),
|
|
3688
|
+
branchName: z6.string(),
|
|
3689
|
+
title: z6.string().optional()
|
|
3391
3690
|
},
|
|
3392
3691
|
async ({ ref, branchName, title }) => {
|
|
3393
3692
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -3416,11 +3715,11 @@ async function startMcpServer() {
|
|
|
3416
3715
|
"create_pr_stack",
|
|
3417
3716
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
3418
3717
|
{
|
|
3419
|
-
repoPath:
|
|
3420
|
-
branches:
|
|
3421
|
-
agent:
|
|
3422
|
-
base:
|
|
3423
|
-
title:
|
|
3718
|
+
repoPath: z6.string(),
|
|
3719
|
+
branches: z6.array(z6.string()).min(1),
|
|
3720
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
3721
|
+
base: z6.string().optional(),
|
|
3722
|
+
title: z6.string().optional()
|
|
3424
3723
|
},
|
|
3425
3724
|
async (args) => {
|
|
3426
3725
|
const result = await orch.createPrStack({
|
|
@@ -3459,10 +3758,10 @@ async function startMcpServer() {
|
|
|
3459
3758
|
"list_issues",
|
|
3460
3759
|
"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
3760
|
{
|
|
3462
|
-
repoPath:
|
|
3463
|
-
query:
|
|
3464
|
-
assignee:
|
|
3465
|
-
limit:
|
|
3761
|
+
repoPath: z6.string(),
|
|
3762
|
+
query: z6.string().optional().describe("Search title, identifier, or description"),
|
|
3763
|
+
assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
3764
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
3466
3765
|
},
|
|
3467
3766
|
async ({ repoPath, query, assignee, limit }) => {
|
|
3468
3767
|
const root = await resolveRepoRoot(repoPath);
|