@sideboard-ai/core 0.1.73 → 0.1.77
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/agents/cursor-runner.cjs +1 -1
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-77GE7VRW.js → agents-LKUHPPEQ.js} +3 -3
- package/dist/{agents-GUFUYXKP.js → agents-XJV6J3K2.js} +4 -4
- package/dist/{caffeinate-hold-KLC6SABD.js → caffeinate-hold-EAZNWJBE.js} +5 -1
- package/dist/caffeinate-hold-OHAUWOA6.js +20 -0
- package/dist/{chunk-AFW3M6LU.js → chunk-2FJI5JXX.js} +17 -11
- package/dist/{chunk-CXT2PLO7.js → chunk-2ZIPJRJE.js} +1 -1
- package/dist/{chunk-CYKPUTXN.js → chunk-75U65MBE.js} +1 -1
- package/dist/{chunk-V4ACEJX2.js → chunk-EEKSZTMU.js} +14 -12
- package/dist/{chunk-POW7JCB5.js → chunk-GWTLKXXP.js} +1 -1
- package/dist/chunk-MFF2RE3I.js +168 -0
- package/dist/{chunk-WSFZPOPH.js → chunk-QD37IILI.js} +17 -11
- package/dist/chunk-RV6DBEDQ.js +170 -0
- package/dist/{chunk-3WJAUKIL.js → chunk-SEOICVGB.js} +1 -1
- package/dist/{chunk-NF6Y4GTE.js → chunk-VERLUKN2.js} +1 -1
- package/dist/{chunk-6RFPKZNC.js → chunk-XJNCWYFR.js} +14 -12
- package/dist/{coordinator-prompt-4U2QNHGT.js → coordinator-prompt-LLFJUO2R.js} +1 -1
- package/dist/{coordinator-prompt-ZHBDHMZB.js → coordinator-prompt-MRCMMRSF.js} +1 -1
- package/dist/{global-workspace-VF56FTPY.js → global-workspace-TWHMYLTZ.js} +2 -2
- package/dist/{global-workspace-S3B6ESZS.js → global-workspace-YNMNO4CL.js} +2 -2
- package/dist/index.cjs +1203 -468
- package/dist/index.d.cts +177 -23
- package/dist/index.d.ts +177 -23
- package/dist/index.js +1049 -401
- package/dist/mcp/run-stdio.cjs +848 -306
- package/dist/mcp/run-stdio.js +656 -182
- package/dist/{workspaces-ZJ45O4CD.js → workspaces-GQ4XKBD3.js} +3 -3
- package/dist/{workspaces-R66324S7.js → workspaces-O3U5BENH.js} +3 -3
- package/package.json +1 -1
- package/dist/caffeinate-hold-BEJIYPJ7.js +0 -107
- package/dist/chunk-JAWEDVVA.js +0 -106
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -16,19 +16,22 @@ import {
|
|
|
16
16
|
resolveQuotaFallbackAgent,
|
|
17
17
|
sideboardMcpProfile,
|
|
18
18
|
summarizeTurnStderr
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-2FJI5JXX.js";
|
|
20
20
|
import "../chunk-VROPG6QF.js";
|
|
21
21
|
import {
|
|
22
22
|
addWorkspace,
|
|
23
23
|
ensureWorkspace,
|
|
24
24
|
removeWorkspace,
|
|
25
25
|
syncWorkspacesFromThreads
|
|
26
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-2ZIPJRJE.js";
|
|
27
27
|
import {
|
|
28
28
|
extractPresentedPlan,
|
|
29
29
|
readPlanFile,
|
|
30
30
|
writePlanFile
|
|
31
31
|
} from "../chunk-N5PM7HGQ.js";
|
|
32
|
+
import {
|
|
33
|
+
releaseCaffeinateHoldForThread
|
|
34
|
+
} from "../chunk-RV6DBEDQ.js";
|
|
32
35
|
import {
|
|
33
36
|
GLOBAL_WORKSPACE_ID,
|
|
34
37
|
assertOrchestratorCapableAgent,
|
|
@@ -38,14 +41,14 @@ import {
|
|
|
38
41
|
isGlobalThread,
|
|
39
42
|
isOrchestratorThread,
|
|
40
43
|
orchestratorSessionPoisonedByBuiltins
|
|
41
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-GWTLKXXP.js";
|
|
42
45
|
import {
|
|
43
46
|
SLACK_REPLY_FORMATTING,
|
|
44
47
|
coordinatorSystemPrompt,
|
|
45
48
|
coordinatorTurnReminder,
|
|
46
49
|
enrichWorkspacesWithGithub,
|
|
47
50
|
ensureGlobalCoordinatorCwd
|
|
48
|
-
} from "../chunk-
|
|
51
|
+
} from "../chunk-XJNCWYFR.js";
|
|
49
52
|
import {
|
|
50
53
|
addPrStackLayer,
|
|
51
54
|
allocateTeamName,
|
|
@@ -140,7 +143,7 @@ import {
|
|
|
140
143
|
// src/mcp/server.ts
|
|
141
144
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
142
145
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
143
|
-
import { z as
|
|
146
|
+
import { z as z3 } from "zod";
|
|
144
147
|
import { basename as basename4 } from "path";
|
|
145
148
|
|
|
146
149
|
// src/orchestrator/orchestrator.ts
|
|
@@ -341,8 +344,8 @@ ${input.permalink}` : "";
|
|
|
341
344
|
|
|
342
345
|
${body}${link}`;
|
|
343
346
|
}
|
|
344
|
-
function isSlackExternalReplyPrompt(
|
|
345
|
-
return
|
|
347
|
+
function isSlackExternalReplyPrompt(text3) {
|
|
348
|
+
return text3.startsWith("Slack reply from ") && text3.includes("not a command");
|
|
346
349
|
}
|
|
347
350
|
function pendingSlackExternalReplies(messages) {
|
|
348
351
|
let i = messages.length - 1;
|
|
@@ -366,9 +369,9 @@ function formatSlackRepliesForTurn(replies) {
|
|
|
366
369
|
function listSlackOutboundWatches() {
|
|
367
370
|
return pruneWatches(readStore3());
|
|
368
371
|
}
|
|
369
|
-
function formatOwnerSlackFyi(userName,
|
|
372
|
+
function formatOwnerSlackFyi(userName, text3) {
|
|
370
373
|
const who = userName.trim() || "Someone";
|
|
371
|
-
const body =
|
|
374
|
+
const body = text3.trim() || "(no text)";
|
|
372
375
|
return `${who} replied in Slack:
|
|
373
376
|
${body}`;
|
|
374
377
|
}
|
|
@@ -383,7 +386,7 @@ async function relayExternalReply(opts) {
|
|
|
383
386
|
const thread = readThread(threadId);
|
|
384
387
|
if (!thread || thread.status === "archived") return true;
|
|
385
388
|
try {
|
|
386
|
-
const
|
|
389
|
+
const text3 = formatSlackExternalReplyPrompt({
|
|
387
390
|
userName: opts.reply.userName,
|
|
388
391
|
kind: opts.watch.kind,
|
|
389
392
|
toLabel: opts.watch.toLabel,
|
|
@@ -392,7 +395,7 @@ async function relayExternalReply(opts) {
|
|
|
392
395
|
});
|
|
393
396
|
appendMessage(threadId, {
|
|
394
397
|
role: "agent",
|
|
395
|
-
text:
|
|
398
|
+
text: text3,
|
|
396
399
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
397
400
|
});
|
|
398
401
|
} catch {
|
|
@@ -576,7 +579,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
576
579
|
const messages = await fetchMessages(token, watch, opts?.fetchImpl);
|
|
577
580
|
const replies = messages.filter((m) => isHumanReply(m, watch)).sort((a, b) => Number(a.ts) - Number(b.ts));
|
|
578
581
|
if (replies.length === 0) continue;
|
|
579
|
-
const
|
|
582
|
+
const injected2 = new Set(watch.injectedReplyTs ?? []);
|
|
580
583
|
const collected = [...watch.replies ?? []];
|
|
581
584
|
let lastSeenTs = watch.lastSeenTs;
|
|
582
585
|
let latestUser;
|
|
@@ -610,7 +613,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
610
613
|
latestName = replyUserName;
|
|
611
614
|
latestText = reply.text;
|
|
612
615
|
latestPermalink = permalink;
|
|
613
|
-
if (
|
|
616
|
+
if (injected2.has(ts)) {
|
|
614
617
|
lastSeenTs = ts;
|
|
615
618
|
continue;
|
|
616
619
|
}
|
|
@@ -621,7 +624,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
621
624
|
fetchImpl: opts?.fetchImpl
|
|
622
625
|
});
|
|
623
626
|
if (!delivered) break;
|
|
624
|
-
|
|
627
|
+
injected2.add(ts);
|
|
625
628
|
lastSeenTs = ts;
|
|
626
629
|
}
|
|
627
630
|
watches[i] = {
|
|
@@ -633,7 +636,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
633
636
|
replyTs: lastSeenTs,
|
|
634
637
|
replyPreview: latestText.slice(0, 140),
|
|
635
638
|
permalink: latestPermalink,
|
|
636
|
-
injectedReplyTs: [...
|
|
639
|
+
injectedReplyTs: [...injected2],
|
|
637
640
|
replies: collected.slice(-MAX_REPLIES_PER_WATCH)
|
|
638
641
|
};
|
|
639
642
|
changed = true;
|
|
@@ -723,9 +726,9 @@ function toolFilePath(input) {
|
|
|
723
726
|
if (!input) return void 0;
|
|
724
727
|
return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
|
|
725
728
|
}
|
|
726
|
-
function countLines(
|
|
727
|
-
if (!
|
|
728
|
-
return
|
|
729
|
+
function countLines(text3) {
|
|
730
|
+
if (!text3) return 0;
|
|
731
|
+
return text3.split("\n").length;
|
|
729
732
|
}
|
|
730
733
|
function diffFromInput(input) {
|
|
731
734
|
if (!input) return {};
|
|
@@ -852,9 +855,9 @@ function applyAgentEvent(parts, event) {
|
|
|
852
855
|
function partsToAssistantText(parts) {
|
|
853
856
|
return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
|
|
854
857
|
}
|
|
855
|
-
function stripBrightsyNdjsonNoise(
|
|
856
|
-
if (!
|
|
857
|
-
let out =
|
|
858
|
+
function stripBrightsyNdjsonNoise(text3) {
|
|
859
|
+
if (!text3 || !text3.includes('"type"')) return text3;
|
|
860
|
+
let out = text3;
|
|
858
861
|
out = out.replace(
|
|
859
862
|
/\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
|
|
860
863
|
""
|
|
@@ -882,14 +885,28 @@ function sumOptional(a, b) {
|
|
|
882
885
|
if (a == null && b == null) return void 0;
|
|
883
886
|
return (a ?? 0) + (b ?? 0);
|
|
884
887
|
}
|
|
888
|
+
function requestOccupancy(u) {
|
|
889
|
+
return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
|
|
890
|
+
}
|
|
885
891
|
function mergeUsage(a, b) {
|
|
886
892
|
return {
|
|
887
893
|
inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
|
|
888
894
|
outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
|
|
889
895
|
cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
|
|
890
|
-
cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens)
|
|
896
|
+
cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
|
|
897
|
+
lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
|
|
891
898
|
};
|
|
892
899
|
}
|
|
900
|
+
function applyTurnUsage(current, incoming, scope = "request") {
|
|
901
|
+
if (scope === "turn") {
|
|
902
|
+
return {
|
|
903
|
+
...incoming,
|
|
904
|
+
lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
const merged = mergeUsage(current, incoming);
|
|
908
|
+
return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
|
|
909
|
+
}
|
|
893
910
|
|
|
894
911
|
// src/agents/spawn.ts
|
|
895
912
|
async function spawnAgentTurn(thread, input, onEvent) {
|
|
@@ -899,9 +916,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
899
916
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
900
917
|
);
|
|
901
918
|
}
|
|
902
|
-
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-
|
|
919
|
+
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-TWHMYLTZ.js");
|
|
903
920
|
if (isGlobalThread2(thread)) {
|
|
904
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-
|
|
921
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-MRCMMRSF.js");
|
|
905
922
|
ensureGlobalCoordinatorCwd2(
|
|
906
923
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
907
924
|
);
|
|
@@ -963,7 +980,7 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
963
980
|
continue;
|
|
964
981
|
}
|
|
965
982
|
if (parsed.type === "usage") {
|
|
966
|
-
usage =
|
|
983
|
+
usage = applyTurnUsage(usage, parsed.data, parsed.scope ?? "request");
|
|
967
984
|
onEvent(parsed);
|
|
968
985
|
continue;
|
|
969
986
|
}
|
|
@@ -988,8 +1005,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
988
1005
|
onEvent({ type: "exit", data: exitCode });
|
|
989
1006
|
const finalized = finalizeParts(parts);
|
|
990
1007
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
991
|
-
const
|
|
992
|
-
return { exitCode, sessionId, assistantText:
|
|
1008
|
+
const text3 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
1009
|
+
return { exitCode, sessionId, assistantText: text3, parts: finalized, usage };
|
|
993
1010
|
});
|
|
994
1011
|
return {
|
|
995
1012
|
pid: child.pid,
|
|
@@ -1003,6 +1020,31 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1003
1020
|
};
|
|
1004
1021
|
}
|
|
1005
1022
|
|
|
1023
|
+
// src/git/agent-git-actions.ts
|
|
1024
|
+
var AGENT_GIT_ACTIONS = [
|
|
1025
|
+
"commit-push",
|
|
1026
|
+
"create-draft",
|
|
1027
|
+
"create-web",
|
|
1028
|
+
"resolve-conflicts",
|
|
1029
|
+
"merge"
|
|
1030
|
+
];
|
|
1031
|
+
function agentGitPrompt(action, opts) {
|
|
1032
|
+
switch (action) {
|
|
1033
|
+
case "commit-push":
|
|
1034
|
+
return "Commit and push.";
|
|
1035
|
+
case "create-draft":
|
|
1036
|
+
return "Commit, push, and open a draft PR.";
|
|
1037
|
+
case "create-web":
|
|
1038
|
+
return "Commit, push, and open a PR in the browser.";
|
|
1039
|
+
case "resolve-conflicts": {
|
|
1040
|
+
const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
|
|
1041
|
+
return base ? `Merge origin/${base} into this branch. Then push.` : "Fix merge conflicts.";
|
|
1042
|
+
}
|
|
1043
|
+
case "merge":
|
|
1044
|
+
return "Merge PR.";
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1006
1048
|
// src/git/pr-merge-archive.ts
|
|
1007
1049
|
function normalizePrState(state) {
|
|
1008
1050
|
return (state ?? "").trim().toUpperCase();
|
|
@@ -1717,7 +1759,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
1717
1759
|
return readThread(thread.id) ?? thread;
|
|
1718
1760
|
}
|
|
1719
1761
|
async function listLinearIssues(agent, repoPath) {
|
|
1720
|
-
const { getAdapter: getAdapter2 } = await import("../agents-
|
|
1762
|
+
const { getAdapter: getAdapter2 } = await import("../agents-LKUHPPEQ.js");
|
|
1721
1763
|
await requireAgent(agent, { requireLinear: true });
|
|
1722
1764
|
const adapter = getAdapter2(agent);
|
|
1723
1765
|
if (!adapter.listLinearIssues) {
|
|
@@ -1773,9 +1815,9 @@ async function tryClaudeSummary(transcript, opts) {
|
|
|
1773
1815
|
{ cwd: opts?.cwd, reject: false }
|
|
1774
1816
|
);
|
|
1775
1817
|
if (exitCode !== 0) return null;
|
|
1776
|
-
const
|
|
1777
|
-
if (
|
|
1778
|
-
return
|
|
1818
|
+
const text3 = stdout.trim();
|
|
1819
|
+
if (text3.length < 40) return null;
|
|
1820
|
+
return text3;
|
|
1779
1821
|
} catch {
|
|
1780
1822
|
return null;
|
|
1781
1823
|
}
|
|
@@ -1824,9 +1866,9 @@ function extractiveSummary(transcript) {
|
|
|
1824
1866
|
);
|
|
1825
1867
|
return parts.join("\n");
|
|
1826
1868
|
}
|
|
1827
|
-
function clipSummary(
|
|
1828
|
-
if (
|
|
1829
|
-
return `${
|
|
1869
|
+
function clipSummary(text3) {
|
|
1870
|
+
if (text3.length <= MAX_SUMMARY_CHARS) return text3;
|
|
1871
|
+
return `${text3.slice(0, MAX_SUMMARY_CHARS)}
|
|
1830
1872
|
|
|
1831
1873
|
[\u2026summary truncated\u2026]`;
|
|
1832
1874
|
}
|
|
@@ -2439,8 +2481,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
2439
2481
|
);
|
|
2440
2482
|
const recent = from.messages.slice(-8).map((m) => {
|
|
2441
2483
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
2442
|
-
const
|
|
2443
|
-
return
|
|
2484
|
+
const text3 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
2485
|
+
return text3 ? `- ${role}: ${text3}` : null;
|
|
2444
2486
|
}).filter(Boolean);
|
|
2445
2487
|
const body = [
|
|
2446
2488
|
`# Orchestration handoff`,
|
|
@@ -2536,13 +2578,13 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
2536
2578
|
for (const hash of hashes) {
|
|
2537
2579
|
const agentsFile = join9(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
2538
2580
|
if (!existsSync9(agentsFile)) continue;
|
|
2539
|
-
let
|
|
2581
|
+
let text3;
|
|
2540
2582
|
try {
|
|
2541
|
-
|
|
2583
|
+
text3 = readFileSync6(agentsFile, "utf8");
|
|
2542
2584
|
} catch {
|
|
2543
2585
|
continue;
|
|
2544
2586
|
}
|
|
2545
|
-
for (const line of
|
|
2587
|
+
for (const line of text3.split("\n")) {
|
|
2546
2588
|
const trimmed = line.trim();
|
|
2547
2589
|
if (!trimmed) continue;
|
|
2548
2590
|
try {
|
|
@@ -2581,7 +2623,7 @@ async function adoptThread(input) {
|
|
|
2581
2623
|
messages: input.messages ?? []
|
|
2582
2624
|
});
|
|
2583
2625
|
writeThread(thread);
|
|
2584
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
2626
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-GQ4XKBD3.js");
|
|
2585
2627
|
await ensureWorkspace2(repoPath);
|
|
2586
2628
|
return thread;
|
|
2587
2629
|
}
|
|
@@ -3305,13 +3347,13 @@ function formatStat(files) {
|
|
|
3305
3347
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
3306
3348
|
}
|
|
3307
3349
|
function emptyScopeStats() {
|
|
3308
|
-
const
|
|
3350
|
+
const z4 = emptyStat();
|
|
3309
3351
|
return {
|
|
3310
|
-
commits:
|
|
3311
|
-
uncommitted:
|
|
3312
|
-
staged:
|
|
3313
|
-
unstaged:
|
|
3314
|
-
last_turn:
|
|
3352
|
+
commits: z4,
|
|
3353
|
+
uncommitted: z4,
|
|
3354
|
+
staged: z4,
|
|
3355
|
+
unstaged: z4,
|
|
3356
|
+
last_turn: z4
|
|
3315
3357
|
};
|
|
3316
3358
|
}
|
|
3317
3359
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -3582,10 +3624,10 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
3582
3624
|
listBranchCommits(worktreePath, repoPath, { base: baseLabel }),
|
|
3583
3625
|
isDirty(worktreePath),
|
|
3584
3626
|
countUnpushedCommits(worktreePath)
|
|
3585
|
-
]).then(([scopeStats, commits,
|
|
3627
|
+
]).then(([scopeStats, commits, dirty2, unpushed]) => ({
|
|
3586
3628
|
scopeStats,
|
|
3587
3629
|
commits,
|
|
3588
|
-
dirty,
|
|
3630
|
+
dirty: dirty2,
|
|
3589
3631
|
unpushed
|
|
3590
3632
|
})) : Promise.resolve({
|
|
3591
3633
|
scopeStats: emptyScopeStats(),
|
|
@@ -3662,13 +3704,14 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
3662
3704
|
}
|
|
3663
3705
|
}
|
|
3664
3706
|
const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
3707
|
+
const dirty = includeMeta ? meta.dirty : scope === "commits" ? false : files.length > 0;
|
|
3665
3708
|
return {
|
|
3666
3709
|
scope,
|
|
3667
3710
|
commitSha: scope === "commits" ? commitSha : null,
|
|
3668
3711
|
base: labelBase,
|
|
3669
3712
|
files,
|
|
3670
3713
|
stat: formatStat(files),
|
|
3671
|
-
dirty
|
|
3714
|
+
dirty,
|
|
3672
3715
|
unpushed: meta.unpushed,
|
|
3673
3716
|
hasLastTurnBase,
|
|
3674
3717
|
commits: meta.commits,
|
|
@@ -4380,7 +4423,7 @@ function formatRenameBranchDirective(thread, opts) {
|
|
|
4380
4423
|
"- Rename the git branch to a short kebab-case name that describes this task (what you are changing), e.g. `fix/panel-width` or `feat/dark-mode`:",
|
|
4381
4424
|
" `git branch -m <new-name>`",
|
|
4382
4425
|
"- Prefer Conventional Commits style prefixes when they fit (`fix/`, `feat/`, `chore/`, `docs/`).",
|
|
4383
|
-
"- Never push
|
|
4426
|
+
"- Never push this placeholder to main/master."
|
|
4384
4427
|
];
|
|
4385
4428
|
const custom = opts?.customPrompt?.trim();
|
|
4386
4429
|
if (custom) {
|
|
@@ -4423,7 +4466,7 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
4423
4466
|
"- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
|
|
4424
4467
|
);
|
|
4425
4468
|
lines.push(
|
|
4426
|
-
"- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch
|
|
4469
|
+
"- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. When asked to merge the PR, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
|
|
4427
4470
|
);
|
|
4428
4471
|
if (thread.prUrl) {
|
|
4429
4472
|
lines.push(
|
|
@@ -4455,13 +4498,13 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
4455
4498
|
'- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push.'
|
|
4456
4499
|
);
|
|
4457
4500
|
lines.push(
|
|
4458
|
-
'- "Update the branch." / "Fix merge conflicts." \u2192 sync with the PR base (merge or rebase), resolve conflicts carefully, commit, and push until the PR is mergeable.'
|
|
4501
|
+
'- "Update the branch." / "Fix merge conflicts." / "Merge origin/<base> into this branch. Then push." \u2192 sync with the PR base (merge or rebase), resolve conflicts carefully, commit, and push until the PR is mergeable.'
|
|
4459
4502
|
);
|
|
4460
4503
|
lines.push(
|
|
4461
4504
|
'- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
|
|
4462
4505
|
);
|
|
4463
4506
|
lines.push(
|
|
4464
|
-
'- "Merge PR." \u2192 merge this thread\'s open pull request
|
|
4507
|
+
'- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
|
|
4465
4508
|
);
|
|
4466
4509
|
return lines.join("\n");
|
|
4467
4510
|
}
|
|
@@ -4857,11 +4900,11 @@ var Orchestrator = class {
|
|
|
4857
4900
|
return results;
|
|
4858
4901
|
}
|
|
4859
4902
|
/** Edit the text of a not-yet-started queued message. */
|
|
4860
|
-
async editQueuedMessage(threadRef, index,
|
|
4903
|
+
async editQueuedMessage(threadRef, index, text3) {
|
|
4861
4904
|
const thread = this.requireThread(threadRef);
|
|
4862
4905
|
return withThreadLock(thread.id, async () => {
|
|
4863
4906
|
const current = this.requireThread(thread.id);
|
|
4864
|
-
const trimmed =
|
|
4907
|
+
const trimmed = text3.trim();
|
|
4865
4908
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
4866
4909
|
return current;
|
|
4867
4910
|
}
|
|
@@ -5621,6 +5664,14 @@ var Orchestrator = class {
|
|
|
5621
5664
|
throw new Error(`${action} is not available on the global coordinator`);
|
|
5622
5665
|
}
|
|
5623
5666
|
}
|
|
5667
|
+
/** MCP set_caffeinate is a detached hold — closing the chat must not leave the Mac awake. */
|
|
5668
|
+
releaseOrchestratorCaffeinate(thread) {
|
|
5669
|
+
if (!isOrchestratorThread(thread)) return;
|
|
5670
|
+
try {
|
|
5671
|
+
releaseCaffeinateHoldForThread(thread.id);
|
|
5672
|
+
} catch {
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5624
5675
|
async diff(threadRef, opts) {
|
|
5625
5676
|
const thread = this.requireThread(threadRef);
|
|
5626
5677
|
this.assertNotGlobal(thread, "Diff");
|
|
@@ -5890,6 +5941,36 @@ var Orchestrator = class {
|
|
|
5890
5941
|
this.emit({ type: "status_changed", threadId: tab.id, status: tab.status });
|
|
5891
5942
|
return tab;
|
|
5892
5943
|
}
|
|
5944
|
+
/**
|
|
5945
|
+
* Queue a desktop-git-button prompt on a worktree agent (commit/push/PR/merge).
|
|
5946
|
+
* Orchestrators use this instead of running git/gh from the synthetic home.
|
|
5947
|
+
*/
|
|
5948
|
+
async askGit(threadRef, action) {
|
|
5949
|
+
if (!AGENT_GIT_ACTIONS.includes(action)) {
|
|
5950
|
+
throw new Error(`Unknown git action: ${action}`);
|
|
5951
|
+
}
|
|
5952
|
+
const thread = this.requireThread(threadRef);
|
|
5953
|
+
this.assertNotGlobal(thread, "ask_git");
|
|
5954
|
+
if (isOrchestratorThread(thread)) {
|
|
5955
|
+
throw new Error(
|
|
5956
|
+
"ask_git targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
|
|
5957
|
+
);
|
|
5958
|
+
}
|
|
5959
|
+
if (action === "merge" && !thread.prUrl) {
|
|
5960
|
+
throw new Error(
|
|
5961
|
+
"No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
|
|
5962
|
+
);
|
|
5963
|
+
}
|
|
5964
|
+
let prBase;
|
|
5965
|
+
if (action === "resolve-conflicts") {
|
|
5966
|
+
try {
|
|
5967
|
+
const details = await this.getPrDetails(threadRef);
|
|
5968
|
+
prBase = details?.baseRefName?.trim() || void 0;
|
|
5969
|
+
} catch {
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
return this.send(threadRef, agentGitPrompt(action, { prBase }));
|
|
5973
|
+
}
|
|
5893
5974
|
setThreadOptions(threadRef, patch) {
|
|
5894
5975
|
const thread = this.requireThread(threadRef);
|
|
5895
5976
|
const next = {};
|
|
@@ -5960,6 +6041,7 @@ var Orchestrator = class {
|
|
|
5960
6041
|
async archive(threadRef) {
|
|
5961
6042
|
const thread = this.requireThread(threadRef);
|
|
5962
6043
|
this.stop(thread.id);
|
|
6044
|
+
this.releaseOrchestratorCaffeinate(thread);
|
|
5963
6045
|
if (isGlobalThread(thread)) {
|
|
5964
6046
|
const archived2 = setStatus(thread.id, "archived");
|
|
5965
6047
|
this.emit({ type: "status_changed", threadId: archived2.id, status: "archived" });
|
|
@@ -5984,7 +6066,7 @@ var Orchestrator = class {
|
|
|
5984
6066
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
5985
6067
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
5986
6068
|
try {
|
|
5987
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
6069
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-GQ4XKBD3.js");
|
|
5988
6070
|
await ensureWorkspace2(thread.repoPath);
|
|
5989
6071
|
} catch {
|
|
5990
6072
|
}
|
|
@@ -5994,6 +6076,7 @@ var Orchestrator = class {
|
|
|
5994
6076
|
async purge(threadRef, opts) {
|
|
5995
6077
|
const thread = this.requireThread(threadRef);
|
|
5996
6078
|
this.stop(thread.id);
|
|
6079
|
+
this.releaseOrchestratorCaffeinate(thread);
|
|
5997
6080
|
if (isGlobalThread(thread)) {
|
|
5998
6081
|
deleteThreadRecord(thread.id);
|
|
5999
6082
|
return;
|
|
@@ -6098,13 +6181,36 @@ function getOrchestrator() {
|
|
|
6098
6181
|
return singleton;
|
|
6099
6182
|
}
|
|
6100
6183
|
|
|
6184
|
+
// src/http/fetch.ts
|
|
6185
|
+
var injected = null;
|
|
6186
|
+
function formatFetchError(err, url) {
|
|
6187
|
+
if (!(err instanceof Error)) return `${String(err)} (${url})`;
|
|
6188
|
+
const cause = err.cause;
|
|
6189
|
+
let detail = "";
|
|
6190
|
+
if (cause instanceof Error) {
|
|
6191
|
+
const code = typeof cause.code === "string" ? cause.code : void 0;
|
|
6192
|
+
detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
|
|
6193
|
+
} else if (cause != null) {
|
|
6194
|
+
detail = ` [${String(cause)}]`;
|
|
6195
|
+
}
|
|
6196
|
+
return `${err.message}${detail} (${url})`;
|
|
6197
|
+
}
|
|
6198
|
+
async function httpFetch(input, init) {
|
|
6199
|
+
const url = typeof input === "string" ? input : input.href;
|
|
6200
|
+
const fn = injected ?? globalThis.fetch.bind(globalThis);
|
|
6201
|
+
try {
|
|
6202
|
+
return await fn(url, init);
|
|
6203
|
+
} catch (err) {
|
|
6204
|
+
throw new Error(formatFetchError(err, url));
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6101
6208
|
// src/integrations/linear-oauth.ts
|
|
6102
6209
|
import { createHash, randomBytes } from "crypto";
|
|
6103
6210
|
import { createServer as createServer2 } from "http";
|
|
6104
6211
|
|
|
6105
6212
|
// src/integrations/linear-app.ts
|
|
6106
6213
|
var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
|
|
6107
|
-
var BAKED_LINEAR_CLIENT_SECRET = "";
|
|
6108
6214
|
|
|
6109
6215
|
// src/integrations/linear-oauth.ts
|
|
6110
6216
|
var LINEAR_OAUTH_PORT = 19848;
|
|
@@ -6114,7 +6220,7 @@ var REFRESH_SKEW_MS = 5 * 6e4;
|
|
|
6114
6220
|
function linearOAuthCredentials() {
|
|
6115
6221
|
const settings = loadAppSettings();
|
|
6116
6222
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
6117
|
-
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() ||
|
|
6223
|
+
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || "";
|
|
6118
6224
|
if (!clientId) {
|
|
6119
6225
|
throw new Error(
|
|
6120
6226
|
`Linear browser sign-in needs a Linear OAuth app Client ID. Create one at linear.app/settings/api/applications/new with callback ${LINEAR_OAUTH_REDIRECT}, then set SIDEBOARD_LINEAR_CLIENT_ID (PKCE does not require a secret). You can still paste a personal API key.`
|
|
@@ -6130,7 +6236,7 @@ function linearAuthorizationHeader(token) {
|
|
|
6130
6236
|
return `Bearer ${t}`;
|
|
6131
6237
|
}
|
|
6132
6238
|
async function exchangeLinearToken(body) {
|
|
6133
|
-
const res = await
|
|
6239
|
+
const res = await httpFetch(LINEAR_TOKEN, {
|
|
6134
6240
|
method: "POST",
|
|
6135
6241
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6136
6242
|
body
|
|
@@ -6197,6 +6303,18 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
6197
6303
|
|
|
6198
6304
|
// src/integrations/linear.ts
|
|
6199
6305
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
6306
|
+
var ISSUE_FIELDS = `
|
|
6307
|
+
id
|
|
6308
|
+
identifier
|
|
6309
|
+
title
|
|
6310
|
+
description
|
|
6311
|
+
url
|
|
6312
|
+
priority
|
|
6313
|
+
state { id name type }
|
|
6314
|
+
assignee { id name }
|
|
6315
|
+
team { id key name states { nodes { id name type } } }
|
|
6316
|
+
labels { nodes { name } }
|
|
6317
|
+
`;
|
|
6200
6318
|
var ASSIGNED_ISSUES_QUERY = `
|
|
6201
6319
|
query SideboardAssignedIssues($first: Int!) {
|
|
6202
6320
|
viewer {
|
|
@@ -6205,53 +6323,280 @@ query SideboardAssignedIssues($first: Int!) {
|
|
|
6205
6323
|
orderBy: updatedAt
|
|
6206
6324
|
filter: { state: { type: { nin: ["completed", "canceled"] } } }
|
|
6207
6325
|
) {
|
|
6208
|
-
nodes {
|
|
6209
|
-
id
|
|
6210
|
-
identifier
|
|
6211
|
-
title
|
|
6212
|
-
url
|
|
6213
|
-
labels { nodes { name } }
|
|
6214
|
-
}
|
|
6326
|
+
nodes { ${ISSUE_FIELDS} }
|
|
6215
6327
|
}
|
|
6216
6328
|
}
|
|
6217
6329
|
}
|
|
6218
6330
|
`;
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6331
|
+
var TEAMS_QUERY = `
|
|
6332
|
+
query SideboardTeams {
|
|
6333
|
+
viewer { id name }
|
|
6334
|
+
teams(first: 50) {
|
|
6335
|
+
nodes {
|
|
6336
|
+
id
|
|
6337
|
+
key
|
|
6338
|
+
name
|
|
6339
|
+
states { nodes { id name type } }
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
`;
|
|
6344
|
+
var ISSUE_QUERY = `
|
|
6345
|
+
query SideboardIssue($id: String!) {
|
|
6346
|
+
issue(id: $id) { ${ISSUE_FIELDS} }
|
|
6347
|
+
}
|
|
6348
|
+
`;
|
|
6349
|
+
var ISSUE_CREATE = `
|
|
6350
|
+
mutation SideboardIssueCreate($input: IssueCreateInput!) {
|
|
6351
|
+
issueCreate(input: $input) {
|
|
6352
|
+
success
|
|
6353
|
+
issue { ${ISSUE_FIELDS} }
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
`;
|
|
6357
|
+
var ISSUE_UPDATE = `
|
|
6358
|
+
mutation SideboardIssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
6359
|
+
issueUpdate(id: $id, input: $input) {
|
|
6360
|
+
success
|
|
6361
|
+
issue { ${ISSUE_FIELDS} }
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
`;
|
|
6365
|
+
var COMMENT_CREATE = `
|
|
6366
|
+
mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
6367
|
+
commentCreate(input: $input) {
|
|
6368
|
+
success
|
|
6369
|
+
comment { id body url }
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
`;
|
|
6373
|
+
function rewriteLinearError(message) {
|
|
6374
|
+
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
6375
|
+
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
6376
|
+
}
|
|
6377
|
+
return message;
|
|
6378
|
+
}
|
|
6379
|
+
async function requireLinearToken(apiKey) {
|
|
6380
|
+
const token = (apiKey ?? await getLinearAuthToken())?.trim();
|
|
6381
|
+
if (!token) {
|
|
6222
6382
|
throw new Error("Linear is not connected \u2014 sign in from Account settings");
|
|
6223
6383
|
}
|
|
6224
|
-
|
|
6225
|
-
|
|
6384
|
+
return token;
|
|
6385
|
+
}
|
|
6386
|
+
async function linearGraphql(query, variables, opts) {
|
|
6387
|
+
const apiKey = await requireLinearToken(opts?.apiKey);
|
|
6388
|
+
const res = await httpFetch(LINEAR_GRAPHQL, {
|
|
6226
6389
|
method: "POST",
|
|
6227
6390
|
headers: {
|
|
6228
6391
|
"Content-Type": "application/json",
|
|
6229
6392
|
Authorization: linearAuthorizationHeader(apiKey)
|
|
6230
6393
|
},
|
|
6231
|
-
body: JSON.stringify({
|
|
6232
|
-
query: ASSIGNED_ISSUES_QUERY,
|
|
6233
|
-
variables: { first }
|
|
6234
|
-
})
|
|
6394
|
+
body: JSON.stringify({ query, variables })
|
|
6235
6395
|
});
|
|
6236
6396
|
if (!res.ok) {
|
|
6237
6397
|
const body = await res.text().catch(() => "");
|
|
6238
6398
|
throw new Error(
|
|
6239
|
-
|
|
6399
|
+
rewriteLinearError(
|
|
6400
|
+
`Linear API error ${res.status}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
6401
|
+
)
|
|
6240
6402
|
);
|
|
6241
6403
|
}
|
|
6242
6404
|
const json = await res.json();
|
|
6243
6405
|
if (json.errors?.length) {
|
|
6244
|
-
throw new Error(
|
|
6406
|
+
throw new Error(
|
|
6407
|
+
rewriteLinearError(json.errors.map((e) => e.message ?? "Linear error").join("; "))
|
|
6408
|
+
);
|
|
6409
|
+
}
|
|
6410
|
+
if (json.data == null) {
|
|
6411
|
+
throw new Error("Linear API returned no data");
|
|
6245
6412
|
}
|
|
6246
|
-
|
|
6247
|
-
|
|
6413
|
+
return json.data;
|
|
6414
|
+
}
|
|
6415
|
+
function mapState(node) {
|
|
6416
|
+
if (!node?.id) return void 0;
|
|
6417
|
+
return {
|
|
6418
|
+
id: String(node.id),
|
|
6419
|
+
name: String(node.name ?? ""),
|
|
6420
|
+
type: String(node.type ?? "")
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
function mapIssue(node) {
|
|
6424
|
+
const team = node.team;
|
|
6425
|
+
return {
|
|
6248
6426
|
id: String(node.id ?? node.identifier ?? ""),
|
|
6249
6427
|
identifier: String(node.identifier ?? node.id ?? ""),
|
|
6250
6428
|
title: String(node.title ?? ""),
|
|
6251
6429
|
url: String(node.url ?? ""),
|
|
6252
|
-
|
|
6430
|
+
description: node.description?.trim() || void 0,
|
|
6431
|
+
priority: typeof node.priority === "number" ? node.priority : void 0,
|
|
6432
|
+
state: mapState(node.state),
|
|
6433
|
+
assignee: node.assignee?.id ? { id: String(node.assignee.id), name: String(node.assignee.name ?? "") } : void 0,
|
|
6434
|
+
team: team?.id ? {
|
|
6435
|
+
id: String(team.id),
|
|
6436
|
+
key: String(team.key ?? ""),
|
|
6437
|
+
name: String(team.name ?? ""),
|
|
6438
|
+
states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
6439
|
+
} : void 0,
|
|
6440
|
+
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
|
|
6441
|
+
};
|
|
6442
|
+
}
|
|
6443
|
+
function toIssueInfo(issue) {
|
|
6444
|
+
return {
|
|
6445
|
+
id: issue.id,
|
|
6446
|
+
identifier: issue.identifier,
|
|
6447
|
+
title: issue.title,
|
|
6448
|
+
url: issue.url,
|
|
6449
|
+
labels: issue.labels,
|
|
6253
6450
|
provider: "linear"
|
|
6254
|
-
}
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6453
|
+
function mapTeam(node) {
|
|
6454
|
+
return {
|
|
6455
|
+
id: String(node.id ?? ""),
|
|
6456
|
+
key: String(node.key ?? ""),
|
|
6457
|
+
name: String(node.name ?? ""),
|
|
6458
|
+
states: (node.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
6459
|
+
};
|
|
6460
|
+
}
|
|
6461
|
+
function resolveLinearTeam(teams, team) {
|
|
6462
|
+
const t = team.trim();
|
|
6463
|
+
if (!t) throw new Error("Linear team is required (id, key, or name from linear_list_teams)");
|
|
6464
|
+
const lower = t.toLowerCase();
|
|
6465
|
+
const found = teams.find(
|
|
6466
|
+
(x) => x.id === t || x.key.toLowerCase() === lower || x.name.toLowerCase() === lower
|
|
6467
|
+
);
|
|
6468
|
+
if (!found) {
|
|
6469
|
+
const keys = teams.map((x) => x.key).filter(Boolean).join(", ") || "(none)";
|
|
6470
|
+
throw new Error(`Linear team not found: ${team}. Known keys: ${keys}`);
|
|
6471
|
+
}
|
|
6472
|
+
return found;
|
|
6473
|
+
}
|
|
6474
|
+
function resolveLinearState(team, state) {
|
|
6475
|
+
const s = state.trim();
|
|
6476
|
+
if (!s) throw new Error("Linear state is empty");
|
|
6477
|
+
const lower = s.toLowerCase();
|
|
6478
|
+
const found = team.states.find((x) => x.id === s) || team.states.find((x) => x.name.toLowerCase() === lower) || team.states.find((x) => x.type.toLowerCase() === lower);
|
|
6479
|
+
if (!found) {
|
|
6480
|
+
const available = team.states.map((x) => `${x.name} (${x.type})`).join(", ") || "(none)";
|
|
6481
|
+
throw new Error(`Linear state not found on ${team.key}: ${state}. Available: ${available}`);
|
|
6482
|
+
}
|
|
6483
|
+
return found;
|
|
6484
|
+
}
|
|
6485
|
+
function normalizePriority(priority) {
|
|
6486
|
+
if (priority == null) return void 0;
|
|
6487
|
+
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
6488
|
+
throw new Error("Linear priority must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low)");
|
|
6489
|
+
}
|
|
6490
|
+
return priority;
|
|
6491
|
+
}
|
|
6492
|
+
async function listLinearIssuesDirect(opts) {
|
|
6493
|
+
const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
|
|
6494
|
+
const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
|
|
6495
|
+
return (json.viewer?.assignedIssues?.nodes ?? []).map((node) => toIssueInfo(mapIssue(node)));
|
|
6496
|
+
}
|
|
6497
|
+
async function listLinearTeams(opts) {
|
|
6498
|
+
const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
|
|
6499
|
+
return {
|
|
6500
|
+
viewer: {
|
|
6501
|
+
id: String(json.viewer?.id ?? ""),
|
|
6502
|
+
name: String(json.viewer?.name ?? "")
|
|
6503
|
+
},
|
|
6504
|
+
teams: (json.teams?.nodes ?? []).map(mapTeam)
|
|
6505
|
+
};
|
|
6506
|
+
}
|
|
6507
|
+
async function getLinearIssue(id, opts) {
|
|
6508
|
+
const issueId = id.trim();
|
|
6509
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
6510
|
+
const json = await linearGraphql(
|
|
6511
|
+
ISSUE_QUERY,
|
|
6512
|
+
{ id: issueId },
|
|
6513
|
+
opts
|
|
6514
|
+
);
|
|
6515
|
+
if (!json.issue) {
|
|
6516
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
6517
|
+
}
|
|
6518
|
+
return mapIssue(json.issue);
|
|
6519
|
+
}
|
|
6520
|
+
async function createLinearIssue(input, opts) {
|
|
6521
|
+
const title = input.title.trim();
|
|
6522
|
+
if (!title) throw new Error("Linear issue title is required");
|
|
6523
|
+
const { viewer, teams } = await listLinearTeams(opts);
|
|
6524
|
+
const team = resolveLinearTeam(teams, input.team);
|
|
6525
|
+
const mutationInput = {
|
|
6526
|
+
teamId: team.id,
|
|
6527
|
+
title
|
|
6528
|
+
};
|
|
6529
|
+
const description = input.description?.trim();
|
|
6530
|
+
if (description) mutationInput.description = description;
|
|
6531
|
+
if (input.state?.trim()) {
|
|
6532
|
+
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
6533
|
+
}
|
|
6534
|
+
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
6535
|
+
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
6536
|
+
else if (assignee) mutationInput.assigneeId = assignee;
|
|
6537
|
+
const priority = normalizePriority(input.priority);
|
|
6538
|
+
if (priority != null) mutationInput.priority = priority;
|
|
6539
|
+
const json = await linearGraphql(ISSUE_CREATE, { input: mutationInput }, opts);
|
|
6540
|
+
if (!json.issueCreate?.success || !json.issueCreate.issue) {
|
|
6541
|
+
throw new Error("Linear issueCreate failed");
|
|
6542
|
+
}
|
|
6543
|
+
return mapIssue(json.issueCreate.issue);
|
|
6544
|
+
}
|
|
6545
|
+
async function updateLinearIssue(input, opts) {
|
|
6546
|
+
const issueId = input.id.trim();
|
|
6547
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
6548
|
+
const mutationInput = {};
|
|
6549
|
+
if (input.title !== void 0) {
|
|
6550
|
+
const title = input.title.trim();
|
|
6551
|
+
if (!title) throw new Error("Linear issue title cannot be empty");
|
|
6552
|
+
mutationInput.title = title;
|
|
6553
|
+
}
|
|
6554
|
+
if (input.description !== void 0) {
|
|
6555
|
+
mutationInput.description = input.description;
|
|
6556
|
+
}
|
|
6557
|
+
if (input.state?.trim()) {
|
|
6558
|
+
const existing = await getLinearIssue(issueId, opts);
|
|
6559
|
+
const team = existing.team;
|
|
6560
|
+
if (!team?.states.length) {
|
|
6561
|
+
throw new Error(`Linear issue ${existing.identifier} has no workflow states to resolve "${input.state}"`);
|
|
6562
|
+
}
|
|
6563
|
+
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
6564
|
+
}
|
|
6565
|
+
if (input.assignee !== void 0) {
|
|
6566
|
+
const assignee = input.assignee?.trim() || null;
|
|
6567
|
+
if (assignee === "me") {
|
|
6568
|
+
const { viewer } = await listLinearTeams(opts);
|
|
6569
|
+
mutationInput.assigneeId = viewer.id;
|
|
6570
|
+
} else {
|
|
6571
|
+
mutationInput.assigneeId = assignee;
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
if (input.priority !== void 0) {
|
|
6575
|
+
mutationInput.priority = normalizePriority(input.priority);
|
|
6576
|
+
}
|
|
6577
|
+
if (Object.keys(mutationInput).length === 0) {
|
|
6578
|
+
throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority");
|
|
6579
|
+
}
|
|
6580
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
6581
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
6582
|
+
throw new Error("Linear issueUpdate failed");
|
|
6583
|
+
}
|
|
6584
|
+
return mapIssue(json.issueUpdate.issue);
|
|
6585
|
+
}
|
|
6586
|
+
async function commentLinearIssue(input, opts) {
|
|
6587
|
+
const issueId = input.id.trim();
|
|
6588
|
+
const body = input.body.trim();
|
|
6589
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
6590
|
+
if (!body) throw new Error("Linear comment body is required");
|
|
6591
|
+
const json = await linearGraphql(COMMENT_CREATE, { input: { issueId, body } }, opts);
|
|
6592
|
+
if (!json.commentCreate?.success || !json.commentCreate.comment?.id) {
|
|
6593
|
+
throw new Error("Linear commentCreate failed");
|
|
6594
|
+
}
|
|
6595
|
+
return {
|
|
6596
|
+
id: String(json.commentCreate.comment.id),
|
|
6597
|
+
body: String(json.commentCreate.comment.body ?? body),
|
|
6598
|
+
url: json.commentCreate.comment.url?.trim() || void 0
|
|
6599
|
+
};
|
|
6255
6600
|
}
|
|
6256
6601
|
|
|
6257
6602
|
// src/integrations/issues.ts
|
|
@@ -6533,8 +6878,8 @@ function labelGithubUrl(url) {
|
|
|
6533
6878
|
}
|
|
6534
6879
|
return { kind: "other", label: "GitHub", url: raw };
|
|
6535
6880
|
}
|
|
6536
|
-
function appendGithubLink(
|
|
6537
|
-
const body =
|
|
6881
|
+
function appendGithubLink(text3, githubUrl) {
|
|
6882
|
+
const body = text3.trimEnd();
|
|
6538
6883
|
if (!githubUrl?.trim()) return body;
|
|
6539
6884
|
const labeled = labelGithubUrl(githubUrl);
|
|
6540
6885
|
if (!labeled) {
|
|
@@ -6817,6 +7162,101 @@ function registerSlackTools(server) {
|
|
|
6817
7162
|
);
|
|
6818
7163
|
}
|
|
6819
7164
|
|
|
7165
|
+
// src/mcp/linear-tools.ts
|
|
7166
|
+
import { z as z2 } from "zod";
|
|
7167
|
+
function text2(payload, isError = false) {
|
|
7168
|
+
return {
|
|
7169
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
7170
|
+
...isError ? { isError: true } : {}
|
|
7171
|
+
};
|
|
7172
|
+
}
|
|
7173
|
+
function fail2(err) {
|
|
7174
|
+
return text2(
|
|
7175
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
7176
|
+
true
|
|
7177
|
+
);
|
|
7178
|
+
}
|
|
7179
|
+
var prioritySchema = z2.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
7180
|
+
function registerLinearTools(server) {
|
|
7181
|
+
server.tool(
|
|
7182
|
+
"linear_list_teams",
|
|
7183
|
+
"List Linear teams (id, key, name) and workflow states for the connected Account. Use team key (e.g. ENG) on linear_create_issue; use state name or type (started, completed) on create/update. Viewer id is for assignee=me.",
|
|
7184
|
+
{},
|
|
7185
|
+
async () => {
|
|
7186
|
+
try {
|
|
7187
|
+
return text2(await listLinearTeams());
|
|
7188
|
+
} catch (err) {
|
|
7189
|
+
return fail2(err);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
);
|
|
7193
|
+
server.tool(
|
|
7194
|
+
"linear_get_issue",
|
|
7195
|
+
"Get a Linear issue by uuid or identifier (ENG-123).",
|
|
7196
|
+
{ id: z2.string() },
|
|
7197
|
+
async ({ id }) => {
|
|
7198
|
+
try {
|
|
7199
|
+
return text2(await getLinearIssue(id));
|
|
7200
|
+
} catch (err) {
|
|
7201
|
+
return fail2(err);
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
7204
|
+
);
|
|
7205
|
+
server.tool(
|
|
7206
|
+
"linear_create_issue",
|
|
7207
|
+
'Create a Linear issue. Call linear_list_teams first. team is id, key, or name. state is name, type (unstarted/started/completed/canceled/backlog), or id. assignee is "me" or a user id.',
|
|
7208
|
+
{
|
|
7209
|
+
team: z2.string(),
|
|
7210
|
+
title: z2.string(),
|
|
7211
|
+
description: z2.string().optional(),
|
|
7212
|
+
state: z2.string().optional(),
|
|
7213
|
+
assignee: z2.string().nullable().optional(),
|
|
7214
|
+
priority: prioritySchema
|
|
7215
|
+
},
|
|
7216
|
+
async (args) => {
|
|
7217
|
+
try {
|
|
7218
|
+
return text2(await createLinearIssue(args));
|
|
7219
|
+
} catch (err) {
|
|
7220
|
+
return fail2(err);
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
);
|
|
7224
|
+
server.tool(
|
|
7225
|
+
"linear_update_issue",
|
|
7226
|
+
'Update a Linear issue (uuid or ENG-123). Pass at least one of title, description, state, assignee, priority. state is name, type, or id. assignee is "me", a user id, or null to unassign.',
|
|
7227
|
+
{
|
|
7228
|
+
id: z2.string(),
|
|
7229
|
+
title: z2.string().optional(),
|
|
7230
|
+
description: z2.string().optional(),
|
|
7231
|
+
state: z2.string().optional(),
|
|
7232
|
+
assignee: z2.string().nullable().optional(),
|
|
7233
|
+
priority: prioritySchema
|
|
7234
|
+
},
|
|
7235
|
+
async (args) => {
|
|
7236
|
+
try {
|
|
7237
|
+
return text2(await updateLinearIssue(args));
|
|
7238
|
+
} catch (err) {
|
|
7239
|
+
return fail2(err);
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
);
|
|
7243
|
+
server.tool(
|
|
7244
|
+
"linear_comment",
|
|
7245
|
+
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
7246
|
+
{
|
|
7247
|
+
id: z2.string(),
|
|
7248
|
+
body: z2.string()
|
|
7249
|
+
},
|
|
7250
|
+
async (args) => {
|
|
7251
|
+
try {
|
|
7252
|
+
return text2(await commentLinearIssue(args));
|
|
7253
|
+
} catch (err) {
|
|
7254
|
+
return fail2(err);
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
);
|
|
7258
|
+
}
|
|
7259
|
+
|
|
6820
7260
|
// src/mcp/server.ts
|
|
6821
7261
|
var MAX_ORCH_THREADS = 5;
|
|
6822
7262
|
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
@@ -6893,7 +7333,7 @@ async function startMcpServer() {
|
|
|
6893
7333
|
server.tool(
|
|
6894
7334
|
"get_thread",
|
|
6895
7335
|
"Get a compact thread summary by id/ref",
|
|
6896
|
-
{ ref:
|
|
7336
|
+
{ ref: z3.string() },
|
|
6897
7337
|
async ({ ref }) => {
|
|
6898
7338
|
const t = orch.getThread(ref);
|
|
6899
7339
|
if (!t) {
|
|
@@ -6922,14 +7362,14 @@ async function startMcpServer() {
|
|
|
6922
7362
|
"present_artifact",
|
|
6923
7363
|
"Show an HTML, SVG, markdown, or React document in Sideboard\u2019s Claude-style side column (desktop). Use instead of claude.ai\u2019s artifact tool \u2014 pass the full document content. Prefer type=html for interactive pages. For type=react, pass a single component module that `export default`s a component (JSX/TSX ok) \u2014 Sideboard bootstraps React/ReactDOM/Babel and renders it; only `react`/`react-dom` imports are available, no other npm packages.",
|
|
6924
7364
|
{
|
|
6925
|
-
title:
|
|
6926
|
-
type:
|
|
7365
|
+
title: z3.string().describe("Short title shown in the artifact pane header"),
|
|
7366
|
+
type: z3.enum(["html", "svg", "markdown", "react"]).describe(
|
|
6927
7367
|
"Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
|
|
6928
7368
|
),
|
|
6929
|
-
content:
|
|
7369
|
+
content: z3.string().describe(
|
|
6930
7370
|
"Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
|
|
6931
7371
|
),
|
|
6932
|
-
artifact_id:
|
|
7372
|
+
artifact_id: z3.string().optional().describe("Stable id when updating the same artifact across turns")
|
|
6933
7373
|
},
|
|
6934
7374
|
async ({ title, type, artifact_id }) => {
|
|
6935
7375
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -6947,15 +7387,15 @@ async function startMcpServer() {
|
|
|
6947
7387
|
"ask_user",
|
|
6948
7388
|
"Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
|
|
6949
7389
|
{
|
|
6950
|
-
questions:
|
|
6951
|
-
|
|
6952
|
-
question:
|
|
6953
|
-
header:
|
|
6954
|
-
multiSelect:
|
|
6955
|
-
options:
|
|
6956
|
-
|
|
6957
|
-
label:
|
|
6958
|
-
description:
|
|
7390
|
+
questions: z3.array(
|
|
7391
|
+
z3.object({
|
|
7392
|
+
question: z3.string().describe("Full question text ending with ?"),
|
|
7393
|
+
header: z3.string().max(24).optional().describe("Short label shown above the question"),
|
|
7394
|
+
multiSelect: z3.boolean().optional().describe("Allow selecting multiple options"),
|
|
7395
|
+
options: z3.array(
|
|
7396
|
+
z3.object({
|
|
7397
|
+
label: z3.string(),
|
|
7398
|
+
description: z3.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
6959
7399
|
})
|
|
6960
7400
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
6961
7401
|
})
|
|
@@ -6974,9 +7414,9 @@ async function startMcpServer() {
|
|
|
6974
7414
|
"present_plan",
|
|
6975
7415
|
"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.",
|
|
6976
7416
|
{
|
|
6977
|
-
title:
|
|
6978
|
-
content:
|
|
6979
|
-
thread_id:
|
|
7417
|
+
title: z3.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
7418
|
+
content: z3.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
7419
|
+
thread_id: z3.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
6980
7420
|
},
|
|
6981
7421
|
async ({ title, content, thread_id }) => {
|
|
6982
7422
|
const { writePlanFile: writePlanFile2 } = await import("../plan-file-PHVKUAEE.js");
|
|
@@ -6999,15 +7439,15 @@ async function startMcpServer() {
|
|
|
6999
7439
|
"present_schema",
|
|
7000
7440
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form). 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.",
|
|
7001
7441
|
{
|
|
7002
|
-
title:
|
|
7003
|
-
mode:
|
|
7004
|
-
datasource:
|
|
7005
|
-
resource_id:
|
|
7006
|
-
record_id:
|
|
7007
|
-
resource:
|
|
7008
|
-
record:
|
|
7009
|
-
records:
|
|
7010
|
-
pane_id:
|
|
7442
|
+
title: z3.string().describe("Pane title"),
|
|
7443
|
+
mode: z3.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
7444
|
+
datasource: z3.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
7445
|
+
resource_id: z3.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
7446
|
+
record_id: z3.string().optional().describe("Record id when opening form mode"),
|
|
7447
|
+
resource: z3.record(z3.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
7448
|
+
record: z3.record(z3.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
7449
|
+
records: z3.array(z3.record(z3.unknown())).optional().describe("Inline records for table mode"),
|
|
7450
|
+
pane_id: z3.string().optional().describe("Stable pane id across updates")
|
|
7011
7451
|
},
|
|
7012
7452
|
async (args) => {
|
|
7013
7453
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7028,10 +7468,10 @@ async function startMcpServer() {
|
|
|
7028
7468
|
"present_files",
|
|
7029
7469
|
"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.",
|
|
7030
7470
|
{
|
|
7031
|
-
title:
|
|
7032
|
-
datasource:
|
|
7033
|
-
path:
|
|
7034
|
-
pane_id:
|
|
7471
|
+
title: z3.string().optional().describe("Pane title (default: Files)"),
|
|
7472
|
+
datasource: z3.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
7473
|
+
path: z3.string().optional().describe("Initial folder path (e.g. public)"),
|
|
7474
|
+
pane_id: z3.string().optional().describe("Stable pane id across updates")
|
|
7035
7475
|
},
|
|
7036
7476
|
async (args) => {
|
|
7037
7477
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7047,19 +7487,21 @@ async function startMcpServer() {
|
|
|
7047
7487
|
}
|
|
7048
7488
|
);
|
|
7049
7489
|
registerSlackTools(server);
|
|
7490
|
+
registerLinearTools(server);
|
|
7050
7491
|
if (sideboardMcpProfile() !== "worktree") {
|
|
7051
|
-
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-
|
|
7492
|
+
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-OHAUWOA6.js");
|
|
7052
7493
|
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults } = await import("../app-settings-P42Z3VOB.js");
|
|
7053
7494
|
const accountDefaults = resolveThreadDefaults();
|
|
7054
7495
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
7055
7496
|
server.tool(
|
|
7056
7497
|
"set_caffeinate",
|
|
7057
|
-
"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. macOS only.",
|
|
7498
|
+
"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.",
|
|
7058
7499
|
{
|
|
7059
|
-
enabled:
|
|
7500
|
+
enabled: z3.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
7060
7501
|
},
|
|
7061
7502
|
async ({ enabled }) => {
|
|
7062
|
-
const
|
|
7503
|
+
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
7504
|
+
const state = setCaffeinateHold(enabled, { threadId });
|
|
7063
7505
|
if (enabled && !state.held) {
|
|
7064
7506
|
return {
|
|
7065
7507
|
content: [
|
|
@@ -7082,7 +7524,7 @@ async function startMcpServer() {
|
|
|
7082
7524
|
text: JSON.stringify({
|
|
7083
7525
|
...getCaffeinateHold(),
|
|
7084
7526
|
ok: true,
|
|
7085
|
-
message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false
|
|
7527
|
+
message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false, the user says they are done, or this orchestration chat is closed." : "Caffeinate hold released. The Mac can sleep (unless Settings caffeinate toggles are on)."
|
|
7086
7528
|
})
|
|
7087
7529
|
}
|
|
7088
7530
|
]
|
|
@@ -7093,15 +7535,15 @@ async function startMcpServer() {
|
|
|
7093
7535
|
"create_thread",
|
|
7094
7536
|
`Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. 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}. Then use send_to_thread to chat.`,
|
|
7095
7537
|
{
|
|
7096
|
-
sourceType:
|
|
7097
|
-
sourceRef:
|
|
7098
|
-
agent:
|
|
7099
|
-
model:
|
|
7538
|
+
sourceType: z3.enum(["branch", "pr", "ticket"]),
|
|
7539
|
+
sourceRef: z3.string(),
|
|
7540
|
+
agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
7541
|
+
model: z3.string().nullable().optional().describe(
|
|
7100
7542
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
7101
7543
|
),
|
|
7102
|
-
repoPath:
|
|
7103
|
-
title:
|
|
7104
|
-
parentThreadId:
|
|
7544
|
+
repoPath: z3.string(),
|
|
7545
|
+
title: z3.string().optional(),
|
|
7546
|
+
parentThreadId: z3.string().optional().describe(
|
|
7105
7547
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
7106
7548
|
)
|
|
7107
7549
|
},
|
|
@@ -7206,11 +7648,11 @@ async function startMcpServer() {
|
|
|
7206
7648
|
);
|
|
7207
7649
|
server.tool(
|
|
7208
7650
|
"send_to_thread",
|
|
7209
|
-
"Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
|
|
7651
|
+
"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/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
|
|
7210
7652
|
{
|
|
7211
|
-
ref:
|
|
7212
|
-
prompt:
|
|
7213
|
-
force_stop:
|
|
7653
|
+
ref: z3.string(),
|
|
7654
|
+
prompt: z3.string(),
|
|
7655
|
+
force_stop: z3.boolean().optional()
|
|
7214
7656
|
},
|
|
7215
7657
|
async ({ ref, prompt, force_stop }) => {
|
|
7216
7658
|
if (force_stop) {
|
|
@@ -7237,10 +7679,10 @@ async function startMcpServer() {
|
|
|
7237
7679
|
);
|
|
7238
7680
|
server.tool(
|
|
7239
7681
|
"wait_for_turn",
|
|
7240
|
-
"Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
|
|
7682
|
+
"Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
|
|
7241
7683
|
{
|
|
7242
|
-
ref:
|
|
7243
|
-
timeoutMs:
|
|
7684
|
+
ref: z3.string(),
|
|
7685
|
+
timeoutMs: z3.number().optional()
|
|
7244
7686
|
},
|
|
7245
7687
|
async ({ ref, timeoutMs }) => {
|
|
7246
7688
|
const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
|
|
@@ -7262,7 +7704,7 @@ async function startMcpServer() {
|
|
|
7262
7704
|
server.tool(
|
|
7263
7705
|
"get_turn_result",
|
|
7264
7706
|
"Final assistant message only (not full transcript)",
|
|
7265
|
-
{ ref:
|
|
7707
|
+
{ ref: z3.string() },
|
|
7266
7708
|
async ({ ref }) => {
|
|
7267
7709
|
const result = orch.getTurnResult(ref);
|
|
7268
7710
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
@@ -7272,8 +7714,8 @@ async function startMcpServer() {
|
|
|
7272
7714
|
"stop_thread",
|
|
7273
7715
|
"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.",
|
|
7274
7716
|
{
|
|
7275
|
-
ref:
|
|
7276
|
-
force:
|
|
7717
|
+
ref: z3.string(),
|
|
7718
|
+
force: z3.boolean().optional()
|
|
7277
7719
|
},
|
|
7278
7720
|
async ({ ref, force }) => {
|
|
7279
7721
|
const t = orch.getThread(ref);
|
|
@@ -7302,8 +7744,8 @@ async function startMcpServer() {
|
|
|
7302
7744
|
);
|
|
7303
7745
|
server.tool(
|
|
7304
7746
|
"archive_thread",
|
|
7305
|
-
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators open PRs only by asking the worktree agent.",
|
|
7306
|
-
{ ref:
|
|
7747
|
+
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
|
|
7748
|
+
{ ref: z3.string() },
|
|
7307
7749
|
async ({ ref }) => {
|
|
7308
7750
|
const t = orch.getThread(ref);
|
|
7309
7751
|
if (!t) {
|
|
@@ -7336,7 +7778,7 @@ async function startMcpServer() {
|
|
|
7336
7778
|
server.tool(
|
|
7337
7779
|
"restore_thread",
|
|
7338
7780
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
7339
|
-
{ ref:
|
|
7781
|
+
{ ref: z3.string() },
|
|
7340
7782
|
async ({ ref }) => {
|
|
7341
7783
|
try {
|
|
7342
7784
|
const restored = await orch.restore(ref);
|
|
@@ -7362,8 +7804,8 @@ async function startMcpServer() {
|
|
|
7362
7804
|
"get_diff",
|
|
7363
7805
|
"Compact diff summary (capped hunks, paginated)",
|
|
7364
7806
|
{
|
|
7365
|
-
ref:
|
|
7366
|
-
maxFiles:
|
|
7807
|
+
ref: z3.string(),
|
|
7808
|
+
maxFiles: z3.number().optional()
|
|
7367
7809
|
},
|
|
7368
7810
|
async ({ ref, maxFiles }) => {
|
|
7369
7811
|
const summary = await orch.diffSummary(ref);
|
|
@@ -7383,7 +7825,7 @@ async function startMcpServer() {
|
|
|
7383
7825
|
server.tool(
|
|
7384
7826
|
"request_review",
|
|
7385
7827
|
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / 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 / get_turn_result on the returned review tab id.',
|
|
7386
|
-
{ ref:
|
|
7828
|
+
{ ref: z3.string().describe("Worktree thread id/ref to review") },
|
|
7387
7829
|
async ({ ref }) => {
|
|
7388
7830
|
try {
|
|
7389
7831
|
const tab = await orch.requestReview(ref);
|
|
@@ -7408,7 +7850,39 @@ async function startMcpServer() {
|
|
|
7408
7850
|
}
|
|
7409
7851
|
}
|
|
7410
7852
|
);
|
|
7411
|
-
|
|
7853
|
+
server.tool(
|
|
7854
|
+
"ask_git",
|
|
7855
|
+
"Tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge the linked PR \u2014 same short prompts as the desktop git buttons. The worktree agent runs git/gh (`gh pr merge`); this only queues the prompt. Pass a worktree thread ref (not the orchestrator). Then wait_for_turn / get_turn_result. Do not run git or gh from the orchestration cwd.",
|
|
7856
|
+
{
|
|
7857
|
+
ref: z3.string().describe("Worktree thread id/ref"),
|
|
7858
|
+
action: z3.enum(AGENT_GIT_ACTIONS).describe(
|
|
7859
|
+
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
7860
|
+
)
|
|
7861
|
+
},
|
|
7862
|
+
async ({ ref, action }) => {
|
|
7863
|
+
try {
|
|
7864
|
+
const thread = await orch.askGit(ref, action);
|
|
7865
|
+
return {
|
|
7866
|
+
content: [
|
|
7867
|
+
{
|
|
7868
|
+
type: "text",
|
|
7869
|
+
text: JSON.stringify({
|
|
7870
|
+
id: thread.id,
|
|
7871
|
+
status: thread.status,
|
|
7872
|
+
queueLength: thread.queue.length,
|
|
7873
|
+
action,
|
|
7874
|
+
link: `sideboard://thread/${thread.id}`
|
|
7875
|
+
})
|
|
7876
|
+
}
|
|
7877
|
+
]
|
|
7878
|
+
};
|
|
7879
|
+
} catch (err) {
|
|
7880
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7881
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
7882
|
+
}
|
|
7883
|
+
}
|
|
7884
|
+
);
|
|
7885
|
+
const agentEnum = z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
7412
7886
|
server.tool(
|
|
7413
7887
|
"list_models",
|
|
7414
7888
|
"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.",
|
|
@@ -7431,11 +7905,11 @@ async function startMcpServer() {
|
|
|
7431
7905
|
"fork_worktree",
|
|
7432
7906
|
"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 on the returned id.",
|
|
7433
7907
|
{
|
|
7434
|
-
ref:
|
|
7435
|
-
through_index:
|
|
7908
|
+
ref: z3.string().describe("Worktree thread id/ref to fork"),
|
|
7909
|
+
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
7436
7910
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
7437
|
-
model:
|
|
7438
|
-
title:
|
|
7911
|
+
model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
7912
|
+
title: z3.string().optional()
|
|
7439
7913
|
},
|
|
7440
7914
|
async ({ ref, through_index, agent, model, title }) => {
|
|
7441
7915
|
try {
|
|
@@ -7476,11 +7950,11 @@ async function startMcpServer() {
|
|
|
7476
7950
|
"fork_chat",
|
|
7477
7951
|
"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 on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
7478
7952
|
{
|
|
7479
|
-
ref:
|
|
7480
|
-
through_index:
|
|
7953
|
+
ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
7954
|
+
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
7481
7955
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
7482
|
-
model:
|
|
7483
|
-
title:
|
|
7956
|
+
model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
7957
|
+
title: z3.string().optional()
|
|
7484
7958
|
},
|
|
7485
7959
|
async ({ ref, through_index, agent, model, title }) => {
|
|
7486
7960
|
try {
|
|
@@ -7526,8 +8000,8 @@ async function startMcpServer() {
|
|
|
7526
8000
|
"run_dev_script",
|
|
7527
8001
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
7528
8002
|
{
|
|
7529
|
-
ref:
|
|
7530
|
-
name:
|
|
8003
|
+
ref: z3.string(),
|
|
8004
|
+
name: z3.string().optional()
|
|
7531
8005
|
},
|
|
7532
8006
|
async ({ ref, name }) => {
|
|
7533
8007
|
const result = await orch.startDev(ref, name);
|
|
@@ -7549,7 +8023,7 @@ async function startMcpServer() {
|
|
|
7549
8023
|
server.tool(
|
|
7550
8024
|
"list_run_scripts",
|
|
7551
8025
|
"List named run scripts available for a thread",
|
|
7552
|
-
{ ref:
|
|
8026
|
+
{ ref: z3.string() },
|
|
7553
8027
|
async ({ ref }) => {
|
|
7554
8028
|
const scripts = orch.listThreadRunScripts(ref);
|
|
7555
8029
|
const active = orch.getActiveRuns(ref);
|
|
@@ -7567,8 +8041,8 @@ async function startMcpServer() {
|
|
|
7567
8041
|
"stop_dev_script",
|
|
7568
8042
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
7569
8043
|
{
|
|
7570
|
-
ref:
|
|
7571
|
-
name:
|
|
8044
|
+
ref: z3.string(),
|
|
8045
|
+
name: z3.string().optional()
|
|
7572
8046
|
},
|
|
7573
8047
|
async ({ ref, name }) => {
|
|
7574
8048
|
orch.stopDev(ref, name);
|
|
@@ -7578,7 +8052,7 @@ async function startMcpServer() {
|
|
|
7578
8052
|
server.tool(
|
|
7579
8053
|
"run_setup",
|
|
7580
8054
|
"Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
|
|
7581
|
-
{ ref:
|
|
8055
|
+
{ ref: z3.string() },
|
|
7582
8056
|
async ({ ref }) => {
|
|
7583
8057
|
const result = await orch.runSetup(ref);
|
|
7584
8058
|
return {
|
|
@@ -7589,7 +8063,7 @@ async function startMcpServer() {
|
|
|
7589
8063
|
server.tool(
|
|
7590
8064
|
"add_workspace",
|
|
7591
8065
|
"Register a git repo as a Sideboard workspace",
|
|
7592
|
-
{ repoPath:
|
|
8066
|
+
{ repoPath: z3.string() },
|
|
7593
8067
|
async ({ repoPath }) => {
|
|
7594
8068
|
const ws = await orch.addWorkspace(repoPath);
|
|
7595
8069
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -7598,7 +8072,7 @@ async function startMcpServer() {
|
|
|
7598
8072
|
server.tool(
|
|
7599
8073
|
"remove_workspace",
|
|
7600
8074
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
7601
|
-
{ repoPath:
|
|
8075
|
+
{ repoPath: z3.string() },
|
|
7602
8076
|
async ({ repoPath }) => {
|
|
7603
8077
|
orch.removeWorkspace(repoPath);
|
|
7604
8078
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -7608,12 +8082,12 @@ async function startMcpServer() {
|
|
|
7608
8082
|
"fanout",
|
|
7609
8083
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
7610
8084
|
{
|
|
7611
|
-
prompt:
|
|
7612
|
-
agents:
|
|
7613
|
-
repoPath:
|
|
7614
|
-
sourceType:
|
|
7615
|
-
sourceRef:
|
|
7616
|
-
title:
|
|
8085
|
+
prompt: z3.string(),
|
|
8086
|
+
agents: z3.array(z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
8087
|
+
repoPath: z3.string(),
|
|
8088
|
+
sourceType: z3.enum(["branch", "pr", "ticket"]).optional(),
|
|
8089
|
+
sourceRef: z3.string().optional(),
|
|
8090
|
+
title: z3.string().optional()
|
|
7617
8091
|
},
|
|
7618
8092
|
async (args) => {
|
|
7619
8093
|
const threads = await orch.bestOfN(args);
|
|
@@ -7640,8 +8114,8 @@ async function startMcpServer() {
|
|
|
7640
8114
|
"list_branches",
|
|
7641
8115
|
"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).",
|
|
7642
8116
|
{
|
|
7643
|
-
repoPath:
|
|
7644
|
-
unmergedOnly:
|
|
8117
|
+
repoPath: z3.string(),
|
|
8118
|
+
unmergedOnly: z3.boolean().optional()
|
|
7645
8119
|
},
|
|
7646
8120
|
async ({ repoPath, unmergedOnly }) => {
|
|
7647
8121
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -7661,7 +8135,7 @@ async function startMcpServer() {
|
|
|
7661
8135
|
server.tool(
|
|
7662
8136
|
"list_prs",
|
|
7663
8137
|
"List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
|
|
7664
|
-
{ repoPath:
|
|
8138
|
+
{ repoPath: z3.string() },
|
|
7665
8139
|
async ({ repoPath }) => {
|
|
7666
8140
|
const root = await resolveRepoRoot(repoPath);
|
|
7667
8141
|
const prs = await listPrs(root);
|
|
@@ -7679,8 +8153,8 @@ async function startMcpServer() {
|
|
|
7679
8153
|
);
|
|
7680
8154
|
server.tool(
|
|
7681
8155
|
"get_pr_stack",
|
|
7682
|
-
"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
|
|
7683
|
-
{ ref:
|
|
8156
|
+
"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.",
|
|
8157
|
+
{ ref: z3.string() },
|
|
7684
8158
|
async ({ ref }) => {
|
|
7685
8159
|
const stack = await orch.getPrStack(ref);
|
|
7686
8160
|
return {
|
|
@@ -7692,8 +8166,8 @@ async function startMcpServer() {
|
|
|
7692
8166
|
"open_pr_stack_layers",
|
|
7693
8167
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
7694
8168
|
{
|
|
7695
|
-
ref:
|
|
7696
|
-
layer:
|
|
8169
|
+
ref: z3.string(),
|
|
8170
|
+
layer: z3.number().int().positive().optional()
|
|
7697
8171
|
},
|
|
7698
8172
|
async ({ ref, layer }) => {
|
|
7699
8173
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -7727,9 +8201,9 @@ async function startMcpServer() {
|
|
|
7727
8201
|
"add_stack_layer",
|
|
7728
8202
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
7729
8203
|
{
|
|
7730
|
-
ref:
|
|
7731
|
-
branchName:
|
|
7732
|
-
title:
|
|
8204
|
+
ref: z3.string(),
|
|
8205
|
+
branchName: z3.string(),
|
|
8206
|
+
title: z3.string().optional()
|
|
7733
8207
|
},
|
|
7734
8208
|
async ({ ref, branchName, title }) => {
|
|
7735
8209
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -7758,11 +8232,11 @@ async function startMcpServer() {
|
|
|
7758
8232
|
"create_pr_stack",
|
|
7759
8233
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
7760
8234
|
{
|
|
7761
|
-
repoPath:
|
|
7762
|
-
branches:
|
|
7763
|
-
agent:
|
|
7764
|
-
base:
|
|
7765
|
-
title:
|
|
8235
|
+
repoPath: z3.string(),
|
|
8236
|
+
branches: z3.array(z3.string()).min(1),
|
|
8237
|
+
agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
8238
|
+
base: z3.string().optional(),
|
|
8239
|
+
title: z3.string().optional()
|
|
7766
8240
|
},
|
|
7767
8241
|
async (args) => {
|
|
7768
8242
|
const result = await orch.createPrStack({
|
|
@@ -7800,7 +8274,7 @@ async function startMcpServer() {
|
|
|
7800
8274
|
server.tool(
|
|
7801
8275
|
"list_issues",
|
|
7802
8276
|
"List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
|
|
7803
|
-
{ repoPath:
|
|
8277
|
+
{ repoPath: z3.string() },
|
|
7804
8278
|
async ({ repoPath }) => {
|
|
7805
8279
|
const root = await resolveRepoRoot(repoPath);
|
|
7806
8280
|
const result = await listIssues(root);
|
|
@@ -7811,8 +8285,8 @@ async function startMcpServer() {
|
|
|
7811
8285
|
"list_linear_issues",
|
|
7812
8286
|
"Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
|
|
7813
8287
|
{
|
|
7814
|
-
agent:
|
|
7815
|
-
repoPath:
|
|
8288
|
+
agent: z3.enum(["claude", "codex", "opencode"]),
|
|
8289
|
+
repoPath: z3.string()
|
|
7816
8290
|
},
|
|
7817
8291
|
async ({ agent, repoPath }) => {
|
|
7818
8292
|
const issues = await listLinearIssues(agent, repoPath);
|