@sideboard-ai/core 0.1.155 → 0.1.157
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{abletime-JSTJLXAW.js → abletime-BEIDJG7W.js} +5 -1
- package/dist/{abletime-H7LPGIP2.js → abletime-GWZE6OYM.js} +5 -1
- package/dist/agents/cursor-runner.cjs +6 -1
- package/dist/agents/cursor-runner.js +6 -1
- package/dist/{agents-V24DYZI4.js → agents-C55LAEUR.js} +4 -2
- package/dist/{agents-YVW75KBO.js → agents-LCDXN2B2.js} +4 -2
- package/dist/{chunk-RNC3GR6L.js → chunk-4I4VKAPZ.js} +5 -4
- package/dist/{chunk-2F63IMCL.js → chunk-CVR4RJ6G.js} +5 -4
- package/dist/{chunk-JOK4XWBG.js → chunk-DVJBO3M7.js} +87 -3
- package/dist/{chunk-J2OKVOBM.js → chunk-GWEEGE6L.js} +119 -10
- package/dist/{chunk-4THE3EY7.js → chunk-KQ4JOGUH.js} +214 -39
- package/dist/{chunk-FDCFXMDG.js → chunk-N27GVFZY.js} +87 -3
- package/dist/{chunk-46ZTBUVJ.js → chunk-SUCJAWV4.js} +90 -9
- package/dist/{chunk-QZEZXYVV.js → chunk-TIJ6QVX5.js} +305 -33
- package/dist/{coordinator-prompt-7FHKCWXB.js → coordinator-prompt-26IJU2AV.js} +1 -1
- package/dist/{coordinator-prompt-KX2FVVYT.js → coordinator-prompt-AM47SATF.js} +1 -1
- package/dist/{global-workspace-2BRRKSDN.js → global-workspace-5BIW26YR.js} +1 -1
- package/dist/{global-workspace-4LHCZ2DC.js → global-workspace-QV7CC7SK.js} +1 -1
- package/dist/index.cjs +1438 -695
- package/dist/index.d.cts +150 -3
- package/dist/index.d.ts +150 -3
- package/dist/index.js +600 -322
- package/dist/mcp/run-stdio.cjs +1223 -573
- package/dist/mcp/run-stdio.js +501 -199
- package/dist/{orchestrator-HC7XVGJB.js → orchestrator-BUH3LJLL.js} +3 -3
- package/dist/{orchestrator-3FWEKDAQ.js → orchestrator-KQO4MXBI.js} +3 -3
- package/dist/{workspaces-5UNDTN4G.js → workspaces-NYSM2EPX.js} +1 -1
- package/dist/{workspaces-MQYNZXUK.js → workspaces-Q2VCHIAS.js} +1 -1
- package/package.json +1 -1
package/dist/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,
|
|
@@ -269,6 +278,7 @@ import {
|
|
|
269
278
|
isBrightsyConnected,
|
|
270
279
|
isBrightsyNdjsonLine,
|
|
271
280
|
isCursorAutoModel,
|
|
281
|
+
isInjectedOrchMcpName,
|
|
272
282
|
isInternalAgentStatusText,
|
|
273
283
|
isPollWrapperToolName,
|
|
274
284
|
isSessionQuotaLimit,
|
|
@@ -284,6 +294,8 @@ import {
|
|
|
284
294
|
loginAgent,
|
|
285
295
|
mcpAllowTools,
|
|
286
296
|
mcpAuthWarnings,
|
|
297
|
+
mcpStatusThinkingFromClaudeInit,
|
|
298
|
+
mergeSideboardIntoMcpServersJson,
|
|
287
299
|
mergeUsage,
|
|
288
300
|
messagePartParentId,
|
|
289
301
|
normalizeParseResult,
|
|
@@ -296,11 +308,11 @@ import {
|
|
|
296
308
|
permissionMode,
|
|
297
309
|
prepareTerminalCommand,
|
|
298
310
|
promptMentionsBrightsy,
|
|
311
|
+
registerPackagedUserMcpClients,
|
|
299
312
|
requestOccupancy,
|
|
300
313
|
resolveCursorModelId,
|
|
301
314
|
resolveLoginCommand,
|
|
302
315
|
resolveQuotaFallbackAgent,
|
|
303
|
-
resolveSideboardMcpServer,
|
|
304
316
|
sanitizeMcpServerName,
|
|
305
317
|
shouldInjectBrightsyMcp,
|
|
306
318
|
sideboardMcpProfile,
|
|
@@ -312,12 +324,15 @@ import {
|
|
|
312
324
|
toolDetail,
|
|
313
325
|
toolFilePath,
|
|
314
326
|
totalTokens,
|
|
327
|
+
userClaudeMcpConfigPath,
|
|
328
|
+
userCursorMcpConfigPath,
|
|
329
|
+
userMcpNamesToDisable,
|
|
315
330
|
visibleToolRowDetail,
|
|
316
331
|
whichOnPath,
|
|
317
332
|
withEventParentId,
|
|
318
333
|
withEventsParentId,
|
|
319
334
|
writeInjectedMcpConfig
|
|
320
|
-
} from "./chunk-
|
|
335
|
+
} from "./chunk-TIJ6QVX5.js";
|
|
321
336
|
import {
|
|
322
337
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
323
338
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -364,7 +379,7 @@ import {
|
|
|
364
379
|
stageBuffersAsAttachments,
|
|
365
380
|
syncWorkspacesFromThreads,
|
|
366
381
|
takenTeamSlugsForOrchestration
|
|
367
|
-
} from "./chunk-
|
|
382
|
+
} from "./chunk-CVR4RJ6G.js";
|
|
368
383
|
import {
|
|
369
384
|
FAMOUS_SOCCER_TEAMS,
|
|
370
385
|
GITHUB_PR_BODY_MAX_CHARS,
|
|
@@ -490,6 +505,7 @@ import {
|
|
|
490
505
|
abletimeMcpRequest,
|
|
491
506
|
abletimeMcpUrl,
|
|
492
507
|
callAbleTimeTool,
|
|
508
|
+
commentAbleTimeTask,
|
|
493
509
|
createAbleTimeTask,
|
|
494
510
|
ensureAbleTimeTask,
|
|
495
511
|
getAbleTimeOrientation,
|
|
@@ -504,8 +520,9 @@ import {
|
|
|
504
520
|
searchAbleTimeTasks,
|
|
505
521
|
taskUrl,
|
|
506
522
|
toAbleTimeIssueInfo,
|
|
523
|
+
updateAbleTimeTask,
|
|
507
524
|
verifyAbleTimeConnection
|
|
508
|
-
} from "./chunk-
|
|
525
|
+
} from "./chunk-DVJBO3M7.js";
|
|
509
526
|
import {
|
|
510
527
|
formatFetchError,
|
|
511
528
|
httpFetch,
|
|
@@ -716,9 +733,9 @@ async function getGitHubStatus() {
|
|
|
716
733
|
};
|
|
717
734
|
}
|
|
718
735
|
const status = await run("gh", ["auth", "status"], { reject: false });
|
|
719
|
-
const
|
|
736
|
+
const text6 = `${status.stdout}
|
|
720
737
|
${status.stderr}`;
|
|
721
|
-
const loginMatch =
|
|
738
|
+
const loginMatch = text6.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text6.match(/Logged in to ([^\s]+) as (\S+)/i);
|
|
722
739
|
if (loginMatch) {
|
|
723
740
|
return {
|
|
724
741
|
connected: true,
|
|
@@ -1498,6 +1515,11 @@ async function createLinearIssue(input, opts) {
|
|
|
1498
1515
|
if (input.state?.trim()) {
|
|
1499
1516
|
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
1500
1517
|
}
|
|
1518
|
+
const parentRef = input.parent?.trim();
|
|
1519
|
+
if (parentRef) {
|
|
1520
|
+
const parent = await getLinearIssue(parentRef, opts);
|
|
1521
|
+
mutationInput.parentId = parent.id;
|
|
1522
|
+
}
|
|
1501
1523
|
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
1502
1524
|
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
1503
1525
|
else if (assignee) mutationInput.assigneeId = assignee;
|
|
@@ -1578,6 +1600,190 @@ async function validateLinearApiKey(apiKey) {
|
|
|
1578
1600
|
}
|
|
1579
1601
|
}
|
|
1580
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
|
+
|
|
1581
1787
|
// src/integrations/issues.ts
|
|
1582
1788
|
function assigneeKey(assignee) {
|
|
1583
1789
|
return (assignee ?? "").trim().toLowerCase();
|
|
@@ -1854,11 +2060,11 @@ function fenceLanguage(path2, language) {
|
|
|
1854
2060
|
}
|
|
1855
2061
|
function buildCodeRefAttachment(input) {
|
|
1856
2062
|
const path2 = input.path.trim();
|
|
1857
|
-
const
|
|
2063
|
+
const text6 = input.text.replace(/\n$/, "");
|
|
1858
2064
|
if (!path2) {
|
|
1859
2065
|
throw new Error("code reference requires a file path");
|
|
1860
2066
|
}
|
|
1861
|
-
if (!
|
|
2067
|
+
if (!text6.trim()) {
|
|
1862
2068
|
throw new Error("code reference requires selected text");
|
|
1863
2069
|
}
|
|
1864
2070
|
if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
|
|
@@ -1871,7 +2077,7 @@ function buildCodeRefAttachment(input) {
|
|
|
1871
2077
|
`Referenced code from \`${path2}\` (${range}).`,
|
|
1872
2078
|
"",
|
|
1873
2079
|
fence,
|
|
1874
|
-
|
|
2080
|
+
text6,
|
|
1875
2081
|
"```"
|
|
1876
2082
|
].join("\n");
|
|
1877
2083
|
return {
|
|
@@ -1925,16 +2131,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
|
1925
2131
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
1926
2132
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
1927
2133
|
var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
|
|
1928
|
-
function pastedTextStats(
|
|
1929
|
-
const chars =
|
|
2134
|
+
function pastedTextStats(text6) {
|
|
2135
|
+
const chars = text6.length;
|
|
1930
2136
|
if (chars === 0) return { chars: 0, lines: 0 };
|
|
1931
|
-
const lines =
|
|
2137
|
+
const lines = text6.split(/\r\n|\r|\n/).length;
|
|
1932
2138
|
return { chars, lines };
|
|
1933
2139
|
}
|
|
1934
|
-
function shouldAttachPastedText(
|
|
1935
|
-
const trimmed =
|
|
2140
|
+
function shouldAttachPastedText(text6) {
|
|
2141
|
+
const trimmed = text6.trim();
|
|
1936
2142
|
if (!trimmed) return false;
|
|
1937
|
-
const { chars, lines } = pastedTextStats(
|
|
2143
|
+
const { chars, lines } = pastedTextStats(text6);
|
|
1938
2144
|
return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
|
|
1939
2145
|
}
|
|
1940
2146
|
function nextPastedTextName(existing) {
|
|
@@ -1945,13 +2151,13 @@ function nextPastedTextName(existing) {
|
|
|
1945
2151
|
}
|
|
1946
2152
|
return `Pasted text #${max + 1}.txt`;
|
|
1947
2153
|
}
|
|
1948
|
-
function buildPastedTextAttachment(
|
|
2154
|
+
function buildPastedTextAttachment(text6, opts) {
|
|
1949
2155
|
return {
|
|
1950
2156
|
id: opts?.id ?? randomUUID(),
|
|
1951
2157
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
1952
2158
|
kind: "file",
|
|
1953
2159
|
path: opts?.path,
|
|
1954
|
-
content:
|
|
2160
|
+
content: text6
|
|
1955
2161
|
};
|
|
1956
2162
|
}
|
|
1957
2163
|
|
|
@@ -2036,8 +2242,8 @@ function latestPendingPlanQuestions(input) {
|
|
|
2036
2242
|
return null;
|
|
2037
2243
|
}
|
|
2038
2244
|
var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
|
|
2039
|
-
function isPlanQuestionAnswersMessage(
|
|
2040
|
-
return
|
|
2245
|
+
function isPlanQuestionAnswersMessage(text6) {
|
|
2246
|
+
return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
|
|
2041
2247
|
}
|
|
2042
2248
|
function formatPlanQuestionAnswers(questions, answers) {
|
|
2043
2249
|
const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
|
|
@@ -2076,7 +2282,7 @@ function formatPlanQuestionsForChat(questions) {
|
|
|
2076
2282
|
// src/mcp/server.ts
|
|
2077
2283
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2078
2284
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2079
|
-
import { z as
|
|
2285
|
+
import { z as z6 } from "zod";
|
|
2080
2286
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2081
2287
|
import { basename } from "path";
|
|
2082
2288
|
|
|
@@ -2114,9 +2320,9 @@ function mcpWaitFinishedHint(status) {
|
|
|
2114
2320
|
function lastMessagePreview(messages, max = 160) {
|
|
2115
2321
|
if (!messages?.length) return null;
|
|
2116
2322
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2117
|
-
const
|
|
2118
|
-
if (!
|
|
2119
|
-
const flat =
|
|
2323
|
+
const text6 = messages[i]?.text?.trim();
|
|
2324
|
+
if (!text6 || isInternalAgentStatusText(text6)) continue;
|
|
2325
|
+
const flat = text6.replace(/\s+/g, " ");
|
|
2120
2326
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
2121
2327
|
}
|
|
2122
2328
|
return null;
|
|
@@ -2348,8 +2554,8 @@ function labelGithubUrl(url) {
|
|
|
2348
2554
|
}
|
|
2349
2555
|
return { kind: "other", label: "GitHub", url: raw };
|
|
2350
2556
|
}
|
|
2351
|
-
function appendGithubLink(
|
|
2352
|
-
const body =
|
|
2557
|
+
function appendGithubLink(text6, githubUrl) {
|
|
2558
|
+
const body = text6.trimEnd();
|
|
2353
2559
|
if (!githubUrl?.trim()) return body;
|
|
2354
2560
|
const labeled = labelGithubUrl(githubUrl);
|
|
2355
2561
|
if (!labeled) {
|
|
@@ -2745,11 +2951,51 @@ function registerAbleTimeTools(server) {
|
|
|
2745
2951
|
);
|
|
2746
2952
|
server.tool(
|
|
2747
2953
|
"abletime_get_task",
|
|
2748
|
-
"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.",
|
|
2749
2955
|
{ id: z2.string() },
|
|
2750
2956
|
async ({ id }) => {
|
|
2751
2957
|
try {
|
|
2752
|
-
|
|
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
|
+
});
|
|
2753
2999
|
} catch (err) {
|
|
2754
3000
|
return fail2(err);
|
|
2755
3001
|
}
|
|
@@ -2757,13 +3003,14 @@ function registerAbleTimeTools(server) {
|
|
|
2757
3003
|
);
|
|
2758
3004
|
server.tool(
|
|
2759
3005
|
"abletime_create_task",
|
|
2760
|
-
"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).",
|
|
2761
3007
|
{
|
|
2762
3008
|
title: z2.string(),
|
|
2763
3009
|
description: z2.string().optional(),
|
|
2764
3010
|
projectId: z2.string().optional(),
|
|
2765
3011
|
categoryId: z2.string().optional(),
|
|
2766
|
-
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.")
|
|
2767
3014
|
},
|
|
2768
3015
|
async (args) => {
|
|
2769
3016
|
try {
|
|
@@ -2793,7 +3040,7 @@ function registerAbleTimeTools(server) {
|
|
|
2793
3040
|
);
|
|
2794
3041
|
}
|
|
2795
3042
|
|
|
2796
|
-
// src/mcp/
|
|
3043
|
+
// src/mcp/github-tools.ts
|
|
2797
3044
|
import { z as z3 } from "zod";
|
|
2798
3045
|
function text3(payload, isError = false) {
|
|
2799
3046
|
return mcpJson(payload, isError);
|
|
@@ -2804,7 +3051,81 @@ function fail3(err) {
|
|
|
2804
3051
|
true
|
|
2805
3052
|
);
|
|
2806
3053
|
}
|
|
2807
|
-
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");
|
|
2808
3129
|
function registerLinearTools(server) {
|
|
2809
3130
|
server.tool(
|
|
2810
3131
|
"linear_list_teams",
|
|
@@ -2812,9 +3133,9 @@ function registerLinearTools(server) {
|
|
|
2812
3133
|
{},
|
|
2813
3134
|
async () => {
|
|
2814
3135
|
try {
|
|
2815
|
-
return
|
|
3136
|
+
return text4(await listLinearTeams());
|
|
2816
3137
|
} catch (err) {
|
|
2817
|
-
return
|
|
3138
|
+
return fail4(err);
|
|
2818
3139
|
}
|
|
2819
3140
|
}
|
|
2820
3141
|
);
|
|
@@ -2822,9 +3143,9 @@ function registerLinearTools(server) {
|
|
|
2822
3143
|
"linear_search_issues",
|
|
2823
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.",
|
|
2824
3145
|
{
|
|
2825
|
-
query:
|
|
2826
|
-
assignee:
|
|
2827
|
-
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.")
|
|
2828
3149
|
},
|
|
2829
3150
|
async ({ query, assignee, limit }) => {
|
|
2830
3151
|
try {
|
|
@@ -2845,38 +3166,39 @@ function registerLinearTools(server) {
|
|
|
2845
3166
|
})
|
|
2846
3167
|
);
|
|
2847
3168
|
} catch (err) {
|
|
2848
|
-
return
|
|
3169
|
+
return fail4(err);
|
|
2849
3170
|
}
|
|
2850
3171
|
}
|
|
2851
3172
|
);
|
|
2852
3173
|
server.tool(
|
|
2853
3174
|
"linear_get_issue",
|
|
2854
3175
|
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
2855
|
-
{ id:
|
|
3176
|
+
{ id: z4.string() },
|
|
2856
3177
|
async ({ id }) => {
|
|
2857
3178
|
try {
|
|
2858
|
-
return
|
|
3179
|
+
return text4(await getLinearIssue(id));
|
|
2859
3180
|
} catch (err) {
|
|
2860
|
-
return
|
|
3181
|
+
return fail4(err);
|
|
2861
3182
|
}
|
|
2862
3183
|
}
|
|
2863
3184
|
);
|
|
2864
3185
|
server.tool(
|
|
2865
3186
|
"linear_create_issue",
|
|
2866
|
-
'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.',
|
|
2867
3188
|
{
|
|
2868
|
-
team:
|
|
2869
|
-
title:
|
|
2870
|
-
description:
|
|
2871
|
-
state:
|
|
2872
|
-
assignee:
|
|
2873
|
-
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.")
|
|
2874
3196
|
},
|
|
2875
3197
|
async (args) => {
|
|
2876
3198
|
try {
|
|
2877
|
-
return
|
|
3199
|
+
return text4(await createLinearIssue(args));
|
|
2878
3200
|
} catch (err) {
|
|
2879
|
-
return
|
|
3201
|
+
return fail4(err);
|
|
2880
3202
|
}
|
|
2881
3203
|
}
|
|
2882
3204
|
);
|
|
@@ -2884,18 +3206,18 @@ function registerLinearTools(server) {
|
|
|
2884
3206
|
"linear_update_issue",
|
|
2885
3207
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
2886
3208
|
{
|
|
2887
|
-
id:
|
|
2888
|
-
title:
|
|
2889
|
-
description:
|
|
2890
|
-
state:
|
|
2891
|
-
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(),
|
|
2892
3214
|
priority: prioritySchema
|
|
2893
3215
|
},
|
|
2894
3216
|
async (args) => {
|
|
2895
3217
|
try {
|
|
2896
|
-
return
|
|
3218
|
+
return text4(await updateLinearIssue(args));
|
|
2897
3219
|
} catch (err) {
|
|
2898
|
-
return
|
|
3220
|
+
return fail4(err);
|
|
2899
3221
|
}
|
|
2900
3222
|
}
|
|
2901
3223
|
);
|
|
@@ -2903,14 +3225,14 @@ function registerLinearTools(server) {
|
|
|
2903
3225
|
"linear_comment",
|
|
2904
3226
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
2905
3227
|
{
|
|
2906
|
-
id:
|
|
2907
|
-
body:
|
|
3228
|
+
id: z4.string(),
|
|
3229
|
+
body: z4.string()
|
|
2908
3230
|
},
|
|
2909
3231
|
async (args) => {
|
|
2910
3232
|
try {
|
|
2911
|
-
return
|
|
3233
|
+
return text4(await commentLinearIssue(args));
|
|
2912
3234
|
} catch (err) {
|
|
2913
|
-
return
|
|
3235
|
+
return fail4(err);
|
|
2914
3236
|
}
|
|
2915
3237
|
}
|
|
2916
3238
|
);
|
|
@@ -2918,6 +3240,7 @@ function registerLinearTools(server) {
|
|
|
2918
3240
|
|
|
2919
3241
|
// src/mcp/issue-vendor-tools.ts
|
|
2920
3242
|
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
3243
|
+
registerGithubIssueTools(server);
|
|
2921
3244
|
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
2922
3245
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
2923
3246
|
}
|
|
@@ -2957,15 +3280,15 @@ function formatMcpPrList(input) {
|
|
|
2957
3280
|
}
|
|
2958
3281
|
|
|
2959
3282
|
// src/mcp/schedule-tools.ts
|
|
2960
|
-
import { z as
|
|
2961
|
-
function
|
|
3283
|
+
import { z as z5 } from "zod";
|
|
3284
|
+
function text5(payload, isError = false) {
|
|
2962
3285
|
return {
|
|
2963
3286
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
2964
3287
|
...isError ? { isError: true } : {}
|
|
2965
3288
|
};
|
|
2966
3289
|
}
|
|
2967
|
-
function
|
|
2968
|
-
return
|
|
3290
|
+
function fail5(err) {
|
|
3291
|
+
return text5(
|
|
2969
3292
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
2970
3293
|
true
|
|
2971
3294
|
);
|
|
@@ -2990,9 +3313,9 @@ function registerScheduleTools(server) {
|
|
|
2990
3313
|
{},
|
|
2991
3314
|
async () => {
|
|
2992
3315
|
try {
|
|
2993
|
-
return
|
|
3316
|
+
return text5({ schedules: listSchedules() });
|
|
2994
3317
|
} catch (err) {
|
|
2995
|
-
return
|
|
3318
|
+
return fail5(err);
|
|
2996
3319
|
}
|
|
2997
3320
|
}
|
|
2998
3321
|
);
|
|
@@ -3000,24 +3323,24 @@ function registerScheduleTools(server) {
|
|
|
3000
3323
|
"create_schedule",
|
|
3001
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.",
|
|
3002
3325
|
{
|
|
3003
|
-
prompt:
|
|
3004
|
-
name:
|
|
3005
|
-
at:
|
|
3006
|
-
every:
|
|
3007
|
-
cron:
|
|
3008
|
-
tz:
|
|
3009
|
-
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(
|
|
3010
3333
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
3011
3334
|
),
|
|
3012
|
-
agent:
|
|
3013
|
-
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()
|
|
3014
3337
|
},
|
|
3015
3338
|
async (args) => {
|
|
3016
3339
|
try {
|
|
3017
3340
|
const when = parseWhen(args);
|
|
3018
3341
|
const threadId = resolveScheduleThreadId(args.threadId);
|
|
3019
3342
|
if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
|
|
3020
|
-
return
|
|
3343
|
+
return fail5(
|
|
3021
3344
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
3022
3345
|
);
|
|
3023
3346
|
}
|
|
@@ -3030,12 +3353,12 @@ function registerScheduleTools(server) {
|
|
|
3030
3353
|
model: args.model,
|
|
3031
3354
|
createdBy: "mcp"
|
|
3032
3355
|
});
|
|
3033
|
-
return
|
|
3356
|
+
return text5({
|
|
3034
3357
|
schedule,
|
|
3035
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."
|
|
3036
3359
|
});
|
|
3037
3360
|
} catch (err) {
|
|
3038
|
-
return
|
|
3361
|
+
return fail5(err);
|
|
3039
3362
|
}
|
|
3040
3363
|
}
|
|
3041
3364
|
);
|
|
@@ -3043,17 +3366,17 @@ function registerScheduleTools(server) {
|
|
|
3043
3366
|
"update_schedule",
|
|
3044
3367
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
3045
3368
|
{
|
|
3046
|
-
id:
|
|
3047
|
-
prompt:
|
|
3048
|
-
name:
|
|
3049
|
-
at:
|
|
3050
|
-
every:
|
|
3051
|
-
cron:
|
|
3052
|
-
tz:
|
|
3053
|
-
threadId:
|
|
3054
|
-
agent:
|
|
3055
|
-
model:
|
|
3056
|
-
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()
|
|
3057
3380
|
},
|
|
3058
3381
|
async (args) => {
|
|
3059
3382
|
try {
|
|
@@ -3068,7 +3391,7 @@ function registerScheduleTools(server) {
|
|
|
3068
3391
|
if (args.threadId !== void 0) {
|
|
3069
3392
|
threadId = resolveScheduleThreadId(args.threadId);
|
|
3070
3393
|
if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
|
|
3071
|
-
return
|
|
3394
|
+
return fail5(
|
|
3072
3395
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
3073
3396
|
);
|
|
3074
3397
|
}
|
|
@@ -3082,38 +3405,38 @@ function registerScheduleTools(server) {
|
|
|
3082
3405
|
enabled: args.enabled,
|
|
3083
3406
|
model: args.model
|
|
3084
3407
|
});
|
|
3085
|
-
return
|
|
3408
|
+
return text5({ schedule });
|
|
3086
3409
|
} catch (err) {
|
|
3087
|
-
return
|
|
3410
|
+
return fail5(err);
|
|
3088
3411
|
}
|
|
3089
3412
|
}
|
|
3090
3413
|
);
|
|
3091
3414
|
server.tool(
|
|
3092
3415
|
"delete_schedule",
|
|
3093
3416
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
3094
|
-
{ id:
|
|
3417
|
+
{ id: z5.string() },
|
|
3095
3418
|
async ({ id }) => {
|
|
3096
3419
|
try {
|
|
3097
3420
|
deleteSchedule(id);
|
|
3098
|
-
return
|
|
3421
|
+
return text5({ ok: true, id });
|
|
3099
3422
|
} catch (err) {
|
|
3100
|
-
return
|
|
3423
|
+
return fail5(err);
|
|
3101
3424
|
}
|
|
3102
3425
|
}
|
|
3103
3426
|
);
|
|
3104
3427
|
server.tool(
|
|
3105
3428
|
"run_schedule",
|
|
3106
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.",
|
|
3107
|
-
{ id:
|
|
3430
|
+
{ id: z5.string() },
|
|
3108
3431
|
async ({ id }) => {
|
|
3109
3432
|
try {
|
|
3110
3433
|
if (!getSchedule(id)) {
|
|
3111
|
-
return
|
|
3434
|
+
return fail5(new Error(`Schedule not found: ${id}`));
|
|
3112
3435
|
}
|
|
3113
3436
|
const schedule = await fireSchedule(id);
|
|
3114
|
-
return
|
|
3437
|
+
return text5({ schedule });
|
|
3115
3438
|
} catch (err) {
|
|
3116
|
-
return
|
|
3439
|
+
return fail5(err);
|
|
3117
3440
|
}
|
|
3118
3441
|
}
|
|
3119
3442
|
);
|
|
@@ -3531,6 +3854,9 @@ async function startMcpServer() {
|
|
|
3531
3854
|
version: "0.1.0"
|
|
3532
3855
|
});
|
|
3533
3856
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
3857
|
+
if (worktreeProfile) {
|
|
3858
|
+
registerConnectedIssueVendorTools(server);
|
|
3859
|
+
}
|
|
3534
3860
|
if (!worktreeProfile) {
|
|
3535
3861
|
server.tool(
|
|
3536
3862
|
"list_workspaces",
|
|
@@ -3583,13 +3909,13 @@ async function startMcpServer() {
|
|
|
3583
3909
|
"list_board",
|
|
3584
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.",
|
|
3585
3911
|
{
|
|
3586
|
-
query:
|
|
3587
|
-
repoPath:
|
|
3588
|
-
kind:
|
|
3589
|
-
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(
|
|
3590
3916
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
3591
3917
|
),
|
|
3592
|
-
limit:
|
|
3918
|
+
limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
3593
3919
|
},
|
|
3594
3920
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
3595
3921
|
const workspaces = orch.listWorkspaces();
|
|
@@ -3627,7 +3953,7 @@ async function startMcpServer() {
|
|
|
3627
3953
|
server.tool(
|
|
3628
3954
|
"get_thread",
|
|
3629
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.",
|
|
3630
|
-
{ ref:
|
|
3956
|
+
{ ref: z6.string() },
|
|
3631
3957
|
async ({ ref }) => {
|
|
3632
3958
|
const t = orch.getThread(ref);
|
|
3633
3959
|
if (!t) {
|
|
@@ -3669,17 +3995,17 @@ async function startMcpServer() {
|
|
|
3669
3995
|
"present_artifact",
|
|
3670
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.",
|
|
3671
3997
|
{
|
|
3672
|
-
title:
|
|
3673
|
-
type:
|
|
3998
|
+
title: z6.string().describe("Short title shown in the artifact pane header"),
|
|
3999
|
+
type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
3674
4000
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
3675
4001
|
),
|
|
3676
|
-
content:
|
|
4002
|
+
content: z6.string().describe(
|
|
3677
4003
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
3678
4004
|
),
|
|
3679
|
-
artifact_id:
|
|
3680
|
-
status:
|
|
3681
|
-
phase:
|
|
3682
|
-
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")
|
|
3683
4009
|
},
|
|
3684
4010
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
3685
4011
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3700,15 +4026,15 @@ async function startMcpServer() {
|
|
|
3700
4026
|
"ask_user",
|
|
3701
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.",
|
|
3702
4028
|
{
|
|
3703
|
-
questions:
|
|
3704
|
-
|
|
3705
|
-
question:
|
|
3706
|
-
header:
|
|
3707
|
-
multiSelect:
|
|
3708
|
-
options:
|
|
3709
|
-
|
|
3710
|
-
label:
|
|
3711
|
-
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)")
|
|
3712
4038
|
})
|
|
3713
4039
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
3714
4040
|
})
|
|
@@ -3727,9 +4053,9 @@ async function startMcpServer() {
|
|
|
3727
4053
|
"present_plan",
|
|
3728
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.",
|
|
3729
4055
|
{
|
|
3730
|
-
title:
|
|
3731
|
-
content:
|
|
3732
|
-
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")
|
|
3733
4059
|
},
|
|
3734
4060
|
async ({ title, content, thread_id }) => {
|
|
3735
4061
|
const { writePlanFile: writePlanFile2 } = await import("./plan-file-LSSSQGTQ.js");
|
|
@@ -3752,15 +4078,15 @@ async function startMcpServer() {
|
|
|
3752
4078
|
"present_schema",
|
|
3753
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.",
|
|
3754
4080
|
{
|
|
3755
|
-
title:
|
|
3756
|
-
mode:
|
|
3757
|
-
datasource:
|
|
3758
|
-
resource_id:
|
|
3759
|
-
record_id:
|
|
3760
|
-
resource:
|
|
3761
|
-
record:
|
|
3762
|
-
records:
|
|
3763
|
-
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")
|
|
3764
4090
|
},
|
|
3765
4091
|
async (args) => {
|
|
3766
4092
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3781,10 +4107,10 @@ async function startMcpServer() {
|
|
|
3781
4107
|
"present_files",
|
|
3782
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.",
|
|
3783
4109
|
{
|
|
3784
|
-
title:
|
|
3785
|
-
datasource:
|
|
3786
|
-
path:
|
|
3787
|
-
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")
|
|
3788
4114
|
},
|
|
3789
4115
|
async (args) => {
|
|
3790
4116
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3811,7 +4137,7 @@ async function startMcpServer() {
|
|
|
3811
4137
|
"set_caffeinate",
|
|
3812
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.",
|
|
3813
4139
|
{
|
|
3814
|
-
enabled:
|
|
4140
|
+
enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
3815
4141
|
},
|
|
3816
4142
|
async ({ enabled }) => {
|
|
3817
4143
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -3849,18 +4175,18 @@ async function startMcpServer() {
|
|
|
3849
4175
|
"create_thread",
|
|
3850
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.`,
|
|
3851
4177
|
{
|
|
3852
|
-
sourceType:
|
|
3853
|
-
sourceRef:
|
|
3854
|
-
agent:
|
|
3855
|
-
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(
|
|
3856
4182
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
3857
4183
|
),
|
|
3858
|
-
repoPath:
|
|
3859
|
-
title:
|
|
3860
|
-
cowboy:
|
|
4184
|
+
repoPath: z6.string(),
|
|
4185
|
+
title: z6.string().optional(),
|
|
4186
|
+
cowboy: z6.boolean().optional().describe(
|
|
3861
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."
|
|
3862
4188
|
),
|
|
3863
|
-
parentThreadId:
|
|
4189
|
+
parentThreadId: z6.string().optional().describe(
|
|
3864
4190
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
3865
4191
|
)
|
|
3866
4192
|
},
|
|
@@ -3876,10 +4202,10 @@ async function startMcpServer() {
|
|
|
3876
4202
|
"start_board_card",
|
|
3877
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.",
|
|
3878
4204
|
{
|
|
3879
|
-
kind:
|
|
3880
|
-
ref:
|
|
3881
|
-
repoPath:
|
|
3882
|
-
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()
|
|
3883
4209
|
},
|
|
3884
4210
|
async ({ kind, ref, repoPath, title }) => {
|
|
3885
4211
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3983,9 +4309,9 @@ async function startMcpServer() {
|
|
|
3983
4309
|
"send_to_thread",
|
|
3984
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.',
|
|
3985
4311
|
{
|
|
3986
|
-
ref:
|
|
3987
|
-
prompt:
|
|
3988
|
-
force_stop:
|
|
4312
|
+
ref: z6.string(),
|
|
4313
|
+
prompt: z6.string(),
|
|
4314
|
+
force_stop: z6.boolean().optional()
|
|
3989
4315
|
},
|
|
3990
4316
|
async ({ ref, prompt, force_stop }) => {
|
|
3991
4317
|
if (force_stop) {
|
|
@@ -4014,8 +4340,8 @@ async function startMcpServer() {
|
|
|
4014
4340
|
"wait_for_turn",
|
|
4015
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).",
|
|
4016
4342
|
{
|
|
4017
|
-
ref:
|
|
4018
|
-
timeoutMs:
|
|
4343
|
+
ref: z6.string(),
|
|
4344
|
+
timeoutMs: z6.number().optional()
|
|
4019
4345
|
},
|
|
4020
4346
|
async ({ ref, timeoutMs }) => {
|
|
4021
4347
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -4045,7 +4371,7 @@ async function startMcpServer() {
|
|
|
4045
4371
|
server.tool(
|
|
4046
4372
|
"get_turn_result",
|
|
4047
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).",
|
|
4048
|
-
{ ref:
|
|
4374
|
+
{ ref: z6.string() },
|
|
4049
4375
|
async ({ ref }) => {
|
|
4050
4376
|
const result = orch.getTurnResult(ref);
|
|
4051
4377
|
return {
|
|
@@ -4066,8 +4392,8 @@ async function startMcpServer() {
|
|
|
4066
4392
|
"stop_thread",
|
|
4067
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.",
|
|
4068
4394
|
{
|
|
4069
|
-
ref:
|
|
4070
|
-
force:
|
|
4395
|
+
ref: z6.string(),
|
|
4396
|
+
force: z6.boolean().optional()
|
|
4071
4397
|
},
|
|
4072
4398
|
async ({ ref, force }) => {
|
|
4073
4399
|
const t = orch.getThread(ref);
|
|
@@ -4097,7 +4423,7 @@ async function startMcpServer() {
|
|
|
4097
4423
|
server.tool(
|
|
4098
4424
|
"archive_thread",
|
|
4099
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.",
|
|
4100
|
-
{ ref:
|
|
4426
|
+
{ ref: z6.string() },
|
|
4101
4427
|
async ({ ref }) => {
|
|
4102
4428
|
const t = orch.getThread(ref);
|
|
4103
4429
|
if (!t) {
|
|
@@ -4130,7 +4456,7 @@ async function startMcpServer() {
|
|
|
4130
4456
|
server.tool(
|
|
4131
4457
|
"restore_thread",
|
|
4132
4458
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
4133
|
-
{ ref:
|
|
4459
|
+
{ ref: z6.string() },
|
|
4134
4460
|
async ({ ref }) => {
|
|
4135
4461
|
try {
|
|
4136
4462
|
const restored = await orch.restore(ref);
|
|
@@ -4156,8 +4482,8 @@ async function startMcpServer() {
|
|
|
4156
4482
|
"get_diff",
|
|
4157
4483
|
"Compact diff summary (capped hunks, paginated)",
|
|
4158
4484
|
{
|
|
4159
|
-
ref:
|
|
4160
|
-
maxFiles:
|
|
4485
|
+
ref: z6.string(),
|
|
4486
|
+
maxFiles: z6.number().optional()
|
|
4161
4487
|
},
|
|
4162
4488
|
async ({ ref, maxFiles }) => {
|
|
4163
4489
|
const summary = await orch.diffSummary(ref);
|
|
@@ -4177,7 +4503,7 @@ async function startMcpServer() {
|
|
|
4177
4503
|
server.tool(
|
|
4178
4504
|
"get_pr_checks",
|
|
4179
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.",
|
|
4180
|
-
{ ref:
|
|
4506
|
+
{ ref: z6.string().describe("Worktree thread id/ref") },
|
|
4181
4507
|
async ({ ref }) => {
|
|
4182
4508
|
try {
|
|
4183
4509
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -4193,7 +4519,7 @@ async function startMcpServer() {
|
|
|
4193
4519
|
server.tool(
|
|
4194
4520
|
"request_review",
|
|
4195
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.',
|
|
4196
|
-
{ ref:
|
|
4522
|
+
{ ref: z6.string().describe("Worktree thread id/ref to review") },
|
|
4197
4523
|
async ({ ref }) => {
|
|
4198
4524
|
try {
|
|
4199
4525
|
const tab = await orch.requestReview(ref);
|
|
@@ -4222,8 +4548,8 @@ async function startMcpServer() {
|
|
|
4222
4548
|
"ask_git",
|
|
4223
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.",
|
|
4224
4550
|
{
|
|
4225
|
-
ref:
|
|
4226
|
-
action:
|
|
4551
|
+
ref: z6.string().describe("Worktree thread id/ref"),
|
|
4552
|
+
action: z6.enum(AGENT_GIT_ACTIONS).describe(
|
|
4227
4553
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
4228
4554
|
)
|
|
4229
4555
|
},
|
|
@@ -4268,7 +4594,7 @@ async function startMcpServer() {
|
|
|
4268
4594
|
}
|
|
4269
4595
|
}
|
|
4270
4596
|
);
|
|
4271
|
-
const agentEnum =
|
|
4597
|
+
const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
4272
4598
|
server.tool(
|
|
4273
4599
|
"list_models",
|
|
4274
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.",
|
|
@@ -4291,11 +4617,11 @@ async function startMcpServer() {
|
|
|
4291
4617
|
"fork_worktree",
|
|
4292
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.",
|
|
4293
4619
|
{
|
|
4294
|
-
ref:
|
|
4295
|
-
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)"),
|
|
4296
4622
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4297
|
-
model:
|
|
4298
|
-
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()
|
|
4299
4625
|
},
|
|
4300
4626
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4301
4627
|
try {
|
|
@@ -4336,11 +4662,11 @@ async function startMcpServer() {
|
|
|
4336
4662
|
"fork_chat",
|
|
4337
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.",
|
|
4338
4664
|
{
|
|
4339
|
-
ref:
|
|
4340
|
-
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)"),
|
|
4341
4667
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4342
|
-
model:
|
|
4343
|
-
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()
|
|
4344
4670
|
},
|
|
4345
4671
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4346
4672
|
try {
|
|
@@ -4386,8 +4712,8 @@ async function startMcpServer() {
|
|
|
4386
4712
|
"run_dev_script",
|
|
4387
4713
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
4388
4714
|
{
|
|
4389
|
-
ref:
|
|
4390
|
-
name:
|
|
4715
|
+
ref: z6.string(),
|
|
4716
|
+
name: z6.string().optional()
|
|
4391
4717
|
},
|
|
4392
4718
|
async ({ ref, name }) => {
|
|
4393
4719
|
const result = await orch.startDev(ref, name);
|
|
@@ -4409,7 +4735,7 @@ async function startMcpServer() {
|
|
|
4409
4735
|
server.tool(
|
|
4410
4736
|
"list_run_scripts",
|
|
4411
4737
|
"List named run scripts available for a thread",
|
|
4412
|
-
{ ref:
|
|
4738
|
+
{ ref: z6.string() },
|
|
4413
4739
|
async ({ ref }) => {
|
|
4414
4740
|
const scripts = orch.listThreadRunScripts(ref);
|
|
4415
4741
|
const active = orch.getActiveRuns(ref);
|
|
@@ -4427,8 +4753,8 @@ async function startMcpServer() {
|
|
|
4427
4753
|
"stop_dev_script",
|
|
4428
4754
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
4429
4755
|
{
|
|
4430
|
-
ref:
|
|
4431
|
-
name:
|
|
4756
|
+
ref: z6.string(),
|
|
4757
|
+
name: z6.string().optional()
|
|
4432
4758
|
},
|
|
4433
4759
|
async ({ ref, name }) => {
|
|
4434
4760
|
orch.stopDev(ref, name);
|
|
@@ -4438,7 +4764,7 @@ async function startMcpServer() {
|
|
|
4438
4764
|
server.tool(
|
|
4439
4765
|
"run_setup",
|
|
4440
4766
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
4441
|
-
{ ref:
|
|
4767
|
+
{ ref: z6.string() },
|
|
4442
4768
|
async ({ ref }) => {
|
|
4443
4769
|
const result = await orch.runSetup(ref);
|
|
4444
4770
|
return {
|
|
@@ -4449,7 +4775,7 @@ async function startMcpServer() {
|
|
|
4449
4775
|
server.tool(
|
|
4450
4776
|
"add_workspace",
|
|
4451
4777
|
"Register a git repo as a Sideboard workspace",
|
|
4452
|
-
{ repoPath:
|
|
4778
|
+
{ repoPath: z6.string() },
|
|
4453
4779
|
async ({ repoPath }) => {
|
|
4454
4780
|
const ws = await orch.addWorkspace(repoPath);
|
|
4455
4781
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -4458,7 +4784,7 @@ async function startMcpServer() {
|
|
|
4458
4784
|
server.tool(
|
|
4459
4785
|
"remove_workspace",
|
|
4460
4786
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
4461
|
-
{ repoPath:
|
|
4787
|
+
{ repoPath: z6.string() },
|
|
4462
4788
|
async ({ repoPath }) => {
|
|
4463
4789
|
orch.removeWorkspace(repoPath);
|
|
4464
4790
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -4468,12 +4794,12 @@ async function startMcpServer() {
|
|
|
4468
4794
|
"fanout",
|
|
4469
4795
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
4470
4796
|
{
|
|
4471
|
-
prompt:
|
|
4472
|
-
agents:
|
|
4473
|
-
repoPath:
|
|
4474
|
-
sourceType:
|
|
4475
|
-
sourceRef:
|
|
4476
|
-
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()
|
|
4477
4803
|
},
|
|
4478
4804
|
async (args) => {
|
|
4479
4805
|
const threads = await orch.bestOfN(args);
|
|
@@ -4500,8 +4826,8 @@ async function startMcpServer() {
|
|
|
4500
4826
|
"list_branches",
|
|
4501
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).",
|
|
4502
4828
|
{
|
|
4503
|
-
repoPath:
|
|
4504
|
-
unmergedOnly:
|
|
4829
|
+
repoPath: z6.string(),
|
|
4830
|
+
unmergedOnly: z6.boolean().optional()
|
|
4505
4831
|
},
|
|
4506
4832
|
async ({ repoPath, unmergedOnly }) => {
|
|
4507
4833
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4522,19 +4848,19 @@ async function startMcpServer() {
|
|
|
4522
4848
|
"list_prs",
|
|
4523
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.',
|
|
4524
4850
|
{
|
|
4525
|
-
repoPath:
|
|
4526
|
-
query:
|
|
4527
|
-
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(
|
|
4528
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.'
|
|
4529
4855
|
),
|
|
4530
|
-
state:
|
|
4531
|
-
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(
|
|
4532
4858
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
4533
4859
|
),
|
|
4534
|
-
reviewer:
|
|
4860
|
+
reviewer: z6.string().optional().describe(
|
|
4535
4861
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
4536
4862
|
),
|
|
4537
|
-
limit:
|
|
4863
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
4538
4864
|
},
|
|
4539
4865
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
4540
4866
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4566,7 +4892,7 @@ async function startMcpServer() {
|
|
|
4566
4892
|
server.tool(
|
|
4567
4893
|
"get_pr_stack",
|
|
4568
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).",
|
|
4569
|
-
{ ref:
|
|
4895
|
+
{ ref: z6.string() },
|
|
4570
4896
|
async ({ ref }) => {
|
|
4571
4897
|
const stack = await orch.getPrStack(ref);
|
|
4572
4898
|
return {
|
|
@@ -4578,8 +4904,8 @@ async function startMcpServer() {
|
|
|
4578
4904
|
"open_pr_stack_layers",
|
|
4579
4905
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
4580
4906
|
{
|
|
4581
|
-
ref:
|
|
4582
|
-
layer:
|
|
4907
|
+
ref: z6.string(),
|
|
4908
|
+
layer: z6.number().int().positive().optional()
|
|
4583
4909
|
},
|
|
4584
4910
|
async ({ ref, layer }) => {
|
|
4585
4911
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -4613,9 +4939,9 @@ async function startMcpServer() {
|
|
|
4613
4939
|
"add_stack_layer",
|
|
4614
4940
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
4615
4941
|
{
|
|
4616
|
-
ref:
|
|
4617
|
-
branchName:
|
|
4618
|
-
title:
|
|
4942
|
+
ref: z6.string(),
|
|
4943
|
+
branchName: z6.string(),
|
|
4944
|
+
title: z6.string().optional()
|
|
4619
4945
|
},
|
|
4620
4946
|
async ({ ref, branchName, title }) => {
|
|
4621
4947
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -4644,11 +4970,11 @@ async function startMcpServer() {
|
|
|
4644
4970
|
"create_pr_stack",
|
|
4645
4971
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
4646
4972
|
{
|
|
4647
|
-
repoPath:
|
|
4648
|
-
branches:
|
|
4649
|
-
agent:
|
|
4650
|
-
base:
|
|
4651
|
-
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()
|
|
4652
4978
|
},
|
|
4653
4979
|
async (args) => {
|
|
4654
4980
|
const result = await orch.createPrStack({
|
|
@@ -4687,10 +5013,10 @@ async function startMcpServer() {
|
|
|
4687
5013
|
"list_issues",
|
|
4688
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.",
|
|
4689
5015
|
{
|
|
4690
|
-
repoPath:
|
|
4691
|
-
query:
|
|
4692
|
-
assignee:
|
|
4693
|
-
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.")
|
|
4694
5020
|
},
|
|
4695
5021
|
async ({ repoPath, query, assignee, limit }) => {
|
|
4696
5022
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4822,8 +5148,8 @@ var BrightsySideboardApi = class {
|
|
|
4822
5148
|
};
|
|
4823
5149
|
function taskMessageText(task) {
|
|
4824
5150
|
const parts = task.message?.parts ?? [];
|
|
4825
|
-
const
|
|
4826
|
-
return
|
|
5151
|
+
const text6 = parts.find((p) => p.kind === "text")?.text ?? "";
|
|
5152
|
+
return text6.trim();
|
|
4827
5153
|
}
|
|
4828
5154
|
|
|
4829
5155
|
// src/brightsy/cloud-connect.ts
|
|
@@ -5017,75 +5343,6 @@ async function runCloudConnect(opts) {
|
|
|
5017
5343
|
}
|
|
5018
5344
|
}
|
|
5019
5345
|
|
|
5020
|
-
// src/agents/user-mcp-config.ts
|
|
5021
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5022
|
-
import { homedir } from "os";
|
|
5023
|
-
import { dirname, join as join3 } from "path";
|
|
5024
|
-
function userCursorMcpConfigPath() {
|
|
5025
|
-
return join3(homedir(), ".cursor", "mcp.json");
|
|
5026
|
-
}
|
|
5027
|
-
function userClaudeMcpConfigPath() {
|
|
5028
|
-
return join3(homedir(), ".claude.json");
|
|
5029
|
-
}
|
|
5030
|
-
function asObject(value) {
|
|
5031
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
5032
|
-
return { ...value };
|
|
5033
|
-
}
|
|
5034
|
-
function stripElectronSpawnEnv(env) {
|
|
5035
|
-
if (!env) return void 0;
|
|
5036
|
-
const next = { ...env };
|
|
5037
|
-
delete next.ELECTRON_RUN_AS_NODE;
|
|
5038
|
-
delete next.ELECTRON_RUN_AS_NODE;
|
|
5039
|
-
return Object.keys(next).length > 0 ? next : void 0;
|
|
5040
|
-
}
|
|
5041
|
-
function mergeSideboardIntoMcpServersJson(existing, sideboard) {
|
|
5042
|
-
const root = asObject(existing);
|
|
5043
|
-
const servers = asObject(root.mcpServers);
|
|
5044
|
-
const env = stripElectronSpawnEnv(sideboard.env);
|
|
5045
|
-
servers.sideboard = {
|
|
5046
|
-
type: "stdio",
|
|
5047
|
-
command: sideboard.command,
|
|
5048
|
-
...sideboard.args && sideboard.args.length > 0 ? { args: sideboard.args } : {},
|
|
5049
|
-
...env ? { env } : {}
|
|
5050
|
-
};
|
|
5051
|
-
return { ...root, mcpServers: servers };
|
|
5052
|
-
}
|
|
5053
|
-
function writeMergedMcpServersJson(configPath, sideboard) {
|
|
5054
|
-
let existing = {};
|
|
5055
|
-
if (existsSync3(configPath)) {
|
|
5056
|
-
try {
|
|
5057
|
-
existing = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
5058
|
-
} catch {
|
|
5059
|
-
existing = {};
|
|
5060
|
-
}
|
|
5061
|
-
}
|
|
5062
|
-
const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
|
|
5063
|
-
mkdirSync3(dirname(configPath), { recursive: true });
|
|
5064
|
-
writeFileSync3(configPath, `${JSON.stringify(next, null, 2)}
|
|
5065
|
-
`);
|
|
5066
|
-
}
|
|
5067
|
-
function launchFromResolved(server) {
|
|
5068
|
-
return {
|
|
5069
|
-
command: server.command,
|
|
5070
|
-
args: server.args,
|
|
5071
|
-
env: {
|
|
5072
|
-
...server.env ?? {},
|
|
5073
|
-
SIDEBOARD_APP_DATA: appDataDir()
|
|
5074
|
-
}
|
|
5075
|
-
};
|
|
5076
|
-
}
|
|
5077
|
-
async function registerPackagedUserMcpClients() {
|
|
5078
|
-
const launch = launchFromResolved(await resolveSideboardMcpServer());
|
|
5079
|
-
const cursor = userCursorMcpConfigPath();
|
|
5080
|
-
writeMergedMcpServersJson(cursor, launch);
|
|
5081
|
-
const claude = userClaudeMcpConfigPath();
|
|
5082
|
-
if (existsSync3(claude)) {
|
|
5083
|
-
writeMergedMcpServersJson(claude, launch);
|
|
5084
|
-
return { cursor, claude };
|
|
5085
|
-
}
|
|
5086
|
-
return { cursor };
|
|
5087
|
-
}
|
|
5088
|
-
|
|
5089
5346
|
// src/slack/oauth.ts
|
|
5090
5347
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5091
5348
|
|
|
@@ -5365,11 +5622,11 @@ async function startSlackOAuth(opts) {
|
|
|
5365
5622
|
import { WebSocket as WsWebSocket } from "ws";
|
|
5366
5623
|
var BACKOFF_START_MS = 1e3;
|
|
5367
5624
|
var BACKOFF_MAX_MS = 3e4;
|
|
5368
|
-
function stripSlackMentions(
|
|
5369
|
-
return
|
|
5625
|
+
function stripSlackMentions(text6) {
|
|
5626
|
+
return text6.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
5370
5627
|
}
|
|
5371
|
-
function isSlackStopCommand(
|
|
5372
|
-
const t = stripSlackMentions(
|
|
5628
|
+
function isSlackStopCommand(text6) {
|
|
5629
|
+
const t = stripSlackMentions(text6).toLowerCase();
|
|
5373
5630
|
return t === "stop" || t === "sideboard_force_stop";
|
|
5374
5631
|
}
|
|
5375
5632
|
function parseSlackSocketFrame(raw) {
|
|
@@ -5391,8 +5648,8 @@ function inboundFromSocketFrame(frame) {
|
|
|
5391
5648
|
const ts = event.ts?.trim();
|
|
5392
5649
|
if (!channelId || !ts) return null;
|
|
5393
5650
|
const rawText = event.text?.trim() ?? "";
|
|
5394
|
-
const
|
|
5395
|
-
if (!
|
|
5651
|
+
const text6 = stripSlackMentions(rawText);
|
|
5652
|
+
if (!text6) return null;
|
|
5396
5653
|
const teamId = (frame.payload?.team_id || event.team || "").trim();
|
|
5397
5654
|
if (!teamId) return null;
|
|
5398
5655
|
const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
|
|
@@ -5404,7 +5661,7 @@ function inboundFromSocketFrame(frame) {
|
|
|
5404
5661
|
ts,
|
|
5405
5662
|
threadTs: event.thread_ts?.trim() || void 0,
|
|
5406
5663
|
userId: event.user?.trim(),
|
|
5407
|
-
text:
|
|
5664
|
+
text: text6,
|
|
5408
5665
|
kind: isMention ? "mention" : "dm"
|
|
5409
5666
|
};
|
|
5410
5667
|
}
|
|
@@ -5944,12 +6201,12 @@ function formatSlackInboundPrompt(msg) {
|
|
|
5944
6201
|
|
|
5945
6202
|
${msg.text}`;
|
|
5946
6203
|
}
|
|
5947
|
-
function isSlackInboundUserPrompt(
|
|
5948
|
-
return
|
|
6204
|
+
function isSlackInboundUserPrompt(text6) {
|
|
6205
|
+
return text6.startsWith("Slack DM\n") || text6.startsWith("Slack @mention\n");
|
|
5949
6206
|
}
|
|
5950
|
-
function formatSlackSignedReply(deviceLabel,
|
|
6207
|
+
function formatSlackSignedReply(deviceLabel, text6) {
|
|
5951
6208
|
const label = deviceLabel.trim();
|
|
5952
|
-
const body =
|
|
6209
|
+
const body = text6.trim();
|
|
5953
6210
|
if (!body) return body;
|
|
5954
6211
|
if (!label) return body;
|
|
5955
6212
|
const head = `${label}:`;
|
|
@@ -5958,8 +6215,8 @@ function formatSlackSignedReply(deviceLabel, text5) {
|
|
|
5958
6215
|
}
|
|
5959
6216
|
return `${label}: ${body}`;
|
|
5960
6217
|
}
|
|
5961
|
-
function signForThisMac(
|
|
5962
|
-
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel,
|
|
6218
|
+
function signForThisMac(text6) {
|
|
6219
|
+
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text6);
|
|
5963
6220
|
}
|
|
5964
6221
|
function slackReplyThreadTs(msg) {
|
|
5965
6222
|
if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
|
|
@@ -6020,9 +6277,9 @@ function replyStub(target) {
|
|
|
6020
6277
|
kind: "dm"
|
|
6021
6278
|
};
|
|
6022
6279
|
}
|
|
6023
|
-
async function postSlackText(target,
|
|
6280
|
+
async function postSlackText(target, text6, opts) {
|
|
6024
6281
|
if (opts.postReply) {
|
|
6025
|
-
const result = await opts.postReply(replyStub(target),
|
|
6282
|
+
const result = await opts.postReply(replyStub(target), text6);
|
|
6026
6283
|
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
6027
6284
|
return ts2 ? { ts: ts2 } : null;
|
|
6028
6285
|
}
|
|
@@ -6032,7 +6289,7 @@ async function postSlackText(target, text5, opts) {
|
|
|
6032
6289
|
"chat.postMessage",
|
|
6033
6290
|
{
|
|
6034
6291
|
channel: target.channelId,
|
|
6035
|
-
text:
|
|
6292
|
+
text: text6,
|
|
6036
6293
|
thread_ts: target.threadTs
|
|
6037
6294
|
},
|
|
6038
6295
|
opts.fetchImpl
|
|
@@ -6040,9 +6297,9 @@ async function postSlackText(target, text5, opts) {
|
|
|
6040
6297
|
const ts = json.ts?.trim();
|
|
6041
6298
|
return ts ? { ts } : null;
|
|
6042
6299
|
}
|
|
6043
|
-
async function updateSlackText(target, ts,
|
|
6300
|
+
async function updateSlackText(target, ts, text6, opts) {
|
|
6044
6301
|
if (opts.updateReply) {
|
|
6045
|
-
await opts.updateReply(replyStub(target), ts,
|
|
6302
|
+
await opts.updateReply(replyStub(target), ts, text6);
|
|
6046
6303
|
return;
|
|
6047
6304
|
}
|
|
6048
6305
|
const token = writeTokenForTeam(target.teamId);
|
|
@@ -6052,7 +6309,7 @@ async function updateSlackText(target, ts, text5, opts) {
|
|
|
6052
6309
|
{
|
|
6053
6310
|
channel: target.channelId,
|
|
6054
6311
|
ts,
|
|
6055
|
-
text:
|
|
6312
|
+
text: text6
|
|
6056
6313
|
},
|
|
6057
6314
|
opts.fetchImpl
|
|
6058
6315
|
);
|
|
@@ -6080,8 +6337,8 @@ function replyTargetOf(msg) {
|
|
|
6080
6337
|
threadTs: slackReplyThreadTs(msg)
|
|
6081
6338
|
};
|
|
6082
6339
|
}
|
|
6083
|
-
async function postSlackReply(msg,
|
|
6084
|
-
await postSlackText(replyTargetOf(msg), signForThisMac(
|
|
6340
|
+
async function postSlackReply(msg, text6, opts) {
|
|
6341
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text6), opts);
|
|
6085
6342
|
}
|
|
6086
6343
|
function startSlackTurnProgress(msg, threadId, opts) {
|
|
6087
6344
|
const log = opts.onLog ?? (() => void 0);
|
|
@@ -6164,10 +6421,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
6164
6421
|
}
|
|
6165
6422
|
});
|
|
6166
6423
|
},
|
|
6167
|
-
finishWith: (
|
|
6424
|
+
finishWith: (text6) => {
|
|
6168
6425
|
stop();
|
|
6169
6426
|
return run2(async () => {
|
|
6170
|
-
const signed = signForThisMac(
|
|
6427
|
+
const signed = signForThisMac(text6.trim());
|
|
6171
6428
|
if (!signed) {
|
|
6172
6429
|
if (!postedTs) return;
|
|
6173
6430
|
const ts = postedTs;
|
|
@@ -6195,11 +6452,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
6195
6452
|
};
|
|
6196
6453
|
}
|
|
6197
6454
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
6198
|
-
function markRelayed(threadId,
|
|
6199
|
-
lastRelayed.set(threadId, `${threadId}:${
|
|
6455
|
+
function markRelayed(threadId, text6) {
|
|
6456
|
+
lastRelayed.set(threadId, `${threadId}:${text6}`);
|
|
6200
6457
|
}
|
|
6201
|
-
function alreadyRelayed(threadId,
|
|
6202
|
-
return lastRelayed.get(threadId) === `${threadId}:${
|
|
6458
|
+
function alreadyRelayed(threadId, text6) {
|
|
6459
|
+
return lastRelayed.get(threadId) === `${threadId}:${text6}`;
|
|
6203
6460
|
}
|
|
6204
6461
|
async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
6205
6462
|
const target = getSlackReplyTarget(threadId);
|
|
@@ -6208,14 +6465,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
|
6208
6465
|
const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
|
|
6209
6466
|
if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
|
|
6210
6467
|
const result = getOrchestrator().getTurnResult(threadId);
|
|
6211
|
-
const
|
|
6212
|
-
if (!
|
|
6213
|
-
if (alreadyRelayed(threadId,
|
|
6214
|
-
markRelayed(threadId,
|
|
6468
|
+
const text6 = result.text.trim();
|
|
6469
|
+
if (!text6) return;
|
|
6470
|
+
if (alreadyRelayed(threadId, text6)) return;
|
|
6471
|
+
markRelayed(threadId, text6);
|
|
6215
6472
|
const log = opts.onLog ?? (() => void 0);
|
|
6216
6473
|
try {
|
|
6217
|
-
await postSlackText(target, signForThisMac(
|
|
6218
|
-
log(`replied ${target.threadTs ?? target.channelId} (${
|
|
6474
|
+
await postSlackText(target, signForThisMac(text6), opts);
|
|
6475
|
+
log(`replied ${target.threadTs ?? target.channelId} (${text6.length} chars)`);
|
|
6219
6476
|
} catch (err) {
|
|
6220
6477
|
lastRelayed.delete(threadId);
|
|
6221
6478
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -6463,10 +6720,10 @@ function resolveSlackListenMode(opts) {
|
|
|
6463
6720
|
}
|
|
6464
6721
|
|
|
6465
6722
|
// src/slack/relay-hub.ts
|
|
6466
|
-
function parseSlackDeviceDestination(
|
|
6467
|
-
const m =
|
|
6468
|
-
if (!m) return { label: null, rest:
|
|
6469
|
-
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) };
|
|
6470
6727
|
}
|
|
6471
6728
|
var SlackRelayHub = class {
|
|
6472
6729
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -7072,6 +7329,9 @@ export {
|
|
|
7072
7329
|
SlackOAuthCancelledError,
|
|
7073
7330
|
SlackRelayHub,
|
|
7074
7331
|
THINKING_EFFORTS,
|
|
7332
|
+
WORKTREE_ABLETIME_MCP_TOOLS,
|
|
7333
|
+
WORKTREE_GITHUB_MCP_TOOLS,
|
|
7334
|
+
WORKTREE_LINEAR_MCP_TOOLS,
|
|
7075
7335
|
WORKTREE_MCP_TOOLS,
|
|
7076
7336
|
abletimeMcpRequest,
|
|
7077
7337
|
abletimeMcpUrl,
|
|
@@ -7159,6 +7419,8 @@ export {
|
|
|
7159
7419
|
codexUnattendedGitConfigArgs,
|
|
7160
7420
|
coerceOrchestratorAgent,
|
|
7161
7421
|
collectTakenTeamSlugs,
|
|
7422
|
+
commentAbleTimeTask,
|
|
7423
|
+
commentGitHubIssue,
|
|
7162
7424
|
commentLinearIssue,
|
|
7163
7425
|
commitAll,
|
|
7164
7426
|
computeNextRunAt,
|
|
@@ -7180,6 +7442,7 @@ export {
|
|
|
7180
7442
|
createChatTab,
|
|
7181
7443
|
createEmptyThread,
|
|
7182
7444
|
createExistingBranchWorktree,
|
|
7445
|
+
createGitHubIssue,
|
|
7183
7446
|
createGlobalChat,
|
|
7184
7447
|
createLinearIssue,
|
|
7185
7448
|
createLinearPkce,
|
|
@@ -7266,6 +7529,10 @@ export {
|
|
|
7266
7529
|
formatGhLandError,
|
|
7267
7530
|
formatGitAuthModeDirective,
|
|
7268
7531
|
formatIpcInvokeError,
|
|
7532
|
+
formatIssueToolsDirective,
|
|
7533
|
+
formatIssueToolsReminder,
|
|
7534
|
+
formatLinearDirective,
|
|
7535
|
+
formatLinearReminder,
|
|
7269
7536
|
formatLongRunningDirective,
|
|
7270
7537
|
formatLongRunningReminder,
|
|
7271
7538
|
formatMergePrError,
|
|
@@ -7311,6 +7578,7 @@ export {
|
|
|
7311
7578
|
getDefaultRunScript,
|
|
7312
7579
|
getDiff,
|
|
7313
7580
|
getDiffSummary,
|
|
7581
|
+
getGitHubIssue,
|
|
7314
7582
|
getGitHubStatus,
|
|
7315
7583
|
getGithubGitAuthMode,
|
|
7316
7584
|
getGithubPat,
|
|
@@ -7381,6 +7649,7 @@ export {
|
|
|
7381
7649
|
isImageFilePath,
|
|
7382
7650
|
isInPrStack,
|
|
7383
7651
|
isInboundForThisDesktop,
|
|
7652
|
+
isInjectedOrchMcpName,
|
|
7384
7653
|
isInternalAgentStatusText,
|
|
7385
7654
|
isIssueSourceConnected,
|
|
7386
7655
|
isLinearConnected,
|
|
@@ -7412,6 +7681,7 @@ export {
|
|
|
7412
7681
|
issueAttachmentForAbleTimeTask,
|
|
7413
7682
|
issueMatchesAssignee,
|
|
7414
7683
|
issueSourceLabel,
|
|
7684
|
+
issueTicketFromThread,
|
|
7415
7685
|
lastRequestOccupancy,
|
|
7416
7686
|
latestPendingPlanQuestions,
|
|
7417
7687
|
linearAuthorizationHeader,
|
|
@@ -7419,6 +7689,7 @@ export {
|
|
|
7419
7689
|
linearGraphql,
|
|
7420
7690
|
linearOAuthAuthorizeUrl,
|
|
7421
7691
|
linearOAuthCredentials,
|
|
7692
|
+
linearTicketIdFromThread,
|
|
7422
7693
|
listAbleTimeAssignedIssues,
|
|
7423
7694
|
listAbleTimeProjects,
|
|
7424
7695
|
listAbleTimeTasks,
|
|
@@ -7468,6 +7739,7 @@ export {
|
|
|
7468
7739
|
maybeCompactContext,
|
|
7469
7740
|
mcpAllowTools,
|
|
7470
7741
|
mcpAuthWarnings,
|
|
7742
|
+
mcpStatusThinkingFromClaudeInit,
|
|
7471
7743
|
mergeAgentGitAuthEnv,
|
|
7472
7744
|
mergePr,
|
|
7473
7745
|
mergePrStack,
|
|
@@ -7504,6 +7776,7 @@ export {
|
|
|
7504
7776
|
parseDurationMs,
|
|
7505
7777
|
parseForceStopMessage,
|
|
7506
7778
|
parseGhStackViewJson,
|
|
7779
|
+
parseGitHubIssueNumber,
|
|
7507
7780
|
parseGithubSlugFromRemoteUrl,
|
|
7508
7781
|
parseMcpList,
|
|
7509
7782
|
parsePlanQuestionsInput,
|
|
@@ -7567,6 +7840,7 @@ export {
|
|
|
7567
7840
|
resolveFilesToCopy,
|
|
7568
7841
|
resolveGhAuthToken,
|
|
7569
7842
|
resolveGitDirsForLockRecovery,
|
|
7843
|
+
resolveGitHubIssueRepo,
|
|
7570
7844
|
resolveGithubAgentToken,
|
|
7571
7845
|
resolveGithubRepoSlug,
|
|
7572
7846
|
resolveLinearState,
|
|
@@ -7671,6 +7945,7 @@ export {
|
|
|
7671
7945
|
threadsDir,
|
|
7672
7946
|
threadsSharingWorktree,
|
|
7673
7947
|
toAbleTimeIssueInfo,
|
|
7948
|
+
toGitHubIssueInfo,
|
|
7674
7949
|
toPublicAppSettings,
|
|
7675
7950
|
toolActivityLine,
|
|
7676
7951
|
toolDescription,
|
|
@@ -7678,6 +7953,7 @@ export {
|
|
|
7678
7953
|
toolFilePath,
|
|
7679
7954
|
totalTokens,
|
|
7680
7955
|
turnCostUsdFromCursorUsage,
|
|
7956
|
+
updateAbleTimeTask,
|
|
7681
7957
|
updateAdvancedSettings,
|
|
7682
7958
|
updateAgentExecutable,
|
|
7683
7959
|
updateAppEnvironment,
|
|
@@ -7685,6 +7961,7 @@ export {
|
|
|
7685
7961
|
updateClaudeSettings,
|
|
7686
7962
|
updateCodexSettings,
|
|
7687
7963
|
updateDefaultsSettings,
|
|
7964
|
+
updateGitHubIssue,
|
|
7688
7965
|
updateIntegrationsSettings,
|
|
7689
7966
|
updateLinearIssue,
|
|
7690
7967
|
updateOpencodeSettings,
|
|
@@ -7693,6 +7970,7 @@ export {
|
|
|
7693
7970
|
updateThread,
|
|
7694
7971
|
userClaudeMcpConfigPath,
|
|
7695
7972
|
userCursorMcpConfigPath,
|
|
7973
|
+
userMcpNamesToDisable,
|
|
7696
7974
|
validateLinearApiKey,
|
|
7697
7975
|
verifyAbleTimeConnection,
|
|
7698
7976
|
verifyOptionalService,
|