@sideboard-ai/core 0.1.156 → 0.1.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{abletime-JSTJLXAW.js → abletime-BEIDJG7W.js} +5 -1
- package/dist/{abletime-H7LPGIP2.js → abletime-GWZE6OYM.js} +5 -1
- package/dist/{agents-IY4CSPWV.js → agents-E3ZPWZRS.js} +2 -2
- package/dist/{agents-2FTZAXO3.js → agents-RGE7PSUY.js} +4 -4
- package/dist/{chunk-TI6UMRWK.js → chunk-3SUZMPGL.js} +5 -4
- package/dist/{chunk-NVXVEGGM.js → chunk-5U4NZLBH.js} +4 -0
- package/dist/{chunk-CJQLPPEJ.js → chunk-A7QXLYBN.js} +1 -1
- package/dist/{chunk-XLU7DXHH.js → chunk-BLRHO7XQ.js} +54 -5
- package/dist/{chunk-MSFPM5VK.js → chunk-CVR4RJ6G.js} +3 -2
- package/dist/{chunk-JOK4XWBG.js → chunk-DVJBO3M7.js} +87 -3
- package/dist/{chunk-UA3YZKRX.js → chunk-EPQIOAAY.js} +25 -5
- package/dist/{chunk-OLE2BLIX.js → chunk-FBFIOVDH.js} +457 -106
- package/dist/{chunk-2SDMENAL.js → chunk-JV4VNFMS.js} +1 -1
- package/dist/{chunk-7EIPEPAH.js → chunk-KYKNFJK5.js} +484 -105
- package/dist/{chunk-FDCFXMDG.js → chunk-N27GVFZY.js} +87 -3
- package/dist/{coordinator-prompt-D2FHU5IZ.js → coordinator-prompt-26IJU2AV.js} +1 -1
- package/dist/{coordinator-prompt-UZDNIVP7.js → coordinator-prompt-GZESIQNH.js} +3 -3
- package/dist/{global-workspace-5WZVSWNT.js → global-workspace-5BIW26YR.js} +1 -1
- package/dist/{global-workspace-MR75BDBY.js → global-workspace-REI55FZ6.js} +3 -3
- package/dist/index.cjs +1405 -560
- package/dist/index.d.cts +138 -4
- package/dist/index.d.ts +138 -4
- package/dist/index.js +609 -274
- package/dist/mcp/run-stdio.cjs +1253 -500
- package/dist/mcp/run-stdio.js +525 -226
- package/dist/{orchestrator-LXO2CDOG.js → orchestrator-J727WIJY.js} +6 -6
- package/dist/{orchestrator-G4WKJG5Y.js → orchestrator-ZVW2AC6B.js} +3 -3
- package/dist/{plan-file-OZEBFSGQ.js → plan-file-I24VJVP2.js} +2 -2
- package/dist/{workspaces-RP32AW6X.js → workspaces-NYSM2EPX.js} +1 -1
- package/dist/{workspaces-HO4EQNNM.js → workspaces-OCEPZXFD.js} +3 -3
- package/dist/{worktree-PEWDAX5O.js → worktree-VTMBZKY6.js} +2 -2
- package/package.json +1 -1
package/dist/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,
|
|
@@ -156,6 +162,10 @@ import {
|
|
|
156
162
|
listWorktreeFiles,
|
|
157
163
|
loadAgentInstructions,
|
|
158
164
|
maybeCompactContext,
|
|
165
|
+
mcpWaitFinishedHint,
|
|
166
|
+
mcpWaitForJobTimeoutMs,
|
|
167
|
+
mcpWaitForTurnTimeoutMs,
|
|
168
|
+
mcpWaitStillRunningHint,
|
|
159
169
|
mergeSetupOutput,
|
|
160
170
|
messagesSinceLastBrightsyContextSummary,
|
|
161
171
|
normalizeServiceOrigin,
|
|
@@ -217,13 +227,14 @@ import {
|
|
|
217
227
|
updateSchedule,
|
|
218
228
|
upsertSlackWorkspace,
|
|
219
229
|
verifyOptionalService,
|
|
230
|
+
waitForDetachedJob,
|
|
220
231
|
waitForPidExit,
|
|
221
232
|
withAgentInstructions,
|
|
222
233
|
worktreeBoardStatus,
|
|
223
234
|
worktreeCleanupSettings,
|
|
224
235
|
wrapReviewSkillMarkdown,
|
|
225
236
|
writeWorktreeFile
|
|
226
|
-
} from "./chunk-
|
|
237
|
+
} from "./chunk-KYKNFJK5.js";
|
|
227
238
|
import {
|
|
228
239
|
LEGACY_PLAN_FILE_REL,
|
|
229
240
|
PLAN_FILE_NAME,
|
|
@@ -242,6 +253,9 @@ import {
|
|
|
242
253
|
PLAN_MODE_INSTRUCTION,
|
|
243
254
|
SIDEBOARD_MCP_ALLOWED_TOOLS,
|
|
244
255
|
SIDEBOARD_MCP_PROFILE_ENV,
|
|
256
|
+
WORKTREE_ABLETIME_MCP_TOOLS,
|
|
257
|
+
WORKTREE_GITHUB_MCP_TOOLS,
|
|
258
|
+
WORKTREE_LINEAR_MCP_TOOLS,
|
|
245
259
|
WORKTREE_MCP_TOOLS,
|
|
246
260
|
allAdapters,
|
|
247
261
|
applyAgentEvent,
|
|
@@ -323,7 +337,7 @@ import {
|
|
|
323
337
|
withEventParentId,
|
|
324
338
|
withEventsParentId,
|
|
325
339
|
writeInjectedMcpConfig
|
|
326
|
-
} from "./chunk-
|
|
340
|
+
} from "./chunk-BLRHO7XQ.js";
|
|
327
341
|
import {
|
|
328
342
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
329
343
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -370,7 +384,7 @@ import {
|
|
|
370
384
|
stageBuffersAsAttachments,
|
|
371
385
|
syncWorkspacesFromThreads,
|
|
372
386
|
takenTeamSlugsForOrchestration
|
|
373
|
-
} from "./chunk-
|
|
387
|
+
} from "./chunk-CVR4RJ6G.js";
|
|
374
388
|
import {
|
|
375
389
|
FAMOUS_SOCCER_TEAMS,
|
|
376
390
|
GITHUB_PR_BODY_MAX_CHARS,
|
|
@@ -496,6 +510,7 @@ import {
|
|
|
496
510
|
abletimeMcpRequest,
|
|
497
511
|
abletimeMcpUrl,
|
|
498
512
|
callAbleTimeTool,
|
|
513
|
+
commentAbleTimeTask,
|
|
499
514
|
createAbleTimeTask,
|
|
500
515
|
ensureAbleTimeTask,
|
|
501
516
|
getAbleTimeOrientation,
|
|
@@ -510,8 +525,9 @@ import {
|
|
|
510
525
|
searchAbleTimeTasks,
|
|
511
526
|
taskUrl,
|
|
512
527
|
toAbleTimeIssueInfo,
|
|
528
|
+
updateAbleTimeTask,
|
|
513
529
|
verifyAbleTimeConnection
|
|
514
|
-
} from "./chunk-
|
|
530
|
+
} from "./chunk-DVJBO3M7.js";
|
|
515
531
|
import {
|
|
516
532
|
formatFetchError,
|
|
517
533
|
httpFetch,
|
|
@@ -722,9 +738,9 @@ async function getGitHubStatus() {
|
|
|
722
738
|
};
|
|
723
739
|
}
|
|
724
740
|
const status = await run("gh", ["auth", "status"], { reject: false });
|
|
725
|
-
const
|
|
741
|
+
const text6 = `${status.stdout}
|
|
726
742
|
${status.stderr}`;
|
|
727
|
-
const loginMatch =
|
|
743
|
+
const loginMatch = text6.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text6.match(/Logged in to ([^\s]+) as (\S+)/i);
|
|
728
744
|
if (loginMatch) {
|
|
729
745
|
return {
|
|
730
746
|
connected: true,
|
|
@@ -1504,6 +1520,11 @@ async function createLinearIssue(input, opts) {
|
|
|
1504
1520
|
if (input.state?.trim()) {
|
|
1505
1521
|
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
1506
1522
|
}
|
|
1523
|
+
const parentRef = input.parent?.trim();
|
|
1524
|
+
if (parentRef) {
|
|
1525
|
+
const parent = await getLinearIssue(parentRef, opts);
|
|
1526
|
+
mutationInput.parentId = parent.id;
|
|
1527
|
+
}
|
|
1507
1528
|
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
1508
1529
|
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
1509
1530
|
else if (assignee) mutationInput.assigneeId = assignee;
|
|
@@ -1584,6 +1605,190 @@ async function validateLinearApiKey(apiKey) {
|
|
|
1584
1605
|
}
|
|
1585
1606
|
}
|
|
1586
1607
|
|
|
1608
|
+
// src/integrations/github-issues.ts
|
|
1609
|
+
function requireGhOk(result, label) {
|
|
1610
|
+
if (result.exitCode !== 0) {
|
|
1611
|
+
const detail = (result.stderr || result.stdout).trim() || "gh failed";
|
|
1612
|
+
throw new Error(`${label}: ${detail}`);
|
|
1613
|
+
}
|
|
1614
|
+
return result.stdout;
|
|
1615
|
+
}
|
|
1616
|
+
function parseGitHubIssueNumber(id) {
|
|
1617
|
+
const trimmed = id.trim();
|
|
1618
|
+
if (!trimmed) throw new Error("GitHub issue id is required (#123 or a URL)");
|
|
1619
|
+
const url = trimmed.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/i);
|
|
1620
|
+
if (url) return Number(url[1]);
|
|
1621
|
+
const prefixed = trimmed.match(/^gh-(\d+)$/i);
|
|
1622
|
+
if (prefixed) return Number(prefixed[1]);
|
|
1623
|
+
const bare = trimmed.match(/^#?(\d+)$/);
|
|
1624
|
+
if (bare) return Number(bare[1]);
|
|
1625
|
+
throw new Error(`GitHub issue id must be #123, a number, or an issue URL (got ${trimmed})`);
|
|
1626
|
+
}
|
|
1627
|
+
async function resolveGitHubIssueRepo(repoPath) {
|
|
1628
|
+
const cwd = await resolveRepoRoot((repoPath ?? "").trim() || process.cwd());
|
|
1629
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1630
|
+
return { cwd, slug, repoArgs: slug ? ghRepoSelectArgs(slug) : [] };
|
|
1631
|
+
}
|
|
1632
|
+
function mapLabels(raw) {
|
|
1633
|
+
if (!Array.isArray(raw)) return [];
|
|
1634
|
+
return raw.map((item) => typeof item === "string" ? item : String(item?.name ?? "")).map((name) => name.trim()).filter(Boolean);
|
|
1635
|
+
}
|
|
1636
|
+
function mapAssignees(raw) {
|
|
1637
|
+
if (!Array.isArray(raw)) return [];
|
|
1638
|
+
return raw.map(
|
|
1639
|
+
(item) => typeof item === "string" ? item : String(item?.login ?? "")
|
|
1640
|
+
).map((login) => login.trim()).filter(Boolean);
|
|
1641
|
+
}
|
|
1642
|
+
function mapComments2(raw) {
|
|
1643
|
+
if (!Array.isArray(raw)) return [];
|
|
1644
|
+
return raw.map((item) => {
|
|
1645
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
1646
|
+
if (!rec) return null;
|
|
1647
|
+
const body = typeof rec.body === "string" ? rec.body : "";
|
|
1648
|
+
const authorRec = rec.author && typeof rec.author === "object" ? rec.author : null;
|
|
1649
|
+
const comment = {
|
|
1650
|
+
body,
|
|
1651
|
+
id: rec.id != null ? String(rec.id) : void 0,
|
|
1652
|
+
url: typeof rec.url === "string" ? rec.url : void 0,
|
|
1653
|
+
createdAt: typeof rec.createdAt === "string" ? rec.createdAt : void 0,
|
|
1654
|
+
author: authorRec?.login?.trim() || void 0
|
|
1655
|
+
};
|
|
1656
|
+
return comment;
|
|
1657
|
+
}).filter((item) => Boolean(item));
|
|
1658
|
+
}
|
|
1659
|
+
function toGitHubIssue(raw) {
|
|
1660
|
+
const number = Number(raw.number);
|
|
1661
|
+
if (!Number.isFinite(number) || number <= 0) {
|
|
1662
|
+
throw new Error("GitHub issue response was missing a number");
|
|
1663
|
+
}
|
|
1664
|
+
const assignees = mapAssignees(raw.assignees);
|
|
1665
|
+
return {
|
|
1666
|
+
id: `gh-${number}`,
|
|
1667
|
+
identifier: `#${number}`,
|
|
1668
|
+
number,
|
|
1669
|
+
title: String(raw.title ?? ""),
|
|
1670
|
+
url: String(raw.url ?? ""),
|
|
1671
|
+
body: typeof raw.body === "string" && raw.body.trim() ? raw.body : void 0,
|
|
1672
|
+
state: typeof raw.state === "string" ? raw.state : void 0,
|
|
1673
|
+
labels: mapLabels(raw.labels),
|
|
1674
|
+
assignees,
|
|
1675
|
+
comments: mapComments2(raw.comments)
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function toGitHubIssueInfo(issue) {
|
|
1679
|
+
return {
|
|
1680
|
+
id: issue.id,
|
|
1681
|
+
identifier: issue.identifier,
|
|
1682
|
+
title: issue.title,
|
|
1683
|
+
url: issue.url,
|
|
1684
|
+
labels: issue.labels,
|
|
1685
|
+
provider: "github",
|
|
1686
|
+
assignee: issue.assignees[0],
|
|
1687
|
+
assignees: issue.assignees.length ? issue.assignees : void 0
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
async function getGitHubIssue(id, opts) {
|
|
1691
|
+
const number = parseGitHubIssueNumber(id);
|
|
1692
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1693
|
+
const stdout = requireGhOk(
|
|
1694
|
+
await gh(
|
|
1695
|
+
[
|
|
1696
|
+
"issue",
|
|
1697
|
+
"view",
|
|
1698
|
+
String(number),
|
|
1699
|
+
...repoArgs,
|
|
1700
|
+
"--json",
|
|
1701
|
+
"number,title,body,url,state,labels,assignees,comments,author"
|
|
1702
|
+
],
|
|
1703
|
+
cwd,
|
|
1704
|
+
{ reject: false }
|
|
1705
|
+
),
|
|
1706
|
+
`GitHub issue ${number}`
|
|
1707
|
+
);
|
|
1708
|
+
let parsed;
|
|
1709
|
+
try {
|
|
1710
|
+
parsed = JSON.parse(stdout);
|
|
1711
|
+
} catch {
|
|
1712
|
+
throw new Error(`GitHub issue ${number}: gh returned non-JSON`);
|
|
1713
|
+
}
|
|
1714
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1715
|
+
throw new Error(`GitHub issue not found: #${number}`);
|
|
1716
|
+
}
|
|
1717
|
+
return toGitHubIssue(parsed);
|
|
1718
|
+
}
|
|
1719
|
+
async function commentGitHubIssue(input, opts) {
|
|
1720
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1721
|
+
const body = input.body.trim();
|
|
1722
|
+
if (!body) throw new Error("GitHub comment body is required");
|
|
1723
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1724
|
+
const stdout = requireGhOk(
|
|
1725
|
+
await gh(
|
|
1726
|
+
["issue", "comment", String(number), ...repoArgs, "--body", body],
|
|
1727
|
+
cwd,
|
|
1728
|
+
{ reject: false }
|
|
1729
|
+
),
|
|
1730
|
+
`GitHub comment on #${number}`
|
|
1731
|
+
);
|
|
1732
|
+
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
1733
|
+
return { body, url };
|
|
1734
|
+
}
|
|
1735
|
+
async function updateGitHubIssue(input, opts) {
|
|
1736
|
+
const number = parseGitHubIssueNumber(input.id);
|
|
1737
|
+
const title = input.title?.trim();
|
|
1738
|
+
const body = input.body;
|
|
1739
|
+
const state = input.state?.trim().toLowerCase();
|
|
1740
|
+
if (!title && body === void 0 && !state) {
|
|
1741
|
+
throw new Error("github_update_issue needs at least one of title, body, state");
|
|
1742
|
+
}
|
|
1743
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1744
|
+
if (state === "closed" || state === "close") {
|
|
1745
|
+
requireGhOk(
|
|
1746
|
+
await gh(["issue", "close", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1747
|
+
`GitHub close #${number}`
|
|
1748
|
+
);
|
|
1749
|
+
} else if (state === "open" || state === "reopen") {
|
|
1750
|
+
requireGhOk(
|
|
1751
|
+
await gh(["issue", "reopen", String(number), ...repoArgs], cwd, { reject: false }),
|
|
1752
|
+
`GitHub reopen #${number}`
|
|
1753
|
+
);
|
|
1754
|
+
} else if (state) {
|
|
1755
|
+
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
1756
|
+
}
|
|
1757
|
+
if (title || body !== void 0) {
|
|
1758
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
1759
|
+
if (title) args.push("--title", title);
|
|
1760
|
+
if (body !== void 0) args.push("--body", body);
|
|
1761
|
+
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
1762
|
+
}
|
|
1763
|
+
return getGitHubIssue(String(number), opts);
|
|
1764
|
+
}
|
|
1765
|
+
async function createGitHubIssue(input, opts) {
|
|
1766
|
+
const title = input.title.trim();
|
|
1767
|
+
if (!title) throw new Error("GitHub issue title is required");
|
|
1768
|
+
const parent = input.parent?.trim();
|
|
1769
|
+
const parentNumber = parent ? parseGitHubIssueNumber(parent) : null;
|
|
1770
|
+
const bodyParts = [
|
|
1771
|
+
parentNumber ? `Spin-off of #${parentNumber}.` : null,
|
|
1772
|
+
input.body?.trim() || null
|
|
1773
|
+
].filter(Boolean);
|
|
1774
|
+
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
1775
|
+
const args = ["issue", "create", ...repoArgs, "--title", title];
|
|
1776
|
+
if (bodyParts.length) args.push("--body", bodyParts.join("\n\n"));
|
|
1777
|
+
const created = await gh([...args, "--json", "number,url,title"], cwd, { reject: false });
|
|
1778
|
+
if (created.exitCode === 0 && created.stdout.trim()) {
|
|
1779
|
+
try {
|
|
1780
|
+
const parsed = JSON.parse(created.stdout);
|
|
1781
|
+
if (parsed.number) return getGitHubIssue(String(parsed.number), opts);
|
|
1782
|
+
} catch {
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
const fallback = created.exitCode === 0 ? created : await gh(args, cwd, { reject: false });
|
|
1786
|
+
const stdout = requireGhOk(fallback, "GitHub create issue");
|
|
1787
|
+
const url = stdout.trim().match(/https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/i);
|
|
1788
|
+
if (url?.[1]) return getGitHubIssue(url[1], opts);
|
|
1789
|
+
throw new Error(`GitHub create issue: could not parse issue from ${stdout.trim() || "empty output"}`);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1587
1792
|
// src/integrations/issues.ts
|
|
1588
1793
|
function assigneeKey(assignee) {
|
|
1589
1794
|
return (assignee ?? "").trim().toLowerCase();
|
|
@@ -1860,11 +2065,11 @@ function fenceLanguage(path2, language) {
|
|
|
1860
2065
|
}
|
|
1861
2066
|
function buildCodeRefAttachment(input) {
|
|
1862
2067
|
const path2 = input.path.trim();
|
|
1863
|
-
const
|
|
2068
|
+
const text6 = input.text.replace(/\n$/, "");
|
|
1864
2069
|
if (!path2) {
|
|
1865
2070
|
throw new Error("code reference requires a file path");
|
|
1866
2071
|
}
|
|
1867
|
-
if (!
|
|
2072
|
+
if (!text6.trim()) {
|
|
1868
2073
|
throw new Error("code reference requires selected text");
|
|
1869
2074
|
}
|
|
1870
2075
|
if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
|
|
@@ -1877,7 +2082,7 @@ function buildCodeRefAttachment(input) {
|
|
|
1877
2082
|
`Referenced code from \`${path2}\` (${range}).`,
|
|
1878
2083
|
"",
|
|
1879
2084
|
fence,
|
|
1880
|
-
|
|
2085
|
+
text6,
|
|
1881
2086
|
"```"
|
|
1882
2087
|
].join("\n");
|
|
1883
2088
|
return {
|
|
@@ -1931,16 +2136,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
|
1931
2136
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
1932
2137
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
1933
2138
|
var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
|
|
1934
|
-
function pastedTextStats(
|
|
1935
|
-
const chars =
|
|
2139
|
+
function pastedTextStats(text6) {
|
|
2140
|
+
const chars = text6.length;
|
|
1936
2141
|
if (chars === 0) return { chars: 0, lines: 0 };
|
|
1937
|
-
const lines =
|
|
2142
|
+
const lines = text6.split(/\r\n|\r|\n/).length;
|
|
1938
2143
|
return { chars, lines };
|
|
1939
2144
|
}
|
|
1940
|
-
function shouldAttachPastedText(
|
|
1941
|
-
const trimmed =
|
|
2145
|
+
function shouldAttachPastedText(text6) {
|
|
2146
|
+
const trimmed = text6.trim();
|
|
1942
2147
|
if (!trimmed) return false;
|
|
1943
|
-
const { chars, lines } = pastedTextStats(
|
|
2148
|
+
const { chars, lines } = pastedTextStats(text6);
|
|
1944
2149
|
return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
|
|
1945
2150
|
}
|
|
1946
2151
|
function nextPastedTextName(existing) {
|
|
@@ -1951,13 +2156,13 @@ function nextPastedTextName(existing) {
|
|
|
1951
2156
|
}
|
|
1952
2157
|
return `Pasted text #${max + 1}.txt`;
|
|
1953
2158
|
}
|
|
1954
|
-
function buildPastedTextAttachment(
|
|
2159
|
+
function buildPastedTextAttachment(text6, opts) {
|
|
1955
2160
|
return {
|
|
1956
2161
|
id: opts?.id ?? randomUUID(),
|
|
1957
2162
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
1958
2163
|
kind: "file",
|
|
1959
2164
|
path: opts?.path,
|
|
1960
|
-
content:
|
|
2165
|
+
content: text6
|
|
1961
2166
|
};
|
|
1962
2167
|
}
|
|
1963
2168
|
|
|
@@ -2042,8 +2247,8 @@ function latestPendingPlanQuestions(input) {
|
|
|
2042
2247
|
return null;
|
|
2043
2248
|
}
|
|
2044
2249
|
var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
|
|
2045
|
-
function isPlanQuestionAnswersMessage(
|
|
2046
|
-
return
|
|
2250
|
+
function isPlanQuestionAnswersMessage(text6) {
|
|
2251
|
+
return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
|
|
2047
2252
|
}
|
|
2048
2253
|
function formatPlanQuestionAnswers(questions, answers) {
|
|
2049
2254
|
const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
|
|
@@ -2082,7 +2287,7 @@ function formatPlanQuestionsForChat(questions) {
|
|
|
2082
2287
|
// src/mcp/server.ts
|
|
2083
2288
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2084
2289
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2085
|
-
import { z as
|
|
2290
|
+
import { z as z6 } from "zod";
|
|
2086
2291
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2087
2292
|
import { basename } from "path";
|
|
2088
2293
|
|
|
@@ -2094,35 +2299,13 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
2094
2299
|
return null;
|
|
2095
2300
|
}
|
|
2096
2301
|
|
|
2097
|
-
// src/mcp/wait-for-turn.ts
|
|
2098
|
-
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
2099
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
2100
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
2101
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
2102
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
2103
|
-
}
|
|
2104
|
-
var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
|
|
2105
|
-
var MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume it failed to start.";
|
|
2106
|
-
function mcpWaitStillRunningHint(status) {
|
|
2107
|
-
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
2108
|
-
}
|
|
2109
|
-
var MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
2110
|
-
var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
2111
|
-
var MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
2112
|
-
function mcpWaitFinishedHint(status) {
|
|
2113
|
-
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
2114
|
-
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
2115
|
-
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
2116
|
-
return void 0;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
2302
|
// src/mcp/thread-visibility.ts
|
|
2120
2303
|
function lastMessagePreview(messages, max = 160) {
|
|
2121
2304
|
if (!messages?.length) return null;
|
|
2122
2305
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2123
|
-
const
|
|
2124
|
-
if (!
|
|
2125
|
-
const flat =
|
|
2306
|
+
const text6 = messages[i]?.text?.trim();
|
|
2307
|
+
if (!text6 || isInternalAgentStatusText(text6)) continue;
|
|
2308
|
+
const flat = text6.replace(/\s+/g, " ");
|
|
2126
2309
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
2127
2310
|
}
|
|
2128
2311
|
return null;
|
|
@@ -2354,8 +2537,8 @@ function labelGithubUrl(url) {
|
|
|
2354
2537
|
}
|
|
2355
2538
|
return { kind: "other", label: "GitHub", url: raw };
|
|
2356
2539
|
}
|
|
2357
|
-
function appendGithubLink(
|
|
2358
|
-
const body =
|
|
2540
|
+
function appendGithubLink(text6, githubUrl) {
|
|
2541
|
+
const body = text6.trimEnd();
|
|
2359
2542
|
if (!githubUrl?.trim()) return body;
|
|
2360
2543
|
const labeled = labelGithubUrl(githubUrl);
|
|
2361
2544
|
if (!labeled) {
|
|
@@ -2751,11 +2934,51 @@ function registerAbleTimeTools(server) {
|
|
|
2751
2934
|
);
|
|
2752
2935
|
server.tool(
|
|
2753
2936
|
"abletime_get_task",
|
|
2754
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232).",
|
|
2937
|
+
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments.",
|
|
2755
2938
|
{ id: z2.string() },
|
|
2756
2939
|
async ({ id }) => {
|
|
2757
2940
|
try {
|
|
2758
|
-
|
|
2941
|
+
const task = await getAbleTimeTask(id);
|
|
2942
|
+
return text2({
|
|
2943
|
+
...toAbleTimeIssueInfo(task),
|
|
2944
|
+
description: task.description,
|
|
2945
|
+
state: task.state,
|
|
2946
|
+
comments: task.comments
|
|
2947
|
+
});
|
|
2948
|
+
} catch (err) {
|
|
2949
|
+
return fail2(err);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
);
|
|
2953
|
+
server.tool(
|
|
2954
|
+
"abletime_comment",
|
|
2955
|
+
"Add a markdown comment on an AbleTime task (id or CRM-232).",
|
|
2956
|
+
{ id: z2.string(), body: z2.string() },
|
|
2957
|
+
async (args) => {
|
|
2958
|
+
try {
|
|
2959
|
+
return text2(await commentAbleTimeTask(args));
|
|
2960
|
+
} catch (err) {
|
|
2961
|
+
return fail2(err);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
);
|
|
2965
|
+
server.tool(
|
|
2966
|
+
"abletime_update_task",
|
|
2967
|
+
"Update an AbleTime task (id or CRM-232). Pass title, description, and/or state.",
|
|
2968
|
+
{
|
|
2969
|
+
id: z2.string(),
|
|
2970
|
+
title: z2.string().optional(),
|
|
2971
|
+
description: z2.string().optional(),
|
|
2972
|
+
state: z2.string().optional()
|
|
2973
|
+
},
|
|
2974
|
+
async (args) => {
|
|
2975
|
+
try {
|
|
2976
|
+
const task = await updateAbleTimeTask(args);
|
|
2977
|
+
return text2({
|
|
2978
|
+
...toAbleTimeIssueInfo(task),
|
|
2979
|
+
description: task.description,
|
|
2980
|
+
state: task.state
|
|
2981
|
+
});
|
|
2759
2982
|
} catch (err) {
|
|
2760
2983
|
return fail2(err);
|
|
2761
2984
|
}
|
|
@@ -2763,13 +2986,14 @@ function registerAbleTimeTools(server) {
|
|
|
2763
2986
|
);
|
|
2764
2987
|
server.tool(
|
|
2765
2988
|
"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).",
|
|
2989
|
+
"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
2990
|
{
|
|
2768
2991
|
title: z2.string(),
|
|
2769
2992
|
description: z2.string().optional(),
|
|
2770
2993
|
projectId: z2.string().optional(),
|
|
2771
2994
|
categoryId: z2.string().optional(),
|
|
2772
|
-
state: z2.enum(["backlog", "todo"]).optional()
|
|
2995
|
+
state: z2.enum(["backlog", "todo"]).optional(),
|
|
2996
|
+
parent: z2.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
2773
2997
|
},
|
|
2774
2998
|
async (args) => {
|
|
2775
2999
|
try {
|
|
@@ -2799,7 +3023,7 @@ function registerAbleTimeTools(server) {
|
|
|
2799
3023
|
);
|
|
2800
3024
|
}
|
|
2801
3025
|
|
|
2802
|
-
// src/mcp/
|
|
3026
|
+
// src/mcp/github-tools.ts
|
|
2803
3027
|
import { z as z3 } from "zod";
|
|
2804
3028
|
function text3(payload, isError = false) {
|
|
2805
3029
|
return mcpJson(payload, isError);
|
|
@@ -2810,7 +3034,81 @@ function fail3(err) {
|
|
|
2810
3034
|
true
|
|
2811
3035
|
);
|
|
2812
3036
|
}
|
|
2813
|
-
var
|
|
3037
|
+
var repoPathSchema = z3.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
3038
|
+
function registerGithubIssueTools(server) {
|
|
3039
|
+
server.tool(
|
|
3040
|
+
"github_get_issue",
|
|
3041
|
+
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Uses Account gh.",
|
|
3042
|
+
{ id: z3.string(), repoPath: repoPathSchema },
|
|
3043
|
+
async ({ id, repoPath }) => {
|
|
3044
|
+
try {
|
|
3045
|
+
return text3(await getGitHubIssue(id, { repoPath }));
|
|
3046
|
+
} catch (err) {
|
|
3047
|
+
return fail3(err);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
);
|
|
3051
|
+
server.tool(
|
|
3052
|
+
"github_comment",
|
|
3053
|
+
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh.",
|
|
3054
|
+
{ id: z3.string(), body: z3.string(), repoPath: repoPathSchema },
|
|
3055
|
+
async ({ id, body, repoPath }) => {
|
|
3056
|
+
try {
|
|
3057
|
+
return text3(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
3058
|
+
} catch (err) {
|
|
3059
|
+
return fail3(err);
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
);
|
|
3063
|
+
server.tool(
|
|
3064
|
+
"github_update_issue",
|
|
3065
|
+
"Update a GitHub issue (#123). Pass title, body, and/or state (open|closed).",
|
|
3066
|
+
{
|
|
3067
|
+
id: z3.string(),
|
|
3068
|
+
title: z3.string().optional(),
|
|
3069
|
+
body: z3.string().optional(),
|
|
3070
|
+
state: z3.string().optional().describe("open or closed"),
|
|
3071
|
+
repoPath: repoPathSchema
|
|
3072
|
+
},
|
|
3073
|
+
async (args) => {
|
|
3074
|
+
try {
|
|
3075
|
+
return text3(await updateGitHubIssue(args, { repoPath: args.repoPath }));
|
|
3076
|
+
} catch (err) {
|
|
3077
|
+
return fail3(err);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
);
|
|
3081
|
+
server.tool(
|
|
3082
|
+
"github_create_issue",
|
|
3083
|
+
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
3084
|
+
{
|
|
3085
|
+
title: z3.string(),
|
|
3086
|
+
body: z3.string().optional(),
|
|
3087
|
+
parent: z3.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
3088
|
+
repoPath: repoPathSchema
|
|
3089
|
+
},
|
|
3090
|
+
async (args) => {
|
|
3091
|
+
try {
|
|
3092
|
+
return text3(await createGitHubIssue(args, { repoPath: args.repoPath }));
|
|
3093
|
+
} catch (err) {
|
|
3094
|
+
return fail3(err);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/mcp/linear-tools.ts
|
|
3101
|
+
import { z as z4 } from "zod";
|
|
3102
|
+
function text4(payload, isError = false) {
|
|
3103
|
+
return mcpJson(payload, isError);
|
|
3104
|
+
}
|
|
3105
|
+
function fail4(err) {
|
|
3106
|
+
return text4(
|
|
3107
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
3108
|
+
true
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
var prioritySchema = z4.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
2814
3112
|
function registerLinearTools(server) {
|
|
2815
3113
|
server.tool(
|
|
2816
3114
|
"linear_list_teams",
|
|
@@ -2818,9 +3116,9 @@ function registerLinearTools(server) {
|
|
|
2818
3116
|
{},
|
|
2819
3117
|
async () => {
|
|
2820
3118
|
try {
|
|
2821
|
-
return
|
|
3119
|
+
return text4(await listLinearTeams());
|
|
2822
3120
|
} catch (err) {
|
|
2823
|
-
return
|
|
3121
|
+
return fail4(err);
|
|
2824
3122
|
}
|
|
2825
3123
|
}
|
|
2826
3124
|
);
|
|
@@ -2828,9 +3126,9 @@ function registerLinearTools(server) {
|
|
|
2828
3126
|
"linear_search_issues",
|
|
2829
3127
|
"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
3128
|
{
|
|
2831
|
-
query:
|
|
2832
|
-
assignee:
|
|
2833
|
-
limit:
|
|
3129
|
+
query: z4.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
3130
|
+
assignee: z4.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
3131
|
+
limit: z4.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
2834
3132
|
},
|
|
2835
3133
|
async ({ query, assignee, limit }) => {
|
|
2836
3134
|
try {
|
|
@@ -2851,38 +3149,39 @@ function registerLinearTools(server) {
|
|
|
2851
3149
|
})
|
|
2852
3150
|
);
|
|
2853
3151
|
} catch (err) {
|
|
2854
|
-
return
|
|
3152
|
+
return fail4(err);
|
|
2855
3153
|
}
|
|
2856
3154
|
}
|
|
2857
3155
|
);
|
|
2858
3156
|
server.tool(
|
|
2859
3157
|
"linear_get_issue",
|
|
2860
3158
|
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
2861
|
-
{ id:
|
|
3159
|
+
{ id: z4.string() },
|
|
2862
3160
|
async ({ id }) => {
|
|
2863
3161
|
try {
|
|
2864
|
-
return
|
|
3162
|
+
return text4(await getLinearIssue(id));
|
|
2865
3163
|
} catch (err) {
|
|
2866
|
-
return
|
|
3164
|
+
return fail4(err);
|
|
2867
3165
|
}
|
|
2868
3166
|
}
|
|
2869
3167
|
);
|
|
2870
3168
|
server.tool(
|
|
2871
3169
|
"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.',
|
|
3170
|
+
'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
3171
|
{
|
|
2874
|
-
team:
|
|
2875
|
-
title:
|
|
2876
|
-
description:
|
|
2877
|
-
state:
|
|
2878
|
-
assignee:
|
|
2879
|
-
priority: prioritySchema
|
|
3172
|
+
team: z4.string(),
|
|
3173
|
+
title: z4.string(),
|
|
3174
|
+
description: z4.string().optional(),
|
|
3175
|
+
state: z4.string().optional(),
|
|
3176
|
+
assignee: z4.string().nullable().optional(),
|
|
3177
|
+
priority: prioritySchema,
|
|
3178
|
+
parent: z4.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
2880
3179
|
},
|
|
2881
3180
|
async (args) => {
|
|
2882
3181
|
try {
|
|
2883
|
-
return
|
|
3182
|
+
return text4(await createLinearIssue(args));
|
|
2884
3183
|
} catch (err) {
|
|
2885
|
-
return
|
|
3184
|
+
return fail4(err);
|
|
2886
3185
|
}
|
|
2887
3186
|
}
|
|
2888
3187
|
);
|
|
@@ -2890,18 +3189,18 @@ function registerLinearTools(server) {
|
|
|
2890
3189
|
"linear_update_issue",
|
|
2891
3190
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
2892
3191
|
{
|
|
2893
|
-
id:
|
|
2894
|
-
title:
|
|
2895
|
-
description:
|
|
2896
|
-
state:
|
|
2897
|
-
assignee:
|
|
3192
|
+
id: z4.string(),
|
|
3193
|
+
title: z4.string().optional(),
|
|
3194
|
+
description: z4.string().optional(),
|
|
3195
|
+
state: z4.string().optional(),
|
|
3196
|
+
assignee: z4.string().nullable().optional(),
|
|
2898
3197
|
priority: prioritySchema
|
|
2899
3198
|
},
|
|
2900
3199
|
async (args) => {
|
|
2901
3200
|
try {
|
|
2902
|
-
return
|
|
3201
|
+
return text4(await updateLinearIssue(args));
|
|
2903
3202
|
} catch (err) {
|
|
2904
|
-
return
|
|
3203
|
+
return fail4(err);
|
|
2905
3204
|
}
|
|
2906
3205
|
}
|
|
2907
3206
|
);
|
|
@@ -2909,14 +3208,14 @@ function registerLinearTools(server) {
|
|
|
2909
3208
|
"linear_comment",
|
|
2910
3209
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
2911
3210
|
{
|
|
2912
|
-
id:
|
|
2913
|
-
body:
|
|
3211
|
+
id: z4.string(),
|
|
3212
|
+
body: z4.string()
|
|
2914
3213
|
},
|
|
2915
3214
|
async (args) => {
|
|
2916
3215
|
try {
|
|
2917
|
-
return
|
|
3216
|
+
return text4(await commentLinearIssue(args));
|
|
2918
3217
|
} catch (err) {
|
|
2919
|
-
return
|
|
3218
|
+
return fail4(err);
|
|
2920
3219
|
}
|
|
2921
3220
|
}
|
|
2922
3221
|
);
|
|
@@ -2924,6 +3223,7 @@ function registerLinearTools(server) {
|
|
|
2924
3223
|
|
|
2925
3224
|
// src/mcp/issue-vendor-tools.ts
|
|
2926
3225
|
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
3226
|
+
registerGithubIssueTools(server);
|
|
2927
3227
|
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
2928
3228
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
2929
3229
|
}
|
|
@@ -2963,15 +3263,15 @@ function formatMcpPrList(input) {
|
|
|
2963
3263
|
}
|
|
2964
3264
|
|
|
2965
3265
|
// src/mcp/schedule-tools.ts
|
|
2966
|
-
import { z as
|
|
2967
|
-
function
|
|
3266
|
+
import { z as z5 } from "zod";
|
|
3267
|
+
function text5(payload, isError = false) {
|
|
2968
3268
|
return {
|
|
2969
3269
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
2970
3270
|
...isError ? { isError: true } : {}
|
|
2971
3271
|
};
|
|
2972
3272
|
}
|
|
2973
|
-
function
|
|
2974
|
-
return
|
|
3273
|
+
function fail5(err) {
|
|
3274
|
+
return text5(
|
|
2975
3275
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
2976
3276
|
true
|
|
2977
3277
|
);
|
|
@@ -2996,9 +3296,9 @@ function registerScheduleTools(server) {
|
|
|
2996
3296
|
{},
|
|
2997
3297
|
async () => {
|
|
2998
3298
|
try {
|
|
2999
|
-
return
|
|
3299
|
+
return text5({ schedules: listSchedules() });
|
|
3000
3300
|
} catch (err) {
|
|
3001
|
-
return
|
|
3301
|
+
return fail5(err);
|
|
3002
3302
|
}
|
|
3003
3303
|
}
|
|
3004
3304
|
);
|
|
@@ -3006,24 +3306,24 @@ function registerScheduleTools(server) {
|
|
|
3006
3306
|
"create_schedule",
|
|
3007
3307
|
"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
3308
|
{
|
|
3009
|
-
prompt:
|
|
3010
|
-
name:
|
|
3011
|
-
at:
|
|
3012
|
-
every:
|
|
3013
|
-
cron:
|
|
3014
|
-
tz:
|
|
3015
|
-
threadId:
|
|
3309
|
+
prompt: z5.string().describe("User message / goal queued when the schedule fires"),
|
|
3310
|
+
name: z5.string().optional(),
|
|
3311
|
+
at: z5.string().optional().describe("ISO datetime for a one-shot"),
|
|
3312
|
+
every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
3313
|
+
cron: z5.string().optional().describe("5-field cron expression"),
|
|
3314
|
+
tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
3315
|
+
threadId: z5.string().optional().describe(
|
|
3016
3316
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
3017
3317
|
),
|
|
3018
|
-
agent:
|
|
3019
|
-
model:
|
|
3318
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
3319
|
+
model: z5.string().optional()
|
|
3020
3320
|
},
|
|
3021
3321
|
async (args) => {
|
|
3022
3322
|
try {
|
|
3023
3323
|
const when = parseWhen(args);
|
|
3024
3324
|
const threadId = resolveScheduleThreadId(args.threadId);
|
|
3025
3325
|
if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
|
|
3026
|
-
return
|
|
3326
|
+
return fail5(
|
|
3027
3327
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
3028
3328
|
);
|
|
3029
3329
|
}
|
|
@@ -3036,12 +3336,12 @@ function registerScheduleTools(server) {
|
|
|
3036
3336
|
model: args.model,
|
|
3037
3337
|
createdBy: "mcp"
|
|
3038
3338
|
});
|
|
3039
|
-
return
|
|
3339
|
+
return text5({
|
|
3040
3340
|
schedule,
|
|
3041
3341
|
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
3342
|
});
|
|
3043
3343
|
} catch (err) {
|
|
3044
|
-
return
|
|
3344
|
+
return fail5(err);
|
|
3045
3345
|
}
|
|
3046
3346
|
}
|
|
3047
3347
|
);
|
|
@@ -3049,17 +3349,17 @@ function registerScheduleTools(server) {
|
|
|
3049
3349
|
"update_schedule",
|
|
3050
3350
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
3051
3351
|
{
|
|
3052
|
-
id:
|
|
3053
|
-
prompt:
|
|
3054
|
-
name:
|
|
3055
|
-
at:
|
|
3056
|
-
every:
|
|
3057
|
-
cron:
|
|
3058
|
-
tz:
|
|
3059
|
-
threadId:
|
|
3060
|
-
agent:
|
|
3061
|
-
model:
|
|
3062
|
-
enabled:
|
|
3352
|
+
id: z5.string(),
|
|
3353
|
+
prompt: z5.string().optional(),
|
|
3354
|
+
name: z5.string().optional(),
|
|
3355
|
+
at: z5.string().optional(),
|
|
3356
|
+
every: z5.string().optional(),
|
|
3357
|
+
cron: z5.string().optional(),
|
|
3358
|
+
tz: z5.string().optional(),
|
|
3359
|
+
threadId: z5.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
3360
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
3361
|
+
model: z5.string().optional(),
|
|
3362
|
+
enabled: z5.boolean().optional()
|
|
3063
3363
|
},
|
|
3064
3364
|
async (args) => {
|
|
3065
3365
|
try {
|
|
@@ -3074,7 +3374,7 @@ function registerScheduleTools(server) {
|
|
|
3074
3374
|
if (args.threadId !== void 0) {
|
|
3075
3375
|
threadId = resolveScheduleThreadId(args.threadId);
|
|
3076
3376
|
if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
|
|
3077
|
-
return
|
|
3377
|
+
return fail5(
|
|
3078
3378
|
new Error("threadId=self requires this turn to be an orchestration chat")
|
|
3079
3379
|
);
|
|
3080
3380
|
}
|
|
@@ -3088,38 +3388,38 @@ function registerScheduleTools(server) {
|
|
|
3088
3388
|
enabled: args.enabled,
|
|
3089
3389
|
model: args.model
|
|
3090
3390
|
});
|
|
3091
|
-
return
|
|
3391
|
+
return text5({ schedule });
|
|
3092
3392
|
} catch (err) {
|
|
3093
|
-
return
|
|
3393
|
+
return fail5(err);
|
|
3094
3394
|
}
|
|
3095
3395
|
}
|
|
3096
3396
|
);
|
|
3097
3397
|
server.tool(
|
|
3098
3398
|
"delete_schedule",
|
|
3099
3399
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
3100
|
-
{ id:
|
|
3400
|
+
{ id: z5.string() },
|
|
3101
3401
|
async ({ id }) => {
|
|
3102
3402
|
try {
|
|
3103
3403
|
deleteSchedule(id);
|
|
3104
|
-
return
|
|
3404
|
+
return text5({ ok: true, id });
|
|
3105
3405
|
} catch (err) {
|
|
3106
|
-
return
|
|
3406
|
+
return fail5(err);
|
|
3107
3407
|
}
|
|
3108
3408
|
}
|
|
3109
3409
|
);
|
|
3110
3410
|
server.tool(
|
|
3111
3411
|
"run_schedule",
|
|
3112
3412
|
"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:
|
|
3413
|
+
{ id: z5.string() },
|
|
3114
3414
|
async ({ id }) => {
|
|
3115
3415
|
try {
|
|
3116
3416
|
if (!getSchedule(id)) {
|
|
3117
|
-
return
|
|
3417
|
+
return fail5(new Error(`Schedule not found: ${id}`));
|
|
3118
3418
|
}
|
|
3119
3419
|
const schedule = await fireSchedule(id);
|
|
3120
|
-
return
|
|
3420
|
+
return text5({ schedule });
|
|
3121
3421
|
} catch (err) {
|
|
3122
|
-
return
|
|
3422
|
+
return fail5(err);
|
|
3123
3423
|
}
|
|
3124
3424
|
}
|
|
3125
3425
|
);
|
|
@@ -3537,6 +3837,9 @@ async function startMcpServer() {
|
|
|
3537
3837
|
version: "0.1.0"
|
|
3538
3838
|
});
|
|
3539
3839
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
3840
|
+
if (worktreeProfile) {
|
|
3841
|
+
registerConnectedIssueVendorTools(server);
|
|
3842
|
+
}
|
|
3540
3843
|
if (!worktreeProfile) {
|
|
3541
3844
|
server.tool(
|
|
3542
3845
|
"list_workspaces",
|
|
@@ -3589,13 +3892,13 @@ async function startMcpServer() {
|
|
|
3589
3892
|
"list_board",
|
|
3590
3893
|
"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
3894
|
{
|
|
3592
|
-
query:
|
|
3593
|
-
repoPath:
|
|
3594
|
-
kind:
|
|
3595
|
-
column:
|
|
3895
|
+
query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
3896
|
+
repoPath: z6.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
3897
|
+
kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
3898
|
+
column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
3596
3899
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
3597
3900
|
),
|
|
3598
|
-
limit:
|
|
3901
|
+
limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
3599
3902
|
},
|
|
3600
3903
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
3601
3904
|
const workspaces = orch.listWorkspaces();
|
|
@@ -3633,7 +3936,7 @@ async function startMcpServer() {
|
|
|
3633
3936
|
server.tool(
|
|
3634
3937
|
"get_thread",
|
|
3635
3938
|
"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:
|
|
3939
|
+
{ ref: z6.string() },
|
|
3637
3940
|
async ({ ref }) => {
|
|
3638
3941
|
const t = orch.getThread(ref);
|
|
3639
3942
|
if (!t) {
|
|
@@ -3675,17 +3978,17 @@ async function startMcpServer() {
|
|
|
3675
3978
|
"present_artifact",
|
|
3676
3979
|
"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
3980
|
{
|
|
3678
|
-
title:
|
|
3679
|
-
type:
|
|
3981
|
+
title: z6.string().describe("Short title shown in the artifact pane header"),
|
|
3982
|
+
type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
3680
3983
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
3681
3984
|
),
|
|
3682
|
-
content:
|
|
3985
|
+
content: z6.string().describe(
|
|
3683
3986
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
3684
3987
|
),
|
|
3685
|
-
artifact_id:
|
|
3686
|
-
status:
|
|
3687
|
-
phase:
|
|
3688
|
-
mode:
|
|
3988
|
+
artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
3989
|
+
status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
3990
|
+
phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
3991
|
+
mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
3689
3992
|
},
|
|
3690
3993
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
3691
3994
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3706,15 +4009,15 @@ async function startMcpServer() {
|
|
|
3706
4009
|
"ask_user",
|
|
3707
4010
|
"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
4011
|
{
|
|
3709
|
-
questions:
|
|
3710
|
-
|
|
3711
|
-
question:
|
|
3712
|
-
header:
|
|
3713
|
-
multiSelect:
|
|
3714
|
-
options:
|
|
3715
|
-
|
|
3716
|
-
label:
|
|
3717
|
-
description:
|
|
4012
|
+
questions: z6.array(
|
|
4013
|
+
z6.object({
|
|
4014
|
+
question: z6.string().describe("Full question text ending with ?"),
|
|
4015
|
+
header: z6.string().max(24).optional().describe("Short label shown above the question"),
|
|
4016
|
+
multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
|
|
4017
|
+
options: z6.array(
|
|
4018
|
+
z6.object({
|
|
4019
|
+
label: z6.string(),
|
|
4020
|
+
description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
3718
4021
|
})
|
|
3719
4022
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
3720
4023
|
})
|
|
@@ -3733,9 +4036,9 @@ async function startMcpServer() {
|
|
|
3733
4036
|
"present_plan",
|
|
3734
4037
|
"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
4038
|
{
|
|
3736
|
-
title:
|
|
3737
|
-
content:
|
|
3738
|
-
thread_id:
|
|
4039
|
+
title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
4040
|
+
content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
4041
|
+
thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
3739
4042
|
},
|
|
3740
4043
|
async ({ title, content, thread_id }) => {
|
|
3741
4044
|
const { writePlanFile: writePlanFile2 } = await import("./plan-file-LSSSQGTQ.js");
|
|
@@ -3758,15 +4061,15 @@ async function startMcpServer() {
|
|
|
3758
4061
|
"present_schema",
|
|
3759
4062
|
"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
4063
|
{
|
|
3761
|
-
title:
|
|
3762
|
-
mode:
|
|
3763
|
-
datasource:
|
|
3764
|
-
resource_id:
|
|
3765
|
-
record_id:
|
|
3766
|
-
resource:
|
|
3767
|
-
record:
|
|
3768
|
-
records:
|
|
3769
|
-
pane_id:
|
|
4064
|
+
title: z6.string().describe("Pane title"),
|
|
4065
|
+
mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
4066
|
+
datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
4067
|
+
resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
4068
|
+
record_id: z6.string().optional().describe("Record id when opening form mode"),
|
|
4069
|
+
resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
4070
|
+
record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
4071
|
+
records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
|
|
4072
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
3770
4073
|
},
|
|
3771
4074
|
async (args) => {
|
|
3772
4075
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3787,10 +4090,10 @@ async function startMcpServer() {
|
|
|
3787
4090
|
"present_files",
|
|
3788
4091
|
"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
4092
|
{
|
|
3790
|
-
title:
|
|
3791
|
-
datasource:
|
|
3792
|
-
path:
|
|
3793
|
-
pane_id:
|
|
4093
|
+
title: z6.string().optional().describe("Pane title (default: Files)"),
|
|
4094
|
+
datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
4095
|
+
path: z6.string().optional().describe("Initial folder path (e.g. public)"),
|
|
4096
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
3794
4097
|
},
|
|
3795
4098
|
async (args) => {
|
|
3796
4099
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3805,6 +4108,20 @@ async function startMcpServer() {
|
|
|
3805
4108
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
3806
4109
|
}
|
|
3807
4110
|
);
|
|
4111
|
+
server.tool(
|
|
4112
|
+
"wait_for_job",
|
|
4113
|
+
"Wait on a detached long job (tests, pack, deploy) started with detached-job.js. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
4114
|
+
{
|
|
4115
|
+
id: z6.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
|
|
4116
|
+
timeoutMs: z6.number().optional()
|
|
4117
|
+
},
|
|
4118
|
+
async ({ id, timeoutMs }) => {
|
|
4119
|
+
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
4120
|
+
timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
|
|
4121
|
+
});
|
|
4122
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
4123
|
+
}
|
|
4124
|
+
);
|
|
3808
4125
|
if (!worktreeProfile) {
|
|
3809
4126
|
registerSlackTools(server);
|
|
3810
4127
|
registerConnectedIssueVendorTools(server);
|
|
@@ -3817,7 +4134,7 @@ async function startMcpServer() {
|
|
|
3817
4134
|
"set_caffeinate",
|
|
3818
4135
|
"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
4136
|
{
|
|
3820
|
-
enabled:
|
|
4137
|
+
enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
3821
4138
|
},
|
|
3822
4139
|
async ({ enabled }) => {
|
|
3823
4140
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -3855,18 +4172,18 @@ async function startMcpServer() {
|
|
|
3855
4172
|
"create_thread",
|
|
3856
4173
|
`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
4174
|
{
|
|
3858
|
-
sourceType:
|
|
3859
|
-
sourceRef:
|
|
3860
|
-
agent:
|
|
3861
|
-
model:
|
|
4175
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]),
|
|
4176
|
+
sourceRef: z6.string(),
|
|
4177
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
4178
|
+
model: z6.string().nullable().optional().describe(
|
|
3862
4179
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
3863
4180
|
),
|
|
3864
|
-
repoPath:
|
|
3865
|
-
title:
|
|
3866
|
-
cowboy:
|
|
4181
|
+
repoPath: z6.string(),
|
|
4182
|
+
title: z6.string().optional(),
|
|
4183
|
+
cowboy: z6.boolean().optional().describe(
|
|
3867
4184
|
"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
4185
|
),
|
|
3869
|
-
parentThreadId:
|
|
4186
|
+
parentThreadId: z6.string().optional().describe(
|
|
3870
4187
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
3871
4188
|
)
|
|
3872
4189
|
},
|
|
@@ -3882,10 +4199,10 @@ async function startMcpServer() {
|
|
|
3882
4199
|
"start_board_card",
|
|
3883
4200
|
"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
4201
|
{
|
|
3885
|
-
kind:
|
|
3886
|
-
ref:
|
|
3887
|
-
repoPath:
|
|
3888
|
-
title:
|
|
4202
|
+
kind: z6.enum(["ticket", "pr", "branch"]),
|
|
4203
|
+
ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
4204
|
+
repoPath: z6.string().describe("Workspace path from list_workspaces / list_board"),
|
|
4205
|
+
title: z6.string().optional()
|
|
3889
4206
|
},
|
|
3890
4207
|
async ({ kind, ref, repoPath, title }) => {
|
|
3891
4208
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -3989,9 +4306,9 @@ async function startMcpServer() {
|
|
|
3989
4306
|
"send_to_thread",
|
|
3990
4307
|
'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
4308
|
{
|
|
3992
|
-
ref:
|
|
3993
|
-
prompt:
|
|
3994
|
-
force_stop:
|
|
4309
|
+
ref: z6.string(),
|
|
4310
|
+
prompt: z6.string(),
|
|
4311
|
+
force_stop: z6.boolean().optional()
|
|
3995
4312
|
},
|
|
3996
4313
|
async ({ ref, prompt, force_stop }) => {
|
|
3997
4314
|
if (force_stop) {
|
|
@@ -4020,8 +4337,8 @@ async function startMcpServer() {
|
|
|
4020
4337
|
"wait_for_turn",
|
|
4021
4338
|
"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
4339
|
{
|
|
4023
|
-
ref:
|
|
4024
|
-
timeoutMs:
|
|
4340
|
+
ref: z6.string(),
|
|
4341
|
+
timeoutMs: z6.number().optional()
|
|
4025
4342
|
},
|
|
4026
4343
|
async ({ ref, timeoutMs }) => {
|
|
4027
4344
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -4051,7 +4368,7 @@ async function startMcpServer() {
|
|
|
4051
4368
|
server.tool(
|
|
4052
4369
|
"get_turn_result",
|
|
4053
4370
|
"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:
|
|
4371
|
+
{ ref: z6.string() },
|
|
4055
4372
|
async ({ ref }) => {
|
|
4056
4373
|
const result = orch.getTurnResult(ref);
|
|
4057
4374
|
return {
|
|
@@ -4072,8 +4389,8 @@ async function startMcpServer() {
|
|
|
4072
4389
|
"stop_thread",
|
|
4073
4390
|
"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
4391
|
{
|
|
4075
|
-
ref:
|
|
4076
|
-
force:
|
|
4392
|
+
ref: z6.string(),
|
|
4393
|
+
force: z6.boolean().optional()
|
|
4077
4394
|
},
|
|
4078
4395
|
async ({ ref, force }) => {
|
|
4079
4396
|
const t = orch.getThread(ref);
|
|
@@ -4103,7 +4420,7 @@ async function startMcpServer() {
|
|
|
4103
4420
|
server.tool(
|
|
4104
4421
|
"archive_thread",
|
|
4105
4422
|
"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:
|
|
4423
|
+
{ ref: z6.string() },
|
|
4107
4424
|
async ({ ref }) => {
|
|
4108
4425
|
const t = orch.getThread(ref);
|
|
4109
4426
|
if (!t) {
|
|
@@ -4136,7 +4453,7 @@ async function startMcpServer() {
|
|
|
4136
4453
|
server.tool(
|
|
4137
4454
|
"restore_thread",
|
|
4138
4455
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
4139
|
-
{ ref:
|
|
4456
|
+
{ ref: z6.string() },
|
|
4140
4457
|
async ({ ref }) => {
|
|
4141
4458
|
try {
|
|
4142
4459
|
const restored = await orch.restore(ref);
|
|
@@ -4162,8 +4479,8 @@ async function startMcpServer() {
|
|
|
4162
4479
|
"get_diff",
|
|
4163
4480
|
"Compact diff summary (capped hunks, paginated)",
|
|
4164
4481
|
{
|
|
4165
|
-
ref:
|
|
4166
|
-
maxFiles:
|
|
4482
|
+
ref: z6.string(),
|
|
4483
|
+
maxFiles: z6.number().optional()
|
|
4167
4484
|
},
|
|
4168
4485
|
async ({ ref, maxFiles }) => {
|
|
4169
4486
|
const summary = await orch.diffSummary(ref);
|
|
@@ -4183,7 +4500,7 @@ async function startMcpServer() {
|
|
|
4183
4500
|
server.tool(
|
|
4184
4501
|
"get_pr_checks",
|
|
4185
4502
|
"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:
|
|
4503
|
+
{ ref: z6.string().describe("Worktree thread id/ref") },
|
|
4187
4504
|
async ({ ref }) => {
|
|
4188
4505
|
try {
|
|
4189
4506
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -4199,7 +4516,7 @@ async function startMcpServer() {
|
|
|
4199
4516
|
server.tool(
|
|
4200
4517
|
"request_review",
|
|
4201
4518
|
'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:
|
|
4519
|
+
{ ref: z6.string().describe("Worktree thread id/ref to review") },
|
|
4203
4520
|
async ({ ref }) => {
|
|
4204
4521
|
try {
|
|
4205
4522
|
const tab = await orch.requestReview(ref);
|
|
@@ -4228,8 +4545,8 @@ async function startMcpServer() {
|
|
|
4228
4545
|
"ask_git",
|
|
4229
4546
|
"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
4547
|
{
|
|
4231
|
-
ref:
|
|
4232
|
-
action:
|
|
4548
|
+
ref: z6.string().describe("Worktree thread id/ref"),
|
|
4549
|
+
action: z6.enum(AGENT_GIT_ACTIONS).describe(
|
|
4233
4550
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
4234
4551
|
)
|
|
4235
4552
|
},
|
|
@@ -4274,7 +4591,7 @@ async function startMcpServer() {
|
|
|
4274
4591
|
}
|
|
4275
4592
|
}
|
|
4276
4593
|
);
|
|
4277
|
-
const agentEnum =
|
|
4594
|
+
const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
4278
4595
|
server.tool(
|
|
4279
4596
|
"list_models",
|
|
4280
4597
|
"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 +4614,11 @@ async function startMcpServer() {
|
|
|
4297
4614
|
"fork_worktree",
|
|
4298
4615
|
"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
4616
|
{
|
|
4300
|
-
ref:
|
|
4301
|
-
through_index:
|
|
4617
|
+
ref: z6.string().describe("Worktree thread id/ref to fork"),
|
|
4618
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
4302
4619
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4303
|
-
model:
|
|
4304
|
-
title:
|
|
4620
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
4621
|
+
title: z6.string().optional()
|
|
4305
4622
|
},
|
|
4306
4623
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4307
4624
|
try {
|
|
@@ -4342,11 +4659,11 @@ async function startMcpServer() {
|
|
|
4342
4659
|
"fork_chat",
|
|
4343
4660
|
"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
4661
|
{
|
|
4345
|
-
ref:
|
|
4346
|
-
through_index:
|
|
4662
|
+
ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
4663
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
4347
4664
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4348
|
-
model:
|
|
4349
|
-
title:
|
|
4665
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
4666
|
+
title: z6.string().optional()
|
|
4350
4667
|
},
|
|
4351
4668
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4352
4669
|
try {
|
|
@@ -4392,8 +4709,8 @@ async function startMcpServer() {
|
|
|
4392
4709
|
"run_dev_script",
|
|
4393
4710
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
4394
4711
|
{
|
|
4395
|
-
ref:
|
|
4396
|
-
name:
|
|
4712
|
+
ref: z6.string(),
|
|
4713
|
+
name: z6.string().optional()
|
|
4397
4714
|
},
|
|
4398
4715
|
async ({ ref, name }) => {
|
|
4399
4716
|
const result = await orch.startDev(ref, name);
|
|
@@ -4415,7 +4732,7 @@ async function startMcpServer() {
|
|
|
4415
4732
|
server.tool(
|
|
4416
4733
|
"list_run_scripts",
|
|
4417
4734
|
"List named run scripts available for a thread",
|
|
4418
|
-
{ ref:
|
|
4735
|
+
{ ref: z6.string() },
|
|
4419
4736
|
async ({ ref }) => {
|
|
4420
4737
|
const scripts = orch.listThreadRunScripts(ref);
|
|
4421
4738
|
const active = orch.getActiveRuns(ref);
|
|
@@ -4433,8 +4750,8 @@ async function startMcpServer() {
|
|
|
4433
4750
|
"stop_dev_script",
|
|
4434
4751
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
4435
4752
|
{
|
|
4436
|
-
ref:
|
|
4437
|
-
name:
|
|
4753
|
+
ref: z6.string(),
|
|
4754
|
+
name: z6.string().optional()
|
|
4438
4755
|
},
|
|
4439
4756
|
async ({ ref, name }) => {
|
|
4440
4757
|
orch.stopDev(ref, name);
|
|
@@ -4444,7 +4761,7 @@ async function startMcpServer() {
|
|
|
4444
4761
|
server.tool(
|
|
4445
4762
|
"run_setup",
|
|
4446
4763
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
4447
|
-
{ ref:
|
|
4764
|
+
{ ref: z6.string() },
|
|
4448
4765
|
async ({ ref }) => {
|
|
4449
4766
|
const result = await orch.runSetup(ref);
|
|
4450
4767
|
return {
|
|
@@ -4455,7 +4772,7 @@ async function startMcpServer() {
|
|
|
4455
4772
|
server.tool(
|
|
4456
4773
|
"add_workspace",
|
|
4457
4774
|
"Register a git repo as a Sideboard workspace",
|
|
4458
|
-
{ repoPath:
|
|
4775
|
+
{ repoPath: z6.string() },
|
|
4459
4776
|
async ({ repoPath }) => {
|
|
4460
4777
|
const ws = await orch.addWorkspace(repoPath);
|
|
4461
4778
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -4464,7 +4781,7 @@ async function startMcpServer() {
|
|
|
4464
4781
|
server.tool(
|
|
4465
4782
|
"remove_workspace",
|
|
4466
4783
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
4467
|
-
{ repoPath:
|
|
4784
|
+
{ repoPath: z6.string() },
|
|
4468
4785
|
async ({ repoPath }) => {
|
|
4469
4786
|
orch.removeWorkspace(repoPath);
|
|
4470
4787
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -4474,12 +4791,12 @@ async function startMcpServer() {
|
|
|
4474
4791
|
"fanout",
|
|
4475
4792
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
4476
4793
|
{
|
|
4477
|
-
prompt:
|
|
4478
|
-
agents:
|
|
4479
|
-
repoPath:
|
|
4480
|
-
sourceType:
|
|
4481
|
-
sourceRef:
|
|
4482
|
-
title:
|
|
4794
|
+
prompt: z6.string(),
|
|
4795
|
+
agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
4796
|
+
repoPath: z6.string(),
|
|
4797
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
|
|
4798
|
+
sourceRef: z6.string().optional(),
|
|
4799
|
+
title: z6.string().optional()
|
|
4483
4800
|
},
|
|
4484
4801
|
async (args) => {
|
|
4485
4802
|
const threads = await orch.bestOfN(args);
|
|
@@ -4506,8 +4823,8 @@ async function startMcpServer() {
|
|
|
4506
4823
|
"list_branches",
|
|
4507
4824
|
"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
4825
|
{
|
|
4509
|
-
repoPath:
|
|
4510
|
-
unmergedOnly:
|
|
4826
|
+
repoPath: z6.string(),
|
|
4827
|
+
unmergedOnly: z6.boolean().optional()
|
|
4511
4828
|
},
|
|
4512
4829
|
async ({ repoPath, unmergedOnly }) => {
|
|
4513
4830
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4528,19 +4845,19 @@ async function startMcpServer() {
|
|
|
4528
4845
|
"list_prs",
|
|
4529
4846
|
'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
4847
|
{
|
|
4531
|
-
repoPath:
|
|
4532
|
-
query:
|
|
4533
|
-
queue:
|
|
4848
|
+
repoPath: z6.string(),
|
|
4849
|
+
query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
4850
|
+
queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
4534
4851
|
'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
4852
|
),
|
|
4536
|
-
state:
|
|
4537
|
-
label:
|
|
4853
|
+
state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
4854
|
+
label: z6.string().optional().describe(
|
|
4538
4855
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
4539
4856
|
),
|
|
4540
|
-
reviewer:
|
|
4857
|
+
reviewer: z6.string().optional().describe(
|
|
4541
4858
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
4542
4859
|
),
|
|
4543
|
-
limit:
|
|
4860
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
4544
4861
|
},
|
|
4545
4862
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
4546
4863
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4572,7 +4889,7 @@ async function startMcpServer() {
|
|
|
4572
4889
|
server.tool(
|
|
4573
4890
|
"get_pr_stack",
|
|
4574
4891
|
"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:
|
|
4892
|
+
{ ref: z6.string() },
|
|
4576
4893
|
async ({ ref }) => {
|
|
4577
4894
|
const stack = await orch.getPrStack(ref);
|
|
4578
4895
|
return {
|
|
@@ -4584,8 +4901,8 @@ async function startMcpServer() {
|
|
|
4584
4901
|
"open_pr_stack_layers",
|
|
4585
4902
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
4586
4903
|
{
|
|
4587
|
-
ref:
|
|
4588
|
-
layer:
|
|
4904
|
+
ref: z6.string(),
|
|
4905
|
+
layer: z6.number().int().positive().optional()
|
|
4589
4906
|
},
|
|
4590
4907
|
async ({ ref, layer }) => {
|
|
4591
4908
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -4619,9 +4936,9 @@ async function startMcpServer() {
|
|
|
4619
4936
|
"add_stack_layer",
|
|
4620
4937
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
4621
4938
|
{
|
|
4622
|
-
ref:
|
|
4623
|
-
branchName:
|
|
4624
|
-
title:
|
|
4939
|
+
ref: z6.string(),
|
|
4940
|
+
branchName: z6.string(),
|
|
4941
|
+
title: z6.string().optional()
|
|
4625
4942
|
},
|
|
4626
4943
|
async ({ ref, branchName, title }) => {
|
|
4627
4944
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -4650,11 +4967,11 @@ async function startMcpServer() {
|
|
|
4650
4967
|
"create_pr_stack",
|
|
4651
4968
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
4652
4969
|
{
|
|
4653
|
-
repoPath:
|
|
4654
|
-
branches:
|
|
4655
|
-
agent:
|
|
4656
|
-
base:
|
|
4657
|
-
title:
|
|
4970
|
+
repoPath: z6.string(),
|
|
4971
|
+
branches: z6.array(z6.string()).min(1),
|
|
4972
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
4973
|
+
base: z6.string().optional(),
|
|
4974
|
+
title: z6.string().optional()
|
|
4658
4975
|
},
|
|
4659
4976
|
async (args) => {
|
|
4660
4977
|
const result = await orch.createPrStack({
|
|
@@ -4693,10 +5010,10 @@ async function startMcpServer() {
|
|
|
4693
5010
|
"list_issues",
|
|
4694
5011
|
"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
5012
|
{
|
|
4696
|
-
repoPath:
|
|
4697
|
-
query:
|
|
4698
|
-
assignee:
|
|
4699
|
-
limit:
|
|
5013
|
+
repoPath: z6.string(),
|
|
5014
|
+
query: z6.string().optional().describe("Search title, identifier, or description"),
|
|
5015
|
+
assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
5016
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
4700
5017
|
},
|
|
4701
5018
|
async ({ repoPath, query, assignee, limit }) => {
|
|
4702
5019
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4828,8 +5145,8 @@ var BrightsySideboardApi = class {
|
|
|
4828
5145
|
};
|
|
4829
5146
|
function taskMessageText(task) {
|
|
4830
5147
|
const parts = task.message?.parts ?? [];
|
|
4831
|
-
const
|
|
4832
|
-
return
|
|
5148
|
+
const text6 = parts.find((p) => p.kind === "text")?.text ?? "";
|
|
5149
|
+
return text6.trim();
|
|
4833
5150
|
}
|
|
4834
5151
|
|
|
4835
5152
|
// src/brightsy/cloud-connect.ts
|
|
@@ -5302,11 +5619,11 @@ async function startSlackOAuth(opts) {
|
|
|
5302
5619
|
import { WebSocket as WsWebSocket } from "ws";
|
|
5303
5620
|
var BACKOFF_START_MS = 1e3;
|
|
5304
5621
|
var BACKOFF_MAX_MS = 3e4;
|
|
5305
|
-
function stripSlackMentions(
|
|
5306
|
-
return
|
|
5622
|
+
function stripSlackMentions(text6) {
|
|
5623
|
+
return text6.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
5307
5624
|
}
|
|
5308
|
-
function isSlackStopCommand(
|
|
5309
|
-
const t = stripSlackMentions(
|
|
5625
|
+
function isSlackStopCommand(text6) {
|
|
5626
|
+
const t = stripSlackMentions(text6).toLowerCase();
|
|
5310
5627
|
return t === "stop" || t === "sideboard_force_stop";
|
|
5311
5628
|
}
|
|
5312
5629
|
function parseSlackSocketFrame(raw) {
|
|
@@ -5328,8 +5645,8 @@ function inboundFromSocketFrame(frame) {
|
|
|
5328
5645
|
const ts = event.ts?.trim();
|
|
5329
5646
|
if (!channelId || !ts) return null;
|
|
5330
5647
|
const rawText = event.text?.trim() ?? "";
|
|
5331
|
-
const
|
|
5332
|
-
if (!
|
|
5648
|
+
const text6 = stripSlackMentions(rawText);
|
|
5649
|
+
if (!text6) return null;
|
|
5333
5650
|
const teamId = (frame.payload?.team_id || event.team || "").trim();
|
|
5334
5651
|
if (!teamId) return null;
|
|
5335
5652
|
const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
|
|
@@ -5341,7 +5658,7 @@ function inboundFromSocketFrame(frame) {
|
|
|
5341
5658
|
ts,
|
|
5342
5659
|
threadTs: event.thread_ts?.trim() || void 0,
|
|
5343
5660
|
userId: event.user?.trim(),
|
|
5344
|
-
text:
|
|
5661
|
+
text: text6,
|
|
5345
5662
|
kind: isMention ? "mention" : "dm"
|
|
5346
5663
|
};
|
|
5347
5664
|
}
|
|
@@ -5881,12 +6198,12 @@ function formatSlackInboundPrompt(msg) {
|
|
|
5881
6198
|
|
|
5882
6199
|
${msg.text}`;
|
|
5883
6200
|
}
|
|
5884
|
-
function isSlackInboundUserPrompt(
|
|
5885
|
-
return
|
|
6201
|
+
function isSlackInboundUserPrompt(text6) {
|
|
6202
|
+
return text6.startsWith("Slack DM\n") || text6.startsWith("Slack @mention\n");
|
|
5886
6203
|
}
|
|
5887
|
-
function formatSlackSignedReply(deviceLabel,
|
|
6204
|
+
function formatSlackSignedReply(deviceLabel, text6) {
|
|
5888
6205
|
const label = deviceLabel.trim();
|
|
5889
|
-
const body =
|
|
6206
|
+
const body = text6.trim();
|
|
5890
6207
|
if (!body) return body;
|
|
5891
6208
|
if (!label) return body;
|
|
5892
6209
|
const head = `${label}:`;
|
|
@@ -5895,8 +6212,8 @@ function formatSlackSignedReply(deviceLabel, text5) {
|
|
|
5895
6212
|
}
|
|
5896
6213
|
return `${label}: ${body}`;
|
|
5897
6214
|
}
|
|
5898
|
-
function signForThisMac(
|
|
5899
|
-
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel,
|
|
6215
|
+
function signForThisMac(text6) {
|
|
6216
|
+
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text6);
|
|
5900
6217
|
}
|
|
5901
6218
|
function slackReplyThreadTs(msg) {
|
|
5902
6219
|
if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
|
|
@@ -5957,9 +6274,9 @@ function replyStub(target) {
|
|
|
5957
6274
|
kind: "dm"
|
|
5958
6275
|
};
|
|
5959
6276
|
}
|
|
5960
|
-
async function postSlackText(target,
|
|
6277
|
+
async function postSlackText(target, text6, opts) {
|
|
5961
6278
|
if (opts.postReply) {
|
|
5962
|
-
const result = await opts.postReply(replyStub(target),
|
|
6279
|
+
const result = await opts.postReply(replyStub(target), text6);
|
|
5963
6280
|
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
5964
6281
|
return ts2 ? { ts: ts2 } : null;
|
|
5965
6282
|
}
|
|
@@ -5969,7 +6286,7 @@ async function postSlackText(target, text5, opts) {
|
|
|
5969
6286
|
"chat.postMessage",
|
|
5970
6287
|
{
|
|
5971
6288
|
channel: target.channelId,
|
|
5972
|
-
text:
|
|
6289
|
+
text: text6,
|
|
5973
6290
|
thread_ts: target.threadTs
|
|
5974
6291
|
},
|
|
5975
6292
|
opts.fetchImpl
|
|
@@ -5977,9 +6294,9 @@ async function postSlackText(target, text5, opts) {
|
|
|
5977
6294
|
const ts = json.ts?.trim();
|
|
5978
6295
|
return ts ? { ts } : null;
|
|
5979
6296
|
}
|
|
5980
|
-
async function updateSlackText(target, ts,
|
|
6297
|
+
async function updateSlackText(target, ts, text6, opts) {
|
|
5981
6298
|
if (opts.updateReply) {
|
|
5982
|
-
await opts.updateReply(replyStub(target), ts,
|
|
6299
|
+
await opts.updateReply(replyStub(target), ts, text6);
|
|
5983
6300
|
return;
|
|
5984
6301
|
}
|
|
5985
6302
|
const token = writeTokenForTeam(target.teamId);
|
|
@@ -5989,7 +6306,7 @@ async function updateSlackText(target, ts, text5, opts) {
|
|
|
5989
6306
|
{
|
|
5990
6307
|
channel: target.channelId,
|
|
5991
6308
|
ts,
|
|
5992
|
-
text:
|
|
6309
|
+
text: text6
|
|
5993
6310
|
},
|
|
5994
6311
|
opts.fetchImpl
|
|
5995
6312
|
);
|
|
@@ -6017,8 +6334,8 @@ function replyTargetOf(msg) {
|
|
|
6017
6334
|
threadTs: slackReplyThreadTs(msg)
|
|
6018
6335
|
};
|
|
6019
6336
|
}
|
|
6020
|
-
async function postSlackReply(msg,
|
|
6021
|
-
await postSlackText(replyTargetOf(msg), signForThisMac(
|
|
6337
|
+
async function postSlackReply(msg, text6, opts) {
|
|
6338
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text6), opts);
|
|
6022
6339
|
}
|
|
6023
6340
|
function startSlackTurnProgress(msg, threadId, opts) {
|
|
6024
6341
|
const log = opts.onLog ?? (() => void 0);
|
|
@@ -6101,10 +6418,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
6101
6418
|
}
|
|
6102
6419
|
});
|
|
6103
6420
|
},
|
|
6104
|
-
finishWith: (
|
|
6421
|
+
finishWith: (text6) => {
|
|
6105
6422
|
stop();
|
|
6106
6423
|
return run2(async () => {
|
|
6107
|
-
const signed = signForThisMac(
|
|
6424
|
+
const signed = signForThisMac(text6.trim());
|
|
6108
6425
|
if (!signed) {
|
|
6109
6426
|
if (!postedTs) return;
|
|
6110
6427
|
const ts = postedTs;
|
|
@@ -6132,11 +6449,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
6132
6449
|
};
|
|
6133
6450
|
}
|
|
6134
6451
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
6135
|
-
function markRelayed(threadId,
|
|
6136
|
-
lastRelayed.set(threadId, `${threadId}:${
|
|
6452
|
+
function markRelayed(threadId, text6) {
|
|
6453
|
+
lastRelayed.set(threadId, `${threadId}:${text6}`);
|
|
6137
6454
|
}
|
|
6138
|
-
function alreadyRelayed(threadId,
|
|
6139
|
-
return lastRelayed.get(threadId) === `${threadId}:${
|
|
6455
|
+
function alreadyRelayed(threadId, text6) {
|
|
6456
|
+
return lastRelayed.get(threadId) === `${threadId}:${text6}`;
|
|
6140
6457
|
}
|
|
6141
6458
|
async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
6142
6459
|
const target = getSlackReplyTarget(threadId);
|
|
@@ -6145,14 +6462,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
|
6145
6462
|
const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
|
|
6146
6463
|
if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
|
|
6147
6464
|
const result = getOrchestrator().getTurnResult(threadId);
|
|
6148
|
-
const
|
|
6149
|
-
if (!
|
|
6150
|
-
if (alreadyRelayed(threadId,
|
|
6151
|
-
markRelayed(threadId,
|
|
6465
|
+
const text6 = result.text.trim();
|
|
6466
|
+
if (!text6) return;
|
|
6467
|
+
if (alreadyRelayed(threadId, text6)) return;
|
|
6468
|
+
markRelayed(threadId, text6);
|
|
6152
6469
|
const log = opts.onLog ?? (() => void 0);
|
|
6153
6470
|
try {
|
|
6154
|
-
await postSlackText(target, signForThisMac(
|
|
6155
|
-
log(`replied ${target.threadTs ?? target.channelId} (${
|
|
6471
|
+
await postSlackText(target, signForThisMac(text6), opts);
|
|
6472
|
+
log(`replied ${target.threadTs ?? target.channelId} (${text6.length} chars)`);
|
|
6156
6473
|
} catch (err) {
|
|
6157
6474
|
lastRelayed.delete(threadId);
|
|
6158
6475
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -6400,10 +6717,10 @@ function resolveSlackListenMode(opts) {
|
|
|
6400
6717
|
}
|
|
6401
6718
|
|
|
6402
6719
|
// src/slack/relay-hub.ts
|
|
6403
|
-
function parseSlackDeviceDestination(
|
|
6404
|
-
const m =
|
|
6405
|
-
if (!m) return { label: null, rest:
|
|
6406
|
-
return { label: m[1], rest:
|
|
6720
|
+
function parseSlackDeviceDestination(text6) {
|
|
6721
|
+
const m = text6.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
|
|
6722
|
+
if (!m) return { label: null, rest: text6 };
|
|
6723
|
+
return { label: m[1], rest: text6.slice(m[0].length) };
|
|
6407
6724
|
}
|
|
6408
6725
|
var SlackRelayHub = class {
|
|
6409
6726
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -7009,6 +7326,9 @@ export {
|
|
|
7009
7326
|
SlackOAuthCancelledError,
|
|
7010
7327
|
SlackRelayHub,
|
|
7011
7328
|
THINKING_EFFORTS,
|
|
7329
|
+
WORKTREE_ABLETIME_MCP_TOOLS,
|
|
7330
|
+
WORKTREE_GITHUB_MCP_TOOLS,
|
|
7331
|
+
WORKTREE_LINEAR_MCP_TOOLS,
|
|
7012
7332
|
WORKTREE_MCP_TOOLS,
|
|
7013
7333
|
abletimeMcpRequest,
|
|
7014
7334
|
abletimeMcpUrl,
|
|
@@ -7096,6 +7416,8 @@ export {
|
|
|
7096
7416
|
codexUnattendedGitConfigArgs,
|
|
7097
7417
|
coerceOrchestratorAgent,
|
|
7098
7418
|
collectTakenTeamSlugs,
|
|
7419
|
+
commentAbleTimeTask,
|
|
7420
|
+
commentGitHubIssue,
|
|
7099
7421
|
commentLinearIssue,
|
|
7100
7422
|
commitAll,
|
|
7101
7423
|
computeNextRunAt,
|
|
@@ -7117,6 +7439,7 @@ export {
|
|
|
7117
7439
|
createChatTab,
|
|
7118
7440
|
createEmptyThread,
|
|
7119
7441
|
createExistingBranchWorktree,
|
|
7442
|
+
createGitHubIssue,
|
|
7120
7443
|
createGlobalChat,
|
|
7121
7444
|
createLinearIssue,
|
|
7122
7445
|
createLinearPkce,
|
|
@@ -7203,6 +7526,10 @@ export {
|
|
|
7203
7526
|
formatGhLandError,
|
|
7204
7527
|
formatGitAuthModeDirective,
|
|
7205
7528
|
formatIpcInvokeError,
|
|
7529
|
+
formatIssueToolsDirective,
|
|
7530
|
+
formatIssueToolsReminder,
|
|
7531
|
+
formatLinearDirective,
|
|
7532
|
+
formatLinearReminder,
|
|
7206
7533
|
formatLongRunningDirective,
|
|
7207
7534
|
formatLongRunningReminder,
|
|
7208
7535
|
formatMergePrError,
|
|
@@ -7248,6 +7575,7 @@ export {
|
|
|
7248
7575
|
getDefaultRunScript,
|
|
7249
7576
|
getDiff,
|
|
7250
7577
|
getDiffSummary,
|
|
7578
|
+
getGitHubIssue,
|
|
7251
7579
|
getGitHubStatus,
|
|
7252
7580
|
getGithubGitAuthMode,
|
|
7253
7581
|
getGithubPat,
|
|
@@ -7350,6 +7678,7 @@ export {
|
|
|
7350
7678
|
issueAttachmentForAbleTimeTask,
|
|
7351
7679
|
issueMatchesAssignee,
|
|
7352
7680
|
issueSourceLabel,
|
|
7681
|
+
issueTicketFromThread,
|
|
7353
7682
|
lastRequestOccupancy,
|
|
7354
7683
|
latestPendingPlanQuestions,
|
|
7355
7684
|
linearAuthorizationHeader,
|
|
@@ -7357,6 +7686,7 @@ export {
|
|
|
7357
7686
|
linearGraphql,
|
|
7358
7687
|
linearOAuthAuthorizeUrl,
|
|
7359
7688
|
linearOAuthCredentials,
|
|
7689
|
+
linearTicketIdFromThread,
|
|
7360
7690
|
listAbleTimeAssignedIssues,
|
|
7361
7691
|
listAbleTimeProjects,
|
|
7362
7692
|
listAbleTimeTasks,
|
|
@@ -7443,6 +7773,7 @@ export {
|
|
|
7443
7773
|
parseDurationMs,
|
|
7444
7774
|
parseForceStopMessage,
|
|
7445
7775
|
parseGhStackViewJson,
|
|
7776
|
+
parseGitHubIssueNumber,
|
|
7446
7777
|
parseGithubSlugFromRemoteUrl,
|
|
7447
7778
|
parseMcpList,
|
|
7448
7779
|
parsePlanQuestionsInput,
|
|
@@ -7506,6 +7837,7 @@ export {
|
|
|
7506
7837
|
resolveFilesToCopy,
|
|
7507
7838
|
resolveGhAuthToken,
|
|
7508
7839
|
resolveGitDirsForLockRecovery,
|
|
7840
|
+
resolveGitHubIssueRepo,
|
|
7509
7841
|
resolveGithubAgentToken,
|
|
7510
7842
|
resolveGithubRepoSlug,
|
|
7511
7843
|
resolveLinearState,
|
|
@@ -7610,6 +7942,7 @@ export {
|
|
|
7610
7942
|
threadsDir,
|
|
7611
7943
|
threadsSharingWorktree,
|
|
7612
7944
|
toAbleTimeIssueInfo,
|
|
7945
|
+
toGitHubIssueInfo,
|
|
7613
7946
|
toPublicAppSettings,
|
|
7614
7947
|
toolActivityLine,
|
|
7615
7948
|
toolDescription,
|
|
@@ -7617,6 +7950,7 @@ export {
|
|
|
7617
7950
|
toolFilePath,
|
|
7618
7951
|
totalTokens,
|
|
7619
7952
|
turnCostUsdFromCursorUsage,
|
|
7953
|
+
updateAbleTimeTask,
|
|
7620
7954
|
updateAdvancedSettings,
|
|
7621
7955
|
updateAgentExecutable,
|
|
7622
7956
|
updateAppEnvironment,
|
|
@@ -7624,6 +7958,7 @@ export {
|
|
|
7624
7958
|
updateClaudeSettings,
|
|
7625
7959
|
updateCodexSettings,
|
|
7626
7960
|
updateDefaultsSettings,
|
|
7961
|
+
updateGitHubIssue,
|
|
7627
7962
|
updateIntegrationsSettings,
|
|
7628
7963
|
updateLinearIssue,
|
|
7629
7964
|
updateOpencodeSettings,
|