@sideboard-ai/core 0.1.156 → 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-2FTZAXO3.js → agents-C55LAEUR.js} +2 -2
- package/dist/{agents-IY4CSPWV.js → agents-LCDXN2B2.js} +2 -2
- package/dist/{chunk-TI6UMRWK.js → chunk-4I4VKAPZ.js} +3 -2
- package/dist/{chunk-MSFPM5VK.js → chunk-CVR4RJ6G.js} +3 -2
- package/dist/{chunk-JOK4XWBG.js → chunk-DVJBO3M7.js} +87 -3
- package/dist/{chunk-7EIPEPAH.js → chunk-GWEEGE6L.js} +119 -10
- package/dist/{chunk-UA3YZKRX.js → chunk-KQ4JOGUH.js} +19 -3
- package/dist/{chunk-FDCFXMDG.js → chunk-N27GVFZY.js} +87 -3
- package/dist/{chunk-OLE2BLIX.js → chunk-SUCJAWV4.js} +90 -9
- package/dist/{chunk-XLU7DXHH.js → chunk-TIJ6QVX5.js} +47 -3
- package/dist/{coordinator-prompt-D2FHU5IZ.js → coordinator-prompt-26IJU2AV.js} +1 -1
- package/dist/{coordinator-prompt-UZDNIVP7.js → coordinator-prompt-AM47SATF.js} +1 -1
- package/dist/{global-workspace-5WZVSWNT.js → global-workspace-5BIW26YR.js} +1 -1
- package/dist/{global-workspace-MR75BDBY.js → global-workspace-QV7CC7SK.js} +1 -1
- package/dist/index.cjs +951 -379
- package/dist/index.d.cts +128 -3
- package/dist/index.d.ts +128 -3
- package/dist/index.js +590 -252
- package/dist/mcp/run-stdio.cjs +799 -320
- package/dist/mcp/run-stdio.js +501 -199
- package/dist/{orchestrator-G4WKJG5Y.js → orchestrator-BUH3LJLL.js} +3 -3
- package/dist/{orchestrator-LXO2CDOG.js → orchestrator-KQO4MXBI.js} +3 -3
- package/dist/{workspaces-RP32AW6X.js → workspaces-NYSM2EPX.js} +1 -1
- package/dist/{workspaces-HO4EQNNM.js → workspaces-Q2VCHIAS.js} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -97,6 +97,10 @@ import {
|
|
|
97
97
|
formatAgentInstructions,
|
|
98
98
|
formatArtifactDirective,
|
|
99
99
|
formatDetachedJobInvoke,
|
|
100
|
+
formatIssueToolsDirective,
|
|
101
|
+
formatIssueToolsReminder,
|
|
102
|
+
formatLinearDirective,
|
|
103
|
+
formatLinearReminder,
|
|
100
104
|
formatLongRunningDirective,
|
|
101
105
|
formatLongRunningReminder,
|
|
102
106
|
formatMessagesAsTranscript,
|
|
@@ -144,7 +148,9 @@ import {
|
|
|
144
148
|
isStaleLastErrorDuringTurn,
|
|
145
149
|
isThisProcessDesktopHost,
|
|
146
150
|
issueNeedsWorkspacePick,
|
|
151
|
+
issueTicketFromThread,
|
|
147
152
|
lastRequestOccupancy,
|
|
153
|
+
linearTicketIdFromThread,
|
|
148
154
|
listBranchCommits,
|
|
149
155
|
listConductorWorkspaces,
|
|
150
156
|
listLinearIssues,
|
|
@@ -223,7 +229,7 @@ import {
|
|
|
223
229
|
worktreeCleanupSettings,
|
|
224
230
|
wrapReviewSkillMarkdown,
|
|
225
231
|
writeWorktreeFile
|
|
226
|
-
} from "./chunk-
|
|
232
|
+
} from "./chunk-GWEEGE6L.js";
|
|
227
233
|
import {
|
|
228
234
|
LEGACY_PLAN_FILE_REL,
|
|
229
235
|
PLAN_FILE_NAME,
|
|
@@ -242,6 +248,9 @@ import {
|
|
|
242
248
|
PLAN_MODE_INSTRUCTION,
|
|
243
249
|
SIDEBOARD_MCP_ALLOWED_TOOLS,
|
|
244
250
|
SIDEBOARD_MCP_PROFILE_ENV,
|
|
251
|
+
WORKTREE_ABLETIME_MCP_TOOLS,
|
|
252
|
+
WORKTREE_GITHUB_MCP_TOOLS,
|
|
253
|
+
WORKTREE_LINEAR_MCP_TOOLS,
|
|
245
254
|
WORKTREE_MCP_TOOLS,
|
|
246
255
|
allAdapters,
|
|
247
256
|
applyAgentEvent,
|
|
@@ -323,7 +332,7 @@ import {
|
|
|
323
332
|
withEventParentId,
|
|
324
333
|
withEventsParentId,
|
|
325
334
|
writeInjectedMcpConfig
|
|
326
|
-
} from "./chunk-
|
|
335
|
+
} from "./chunk-TIJ6QVX5.js";
|
|
327
336
|
import {
|
|
328
337
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
329
338
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -370,7 +379,7 @@ import {
|
|
|
370
379
|
stageBuffersAsAttachments,
|
|
371
380
|
syncWorkspacesFromThreads,
|
|
372
381
|
takenTeamSlugsForOrchestration
|
|
373
|
-
} from "./chunk-
|
|
382
|
+
} from "./chunk-CVR4RJ6G.js";
|
|
374
383
|
import {
|
|
375
384
|
FAMOUS_SOCCER_TEAMS,
|
|
376
385
|
GITHUB_PR_BODY_MAX_CHARS,
|
|
@@ -496,6 +505,7 @@ import {
|
|
|
496
505
|
abletimeMcpRequest,
|
|
497
506
|
abletimeMcpUrl,
|
|
498
507
|
callAbleTimeTool,
|
|
508
|
+
commentAbleTimeTask,
|
|
499
509
|
createAbleTimeTask,
|
|
500
510
|
ensureAbleTimeTask,
|
|
501
511
|
getAbleTimeOrientation,
|
|
@@ -510,8 +520,9 @@ import {
|
|
|
510
520
|
searchAbleTimeTasks,
|
|
511
521
|
taskUrl,
|
|
512
522
|
toAbleTimeIssueInfo,
|
|
523
|
+
updateAbleTimeTask,
|
|
513
524
|
verifyAbleTimeConnection
|
|
514
|
-
} from "./chunk-
|
|
525
|
+
} from "./chunk-DVJBO3M7.js";
|
|
515
526
|
import {
|
|
516
527
|
formatFetchError,
|
|
517
528
|
httpFetch,
|
|
@@ -722,9 +733,9 @@ async function getGitHubStatus() {
|
|
|
722
733
|
};
|
|
723
734
|
}
|
|
724
735
|
const status = await run("gh", ["auth", "status"], { reject: false });
|
|
725
|
-
const
|
|
736
|
+
const text6 = `${status.stdout}
|
|
726
737
|
${status.stderr}`;
|
|
727
|
-
const loginMatch =
|
|
738
|
+
const loginMatch = text6.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text6.match(/Logged in to ([^\s]+) as (\S+)/i);
|
|
728
739
|
if (loginMatch) {
|
|
729
740
|
return {
|
|
730
741
|
connected: true,
|
|
@@ -1504,6 +1515,11 @@ async function createLinearIssue(input, opts) {
|
|
|
1504
1515
|
if (input.state?.trim()) {
|
|
1505
1516
|
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
1506
1517
|
}
|
|
1518
|
+
const parentRef = input.parent?.trim();
|
|
1519
|
+
if (parentRef) {
|
|
1520
|
+
const parent = await getLinearIssue(parentRef, opts);
|
|
1521
|
+
mutationInput.parentId = parent.id;
|
|
1522
|
+
}
|
|
1507
1523
|
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
1508
1524
|
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
1509
1525
|
else if (assignee) mutationInput.assigneeId = assignee;
|
|
@@ -1584,6 +1600,190 @@ async function validateLinearApiKey(apiKey) {
|
|
|
1584
1600
|
}
|
|
1585
1601
|
}
|
|
1586
1602
|
|
|
1603
|
+
// src/integrations/github-issues.ts
|
|
1604
|
+
function requireGhOk(result, label) {
|
|
1605
|
+
if (result.exitCode !== 0) {
|
|
1606
|
+
const detail = (result.stderr || result.stdout).trim() || "gh failed";
|
|
1607
|
+
throw new Error(`${label}: ${detail}`);
|
|
1608
|
+
}
|
|
1609
|
+
return result.stdout;
|
|
1610
|
+
}
|
|
1611
|
+
function parseGitHubIssueNumber(id) {
|
|
1612
|
+
const trimmed = id.trim();
|
|
1613
|
+
if (!trimmed) throw new Error("GitHub issue id is required (#123 or a URL)");
|
|
1614
|
+
const url = trimmed.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/i);
|
|
1615
|
+
if (url) return Number(url[1]);
|
|
1616
|
+
const prefixed = trimmed.match(/^gh-(\d+)$/i);
|
|
1617
|
+
if (prefixed) return Number(prefixed[1]);
|
|
1618
|
+
const bare = trimmed.match(/^#?(\d+)$/);
|
|
1619
|
+
if (bare) return Number(bare[1]);
|
|
1620
|
+
throw new Error(`GitHub issue id must be #123, a number, or an issue URL (got ${trimmed})`);
|
|
1621
|
+
}
|
|
1622
|
+
async function resolveGitHubIssueRepo(repoPath) {
|
|
1623
|
+
const cwd = await resolveRepoRoot((repoPath ?? "").trim() || process.cwd());
|
|
1624
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1625
|
+
return { cwd, slug, repoArgs: slug ? ghRepoSelectArgs(slug) : [] };
|
|
1626
|
+
}
|
|
1627
|
+
function mapLabels(raw) {
|
|
1628
|
+
if (!Array.isArray(raw)) return [];
|
|
1629
|
+
return raw.map((item) => typeof item === "string" ? item : String(item?.name ?? "")).map((name) => name.trim()).filter(Boolean);
|
|
1630
|
+
}
|
|
1631
|
+
function mapAssignees(raw) {
|
|
1632
|
+
if (!Array.isArray(raw)) return [];
|
|
1633
|
+
return raw.map(
|
|
1634
|
+
(item) => typeof item === "string" ? item : String(item?.login ?? "")
|
|
1635
|
+
).map((login) => login.trim()).filter(Boolean);
|
|
1636
|
+
}
|
|
1637
|
+
function mapComments2(raw) {
|
|
1638
|
+
if (!Array.isArray(raw)) return [];
|
|
1639
|
+
return raw.map((item) => {
|
|
1640
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
1641
|
+
if (!rec) return null;
|
|
1642
|
+
const body = typeof rec.body === "string" ? rec.body : "";
|
|
1643
|
+
const authorRec = rec.author && typeof rec.author === "object" ? rec.author : null;
|
|
1644
|
+
const comment = {
|
|
1645
|
+
body,
|
|
1646
|
+
id: rec.id != null ? String(rec.id) : void 0,
|
|
1647
|
+
url: typeof rec.url === "string" ? rec.url : void 0,
|
|
1648
|
+
createdAt: typeof rec.createdAt === "string" ? rec.createdAt : void 0,
|
|
1649
|
+
author: authorRec?.login?.trim() || void 0
|
|
1650
|
+
};
|
|
1651
|
+
return comment;
|
|
1652
|
+
}).filter((item) => Boolean(item));
|
|
1653
|
+
}
|
|
1654
|
+
function toGitHubIssue(raw) {
|
|
1655
|
+
const number = Number(raw.number);
|
|
1656
|
+
if (!Number.isFinite(number) || number <= 0) {
|
|
1657
|
+
throw new Error("GitHub issue response was missing a number");
|
|
1658
|
+
}
|
|
1659
|
+
const assignees = mapAssignees(raw.assignees);
|
|
1660
|
+
return {
|
|
1661
|
+
id: `gh-${number}`,
|
|
1662
|
+
identifier: `#${number}`,
|
|
1663
|
+
number,
|
|
1664
|
+
title: String(raw.title ?? ""),
|
|
1665
|
+
url: String(raw.url ?? ""),
|
|
1666
|
+
body: typeof raw.body === "string" && raw.body.trim() ? raw.body : void 0,
|
|
1667
|
+
state: typeof raw.state === "string" ? raw.state : void 0,
|
|
1668
|
+
labels: mapLabels(raw.labels),
|
|
1669
|
+
assignees,
|
|
1670
|
+
comments: mapComments2(raw.comments)
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
function toGitHubIssueInfo(issue) {
|
|
1674
|
+
return {
|
|
1675
|
+
id: issue.id,
|
|
1676
|
+
identifier: issue.identifier,
|
|
1677
|
+
title: issue.title,
|
|
1678
|
+
url: issue.url,
|
|
1679
|
+
labels: issue.labels,
|
|
1680
|
+
provider: "github",
|
|
1681
|
+
assignee: issue.assignees[0],
|
|
1682
|
+
assignees: issue.assignees.length ? issue.assignees : void 0
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
async function getGitHubIssue(id, opts) {
|
|
1686
|
+
const number = parseGitHubIssueNumber(id);
|
|
1687
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1688
|
+
const stdout = requireGhOk(
|
|
1689
|
+
await gh(
|
|
1690
|
+
[
|
|
1691
|
+
"issue",
|
|
1692
|
+
"view",
|
|
1693
|
+
String(number),
|
|
1694
|
+
...repoArgs,
|
|
1695
|
+
"--json",
|
|
1696
|
+
"number,title,body,url,state,labels,assignees,comments,author"
|
|
1697
|
+
],
|
|
1698
|
+
cwd,
|
|
1699
|
+
{ reject: false }
|
|
1700
|
+
),
|
|
1701
|
+
`GitHub issue ${number}`
|
|
1702
|
+
);
|
|
1703
|
+
let parsed;
|
|
1704
|
+
try {
|
|
1705
|
+
parsed = JSON.parse(stdout);
|
|
1706
|
+
} catch {
|
|
1707
|
+
throw new Error(`GitHub issue ${number}: gh returned non-JSON`);
|
|
1708
|
+
}
|
|
1709
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1710
|
+
throw new Error(`GitHub issue not found: #${number}`);
|
|
1711
|
+
}
|
|
1712
|
+
return toGitHubIssue(parsed);
|
|
1713
|
+
}
|
|
1714
|
+
async function commentGitHubIssue(input, opts) {
|
|
1715
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1716
|
+
const body = input.body.trim();
|
|
1717
|
+
if (!body) throw new Error("GitHub comment body is required");
|
|
1718
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1719
|
+
const stdout = requireGhOk(
|
|
1720
|
+
await gh(
|
|
1721
|
+
["issue", "comment", String(number), ...repoArgs, "--body", body],
|
|
1722
|
+
cwd,
|
|
1723
|
+
{ reject: false }
|
|
1724
|
+
),
|
|
1725
|
+
`GitHub comment on #${number}`
|
|
1726
|
+
);
|
|
1727
|
+
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
1728
|
+
return { body, url };
|
|
1729
|
+
}
|
|
1730
|
+
async function updateGitHubIssue(input, opts) {
|
|
1731
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1732
|
+
const title = input.title?.trim();
|
|
1733
|
+
const body = input.body;
|
|
1734
|
+
const state = input.state?.trim().toLowerCase();
|
|
1735
|
+
if (!title && body === void 0 && !state) {
|
|
1736
|
+
throw new Error("github_update_issue needs at least one of title, body, state");
|
|
1737
|
+
}
|
|
1738
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1739
|
+
if (state === "closed" || state === "close") {
|
|
1740
|
+
requireGhOk(
|
|
1741
|
+
await gh(["issue", "close", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1742
|
+
`GitHub close #${number}`
|
|
1743
|
+
);
|
|
1744
|
+
} else if (state === "open" || state === "reopen") {
|
|
1745
|
+
requireGhOk(
|
|
1746
|
+
await gh(["issue", "reopen", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1747
|
+
`GitHub reopen #${number}`
|
|
1748
|
+
);
|
|
1749
|
+
} else if (state) {
|
|
1750
|
+
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
1751
|
+
}
|
|
1752
|
+
if (title || body !== void 0) {
|
|
1753
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
1754
|
+
if (title) args.push("--title", title);
|
|
1755
|
+
if (body !== void 0) args.push("--body", body);
|
|
1756
|
+
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
1757
|
+
}
|
|
1758
|
+
return getGitHubIssue(String(number), opts);
|
|
1759
|
+
}
|
|
1760
|
+
async function createGitHubIssue(input, opts) {
|
|
1761
|
+
const title = input.title.trim();
|
|
1762
|
+
if (!title) throw new Error("GitHub issue title is required");
|
|
1763
|
+
const parent = input.parent?.trim();
|
|
1764
|
+
const parentNumber = parent ? parseGitHubIssueNumber(parent) : null;
|
|
1765
|
+
const bodyParts = [
|
|
1766
|
+
parentNumber ? `Spin-off of #${parentNumber}.` : null,
|
|
1767
|
+
input.body?.trim() || null
|
|
1768
|
+
].filter(Boolean);
|
|
1769
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1770
|
+
const args = ["issue", "create", ...repoArgs, "--title", title];
|
|
1771
|
+
if (bodyParts.length) args.push("--body", bodyParts.join("\n\n"));
|
|
1772
|
+
const created = await gh([...args, "--json", "number,url,title"], cwd, { reject: false });
|
|
1773
|
+
if (created.exitCode === 0 && created.stdout.trim()) {
|
|
1774
|
+
try {
|
|
1775
|
+
const parsed = JSON.parse(created.stdout);
|
|
1776
|
+
if (parsed.number) return getGitHubIssue(String(parsed.number), opts);
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
const fallback = created.exitCode === 0 ? created : await gh(args, cwd, { reject: false });
|
|
1781
|
+
const stdout = requireGhOk(fallback, "GitHub create issue");
|
|
1782
|
+
const url = stdout.trim().match(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/i);
|
|
1783
|
+
if (url?.[1]) return getGitHubIssue(url[1], opts);
|
|
1784
|
+
throw new Error(`GitHub create issue: could not parse issue from ${stdout.trim() || "empty output"}`);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1587
1787
|
// src/integrations/issues.ts
|
|
1588
1788
|
function assigneeKey(assignee) {
|
|
1589
1789
|
return (assignee ?? "").trim().toLowerCase();
|
|
@@ -1860,11 +2060,11 @@ function fenceLanguage(path2, language) {
|
|
|
1860
2060
|
}
|
|
1861
2061
|
function buildCodeRefAttachment(input) {
|
|
1862
2062
|
const path2 = input.path.trim();
|
|
1863
|
-
const
|
|
2063
|
+
const text6 = input.text.replace(/\n$/, "");
|
|
1864
2064
|
if (!path2) {
|
|
1865
2065
|
throw new Error("code reference requires a file path");
|
|
1866
2066
|
}
|
|
1867
|
-
if (!
|
|
2067
|
+
if (!text6.trim()) {
|
|
1868
2068
|
throw new Error("code reference requires selected text");
|
|
1869
2069
|
}
|
|
1870
2070
|
if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
|
|
@@ -1877,7 +2077,7 @@ function buildCodeRefAttachment(input) {
|
|
|
1877
2077
|
`Referenced code from \`${path2}\` (${range}).`,
|
|
1878
2078
|
"",
|
|
1879
2079
|
fence,
|
|
1880
|
-
|
|
2080
|
+
text6,
|
|
1881
2081
|
"```"
|
|
1882
2082
|
].join("\n");
|
|
1883
2083
|
return {
|
|
@@ -1931,16 +2131,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
|
1931
2131
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
1932
2132
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
1933
2133
|
var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
|
|
1934
|
-
function pastedTextStats(
|
|
1935
|
-
const chars =
|
|
2134
|
+
function pastedTextStats(text6) {
|
|
2135
|
+
const chars = text6.length;
|
|
1936
2136
|
if (chars === 0) return { chars: 0, lines: 0 };
|
|
1937
|
-
const lines =
|
|
2137
|
+
const lines = text6.split(/\r\n|\r|\n/).length;
|
|
1938
2138
|
return { chars, lines };
|
|
1939
2139
|
}
|
|
1940
|
-
function shouldAttachPastedText(
|
|
1941
|
-
const trimmed =
|
|
2140
|
+
function shouldAttachPastedText(text6) {
|
|
2141
|
+
const trimmed = text6.trim();
|
|
1942
2142
|
if (!trimmed) return false;
|
|
1943
|
-
const { chars, lines } = pastedTextStats(
|
|
2143
|
+
const { chars, lines } = pastedTextStats(text6);
|
|
1944
2144
|
return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
|
|
1945
2145
|
}
|
|
1946
2146
|
function nextPastedTextName(existing) {
|
|
@@ -1951,13 +2151,13 @@ function nextPastedTextName(existing) {
|
|
|
1951
2151
|
}
|
|
1952
2152
|
return `Pasted text #${max + 1}.txt`;
|
|
1953
2153
|
}
|
|
1954
|
-
function buildPastedTextAttachment(
|
|
2154
|
+
function buildPastedTextAttachment(text6, opts) {
|
|
1955
2155
|
return {
|
|
1956
2156
|
id: opts?.id ?? randomUUID(),
|
|
1957
2157
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
1958
2158
|
kind: "file",
|
|
1959
2159
|
path: opts?.path,
|
|
1960
|
-
content:
|
|
2160
|
+
content: text6
|
|
1961
2161
|
};
|
|
1962
2162
|
}
|
|
1963
2163
|
|
|
@@ -2042,8 +2242,8 @@ function latestPendingPlanQuestions(input) {
|
|
|
2042
2242
|
return null;
|
|
2043
2243
|
}
|
|
2044
2244
|
var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
|
|
2045
|
-
function isPlanQuestionAnswersMessage(
|
|
2046
|
-
return
|
|
2245
|
+
function isPlanQuestionAnswersMessage(text6) {
|
|
2246
|
+
return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
|
|
2047
2247
|
}
|
|
2048
2248
|
function formatPlanQuestionAnswers(questions, answers) {
|
|
2049
2249
|
const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
|
|
@@ -2082,7 +2282,7 @@ function formatPlanQuestionsForChat(questions) {
|
|
|
2082
2282
|
// src/mcp/server.ts
|
|
2083
2283
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2084
2284
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2085
|
-
import { z as
|
|
2285
|
+
import { z as z6 } from "zod";
|
|
2086
2286
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2087
2287
|
import { basename } from "path";
|
|
2088
2288
|
|
|
@@ -2120,9 +2320,9 @@ function mcpWaitFinishedHint(status) {
|
|
|
2120
2320
|
function lastMessagePreview(messages, max = 160) {
|
|
2121
2321
|
if (!messages?.length) return null;
|
|
2122
2322
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2123
|
-
const
|
|
2124
|
-
if (!
|
|
2125
|
-
const flat =
|
|
2323
|
+
const text6 = messages[i]?.text?.trim();
|
|
2324
|
+
if (!text6 || isInternalAgentStatusText(text6)) continue;
|
|
2325
|
+
const flat = text6.replace(/\s+/g, " ");
|
|
2126
2326
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
2127
2327
|
}
|
|
2128
2328
|
return null;
|
|
@@ -2354,8 +2554,8 @@ function labelGithubUrl(url) {
|
|
|
2354
2554
|
}
|
|
2355
2555
|
return { kind: "other", label: "GitHub", url: raw };
|
|
2356
2556
|
}
|
|
2357
|
-
function appendGithubLink(
|
|
2358
|
-
const body =
|
|
2557
|
+
function appendGithubLink(text6, githubUrl) {
|
|
2558
|
+
const body = text6.trimEnd();
|
|
2359
2559
|
if (!githubUrl?.trim()) return body;
|
|
2360
2560
|
const labeled = labelGithubUrl(githubUrl);
|
|
2361
2561
|
if (!labeled) {
|
|
@@ -2751,11 +2951,51 @@ function registerAbleTimeTools(server) {
|
|
|
2751
2951
|
);
|
|
2752
2952
|
server.tool(
|
|
2753
2953
|
"abletime_get_task",
|
|
2754
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232).",
|
|
2954
|
+
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments.",
|
|
2755
2955
|
{ id: z2.string() },
|
|
2756
2956
|
async ({ id }) => {
|
|
2757
2957
|
try {
|
|
2758
|
-
|
|
2958
|
+
const task = await getAbleTimeTask(id);
|
|
2959
|
+
return text2({
|
|
2960
|
+
...toAbleTimeIssueInfo(task),
|
|
2961
|
+
description: task.description,
|
|
2962
|
+
state: task.state,
|
|
2963
|
+
comments: task.comments
|
|
2964
|
+
});
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
return fail2(err);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
);
|
|
2970
|
+
server.tool(
|
|
2971
|
+
"abletime_comment",
|
|
2972
|
+
"Add a markdown comment on an AbleTime task (id or CRM-232).",
|
|
2973
|
+
{ id: z2.string(), body: z2.string() },
|
|
2974
|
+
async (args) => {
|
|
2975
|
+
try {
|
|
2976
|
+
return text2(await commentAbleTimeTask(args));
|
|
2977
|
+
} catch (err) {
|
|
2978
|
+
return fail2(err);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
);
|
|
2982
|
+
server.tool(
|
|
2983
|
+
"abletime_update_task",
|
|
2984
|
+
"Update an AbleTime task (id or CRM-232). Pass title, description, and/or state.",
|
|
2985
|
+
{
|
|
2986
|
+
id: z2.string(),
|
|
2987
|
+
title: z2.string().optional(),
|
|
2988
|
+
description: z2.string().optional(),
|
|
2989
|
+
state: z2.string().optional()
|
|
2990
|
+
},
|
|
2991
|
+
async (args) => {
|
|
2992
|
+
try {
|
|
2993
|
+
const task = await updateAbleTimeTask(args);
|
|
2994
|
+
return text2({
|
|
2995
|
+
...toAbleTimeIssueInfo(task),
|
|
2996
|
+
description: task.description,
|
|
2997
|
+
state: task.state
|
|
2998
|
+
});
|
|
2759
2999
|
} catch (err) {
|
|
2760
3000
|
return fail2(err);
|
|
2761
3001
|
}
|
|
@@ -2763,13 +3003,14 @@ function registerAbleTimeTools(server) {
|
|
|
2763
3003
|
);
|
|
2764
3004
|
server.tool(
|
|
2765
3005
|
"abletime_create_task",
|
|
2766
|
-
"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).",
|
|
3006
|
+
"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).",
|
|
2767
3007
|
{
|
|
2768
3008
|
title: z2.string(),
|
|
2769
3009
|
description: z2.string().optional(),
|
|
2770
3010
|
projectId: z2.string().optional(),
|
|
2771
3011
|
categoryId: z2.string().optional(),
|
|
2772
|
-
state: z2.enum(["backlog", "todo"]).optional()
|
|
3012
|
+
state: z2.enum(["backlog", "todo"]).optional(),
|
|
3013
|
+
parent: z2.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
2773
3014
|
},
|
|
2774
3015
|
async (args) => {
|
|
2775
3016
|
try {
|
|
@@ -2799,7 +3040,7 @@ function registerAbleTimeTools(server) {
|
|
|
2799
3040
|
);
|
|
2800
3041
|
}
|
|
2801
3042
|
|
|
2802
|
-
// src/mcp/
|
|
3043
|
+
// src/mcp/github-tools.ts
|
|
2803
3044
|
import { z as z3 } from "zod";
|
|
2804
3045
|
function text3(payload, isError = false) {
|
|
2805
3046
|
return mcpJson(payload, isError);
|
|
@@ -2810,7 +3051,81 @@ function fail3(err) {
|
|
|
2810
3051
|
true
|
|
2811
3052
|
);
|
|
2812
3053
|
}
|
|
2813
|
-
var
|
|
3054
|
+
var repoPathSchema = z3.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
3055
|
+
function registerGithubIssueTools(server) {
|
|
3056
|
+
server.tool(
|
|
3057
|
+
"github_get_issue",
|
|
3058
|
+
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Uses Account gh.",
|
|
3059
|
+
{ id: z3.string(), repoPath: repoPathSchema },
|
|
3060
|
+
async ({ id, repoPath }) => {
|
|
3061
|
+
try {
|
|
3062
|
+
return text3(await getGitHubIssue(id, { repoPath }));
|
|
3063
|
+
} catch (err) {
|
|
3064
|
+
return fail3(err);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
);
|
|
3068
|
+
server.tool(
|
|
3069
|
+
"github_comment",
|
|
3070
|
+
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh.",
|
|
3071
|
+
{ id: z3.string(), body: z3.string(), repoPath: repoPathSchema },
|
|
3072
|
+
async ({ id, body, repoPath }) => {
|
|
3073
|
+
try {
|
|
3074
|
+
return text3(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
3075
|
+
} catch (err) {
|
|
3076
|
+
return fail3(err);
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
);
|
|
3080
|
+
server.tool(
|
|
3081
|
+
"github_update_issue",
|
|
3082
|
+
"Update a GitHub issue (#123). Pass title, body, and/or state (open|closed).",
|
|
3083
|
+
{
|
|
3084
|
+
id: z3.string(),
|
|
3085
|
+
title: z3.string().optional(),
|
|
3086
|
+
body: z3.string().optional(),
|
|
3087
|
+
state: z3.string().optional().describe("open or closed"),
|
|
3088
|
+
repoPath: repoPathSchema
|
|
3089
|
+
},
|
|
3090
|
+
async (args) => {
|
|
3091
|
+
try {
|
|
3092
|
+
return text3(await updateGitHubIssue(args, { repoPath: args.repoPath }));
|
|
3093
|
+
} catch (err) {
|
|
3094
|
+
return fail3(err);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
);
|
|
3098
|
+
server.tool(
|
|
3099
|
+
"github_create_issue",
|
|
3100
|
+
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
3101
|
+
{
|
|
3102
|
+
title: z3.string(),
|
|
3103
|
+
body: z3.string().optional(),
|
|
3104
|
+
parent: z3.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
3105
|
+
repoPath: repoPathSchema
|
|
3106
|
+
},
|
|
3107
|
+
async (args) => {
|
|
3108
|
+
try {
|
|
3109
|
+
return text3(await createGitHubIssue(args, { repoPath: args.repoPath }));
|
|
3110
|
+
} catch (err) {
|
|
3111
|
+
return fail3(err);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/mcp/linear-tools.ts
|
|
3118
|
+
import { z as z4 } from "zod";
|
|
3119
|
+
function text4(payload, isError = false) {
|
|
3120
|
+
return mcpJson(payload, isError);
|
|
3121
|
+
}
|
|
3122
|
+
function fail4(err) {
|
|
3123
|
+
return text4(
|
|
3124
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
3125
|
+
true
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
var prioritySchema = z4.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
2814
3129
|
function registerLinearTools(server) {
|
|
2815
3130
|
server.tool(
|
|
2816
3131
|
"linear_list_teams",
|
|
@@ -2818,9 +3133,9 @@ function registerLinearTools(server) {
|
|
|
2818
3133
|
{},
|
|
2819
3134
|
async () => {
|
|
2820
3135
|
try {
|
|
2821
|
-
return
|
|
3136
|
+
return text4(await listLinearTeams());
|
|
2822
3137
|
} catch (err) {
|
|
2823
|
-
return
|
|
3138
|
+
return fail4(err);
|
|
2824
3139
|
}
|
|
2825
3140
|
}
|
|
2826
3141
|
);
|
|
@@ -2828,9 +3143,9 @@ function registerLinearTools(server) {
|
|
|
2828
3143
|
"linear_search_issues",
|
|
2829
3144
|
"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.",
|
|
2830
3145
|
{
|
|
2831
|
-
query:
|
|
2832
|
-
assignee:
|
|
2833
|
-
limit:
|
|
3146
|
+
query: z4.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
3147
|
+
assignee: z4.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
3148
|
+
limit: z4.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
2834
3149
|
},
|
|
2835
3150
|
async ({ query, assignee, limit }) => {
|
|
2836
3151
|
try {
|
|
@@ -2851,38 +3166,39 @@ function registerLinearTools(server) {
|
|
|
2851
3166
|
})
|
|
2852
3167
|
);
|
|
2853
3168
|
} catch (err) {
|
|
2854
|
-
return
|
|
3169
|
+
return fail4(err);
|
|
2855
3170
|
}
|
|
2856
3171
|
}
|
|
2857
3172
|
);
|
|
2858
3173
|
server.tool(
|
|
2859
3174
|
"linear_get_issue",
|
|
2860
3175
|
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
2861
|
-
{ id:
|
|
3176
|
+
{ id: z4.string() },
|
|
2862
3177
|
async ({ id }) => {
|
|
2863
3178
|
try {
|
|
2864
|
-
return
|
|
3179
|
+
return text4(await getLinearIssue(id));
|
|
2865
3180
|
} catch (err) {
|
|
2866
|
-
return
|
|
3181
|
+
return fail4(err);
|
|
2867
3182
|
}
|
|
2868
3183
|
}
|
|
2869
3184
|
);
|
|
2870
3185
|
server.tool(
|
|
2871
3186
|
"linear_create_issue",
|
|
2872
|
-
'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.',
|
|
3187
|
+
'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.',
|
|
2873
3188
|
{
|
|
2874
|
-
team:
|
|
2875
|
-
title:
|
|
2876
|
-
description:
|
|
2877
|
-
state:
|
|
2878
|
-
assignee:
|
|
2879
|
-
priority: prioritySchema
|
|
3189
|
+
team: z4.string(),
|
|
3190
|
+
title: z4.string(),
|
|
3191
|
+
description: z4.string().optional(),
|
|
3192
|
+
state: z4.string().optional(),
|
|
3193
|
+
assignee: z4.string().nullable().optional(),
|
|
3194
|
+
priority: prioritySchema,
|
|
3195
|
+
parent: z4.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
2880
3196
|
},
|
|
2881
3197
|
async (args) => {
|
|
2882
3198
|
try {
|
|
2883
|
-
return
|
|
3199
|
+
return text4(await createLinearIssue(args));
|
|
2884
3200
|
} catch (err) {
|
|
2885
|
-
return
|
|
3201
|
+
return fail4(err);
|
|
2886
3202
|
}
|
|
2887
3203
|
}
|
|
2888
3204
|
);
|
|
@@ -2890,18 +3206,18 @@ function registerLinearTools(server) {
|
|
|
2890
3206
|
"linear_update_issue",
|
|
2891
3207
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
2892
3208
|
{
|
|
2893
|
-
id:
|
|
2894
|
-
title:
|
|
2895
|
-
description:
|
|
2896
|
-
state:
|
|
2897
|
-
assignee:
|
|
3209
|
+
id: z4.string(),
|
|
3210
|
+
title: z4.string().optional(),
|
|
3211
|
+
description: z4.string().optional(),
|
|
3212
|
+
state: z4.string().optional(),
|
|
3213
|
+
assignee: z4.string().nullable().optional(),
|
|
2898
3214
|
priority: prioritySchema
|
|
2899
3215
|
},
|
|
2900
3216
|
async (args) => {
|
|
2901
3217
|
try {
|
|
2902
|
-
return
|
|
3218
|
+
return text4(await updateLinearIssue(args));
|
|
2903
3219
|
} catch (err) {
|
|
2904
|
-
return
|
|
3220
|
+
return fail4(err);
|
|
2905
3221
|
}
|
|
2906
3222
|
}
|
|
2907
3223
|
);
|
|
@@ -2909,14 +3225,14 @@ function registerLinearTools(server) {
|
|
|
2909
3225
|
"linear_comment",
|
|
2910
3226
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
2911
3227
|
{
|
|
2912
|
-
id:
|
|
2913
|
-
body:
|
|
3228
|
+
id: z4.string(),
|
|
3229
|
+
body: z4.string()
|
|
2914
3230
|
},
|
|
2915
3231
|
async (args) => {
|
|
2916
3232
|
try {
|
|
2917
|
-
return
|
|
3233
|
+
return text4(await commentLinearIssue(args));
|
|
2918
3234
|
} catch (err) {
|
|
2919
|
-
return
|
|
3235
|
+
return fail4(err);
|
|
2920
3236
|
}
|
|
2921
3237
|
}
|
|
2922
3238
|
);
|
|
@@ -2924,6 +3240,7 @@ function registerLinearTools(server) {
|
|
|
2924
3240
|
|
|
2925
3241
|
// src/mcp/issue-vendor-tools.ts
|
|
2926
3242
|
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
3243
|
+
registerGithubIssueTools(server);
|
|
2927
3244
|
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
2928
3245
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
2929
3246
|
}
|
|
@@ -2963,15 +3280,15 @@ function formatMcpPrList(input) {
|
|
|
2963
3280
|
}
|
|
2964
3281
|
|
|
2965
3282
|
// src/mcp/schedule-tools.ts
|
|
2966
|
-
import { z as
|
|
2967
|
-
function
|
|
3283
|
+
import { z as z5 } from "zod";
|
|
3284
|
+
function text5(payload, isError = false) {
|
|
2968
3285
|
return {
|
|
2969
3286
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
2970
3287
|
...isError ? { isError: true } : {}
|
|
2971
3288
|
};
|
|
2972
3289
|
}
|
|
2973
|
-
function
|
|
2974
|
-
return
|
|
3290
|
+
function fail5(err) {
|
|
3291
|
+
return text5(
|
|
2975
3292
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
2976
3293
|
true
|
|
2977
3294
|
);
|
|
@@ -2996,9 +3313,9 @@ function registerScheduleTools(server) {
|
|
|
2996
3313
|
{},
|
|
2997
3314
|
async () => {
|
|
2998
3315
|
try {
|
|
2999
|
-
return
|
|
3316
|
+
return text5({ schedules: listSchedules() });
|
|
3000
3317
|
} catch (err) {
|
|
3001
|
-
return
|
|
3318
|
+
return fail5(err);
|
|
3002
3319
|
}
|
|
3003
3320
|
}
|
|
3004
3321
|
);
|
|
@@ -3006,24 +3323,24 @@ function registerScheduleTools(server) {
|
|
|
3006
3323
|
"create_schedule",
|
|
3007
3324
|
"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.",
|
|
3008
3325
|
{
|
|
3009
|
-
prompt:
|
|
3010
|
-
name:
|
|
3011
|
-
at:
|
|
3012
|
-
every:
|
|
3013
|
-
cron:
|
|
3014
|
-
tz:
|
|
3015
|
-
threadId:
|
|
3326
|
+
prompt: z5.string().describe("User message / goal queued when the schedule fires"),
|
|
3327
|
+
name: z5.string().optional(),
|
|
3328
|
+
at: z5.string().optional().describe("ISO datetime for a one-shot"),
|
|
3329
|
+
every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
3330
|
+
cron: z5.string().optional().describe("5-field cron expression"),
|
|
3331
|
+
tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
3332
|
+
threadId: z5.string().optional().describe(
|
|
3016
3333
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
3017
3334
|
),
|
|
3018
|
-
agent:
|
|
3019
|
-
model:
|
|
3335
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
3336
|
+
model: z5.string().optional()
|
|
3020
3337
|
},
|
|
3021
3338
|
async (args) => {
|
|
3022
3339
|
try {
|
|
3023
3340
|
const when = parseWhen(args);
|
|
3024
3341
|
const threadId = resolveScheduleThreadId(args.threadId);
|
|
3025
3342
|
if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
|
|
3026
|
-
return
|
|
3343
|
+
return fail5(
|
|
3027
3344
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
3028
3345
|
);
|
|
3029
3346
|
}
|
|
@@ -3036,12 +3353,12 @@ function registerScheduleTools(server) {
|
|
|
3036
3353
|
model: args.model,
|
|
3037
3354
|
createdBy: "mcp"
|
|
3038
3355
|
});
|
|
3039
|
-
return
|
|
3356
|
+
return text5({
|
|
3040
3357
|
schedule,
|
|
3041
3358
|
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."
|
|
3042
3359
|
});
|
|
3043
3360
|
} catch (err) {
|
|
3044
|
-
return
|
|
3361
|
+
return fail5(err);
|
|
3045
3362
|
}
|
|
3046
3363
|
}
|
|
3047
3364
|
);
|
|
@@ -3049,17 +3366,17 @@ function registerScheduleTools(server) {
|
|
|
3049
3366
|
"update_schedule",
|
|
3050
3367
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
3051
3368
|
{
|
|
3052
|
-
id:
|
|
3053
|
-
prompt:
|
|
3054
|
-
name:
|
|
3055
|
-
at:
|
|
3056
|
-
every:
|
|
3057
|
-
cron:
|
|
3058
|
-
tz:
|
|
3059
|
-
threadId:
|
|
3060
|
-
agent:
|
|
3061
|
-
model:
|
|
3062
|
-
enabled:
|
|
3369
|
+
id: z5.string(),
|
|
3370
|
+
prompt: z5.string().optional(),
|
|
3371
|
+
name: z5.string().optional(),
|
|
3372
|
+
at: z5.string().optional(),
|
|
3373
|
+
every: z5.string().optional(),
|
|
3374
|
+
cron: z5.string().optional(),
|
|
3375
|
+
tz: z5.string().optional(),
|
|
3376
|
+
threadId: z5.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
3377
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
3378
|
+
model: z5.string().optional(),
|
|
3379
|
+
enabled: z5.boolean().optional()
|
|
3063
3380
|
},
|
|
3064
3381
|
async (args) => {
|
|
3065
3382
|
try {
|
|
@@ -3074,7 +3391,7 @@ function registerScheduleTools(server) {
|
|
|
3074
3391
|
if (args.threadId !== void 0) {
|
|
3075
3392
|
threadId = resolveScheduleThreadId(args.threadId);
|
|
3076
3393
|
if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
|
|
3077
|
-
return
|
|
3394
|
+
return fail5(
|
|
3078
3395
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
3079
3396
|
);
|
|
3080
3397
|
}
|
|
@@ -3088,38 +3405,38 @@ function registerScheduleTools(server) {
|
|
|
3088
3405
|
enabled: args.enabled,
|
|
3089
3406
|
model: args.model
|
|
3090
3407
|
});
|
|
3091
|
-
return
|
|
3408
|
+
return text5({ schedule });
|
|
3092
3409
|
} catch (err) {
|
|
3093
|
-
return
|
|
3410
|
+
return fail5(err);
|
|
3094
3411
|
}
|
|
3095
3412
|
}
|
|
3096
3413
|
);
|
|
3097
3414
|
server.tool(
|
|
3098
3415
|
"delete_schedule",
|
|
3099
3416
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
3100
|
-
{ id:
|
|
3417
|
+
{ id: z5.string() },
|
|
3101
3418
|
async ({ id }) => {
|
|
3102
3419
|
try {
|
|
3103
3420
|
deleteSchedule(id);
|
|
3104
|
-
return
|
|
3421
|
+
return text5({ ok: true, id });
|
|
3105
3422
|
} catch (err) {
|
|
3106
|
-
return
|
|
3423
|
+
return fail5(err);
|
|
3107
3424
|
}
|
|
3108
3425
|
}
|
|
3109
3426
|
);
|
|
3110
3427
|
server.tool(
|
|
3111
3428
|
"run_schedule",
|
|
3112
3429
|
"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.",
|
|
3113
|
-
{ id:
|
|
3430
|
+
{ id: z5.string() },
|
|
3114
3431
|
async ({ id }) => {
|
|
3115
3432
|
try {
|
|
3116
3433
|
if (!getSchedule(id)) {
|
|
3117
|
-
return
|
|
3434
|
+
return fail5(new Error(`Schedule not found: ${id}`));
|
|
3118
3435
|
}
|
|
3119
3436
|
const schedule = await fireSchedule(id);
|
|
3120
|
-
return
|
|
3437
|
+
return text5({ schedule });
|
|
3121
3438
|
} catch (err) {
|
|
3122
|
-
return
|
|
3439
|
+
return fail5(err);
|
|
3123
3440
|
}
|
|
3124
3441
|
}
|
|
3125
3442
|
);
|
|
@@ -3537,6 +3854,9 @@ async function startMcpServer() {
|
|
|
3537
3854
|
version: "0.1.0"
|
|
3538
3855
|
});
|
|
3539
3856
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
3857
|
+
if (worktreeProfile) {
|
|
3858
|
+
registerConnectedIssueVendorTools(server);
|
|
3859
|
+
}
|
|
3540
3860
|
if (!worktreeProfile) {
|
|
3541
3861
|
server.tool(
|
|
3542
3862
|
"list_workspaces",
|
|
@@ -3589,13 +3909,13 @@ async function startMcpServer() {
|
|
|
3589
3909
|
"list_board",
|
|
3590
3910
|
"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.",
|
|
3591
3911
|
{
|
|
3592
|
-
query:
|
|
3593
|
-
repoPath:
|
|
3594
|
-
kind:
|
|
3595
|
-
column:
|
|
3912
|
+
query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
3913
|
+
repoPath: z6.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
3914
|
+
kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
3915
|
+
column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
3596
3916
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
3597
3917
|
),
|
|
3598
|
-
limit:
|
|
3918
|
+
limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
3599
3919
|
},
|
|
3600
3920
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
3601
3921
|
const workspaces = orch.listWorkspaces();
|
|
@@ -3633,7 +3953,7 @@ async function startMcpServer() {
|
|
|
3633
3953
|
server.tool(
|
|
3634
3954
|
"get_thread",
|
|
3635
3955
|
"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.",
|
|
3636
|
-
{ ref:
|
|
3956
|
+
{ ref: z6.string() },
|
|
3637
3957
|
async ({ ref }) => {
|
|
3638
3958
|
const t = orch.getThread(ref);
|
|
3639
3959
|
if (!t) {
|
|
@@ -3675,17 +3995,17 @@ async function startMcpServer() {
|
|
|
3675
3995
|
"present_artifact",
|
|
3676
3996
|
"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.",
|
|
3677
3997
|
{
|
|
3678
|
-
title:
|
|
3679
|
-
type:
|
|
3998
|
+
title: z6.string().describe("Short title shown in the artifact pane header"),
|
|
3999
|
+
type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
3680
4000
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
3681
4001
|
),
|
|
3682
|
-
content:
|
|
4002
|
+
content: z6.string().describe(
|
|
3683
4003
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
3684
4004
|
),
|
|
3685
|
-
artifact_id:
|
|
3686
|
-
status:
|
|
3687
|
-
phase:
|
|
3688
|
-
mode:
|
|
4005
|
+
artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
4006
|
+
status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
4007
|
+
phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
4008
|
+
mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
3689
4009
|
},
|
|
3690
4010
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
3691
4011
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3706,15 +4026,15 @@ async function startMcpServer() {
|
|
|
3706
4026
|
"ask_user",
|
|
3707
4027
|
"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.",
|
|
3708
4028
|
{
|
|
3709
|
-
questions:
|
|
3710
|
-
|
|
3711
|
-
question:
|
|
3712
|
-
header:
|
|
3713
|
-
multiSelect:
|
|
3714
|
-
options:
|
|
3715
|
-
|
|
3716
|
-
label:
|
|
3717
|
-
description:
|
|
4029
|
+
questions: z6.array(
|
|
4030
|
+
z6.object({
|
|
4031
|
+
question: z6.string().describe("Full question text ending with ?"),
|
|
4032
|
+
header: z6.string().max(24).optional().describe("Short label shown above the question"),
|
|
4033
|
+
multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
|
|
4034
|
+
options: z6.array(
|
|
4035
|
+
z6.object({
|
|
4036
|
+
label: z6.string(),
|
|
4037
|
+
description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
3718
4038
|
})
|
|
3719
4039
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
3720
4040
|
})
|
|
@@ -3733,9 +4053,9 @@ async function startMcpServer() {
|
|
|
3733
4053
|
"present_plan",
|
|
3734
4054
|
"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.",
|
|
3735
4055
|
{
|
|
3736
|
-
title:
|
|
3737
|
-
content:
|
|
3738
|
-
thread_id:
|
|
4056
|
+
title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
4057
|
+
content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
4058
|
+
thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
3739
4059
|
},
|
|
3740
4060
|
async ({ title, content, thread_id }) => {
|
|
3741
4061
|
const { writePlanFile: writePlanFile2 } = await import("./plan-file-LSSSQGTQ.js");
|
|
@@ -3758,15 +4078,15 @@ async function startMcpServer() {
|
|
|
3758
4078
|
"present_schema",
|
|
3759
4079
|
"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.",
|
|
3760
4080
|
{
|
|
3761
|
-
title:
|
|
3762
|
-
mode:
|
|
3763
|
-
datasource:
|
|
3764
|
-
resource_id:
|
|
3765
|
-
record_id:
|
|
3766
|
-
resource:
|
|
3767
|
-
record:
|
|
3768
|
-
records:
|
|
3769
|
-
pane_id:
|
|
4081
|
+
title: z6.string().describe("Pane title"),
|
|
4082
|
+
mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
4083
|
+
datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
4084
|
+
resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
4085
|
+
record_id: z6.string().optional().describe("Record id when opening form mode"),
|
|
4086
|
+
resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
4087
|
+
record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
4088
|
+
records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
|
|
4089
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
3770
4090
|
},
|
|
3771
4091
|
async (args) => {
|
|
3772
4092
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3787,10 +4107,10 @@ async function startMcpServer() {
|
|
|
3787
4107
|
"present_files",
|
|
3788
4108
|
"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.",
|
|
3789
4109
|
{
|
|
3790
|
-
title:
|
|
3791
|
-
datasource:
|
|
3792
|
-
path:
|
|
3793
|
-
pane_id:
|
|
4110
|
+
title: z6.string().optional().describe("Pane title (default: Files)"),
|
|
4111
|
+
datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
4112
|
+
path: z6.string().optional().describe("Initial folder path (e.g. public)"),
|
|
4113
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
3794
4114
|
},
|
|
3795
4115
|
async (args) => {
|
|
3796
4116
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3817,7 +4137,7 @@ async function startMcpServer() {
|
|
|
3817
4137
|
"set_caffeinate",
|
|
3818
4138
|
"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.",
|
|
3819
4139
|
{
|
|
3820
|
-
enabled:
|
|
4140
|
+
enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
3821
4141
|
},
|
|
3822
4142
|
async ({ enabled }) => {
|
|
3823
4143
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -3855,18 +4175,18 @@ async function startMcpServer() {
|
|
|
3855
4175
|
"create_thread",
|
|
3856
4176
|
`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.`,
|
|
3857
4177
|
{
|
|
3858
|
-
sourceType:
|
|
3859
|
-
sourceRef:
|
|
3860
|
-
agent:
|
|
3861
|
-
model:
|
|
4178
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]),
|
|
4179
|
+
sourceRef: z6.string(),
|
|
4180
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
4181
|
+
model: z6.string().nullable().optional().describe(
|
|
3862
4182
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
3863
4183
|
),
|
|
3864
|
-
repoPath:
|
|
3865
|
-
title:
|
|
3866
|
-
cowboy:
|
|
4184
|
+
repoPath: z6.string(),
|
|
4185
|
+
title: z6.string().optional(),
|
|
4186
|
+
cowboy: z6.boolean().optional().describe(
|
|
3867
4187
|
"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."
|
|
3868
4188
|
),
|
|
3869
|
-
parentThreadId:
|
|
4189
|
+
parentThreadId: z6.string().optional().describe(
|
|
3870
4190
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
3871
4191
|
)
|
|
3872
4192
|
},
|
|
@@ -3882,10 +4202,10 @@ async function startMcpServer() {
|
|
|
3882
4202
|
"start_board_card",
|
|
3883
4203
|
"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.",
|
|
3884
4204
|
{
|
|
3885
|
-
kind:
|
|
3886
|
-
ref:
|
|
3887
|
-
repoPath:
|
|
3888
|
-
title:
|
|
4205
|
+
kind: z6.enum(["ticket", "pr", "branch"]),
|
|
4206
|
+
ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
4207
|
+
repoPath: z6.string().describe("Workspace path from list_workspaces / list_board"),
|
|
4208
|
+
title: z6.string().optional()
|
|
3889
4209
|
},
|
|
3890
4210
|
async ({ kind, ref, repoPath, title }) => {
|
|
3891
4211
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3989,9 +4309,9 @@ async function startMcpServer() {
|
|
|
3989
4309
|
"send_to_thread",
|
|
3990
4310
|
'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.',
|
|
3991
4311
|
{
|
|
3992
|
-
ref:
|
|
3993
|
-
prompt:
|
|
3994
|
-
force_stop:
|
|
4312
|
+
ref: z6.string(),
|
|
4313
|
+
prompt: z6.string(),
|
|
4314
|
+
force_stop: z6.boolean().optional()
|
|
3995
4315
|
},
|
|
3996
4316
|
async ({ ref, prompt, force_stop }) => {
|
|
3997
4317
|
if (force_stop) {
|
|
@@ -4020,8 +4340,8 @@ async function startMcpServer() {
|
|
|
4020
4340
|
"wait_for_turn",
|
|
4021
4341
|
"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).",
|
|
4022
4342
|
{
|
|
4023
|
-
ref:
|
|
4024
|
-
timeoutMs:
|
|
4343
|
+
ref: z6.string(),
|
|
4344
|
+
timeoutMs: z6.number().optional()
|
|
4025
4345
|
},
|
|
4026
4346
|
async ({ ref, timeoutMs }) => {
|
|
4027
4347
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -4051,7 +4371,7 @@ async function startMcpServer() {
|
|
|
4051
4371
|
server.tool(
|
|
4052
4372
|
"get_turn_result",
|
|
4053
4373
|
"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).",
|
|
4054
|
-
{ ref:
|
|
4374
|
+
{ ref: z6.string() },
|
|
4055
4375
|
async ({ ref }) => {
|
|
4056
4376
|
const result = orch.getTurnResult(ref);
|
|
4057
4377
|
return {
|
|
@@ -4072,8 +4392,8 @@ async function startMcpServer() {
|
|
|
4072
4392
|
"stop_thread",
|
|
4073
4393
|
"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.",
|
|
4074
4394
|
{
|
|
4075
|
-
ref:
|
|
4076
|
-
force:
|
|
4395
|
+
ref: z6.string(),
|
|
4396
|
+
force: z6.boolean().optional()
|
|
4077
4397
|
},
|
|
4078
4398
|
async ({ ref, force }) => {
|
|
4079
4399
|
const t = orch.getThread(ref);
|
|
@@ -4103,7 +4423,7 @@ async function startMcpServer() {
|
|
|
4103
4423
|
server.tool(
|
|
4104
4424
|
"archive_thread",
|
|
4105
4425
|
"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.",
|
|
4106
|
-
{ ref:
|
|
4426
|
+
{ ref: z6.string() },
|
|
4107
4427
|
async ({ ref }) => {
|
|
4108
4428
|
const t = orch.getThread(ref);
|
|
4109
4429
|
if (!t) {
|
|
@@ -4136,7 +4456,7 @@ async function startMcpServer() {
|
|
|
4136
4456
|
server.tool(
|
|
4137
4457
|
"restore_thread",
|
|
4138
4458
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
4139
|
-
{ ref:
|
|
4459
|
+
{ ref: z6.string() },
|
|
4140
4460
|
async ({ ref }) => {
|
|
4141
4461
|
try {
|
|
4142
4462
|
const restored = await orch.restore(ref);
|
|
@@ -4162,8 +4482,8 @@ async function startMcpServer() {
|
|
|
4162
4482
|
"get_diff",
|
|
4163
4483
|
"Compact diff summary (capped hunks, paginated)",
|
|
4164
4484
|
{
|
|
4165
|
-
ref:
|
|
4166
|
-
maxFiles:
|
|
4485
|
+
ref: z6.string(),
|
|
4486
|
+
maxFiles: z6.number().optional()
|
|
4167
4487
|
},
|
|
4168
4488
|
async ({ ref, maxFiles }) => {
|
|
4169
4489
|
const summary = await orch.diffSummary(ref);
|
|
@@ -4183,7 +4503,7 @@ async function startMcpServer() {
|
|
|
4183
4503
|
server.tool(
|
|
4184
4504
|
"get_pr_checks",
|
|
4185
4505
|
"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.",
|
|
4186
|
-
{ ref:
|
|
4506
|
+
{ ref: z6.string().describe("Worktree thread id/ref") },
|
|
4187
4507
|
async ({ ref }) => {
|
|
4188
4508
|
try {
|
|
4189
4509
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -4199,7 +4519,7 @@ async function startMcpServer() {
|
|
|
4199
4519
|
server.tool(
|
|
4200
4520
|
"request_review",
|
|
4201
4521
|
'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.',
|
|
4202
|
-
{ ref:
|
|
4522
|
+
{ ref: z6.string().describe("Worktree thread id/ref to review") },
|
|
4203
4523
|
async ({ ref }) => {
|
|
4204
4524
|
try {
|
|
4205
4525
|
const tab = await orch.requestReview(ref);
|
|
@@ -4228,8 +4548,8 @@ async function startMcpServer() {
|
|
|
4228
4548
|
"ask_git",
|
|
4229
4549
|
"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.",
|
|
4230
4550
|
{
|
|
4231
|
-
ref:
|
|
4232
|
-
action:
|
|
4551
|
+
ref: z6.string().describe("Worktree thread id/ref"),
|
|
4552
|
+
action: z6.enum(AGENT_GIT_ACTIONS).describe(
|
|
4233
4553
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
4234
4554
|
)
|
|
4235
4555
|
},
|
|
@@ -4274,7 +4594,7 @@ async function startMcpServer() {
|
|
|
4274
4594
|
}
|
|
4275
4595
|
}
|
|
4276
4596
|
);
|
|
4277
|
-
const agentEnum =
|
|
4597
|
+
const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
4278
4598
|
server.tool(
|
|
4279
4599
|
"list_models",
|
|
4280
4600
|
"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.",
|
|
@@ -4297,11 +4617,11 @@ async function startMcpServer() {
|
|
|
4297
4617
|
"fork_worktree",
|
|
4298
4618
|
"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.",
|
|
4299
4619
|
{
|
|
4300
|
-
ref:
|
|
4301
|
-
through_index:
|
|
4620
|
+
ref: z6.string().describe("Worktree thread id/ref to fork"),
|
|
4621
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
4302
4622
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4303
|
-
model:
|
|
4304
|
-
title:
|
|
4623
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
4624
|
+
title: z6.string().optional()
|
|
4305
4625
|
},
|
|
4306
4626
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4307
4627
|
try {
|
|
@@ -4342,11 +4662,11 @@ async function startMcpServer() {
|
|
|
4342
4662
|
"fork_chat",
|
|
4343
4663
|
"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.",
|
|
4344
4664
|
{
|
|
4345
|
-
ref:
|
|
4346
|
-
through_index:
|
|
4665
|
+
ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
4666
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
4347
4667
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4348
|
-
model:
|
|
4349
|
-
title:
|
|
4668
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
4669
|
+
title: z6.string().optional()
|
|
4350
4670
|
},
|
|
4351
4671
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4352
4672
|
try {
|
|
@@ -4392,8 +4712,8 @@ async function startMcpServer() {
|
|
|
4392
4712
|
"run_dev_script",
|
|
4393
4713
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
4394
4714
|
{
|
|
4395
|
-
ref:
|
|
4396
|
-
name:
|
|
4715
|
+
ref: z6.string(),
|
|
4716
|
+
name: z6.string().optional()
|
|
4397
4717
|
},
|
|
4398
4718
|
async ({ ref, name }) => {
|
|
4399
4719
|
const result = await orch.startDev(ref, name);
|
|
@@ -4415,7 +4735,7 @@ async function startMcpServer() {
|
|
|
4415
4735
|
server.tool(
|
|
4416
4736
|
"list_run_scripts",
|
|
4417
4737
|
"List named run scripts available for a thread",
|
|
4418
|
-
{ ref:
|
|
4738
|
+
{ ref: z6.string() },
|
|
4419
4739
|
async ({ ref }) => {
|
|
4420
4740
|
const scripts = orch.listThreadRunScripts(ref);
|
|
4421
4741
|
const active = orch.getActiveRuns(ref);
|
|
@@ -4433,8 +4753,8 @@ async function startMcpServer() {
|
|
|
4433
4753
|
"stop_dev_script",
|
|
4434
4754
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
4435
4755
|
{
|
|
4436
|
-
ref:
|
|
4437
|
-
name:
|
|
4756
|
+
ref: z6.string(),
|
|
4757
|
+
name: z6.string().optional()
|
|
4438
4758
|
},
|
|
4439
4759
|
async ({ ref, name }) => {
|
|
4440
4760
|
orch.stopDev(ref, name);
|
|
@@ -4444,7 +4764,7 @@ async function startMcpServer() {
|
|
|
4444
4764
|
server.tool(
|
|
4445
4765
|
"run_setup",
|
|
4446
4766
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
4447
|
-
{ ref:
|
|
4767
|
+
{ ref: z6.string() },
|
|
4448
4768
|
async ({ ref }) => {
|
|
4449
4769
|
const result = await orch.runSetup(ref);
|
|
4450
4770
|
return {
|
|
@@ -4455,7 +4775,7 @@ async function startMcpServer() {
|
|
|
4455
4775
|
server.tool(
|
|
4456
4776
|
"add_workspace",
|
|
4457
4777
|
"Register a git repo as a Sideboard workspace",
|
|
4458
|
-
{ repoPath:
|
|
4778
|
+
{ repoPath: z6.string() },
|
|
4459
4779
|
async ({ repoPath }) => {
|
|
4460
4780
|
const ws = await orch.addWorkspace(repoPath);
|
|
4461
4781
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -4464,7 +4784,7 @@ async function startMcpServer() {
|
|
|
4464
4784
|
server.tool(
|
|
4465
4785
|
"remove_workspace",
|
|
4466
4786
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
4467
|
-
{ repoPath:
|
|
4787
|
+
{ repoPath: z6.string() },
|
|
4468
4788
|
async ({ repoPath }) => {
|
|
4469
4789
|
orch.removeWorkspace(repoPath);
|
|
4470
4790
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -4474,12 +4794,12 @@ async function startMcpServer() {
|
|
|
4474
4794
|
"fanout",
|
|
4475
4795
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
4476
4796
|
{
|
|
4477
|
-
prompt:
|
|
4478
|
-
agents:
|
|
4479
|
-
repoPath:
|
|
4480
|
-
sourceType:
|
|
4481
|
-
sourceRef:
|
|
4482
|
-
title:
|
|
4797
|
+
prompt: z6.string(),
|
|
4798
|
+
agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
4799
|
+
repoPath: z6.string(),
|
|
4800
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
|
|
4801
|
+
sourceRef: z6.string().optional(),
|
|
4802
|
+
title: z6.string().optional()
|
|
4483
4803
|
},
|
|
4484
4804
|
async (args) => {
|
|
4485
4805
|
const threads = await orch.bestOfN(args);
|
|
@@ -4506,8 +4826,8 @@ async function startMcpServer() {
|
|
|
4506
4826
|
"list_branches",
|
|
4507
4827
|
"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).",
|
|
4508
4828
|
{
|
|
4509
|
-
repoPath:
|
|
4510
|
-
unmergedOnly:
|
|
4829
|
+
repoPath: z6.string(),
|
|
4830
|
+
unmergedOnly: z6.boolean().optional()
|
|
4511
4831
|
},
|
|
4512
4832
|
async ({ repoPath, unmergedOnly }) => {
|
|
4513
4833
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4528,19 +4848,19 @@ async function startMcpServer() {
|
|
|
4528
4848
|
"list_prs",
|
|
4529
4849
|
'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.',
|
|
4530
4850
|
{
|
|
4531
|
-
repoPath:
|
|
4532
|
-
query:
|
|
4533
|
-
queue:
|
|
4851
|
+
repoPath: z6.string(),
|
|
4852
|
+
query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
4853
|
+
queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
4534
4854
|
'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
|
|
4535
4855
|
),
|
|
4536
|
-
state:
|
|
4537
|
-
label:
|
|
4856
|
+
state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
4857
|
+
label: z6.string().optional().describe(
|
|
4538
4858
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
4539
4859
|
),
|
|
4540
|
-
reviewer:
|
|
4860
|
+
reviewer: z6.string().optional().describe(
|
|
4541
4861
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
4542
4862
|
),
|
|
4543
|
-
limit:
|
|
4863
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
4544
4864
|
},
|
|
4545
4865
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
4546
4866
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4572,7 +4892,7 @@ async function startMcpServer() {
|
|
|
4572
4892
|
server.tool(
|
|
4573
4893
|
"get_pr_stack",
|
|
4574
4894
|
"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).",
|
|
4575
|
-
{ ref:
|
|
4895
|
+
{ ref: z6.string() },
|
|
4576
4896
|
async ({ ref }) => {
|
|
4577
4897
|
const stack = await orch.getPrStack(ref);
|
|
4578
4898
|
return {
|
|
@@ -4584,8 +4904,8 @@ async function startMcpServer() {
|
|
|
4584
4904
|
"open_pr_stack_layers",
|
|
4585
4905
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
4586
4906
|
{
|
|
4587
|
-
ref:
|
|
4588
|
-
layer:
|
|
4907
|
+
ref: z6.string(),
|
|
4908
|
+
layer: z6.number().int().positive().optional()
|
|
4589
4909
|
},
|
|
4590
4910
|
async ({ ref, layer }) => {
|
|
4591
4911
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -4619,9 +4939,9 @@ async function startMcpServer() {
|
|
|
4619
4939
|
"add_stack_layer",
|
|
4620
4940
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
4621
4941
|
{
|
|
4622
|
-
ref:
|
|
4623
|
-
branchName:
|
|
4624
|
-
title:
|
|
4942
|
+
ref: z6.string(),
|
|
4943
|
+
branchName: z6.string(),
|
|
4944
|
+
title: z6.string().optional()
|
|
4625
4945
|
},
|
|
4626
4946
|
async ({ ref, branchName, title }) => {
|
|
4627
4947
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -4650,11 +4970,11 @@ async function startMcpServer() {
|
|
|
4650
4970
|
"create_pr_stack",
|
|
4651
4971
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
4652
4972
|
{
|
|
4653
|
-
repoPath:
|
|
4654
|
-
branches:
|
|
4655
|
-
agent:
|
|
4656
|
-
base:
|
|
4657
|
-
title:
|
|
4973
|
+
repoPath: z6.string(),
|
|
4974
|
+
branches: z6.array(z6.string()).min(1),
|
|
4975
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
4976
|
+
base: z6.string().optional(),
|
|
4977
|
+
title: z6.string().optional()
|
|
4658
4978
|
},
|
|
4659
4979
|
async (args) => {
|
|
4660
4980
|
const result = await orch.createPrStack({
|
|
@@ -4693,10 +5013,10 @@ async function startMcpServer() {
|
|
|
4693
5013
|
"list_issues",
|
|
4694
5014
|
"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.",
|
|
4695
5015
|
{
|
|
4696
|
-
repoPath:
|
|
4697
|
-
query:
|
|
4698
|
-
assignee:
|
|
4699
|
-
limit:
|
|
5016
|
+
repoPath: z6.string(),
|
|
5017
|
+
query: z6.string().optional().describe("Search title, identifier, or description"),
|
|
5018
|
+
assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
5019
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
4700
5020
|
},
|
|
4701
5021
|
async ({ repoPath, query, assignee, limit }) => {
|
|
4702
5022
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4828,8 +5148,8 @@ var BrightsySideboardApi = class {
|
|
|
4828
5148
|
};
|
|
4829
5149
|
function taskMessageText(task) {
|
|
4830
5150
|
const parts = task.message?.parts ?? [];
|
|
4831
|
-
const
|
|
4832
|
-
return
|
|
5151
|
+
const text6 = parts.find((p) => p.kind === "text")?.text ?? "";
|
|
5152
|
+
return text6.trim();
|
|
4833
5153
|
}
|
|
4834
5154
|
|
|
4835
5155
|
// src/brightsy/cloud-connect.ts
|
|
@@ -5302,11 +5622,11 @@ async function startSlackOAuth(opts) {
|
|
|
5302
5622
|
import { WebSocket as WsWebSocket } from "ws";
|
|
5303
5623
|
var BACKOFF_START_MS = 1e3;
|
|
5304
5624
|
var BACKOFF_MAX_MS = 3e4;
|
|
5305
|
-
function stripSlackMentions(
|
|
5306
|
-
return
|
|
5625
|
+
function stripSlackMentions(text6) {
|
|
5626
|
+
return text6.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
5307
5627
|
}
|
|
5308
|
-
function isSlackStopCommand(
|
|
5309
|
-
const t = stripSlackMentions(
|
|
5628
|
+
function isSlackStopCommand(text6) {
|
|
5629
|
+
const t = stripSlackMentions(text6).toLowerCase();
|
|
5310
5630
|
return t === "stop" || t === "sideboard_force_stop";
|
|
5311
5631
|
}
|
|
5312
5632
|
function parseSlackSocketFrame(raw) {
|
|
@@ -5328,8 +5648,8 @@ function inboundFromSocketFrame(frame) {
|
|
|
5328
5648
|
const ts = event.ts?.trim();
|
|
5329
5649
|
if (!channelId || !ts) return null;
|
|
5330
5650
|
const rawText = event.text?.trim() ?? "";
|
|
5331
|
-
const
|
|
5332
|
-
if (!
|
|
5651
|
+
const text6 = stripSlackMentions(rawText);
|
|
5652
|
+
if (!text6) return null;
|
|
5333
5653
|
const teamId = (frame.payload?.team_id || event.team || "").trim();
|
|
5334
5654
|
if (!teamId) return null;
|
|
5335
5655
|
const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
|
|
@@ -5341,7 +5661,7 @@ function inboundFromSocketFrame(frame) {
|
|
|
5341
5661
|
ts,
|
|
5342
5662
|
threadTs: event.thread_ts?.trim() || void 0,
|
|
5343
5663
|
userId: event.user?.trim(),
|
|
5344
|
-
text:
|
|
5664
|
+
text: text6,
|
|
5345
5665
|
kind: isMention ? "mention" : "dm"
|
|
5346
5666
|
};
|
|
5347
5667
|
}
|
|
@@ -5881,12 +6201,12 @@ function formatSlackInboundPrompt(msg) {
|
|
|
5881
6201
|
|
|
5882
6202
|
${msg.text}`;
|
|
5883
6203
|
}
|
|
5884
|
-
function isSlackInboundUserPrompt(
|
|
5885
|
-
return
|
|
6204
|
+
function isSlackInboundUserPrompt(text6) {
|
|
6205
|
+
return text6.startsWith("Slack DM\n") || text6.startsWith("Slack @mention\n");
|
|
5886
6206
|
}
|
|
5887
|
-
function formatSlackSignedReply(deviceLabel,
|
|
6207
|
+
function formatSlackSignedReply(deviceLabel, text6) {
|
|
5888
6208
|
const label = deviceLabel.trim();
|
|
5889
|
-
const body =
|
|
6209
|
+
const body = text6.trim();
|
|
5890
6210
|
if (!body) return body;
|
|
5891
6211
|
if (!label) return body;
|
|
5892
6212
|
const head = `${label}:`;
|
|
@@ -5895,8 +6215,8 @@ function formatSlackSignedReply(deviceLabel, text5) {
|
|
|
5895
6215
|
}
|
|
5896
6216
|
return `${label}: ${body}`;
|
|
5897
6217
|
}
|
|
5898
|
-
function signForThisMac(
|
|
5899
|
-
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel,
|
|
6218
|
+
function signForThisMac(text6) {
|
|
6219
|
+
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text6);
|
|
5900
6220
|
}
|
|
5901
6221
|
function slackReplyThreadTs(msg) {
|
|
5902
6222
|
if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
|
|
@@ -5957,9 +6277,9 @@ function replyStub(target) {
|
|
|
5957
6277
|
kind: "dm"
|
|
5958
6278
|
};
|
|
5959
6279
|
}
|
|
5960
|
-
async function postSlackText(target,
|
|
6280
|
+
async function postSlackText(target, text6, opts) {
|
|
5961
6281
|
if (opts.postReply) {
|
|
5962
|
-
const result = await opts.postReply(replyStub(target),
|
|
6282
|
+
const result = await opts.postReply(replyStub(target), text6);
|
|
5963
6283
|
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
5964
6284
|
return ts2 ? { ts: ts2 } : null;
|
|
5965
6285
|
}
|
|
@@ -5969,7 +6289,7 @@ async function postSlackText(target, text5, opts) {
|
|
|
5969
6289
|
"chat.postMessage",
|
|
5970
6290
|
{
|
|
5971
6291
|
channel: target.channelId,
|
|
5972
|
-
text:
|
|
6292
|
+
text: text6,
|
|
5973
6293
|
thread_ts: target.threadTs
|
|
5974
6294
|
},
|
|
5975
6295
|
opts.fetchImpl
|
|
@@ -5977,9 +6297,9 @@ async function postSlackText(target, text5, opts) {
|
|
|
5977
6297
|
const ts = json.ts?.trim();
|
|
5978
6298
|
return ts ? { ts } : null;
|
|
5979
6299
|
}
|
|
5980
|
-
async function updateSlackText(target, ts,
|
|
6300
|
+
async function updateSlackText(target, ts, text6, opts) {
|
|
5981
6301
|
if (opts.updateReply) {
|
|
5982
|
-
await opts.updateReply(replyStub(target), ts,
|
|
6302
|
+
await opts.updateReply(replyStub(target), ts, text6);
|
|
5983
6303
|
return;
|
|
5984
6304
|
}
|
|
5985
6305
|
const token = writeTokenForTeam(target.teamId);
|
|
@@ -5989,7 +6309,7 @@ async function updateSlackText(target, ts, text5, opts) {
|
|
|
5989
6309
|
{
|
|
5990
6310
|
channel: target.channelId,
|
|
5991
6311
|
ts,
|
|
5992
|
-
text:
|
|
6312
|
+
text: text6
|
|
5993
6313
|
},
|
|
5994
6314
|
opts.fetchImpl
|
|
5995
6315
|
);
|
|
@@ -6017,8 +6337,8 @@ function replyTargetOf(msg) {
|
|
|
6017
6337
|
threadTs: slackReplyThreadTs(msg)
|
|
6018
6338
|
};
|
|
6019
6339
|
}
|
|
6020
|
-
async function postSlackReply(msg,
|
|
6021
|
-
await postSlackText(replyTargetOf(msg), signForThisMac(
|
|
6340
|
+
async function postSlackReply(msg, text6, opts) {
|
|
6341
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text6), opts);
|
|
6022
6342
|
}
|
|
6023
6343
|
function startSlackTurnProgress(msg, threadId, opts) {
|
|
6024
6344
|
const log = opts.onLog ?? (() => void 0);
|
|
@@ -6101,10 +6421,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
6101
6421
|
}
|
|
6102
6422
|
});
|
|
6103
6423
|
},
|
|
6104
|
-
finishWith: (
|
|
6424
|
+
finishWith: (text6) => {
|
|
6105
6425
|
stop();
|
|
6106
6426
|
return run2(async () => {
|
|
6107
|
-
const signed = signForThisMac(
|
|
6427
|
+
const signed = signForThisMac(text6.trim());
|
|
6108
6428
|
if (!signed) {
|
|
6109
6429
|
if (!postedTs) return;
|
|
6110
6430
|
const ts = postedTs;
|
|
@@ -6132,11 +6452,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
6132
6452
|
};
|
|
6133
6453
|
}
|
|
6134
6454
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
6135
|
-
function markRelayed(threadId,
|
|
6136
|
-
lastRelayed.set(threadId, `${threadId}:${
|
|
6455
|
+
function markRelayed(threadId, text6) {
|
|
6456
|
+
lastRelayed.set(threadId, `${threadId}:${text6}`);
|
|
6137
6457
|
}
|
|
6138
|
-
function alreadyRelayed(threadId,
|
|
6139
|
-
return lastRelayed.get(threadId) === `${threadId}:${
|
|
6458
|
+
function alreadyRelayed(threadId, text6) {
|
|
6459
|
+
return lastRelayed.get(threadId) === `${threadId}:${text6}`;
|
|
6140
6460
|
}
|
|
6141
6461
|
async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
6142
6462
|
const target = getSlackReplyTarget(threadId);
|
|
@@ -6145,14 +6465,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
|
6145
6465
|
const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
|
|
6146
6466
|
if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
|
|
6147
6467
|
const result = getOrchestrator().getTurnResult(threadId);
|
|
6148
|
-
const
|
|
6149
|
-
if (!
|
|
6150
|
-
if (alreadyRelayed(threadId,
|
|
6151
|
-
markRelayed(threadId,
|
|
6468
|
+
const text6 = result.text.trim();
|
|
6469
|
+
if (!text6) return;
|
|
6470
|
+
if (alreadyRelayed(threadId, text6)) return;
|
|
6471
|
+
markRelayed(threadId, text6);
|
|
6152
6472
|
const log = opts.onLog ?? (() => void 0);
|
|
6153
6473
|
try {
|
|
6154
|
-
await postSlackText(target, signForThisMac(
|
|
6155
|
-
log(`replied ${target.threadTs ?? target.channelId} (${
|
|
6474
|
+
await postSlackText(target, signForThisMac(text6), opts);
|
|
6475
|
+
log(`replied ${target.threadTs ?? target.channelId} (${text6.length} chars)`);
|
|
6156
6476
|
} catch (err) {
|
|
6157
6477
|
lastRelayed.delete(threadId);
|
|
6158
6478
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -6400,10 +6720,10 @@ function resolveSlackListenMode(opts) {
|
|
|
6400
6720
|
}
|
|
6401
6721
|
|
|
6402
6722
|
// src/slack/relay-hub.ts
|
|
6403
|
-
function parseSlackDeviceDestination(
|
|
6404
|
-
const m =
|
|
6405
|
-
if (!m) return { label: null, rest:
|
|
6406
|
-
return { label: m[1], rest:
|
|
6723
|
+
function parseSlackDeviceDestination(text6) {
|
|
6724
|
+
const m = text6.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
|
|
6725
|
+
if (!m) return { label: null, rest: text6 };
|
|
6726
|
+
return { label: m[1], rest: text6.slice(m[0].length) };
|
|
6407
6727
|
}
|
|
6408
6728
|
var SlackRelayHub = class {
|
|
6409
6729
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -7009,6 +7329,9 @@ export {
|
|
|
7009
7329
|
SlackOAuthCancelledError,
|
|
7010
7330
|
SlackRelayHub,
|
|
7011
7331
|
THINKING_EFFORTS,
|
|
7332
|
+
WORKTREE_ABLETIME_MCP_TOOLS,
|
|
7333
|
+
WORKTREE_GITHUB_MCP_TOOLS,
|
|
7334
|
+
WORKTREE_LINEAR_MCP_TOOLS,
|
|
7012
7335
|
WORKTREE_MCP_TOOLS,
|
|
7013
7336
|
abletimeMcpRequest,
|
|
7014
7337
|
abletimeMcpUrl,
|
|
@@ -7096,6 +7419,8 @@ export {
|
|
|
7096
7419
|
codexUnattendedGitConfigArgs,
|
|
7097
7420
|
coerceOrchestratorAgent,
|
|
7098
7421
|
collectTakenTeamSlugs,
|
|
7422
|
+
commentAbleTimeTask,
|
|
7423
|
+
commentGitHubIssue,
|
|
7099
7424
|
commentLinearIssue,
|
|
7100
7425
|
commitAll,
|
|
7101
7426
|
computeNextRunAt,
|
|
@@ -7117,6 +7442,7 @@ export {
|
|
|
7117
7442
|
createChatTab,
|
|
7118
7443
|
createEmptyThread,
|
|
7119
7444
|
createExistingBranchWorktree,
|
|
7445
|
+
createGitHubIssue,
|
|
7120
7446
|
createGlobalChat,
|
|
7121
7447
|
createLinearIssue,
|
|
7122
7448
|
createLinearPkce,
|
|
@@ -7203,6 +7529,10 @@ export {
|
|
|
7203
7529
|
formatGhLandError,
|
|
7204
7530
|
formatGitAuthModeDirective,
|
|
7205
7531
|
formatIpcInvokeError,
|
|
7532
|
+
formatIssueToolsDirective,
|
|
7533
|
+
formatIssueToolsReminder,
|
|
7534
|
+
formatLinearDirective,
|
|
7535
|
+
formatLinearReminder,
|
|
7206
7536
|
formatLongRunningDirective,
|
|
7207
7537
|
formatLongRunningReminder,
|
|
7208
7538
|
formatMergePrError,
|
|
@@ -7248,6 +7578,7 @@ export {
|
|
|
7248
7578
|
getDefaultRunScript,
|
|
7249
7579
|
getDiff,
|
|
7250
7580
|
getDiffSummary,
|
|
7581
|
+
getGitHubIssue,
|
|
7251
7582
|
getGitHubStatus,
|
|
7252
7583
|
getGithubGitAuthMode,
|
|
7253
7584
|
getGithubPat,
|
|
@@ -7350,6 +7681,7 @@ export {
|
|
|
7350
7681
|
issueAttachmentForAbleTimeTask,
|
|
7351
7682
|
issueMatchesAssignee,
|
|
7352
7683
|
issueSourceLabel,
|
|
7684
|
+
issueTicketFromThread,
|
|
7353
7685
|
lastRequestOccupancy,
|
|
7354
7686
|
latestPendingPlanQuestions,
|
|
7355
7687
|
linearAuthorizationHeader,
|
|
@@ -7357,6 +7689,7 @@ export {
|
|
|
7357
7689
|
linearGraphql,
|
|
7358
7690
|
linearOAuthAuthorizeUrl,
|
|
7359
7691
|
linearOAuthCredentials,
|
|
7692
|
+
linearTicketIdFromThread,
|
|
7360
7693
|
listAbleTimeAssignedIssues,
|
|
7361
7694
|
listAbleTimeProjects,
|
|
7362
7695
|
listAbleTimeTasks,
|
|
@@ -7443,6 +7776,7 @@ export {
|
|
|
7443
7776
|
parseDurationMs,
|
|
7444
7777
|
parseForceStopMessage,
|
|
7445
7778
|
parseGhStackViewJson,
|
|
7779
|
+
parseGitHubIssueNumber,
|
|
7446
7780
|
parseGithubSlugFromRemoteUrl,
|
|
7447
7781
|
parseMcpList,
|
|
7448
7782
|
parsePlanQuestionsInput,
|
|
@@ -7506,6 +7840,7 @@ export {
|
|
|
7506
7840
|
resolveFilesToCopy,
|
|
7507
7841
|
resolveGhAuthToken,
|
|
7508
7842
|
resolveGitDirsForLockRecovery,
|
|
7843
|
+
resolveGitHubIssueRepo,
|
|
7509
7844
|
resolveGithubAgentToken,
|
|
7510
7845
|
resolveGithubRepoSlug,
|
|
7511
7846
|
resolveLinearState,
|
|
@@ -7610,6 +7945,7 @@ export {
|
|
|
7610
7945
|
threadsDir,
|
|
7611
7946
|
threadsSharingWorktree,
|
|
7612
7947
|
toAbleTimeIssueInfo,
|
|
7948
|
+
toGitHubIssueInfo,
|
|
7613
7949
|
toPublicAppSettings,
|
|
7614
7950
|
toolActivityLine,
|
|
7615
7951
|
toolDescription,
|
|
@@ -7617,6 +7953,7 @@ export {
|
|
|
7617
7953
|
toolFilePath,
|
|
7618
7954
|
totalTokens,
|
|
7619
7955
|
turnCostUsdFromCursorUsage,
|
|
7956
|
+
updateAbleTimeTask,
|
|
7620
7957
|
updateAdvancedSettings,
|
|
7621
7958
|
updateAgentExecutable,
|
|
7622
7959
|
updateAppEnvironment,
|
|
@@ -7624,6 +7961,7 @@ export {
|
|
|
7624
7961
|
updateClaudeSettings,
|
|
7625
7962
|
updateCodexSettings,
|
|
7626
7963
|
updateDefaultsSettings,
|
|
7964
|
+
updateGitHubIssue,
|
|
7627
7965
|
updateIntegrationsSettings,
|
|
7628
7966
|
updateLinearIssue,
|
|
7629
7967
|
updateOpencodeSettings,
|