@sideboard-ai/core 0.1.159 → 0.1.161
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-LK7GV556.js → abletime-AGF54TNA.js} +2 -2
- package/dist/{abletime-HOQBDQC4.js → abletime-BMS53RIZ.js} +2 -2
- package/dist/{agents-67ZBH5OE.js → agents-6UOXBORU.js} +4 -4
- package/dist/{agents-5HY76BCR.js → agents-RD774UEV.js} +4 -4
- package/dist/{app-settings-BJ5GFAE5.js → app-settings-BRLZQA7N.js} +1 -1
- package/dist/{app-settings-YWJ4MVYM.js → app-settings-JJQDWPEK.js} +1 -1
- package/dist/{chunk-DWUOKF3W.js → chunk-7OWRFJK6.js} +4 -4
- package/dist/{chunk-FLG4A5FJ.js → chunk-CVZ4KXVP.js} +8 -3
- package/dist/{chunk-TGU3LSDG.js → chunk-EZANYS5D.js} +22 -22
- package/dist/{chunk-SIMFKJIB.js → chunk-HZROPNYL.js} +2 -2
- package/dist/{chunk-AGLPYXDT.js → chunk-ILXKJ4ZD.js} +1 -1
- package/dist/{chunk-MEZXWELT.js → chunk-IO5WGZRR.js} +1 -1
- package/dist/{chunk-UBMCEZRE.js → chunk-KJFFQAMA.js} +1 -1
- package/dist/{chunk-EQIJ5MHR.js → chunk-LTXS26K5.js} +8 -3
- package/dist/{chunk-OYOXLY6B.js → chunk-MIX4R72K.js} +23 -23
- package/dist/{chunk-O7RWWPMR.js → chunk-QANLIT2E.js} +2 -2
- package/dist/{chunk-V4R6L2ZL.js → chunk-RQNXLIXO.js} +4 -4
- package/dist/{chunk-KEDHHXKW.js → chunk-UUEBNUEG.js} +1 -1
- package/dist/{coordinator-prompt-CNI6AK65.js → coordinator-prompt-X3LXE5Q4.js} +3 -3
- package/dist/{coordinator-prompt-7NKY7QRK.js → coordinator-prompt-YYUXC7EM.js} +3 -3
- package/dist/{global-workspace-FKAJZHO5.js → global-workspace-E6S5XQZF.js} +3 -3
- package/dist/{global-workspace-IJYCUO4M.js → global-workspace-SPR2Y4YQ.js} +3 -3
- package/dist/index.cjs +756 -614
- package/dist/index.js +746 -612
- package/dist/mcp/run-stdio.cjs +756 -614
- package/dist/mcp/run-stdio.js +746 -612
- package/dist/{orchestrator-NSVP7DJH.js → orchestrator-6FZBSP5B.js} +5 -5
- package/dist/{orchestrator-KFLS5BTP.js → orchestrator-QREP2CFO.js} +5 -5
- package/dist/{workspaces-D2M3AM2M.js → workspaces-MZELP4BI.js} +3 -3
- package/dist/{workspaces-OFSDJ7PZ.js → workspaces-OK2SNNQF.js} +3 -3
- package/dist/{worktree-BQE5KLRY.js → worktree-BX53JZHO.js} +2 -2
- package/dist/{worktree-EA6AD6D6.js → worktree-IY7YCEGH.js} +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -234,7 +234,7 @@ import {
|
|
|
234
234
|
worktreeCleanupSettings,
|
|
235
235
|
wrapReviewSkillMarkdown,
|
|
236
236
|
writeWorktreeFile
|
|
237
|
-
} from "./chunk-
|
|
237
|
+
} from "./chunk-MIX4R72K.js";
|
|
238
238
|
import {
|
|
239
239
|
LEGACY_PLAN_FILE_REL,
|
|
240
240
|
PLAN_FILE_NAME,
|
|
@@ -337,7 +337,7 @@ import {
|
|
|
337
337
|
withEventParentId,
|
|
338
338
|
withEventsParentId,
|
|
339
339
|
writeInjectedMcpConfig
|
|
340
|
-
} from "./chunk-
|
|
340
|
+
} from "./chunk-RQNXLIXO.js";
|
|
341
341
|
import {
|
|
342
342
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
343
343
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -384,7 +384,7 @@ import {
|
|
|
384
384
|
stageBuffersAsAttachments,
|
|
385
385
|
syncWorkspacesFromThreads,
|
|
386
386
|
takenTeamSlugsForOrchestration
|
|
387
|
-
} from "./chunk-
|
|
387
|
+
} from "./chunk-QANLIT2E.js";
|
|
388
388
|
import {
|
|
389
389
|
FAMOUS_SOCCER_TEAMS,
|
|
390
390
|
GITHUB_PR_BODY_MAX_CHARS,
|
|
@@ -477,7 +477,7 @@ import {
|
|
|
477
477
|
worktreeDisplayLabel,
|
|
478
478
|
worktreeDisplayLabelForGroup,
|
|
479
479
|
worktreeNameFromPath
|
|
480
|
-
} from "./chunk-
|
|
480
|
+
} from "./chunk-UUEBNUEG.js";
|
|
481
481
|
import {
|
|
482
482
|
ATTACHMENTS_DIR,
|
|
483
483
|
DETACHED_JOBS_DIR,
|
|
@@ -527,7 +527,7 @@ import {
|
|
|
527
527
|
toAbleTimeIssueInfo,
|
|
528
528
|
updateAbleTimeTask,
|
|
529
529
|
verifyAbleTimeConnection
|
|
530
|
-
} from "./chunk-
|
|
530
|
+
} from "./chunk-ILXKJ4ZD.js";
|
|
531
531
|
import {
|
|
532
532
|
formatFetchError,
|
|
533
533
|
httpFetch,
|
|
@@ -631,7 +631,7 @@ import {
|
|
|
631
631
|
updateOpencodeSettings,
|
|
632
632
|
updateProjectProfileSettings,
|
|
633
633
|
writeViewerContext
|
|
634
|
-
} from "./chunk-
|
|
634
|
+
} from "./chunk-CVZ4KXVP.js";
|
|
635
635
|
import {
|
|
636
636
|
stripNestedElectronEnv
|
|
637
637
|
} from "./chunk-4TR3HZFT.js";
|
|
@@ -2285,7 +2285,7 @@ function formatPlanQuestionsForChat(questions) {
|
|
|
2285
2285
|
// src/mcp/server.ts
|
|
2286
2286
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2287
2287
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2288
|
-
import { z as
|
|
2288
|
+
import { z as z8 } from "zod";
|
|
2289
2289
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2290
2290
|
import { basename } from "path";
|
|
2291
2291
|
|
|
@@ -2548,13 +2548,52 @@ ${githubUrl.trim()}` : githubUrl.trim();
|
|
|
2548
2548
|
${link}` : link;
|
|
2549
2549
|
}
|
|
2550
2550
|
|
|
2551
|
-
// src/mcp/
|
|
2552
|
-
|
|
2551
|
+
// src/mcp/issue-list.ts
|
|
2552
|
+
var MCP_ISSUE_LIST_DEFAULT_LIMIT = 40;
|
|
2553
|
+
var MCP_ISSUE_LIST_MAX_LIMIT = 250;
|
|
2554
|
+
function clampMcpIssueLimit(limit) {
|
|
2555
|
+
if (limit == null || !Number.isFinite(limit)) return MCP_ISSUE_LIST_DEFAULT_LIMIT;
|
|
2556
|
+
return Math.max(1, Math.min(MCP_ISSUE_LIST_MAX_LIMIT, Math.floor(limit)));
|
|
2557
|
+
}
|
|
2558
|
+
function applyIssueListWindow(items, limit) {
|
|
2559
|
+
const truncated = items.length > limit;
|
|
2560
|
+
return {
|
|
2561
|
+
items: truncated ? items.slice(0, limit) : items,
|
|
2562
|
+
truncated
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
function compactIssueRow(issue) {
|
|
2566
|
+
return {
|
|
2567
|
+
identifier: issue.identifier,
|
|
2568
|
+
title: issue.title,
|
|
2569
|
+
...issue.assignee ? { assignee: issue.assignee } : {},
|
|
2570
|
+
...issue.labels.length > 0 ? { labels: issue.labels } : {},
|
|
2571
|
+
...issue.teamKey ? { team: issue.teamKey } : {},
|
|
2572
|
+
...issue.cycle?.name ? { cycle: issue.cycle.name } : {}
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
function formatMcpIssueList(input) {
|
|
2576
|
+
return {
|
|
2577
|
+
...input.source ? { source: input.source } : {},
|
|
2578
|
+
...input.viewer ? { viewer: input.viewer } : {},
|
|
2579
|
+
limit: input.limit,
|
|
2580
|
+
returned: input.issues.length,
|
|
2581
|
+
truncated: input.truncated,
|
|
2582
|
+
...input.truncated ? { hint: "More issues match. Pass a higher limit (max 250) or a tighter query." } : {},
|
|
2583
|
+
issues: input.issues.map(compactIssueRow)
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
function mcpJson(payload, isError = false) {
|
|
2553
2587
|
return {
|
|
2554
|
-
content: [{ type: "text", text: JSON.stringify(payload
|
|
2588
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
2555
2589
|
...isError ? { isError: true } : {}
|
|
2556
2590
|
};
|
|
2557
2591
|
}
|
|
2592
|
+
|
|
2593
|
+
// src/mcp/slack-tools.ts
|
|
2594
|
+
function text(payload, isError = false) {
|
|
2595
|
+
return mcpJson(payload, isError);
|
|
2596
|
+
}
|
|
2558
2597
|
function fail(err) {
|
|
2559
2598
|
return text(
|
|
2560
2599
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
@@ -2820,48 +2859,329 @@ function registerSlackTools(server) {
|
|
|
2820
2859
|
}
|
|
2821
2860
|
|
|
2822
2861
|
// src/mcp/abletime-tools.ts
|
|
2862
|
+
import { z as z3 } from "zod";
|
|
2863
|
+
|
|
2864
|
+
// src/mcp/issue-payload.ts
|
|
2823
2865
|
import { z as z2 } from "zod";
|
|
2824
2866
|
|
|
2825
|
-
// src/mcp/
|
|
2826
|
-
var
|
|
2827
|
-
var
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2867
|
+
// src/mcp/smart-crush.ts
|
|
2868
|
+
var MUST_KEEP_TEXT_RE = /\b(error|exception|failed|failure|fatal|critical|panic|traceback|npm err|assert)\b/i;
|
|
2869
|
+
var CRUSH_MIN_ITEMS = 5;
|
|
2870
|
+
var CRUSH_MIN_CHARS = 800;
|
|
2871
|
+
var CRUSH_COMMENT_MAX_CHARS = 16e3;
|
|
2872
|
+
var CRUSH_BODY_MIN_CHARS = 12e3;
|
|
2873
|
+
var CRUSH_BODY_MAX_CHARS = 8e3;
|
|
2874
|
+
var NEAR_DUP_JACCARD = 0.92;
|
|
2875
|
+
function looksLikeMustKeep(text7) {
|
|
2876
|
+
return MUST_KEEP_TEXT_RE.test(text7);
|
|
2877
|
+
}
|
|
2878
|
+
function normalizeText(text7) {
|
|
2879
|
+
return text7.replace(/\s+/g, " ").trim().toLowerCase();
|
|
2880
|
+
}
|
|
2881
|
+
function wordSet(text7) {
|
|
2882
|
+
return new Set(
|
|
2883
|
+
text7.toLowerCase().split(/\W+/).filter((w) => w.length > 2)
|
|
2884
|
+
);
|
|
2831
2885
|
}
|
|
2832
|
-
function
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
}
|
|
2886
|
+
function jaccard(a, b) {
|
|
2887
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
2888
|
+
let inter = 0;
|
|
2889
|
+
for (const w of a) {
|
|
2890
|
+
if (b.has(w)) inter += 1;
|
|
2891
|
+
}
|
|
2892
|
+
const union = a.size + b.size - inter;
|
|
2893
|
+
return union === 0 ? 0 : inter / union;
|
|
2894
|
+
}
|
|
2895
|
+
function importance(text7) {
|
|
2896
|
+
let score = text7.length;
|
|
2897
|
+
if (/```/.test(text7)) score += 400;
|
|
2898
|
+
if (/https?:\/\//.test(text7)) score += 50;
|
|
2899
|
+
if (looksLikeMustKeep(text7)) score += 1e4;
|
|
2900
|
+
return score;
|
|
2901
|
+
}
|
|
2902
|
+
function meanStd(values) {
|
|
2903
|
+
if (values.length === 0) return { mean: 0, std: 0 };
|
|
2904
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
2905
|
+
const variance = values.reduce((a, b) => a + (b - mean) * (b - mean), 0) / values.length;
|
|
2906
|
+
return { mean, std: Math.sqrt(variance) };
|
|
2907
|
+
}
|
|
2908
|
+
function dedupTextItems(items, getText) {
|
|
2909
|
+
const kept = [];
|
|
2910
|
+
const keptNorm = [];
|
|
2911
|
+
const keptWords = [];
|
|
2912
|
+
for (const item of items) {
|
|
2913
|
+
const text7 = getText(item);
|
|
2914
|
+
const norm = normalizeText(text7);
|
|
2915
|
+
if (!norm) {
|
|
2916
|
+
kept.push(item);
|
|
2917
|
+
keptNorm.push(norm);
|
|
2918
|
+
keptWords.push(wordSet(text7));
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
const words = wordSet(text7);
|
|
2922
|
+
let dupAt = -1;
|
|
2923
|
+
for (let i = 0; i < kept.length; i++) {
|
|
2924
|
+
if (keptNorm[i] === norm) {
|
|
2925
|
+
dupAt = i;
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2928
|
+
if (keptWords[i].size >= 8 && words.size >= 8 && jaccard(keptWords[i], words) >= NEAR_DUP_JACCARD) {
|
|
2929
|
+
dupAt = i;
|
|
2930
|
+
break;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
if (dupAt < 0) {
|
|
2934
|
+
kept.push(item);
|
|
2935
|
+
keptNorm.push(norm);
|
|
2936
|
+
keptWords.push(words);
|
|
2937
|
+
continue;
|
|
2938
|
+
}
|
|
2939
|
+
if (text7.length > getText(kept[dupAt]).length) {
|
|
2940
|
+
kept[dupAt] = item;
|
|
2941
|
+
keptNorm[dupAt] = norm;
|
|
2942
|
+
keptWords[dupAt] = words;
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
return { items: kept, dropped: items.length - kept.length };
|
|
2838
2946
|
}
|
|
2839
|
-
function
|
|
2947
|
+
function crushTextItems(items, getText, opts = {}) {
|
|
2948
|
+
const minItems = opts.minItems ?? CRUSH_MIN_ITEMS;
|
|
2949
|
+
const minChars = opts.minChars ?? CRUSH_MIN_CHARS;
|
|
2950
|
+
const maxChars = opts.maxChars ?? CRUSH_COMMENT_MAX_CHARS;
|
|
2951
|
+
const original = items;
|
|
2952
|
+
const totalChars = items.reduce((n, item) => n + getText(item).length, 0);
|
|
2953
|
+
if (items.length < minItems || totalChars < minChars) {
|
|
2954
|
+
return {
|
|
2955
|
+
items,
|
|
2956
|
+
truncated: false,
|
|
2957
|
+
kept: items.length,
|
|
2958
|
+
dropped: 0,
|
|
2959
|
+
reason: "passthrough"
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
const deduped = dedupTextItems(items, getText);
|
|
2963
|
+
const unique = deduped.items;
|
|
2964
|
+
const uniqueChars = unique.reduce((n, item) => n + getText(item).length, 0);
|
|
2965
|
+
if (uniqueChars <= maxChars) {
|
|
2966
|
+
const changed = unique.length < original.length;
|
|
2967
|
+
return {
|
|
2968
|
+
items: unique,
|
|
2969
|
+
truncated: changed,
|
|
2970
|
+
kept: unique.length,
|
|
2971
|
+
dropped: original.length - unique.length,
|
|
2972
|
+
reason: changed ? "dedup" : "passthrough"
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
const lengths = unique.map((item) => getText(item).length);
|
|
2976
|
+
const { mean, std } = meanStd(lengths);
|
|
2977
|
+
const outlierFloor = mean + 2 * std;
|
|
2978
|
+
const mustKeep = /* @__PURE__ */ new Set();
|
|
2979
|
+
unique.forEach((item, i) => {
|
|
2980
|
+
const text7 = getText(item);
|
|
2981
|
+
if (looksLikeMustKeep(text7) || text7.length > outlierFloor) mustKeep.add(i);
|
|
2982
|
+
});
|
|
2983
|
+
const remainingIdx = unique.map((_, i) => i).filter((i) => !mustKeep.has(i));
|
|
2984
|
+
let used = [...mustKeep].reduce((n, i) => n + lengths[i], 0);
|
|
2985
|
+
const pick = new Set(mustKeep);
|
|
2986
|
+
const canTake = (i) => {
|
|
2987
|
+
if (pick.has(i)) return false;
|
|
2988
|
+
const next = used + lengths[i];
|
|
2989
|
+
if (next > maxChars && pick.size > mustKeep.size) return false;
|
|
2990
|
+
used = next;
|
|
2991
|
+
pick.add(i);
|
|
2992
|
+
return true;
|
|
2993
|
+
};
|
|
2994
|
+
const k = remainingIdx.length;
|
|
2995
|
+
const headN = Math.max(1, Math.ceil(k * 0.3));
|
|
2996
|
+
const tailN = Math.max(1, Math.ceil(k * 0.15));
|
|
2997
|
+
for (const i of remainingIdx.slice(0, headN)) canTake(i);
|
|
2998
|
+
for (const i of remainingIdx.slice(-tailN).reverse()) canTake(i);
|
|
2999
|
+
const middle = remainingIdx.filter((i) => !pick.has(i)).sort((a, b) => importance(getText(unique[b])) - importance(getText(unique[a])));
|
|
3000
|
+
for (const i of middle) {
|
|
3001
|
+
if (used >= maxChars) break;
|
|
3002
|
+
canTake(i);
|
|
3003
|
+
}
|
|
3004
|
+
const crushed = unique.filter((_, i) => pick.has(i));
|
|
3005
|
+
const crushedChars = crushed.reduce((n, item) => n + getText(item).length, 0);
|
|
3006
|
+
if (crushed.length >= original.length && crushedChars >= totalChars) {
|
|
3007
|
+
return {
|
|
3008
|
+
items: original,
|
|
3009
|
+
truncated: false,
|
|
3010
|
+
kept: original.length,
|
|
3011
|
+
dropped: 0,
|
|
3012
|
+
reason: "passthrough"
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
2840
3015
|
return {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
...issue.cycle?.name ? { cycle: issue.cycle.name } : {}
|
|
3016
|
+
items: crushed,
|
|
3017
|
+
truncated: crushed.length < original.length,
|
|
3018
|
+
kept: crushed.length,
|
|
3019
|
+
dropped: original.length - crushed.length,
|
|
3020
|
+
reason: "budget"
|
|
2847
3021
|
};
|
|
2848
3022
|
}
|
|
2849
|
-
function
|
|
2850
|
-
return
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
3023
|
+
function splitBlocks(text7) {
|
|
3024
|
+
if (/\n\n/.test(text7)) return text7.split(/\n\n+/);
|
|
3025
|
+
return text7.split("\n");
|
|
3026
|
+
}
|
|
3027
|
+
function capOversizedBlock(block, maxChars) {
|
|
3028
|
+
if (block.length <= maxChars) return block;
|
|
3029
|
+
const head = Math.floor(maxChars * 0.3);
|
|
3030
|
+
const tail = Math.floor(maxChars * 0.15);
|
|
3031
|
+
return `${block.slice(0, head)}
|
|
3032
|
+
|
|
3033
|
+
[\u2026crushed ${block.length - head - tail} chars; pass include=full to read the rest\u2026]
|
|
3034
|
+
|
|
3035
|
+
${block.slice(-tail)}`;
|
|
3036
|
+
}
|
|
3037
|
+
function crushMarkdown(text7, opts) {
|
|
3038
|
+
const original = text7 ?? "";
|
|
3039
|
+
const minChars = opts?.minChars ?? CRUSH_BODY_MIN_CHARS;
|
|
3040
|
+
const maxChars = opts?.maxChars ?? CRUSH_BODY_MAX_CHARS;
|
|
3041
|
+
if (original.length < minChars) {
|
|
3042
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
3043
|
+
}
|
|
3044
|
+
const rawBlocks = splitBlocks(original).filter((b) => b.length > 0);
|
|
3045
|
+
const blocks = rawBlocks.map((b) => capOversizedBlock(b, maxChars));
|
|
3046
|
+
const cappedABlock = blocks.some((b, i) => b !== rawBlocks[i]);
|
|
3047
|
+
if (blocks.length <= 1) {
|
|
3048
|
+
const cut = blocks[0] ?? original;
|
|
3049
|
+
if (cut.length >= original.length) {
|
|
3050
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
3051
|
+
}
|
|
3052
|
+
return { text: cut, truncated: true, originalChars: original.length };
|
|
3053
|
+
}
|
|
3054
|
+
const must = blocks.filter((b) => looksLikeMustKeep(b));
|
|
3055
|
+
const rest = blocks.filter((b) => !looksLikeMustKeep(b));
|
|
3056
|
+
const marker = `[\u2026crushed body; ${original.length} chars originally. Pass include=full to read the rest.]`;
|
|
3057
|
+
let used = must.reduce((n, b) => n + b.length, 0) + marker.length;
|
|
3058
|
+
const keptRest = [];
|
|
3059
|
+
const headN = Math.max(1, Math.ceil(rest.length * 0.3));
|
|
3060
|
+
const tailN = Math.max(1, Math.ceil(rest.length * 0.15));
|
|
3061
|
+
const head = rest.slice(0, headN);
|
|
3062
|
+
const tail = rest.slice(-tailN);
|
|
3063
|
+
const middle = rest.slice(headN, Math.max(headN, rest.length - tailN));
|
|
3064
|
+
const take = (block) => {
|
|
3065
|
+
if (used + block.length + 2 > maxChars && keptRest.length + must.length > 0) {
|
|
3066
|
+
return false;
|
|
3067
|
+
}
|
|
3068
|
+
used += block.length + 2;
|
|
3069
|
+
keptRest.push(block);
|
|
3070
|
+
return true;
|
|
2858
3071
|
};
|
|
3072
|
+
for (const b of head) take(b);
|
|
3073
|
+
for (const b of tail) take(b);
|
|
3074
|
+
const ranked = [...middle].sort((a, b) => importance(b) - importance(a));
|
|
3075
|
+
for (const b of ranked) {
|
|
3076
|
+
if (used >= maxChars) break;
|
|
3077
|
+
take(b);
|
|
3078
|
+
}
|
|
3079
|
+
const keepSet = /* @__PURE__ */ new Set([...must, ...keptRest]);
|
|
3080
|
+
const ordered = blocks.filter((b) => keepSet.has(b));
|
|
3081
|
+
const omitted = blocks.length - ordered.length;
|
|
3082
|
+
if (omitted <= 0) {
|
|
3083
|
+
const joined = blocks.join("\n\n");
|
|
3084
|
+
if (cappedABlock && joined.length < original.length) {
|
|
3085
|
+
return { text: joined, truncated: true, originalChars: original.length };
|
|
3086
|
+
}
|
|
3087
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
3088
|
+
}
|
|
3089
|
+
const out = [];
|
|
3090
|
+
let inserted = false;
|
|
3091
|
+
for (const block of blocks) {
|
|
3092
|
+
if (keepSet.has(block)) {
|
|
3093
|
+
out.push(block);
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
3096
|
+
if (!inserted) {
|
|
3097
|
+
out.push(marker);
|
|
3098
|
+
inserted = true;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
const textOut = out.join("\n\n");
|
|
3102
|
+
if (textOut.length >= original.length) {
|
|
3103
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
3104
|
+
}
|
|
3105
|
+
return { text: textOut, truncated: true, originalChars: original.length };
|
|
2859
3106
|
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3107
|
+
|
|
3108
|
+
// src/mcp/issue-payload.ts
|
|
3109
|
+
var MCP_ISSUE_INCLUDE_VALUES = ["default", "full"];
|
|
3110
|
+
var mcpIssueIncludeSchema = z2.enum(MCP_ISSUE_INCLUDE_VALUES).optional().describe(
|
|
3111
|
+
"default applies SmartCrusher-style crush (dedup + budget; small unique tickets pass through). full returns the uncompressed vendor payload. Re-call with include=full if crush.truncated."
|
|
3112
|
+
);
|
|
3113
|
+
var MCP_ISSUE_CRUSH_HINT = "Pass include=full to re-fetch the uncompressed ticket from the vendor.";
|
|
3114
|
+
function emptyMeta() {
|
|
3115
|
+
return { truncated: true, hint: MCP_ISSUE_CRUSH_HINT };
|
|
3116
|
+
}
|
|
3117
|
+
function withComments(comments, meta) {
|
|
3118
|
+
const crushed = crushTextItems(comments, (c) => c.body);
|
|
3119
|
+
if (crushed.truncated) {
|
|
3120
|
+
meta.comments = {
|
|
3121
|
+
kept: crushed.kept,
|
|
3122
|
+
dropped: crushed.dropped,
|
|
3123
|
+
reason: crushed.reason
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
return { comments: crushed.items, meta };
|
|
3127
|
+
}
|
|
3128
|
+
function withBody(text7, meta) {
|
|
3129
|
+
const crushed = crushMarkdown(text7);
|
|
3130
|
+
if (crushed.truncated) {
|
|
3131
|
+
meta.body = { originalChars: crushed.originalChars };
|
|
3132
|
+
return { value: crushed.text, meta };
|
|
3133
|
+
}
|
|
3134
|
+
return { value: text7, meta };
|
|
3135
|
+
}
|
|
3136
|
+
function attachCrush(payload, meta) {
|
|
3137
|
+
if (!meta.comments && !meta.body) return payload;
|
|
3138
|
+
return { ...payload, crush: meta };
|
|
3139
|
+
}
|
|
3140
|
+
function formatLinearIssuePayload(issue, include) {
|
|
3141
|
+
if (include === "full") return issue;
|
|
3142
|
+
const meta = emptyMeta();
|
|
3143
|
+
const comments = withComments(issue.comments, meta);
|
|
3144
|
+
const description = withBody(issue.description, comments.meta);
|
|
3145
|
+
const { team, ...rest } = issue;
|
|
3146
|
+
const next = {
|
|
3147
|
+
...rest,
|
|
3148
|
+
...team ? { team: { id: team.id, key: team.key, name: team.name } } : {},
|
|
3149
|
+
description: description.value,
|
|
3150
|
+
comments: comments.comments
|
|
3151
|
+
};
|
|
3152
|
+
return attachCrush(next, description.meta);
|
|
3153
|
+
}
|
|
3154
|
+
function formatGitHubIssuePayload(issue, include) {
|
|
3155
|
+
if (include === "full") return issue;
|
|
3156
|
+
const meta = emptyMeta();
|
|
3157
|
+
const comments = withComments(issue.comments, meta);
|
|
3158
|
+
const body = withBody(issue.body, comments.meta);
|
|
3159
|
+
const next = {
|
|
3160
|
+
...issue,
|
|
3161
|
+
body: body.value,
|
|
3162
|
+
comments: comments.comments
|
|
2864
3163
|
};
|
|
3164
|
+
return attachCrush(next, body.meta);
|
|
3165
|
+
}
|
|
3166
|
+
function formatAbleTimeTaskPayload(task, include) {
|
|
3167
|
+
const base = {
|
|
3168
|
+
...toAbleTimeIssueInfo(task),
|
|
3169
|
+
description: task.description,
|
|
3170
|
+
state: task.state,
|
|
3171
|
+
comments: task.comments
|
|
3172
|
+
};
|
|
3173
|
+
if (include === "full") return base;
|
|
3174
|
+
const meta = emptyMeta();
|
|
3175
|
+
const comments = withComments(task.comments, meta);
|
|
3176
|
+
const description = withBody(task.description, comments.meta);
|
|
3177
|
+
return attachCrush(
|
|
3178
|
+
{
|
|
3179
|
+
...base,
|
|
3180
|
+
description: description.value,
|
|
3181
|
+
comments: comments.comments
|
|
3182
|
+
},
|
|
3183
|
+
description.meta
|
|
3184
|
+
);
|
|
2865
3185
|
}
|
|
2866
3186
|
|
|
2867
3187
|
// src/mcp/abletime-tools.ts
|
|
@@ -2907,7 +3227,7 @@ function registerAbleTimeTools(server) {
|
|
|
2907
3227
|
server.tool(
|
|
2908
3228
|
"abletime_list_tasks",
|
|
2909
3229
|
"List open AbleTime tasks (excludes done/archived). Optional projectId filter.",
|
|
2910
|
-
{ projectId:
|
|
3230
|
+
{ projectId: z3.string().optional() },
|
|
2911
3231
|
async ({ projectId }) => {
|
|
2912
3232
|
try {
|
|
2913
3233
|
const tasks = await listAbleTimeTasks({ projectId });
|
|
@@ -2920,7 +3240,7 @@ function registerAbleTimeTools(server) {
|
|
|
2920
3240
|
server.tool(
|
|
2921
3241
|
"abletime_search_tasks",
|
|
2922
3242
|
"Search AbleTime tasks by text, reference, or tag.",
|
|
2923
|
-
{ query:
|
|
3243
|
+
{ query: z3.string() },
|
|
2924
3244
|
async ({ query }) => {
|
|
2925
3245
|
try {
|
|
2926
3246
|
const tasks = await searchAbleTimeTasks(query);
|
|
@@ -2932,17 +3252,11 @@ function registerAbleTimeTools(server) {
|
|
|
2932
3252
|
);
|
|
2933
3253
|
server.tool(
|
|
2934
3254
|
"abletime_get_task",
|
|
2935
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments.",
|
|
2936
|
-
{ id:
|
|
2937
|
-
async ({ id }) => {
|
|
3255
|
+
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload.",
|
|
3256
|
+
{ id: z3.string(), include: mcpIssueIncludeSchema },
|
|
3257
|
+
async ({ id, include }) => {
|
|
2938
3258
|
try {
|
|
2939
|
-
|
|
2940
|
-
return text2({
|
|
2941
|
-
...toAbleTimeIssueInfo(task),
|
|
2942
|
-
description: task.description,
|
|
2943
|
-
state: task.state,
|
|
2944
|
-
comments: task.comments
|
|
2945
|
-
});
|
|
3259
|
+
return text2(formatAbleTimeTaskPayload(await getAbleTimeTask(id), include));
|
|
2946
3260
|
} catch (err) {
|
|
2947
3261
|
return fail2(err);
|
|
2948
3262
|
}
|
|
@@ -2951,7 +3265,7 @@ function registerAbleTimeTools(server) {
|
|
|
2951
3265
|
server.tool(
|
|
2952
3266
|
"abletime_comment",
|
|
2953
3267
|
"Add a markdown comment on an AbleTime task (id or CRM-232).",
|
|
2954
|
-
{ id:
|
|
3268
|
+
{ id: z3.string(), body: z3.string() },
|
|
2955
3269
|
async (args) => {
|
|
2956
3270
|
try {
|
|
2957
3271
|
return text2(await commentAbleTimeTask(args));
|
|
@@ -2964,10 +3278,10 @@ function registerAbleTimeTools(server) {
|
|
|
2964
3278
|
"abletime_update_task",
|
|
2965
3279
|
"Update an AbleTime task (id or CRM-232). Pass title, description, and/or state.",
|
|
2966
3280
|
{
|
|
2967
|
-
id:
|
|
2968
|
-
title:
|
|
2969
|
-
description:
|
|
2970
|
-
state:
|
|
3281
|
+
id: z3.string(),
|
|
3282
|
+
title: z3.string().optional(),
|
|
3283
|
+
description: z3.string().optional(),
|
|
3284
|
+
state: z3.string().optional()
|
|
2971
3285
|
},
|
|
2972
3286
|
async (args) => {
|
|
2973
3287
|
try {
|
|
@@ -2986,12 +3300,12 @@ function registerAbleTimeTools(server) {
|
|
|
2986
3300
|
"abletime_create_task",
|
|
2987
3301
|
"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).",
|
|
2988
3302
|
{
|
|
2989
|
-
title:
|
|
2990
|
-
description:
|
|
2991
|
-
projectId:
|
|
2992
|
-
categoryId:
|
|
2993
|
-
state:
|
|
2994
|
-
parent:
|
|
3303
|
+
title: z3.string(),
|
|
3304
|
+
description: z3.string().optional(),
|
|
3305
|
+
projectId: z3.string().optional(),
|
|
3306
|
+
categoryId: z3.string().optional(),
|
|
3307
|
+
state: z3.enum(["backlog", "todo"]).optional(),
|
|
3308
|
+
parent: z3.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
2995
3309
|
},
|
|
2996
3310
|
async (args) => {
|
|
2997
3311
|
try {
|
|
@@ -3005,10 +3319,10 @@ function registerAbleTimeTools(server) {
|
|
|
3005
3319
|
"abletime_ensure_task",
|
|
3006
3320
|
"Find or create an AbleTime task for this work, then create_thread with sourceType=ticket.",
|
|
3007
3321
|
{
|
|
3008
|
-
title:
|
|
3009
|
-
description:
|
|
3010
|
-
projectId:
|
|
3011
|
-
categoryId:
|
|
3322
|
+
title: z3.string(),
|
|
3323
|
+
description: z3.string().optional(),
|
|
3324
|
+
projectId: z3.string().optional(),
|
|
3325
|
+
categoryId: z3.string().optional()
|
|
3012
3326
|
},
|
|
3013
3327
|
async (args) => {
|
|
3014
3328
|
try {
|
|
@@ -3022,7 +3336,7 @@ function registerAbleTimeTools(server) {
|
|
|
3022
3336
|
}
|
|
3023
3337
|
|
|
3024
3338
|
// src/mcp/github-tools.ts
|
|
3025
|
-
import { z as
|
|
3339
|
+
import { z as z4 } from "zod";
|
|
3026
3340
|
function text3(payload, isError = false) {
|
|
3027
3341
|
return mcpJson(payload, isError);
|
|
3028
3342
|
}
|
|
@@ -3032,15 +3346,15 @@ function fail3(err) {
|
|
|
3032
3346
|
true
|
|
3033
3347
|
);
|
|
3034
3348
|
}
|
|
3035
|
-
var repoPathSchema =
|
|
3349
|
+
var repoPathSchema = z4.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
3036
3350
|
function registerGithubIssueTools(server) {
|
|
3037
3351
|
server.tool(
|
|
3038
3352
|
"github_get_issue",
|
|
3039
|
-
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Uses Account gh.",
|
|
3040
|
-
{ id:
|
|
3041
|
-
async ({ id, repoPath }) => {
|
|
3353
|
+
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. Uses Account gh.",
|
|
3354
|
+
{ id: z4.string(), include: mcpIssueIncludeSchema, repoPath: repoPathSchema },
|
|
3355
|
+
async ({ id, include, repoPath }) => {
|
|
3042
3356
|
try {
|
|
3043
|
-
return text3(await getGitHubIssue(id, { repoPath }));
|
|
3357
|
+
return text3(formatGitHubIssuePayload(await getGitHubIssue(id, { repoPath }), include));
|
|
3044
3358
|
} catch (err) {
|
|
3045
3359
|
return fail3(err);
|
|
3046
3360
|
}
|
|
@@ -3049,7 +3363,7 @@ function registerGithubIssueTools(server) {
|
|
|
3049
3363
|
server.tool(
|
|
3050
3364
|
"github_comment",
|
|
3051
3365
|
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh.",
|
|
3052
|
-
{ id:
|
|
3366
|
+
{ id: z4.string(), body: z4.string(), repoPath: repoPathSchema },
|
|
3053
3367
|
async ({ id, body, repoPath }) => {
|
|
3054
3368
|
try {
|
|
3055
3369
|
return text3(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
@@ -3062,10 +3376,10 @@ function registerGithubIssueTools(server) {
|
|
|
3062
3376
|
"github_update_issue",
|
|
3063
3377
|
"Update a GitHub issue (#123). Pass title, body, and/or state (open|closed).",
|
|
3064
3378
|
{
|
|
3065
|
-
id:
|
|
3066
|
-
title:
|
|
3067
|
-
body:
|
|
3068
|
-
state:
|
|
3379
|
+
id: z4.string(),
|
|
3380
|
+
title: z4.string().optional(),
|
|
3381
|
+
body: z4.string().optional(),
|
|
3382
|
+
state: z4.string().optional().describe("open or closed"),
|
|
3069
3383
|
repoPath: repoPathSchema
|
|
3070
3384
|
},
|
|
3071
3385
|
async (args) => {
|
|
@@ -3080,9 +3394,9 @@ function registerGithubIssueTools(server) {
|
|
|
3080
3394
|
"github_create_issue",
|
|
3081
3395
|
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
3082
3396
|
{
|
|
3083
|
-
title:
|
|
3084
|
-
body:
|
|
3085
|
-
parent:
|
|
3397
|
+
title: z4.string(),
|
|
3398
|
+
body: z4.string().optional(),
|
|
3399
|
+
parent: z4.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
3086
3400
|
repoPath: repoPathSchema
|
|
3087
3401
|
},
|
|
3088
3402
|
async (args) => {
|
|
@@ -3096,7 +3410,7 @@ function registerGithubIssueTools(server) {
|
|
|
3096
3410
|
}
|
|
3097
3411
|
|
|
3098
3412
|
// src/mcp/linear-tools.ts
|
|
3099
|
-
import { z as
|
|
3413
|
+
import { z as z5 } from "zod";
|
|
3100
3414
|
function text4(payload, isError = false) {
|
|
3101
3415
|
return mcpJson(payload, isError);
|
|
3102
3416
|
}
|
|
@@ -3106,7 +3420,7 @@ function fail4(err) {
|
|
|
3106
3420
|
true
|
|
3107
3421
|
);
|
|
3108
3422
|
}
|
|
3109
|
-
var prioritySchema =
|
|
3423
|
+
var prioritySchema = z5.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
3110
3424
|
function registerLinearTools(server) {
|
|
3111
3425
|
server.tool(
|
|
3112
3426
|
"linear_list_teams",
|
|
@@ -3124,9 +3438,9 @@ function registerLinearTools(server) {
|
|
|
3124
3438
|
"linear_search_issues",
|
|
3125
3439
|
"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.",
|
|
3126
3440
|
{
|
|
3127
|
-
query:
|
|
3128
|
-
assignee:
|
|
3129
|
-
limit:
|
|
3441
|
+
query: z5.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
3442
|
+
assignee: z5.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
3443
|
+
limit: z5.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
3130
3444
|
},
|
|
3131
3445
|
async ({ query, assignee, limit }) => {
|
|
3132
3446
|
try {
|
|
@@ -3153,11 +3467,11 @@ function registerLinearTools(server) {
|
|
|
3153
3467
|
);
|
|
3154
3468
|
server.tool(
|
|
3155
3469
|
"linear_get_issue",
|
|
3156
|
-
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
3157
|
-
{ id:
|
|
3158
|
-
async ({ id }) => {
|
|
3470
|
+
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload.",
|
|
3471
|
+
{ id: z5.string(), include: mcpIssueIncludeSchema },
|
|
3472
|
+
async ({ id, include }) => {
|
|
3159
3473
|
try {
|
|
3160
|
-
return text4(await getLinearIssue(id));
|
|
3474
|
+
return text4(formatLinearIssuePayload(await getLinearIssue(id), include));
|
|
3161
3475
|
} catch (err) {
|
|
3162
3476
|
return fail4(err);
|
|
3163
3477
|
}
|
|
@@ -3167,13 +3481,13 @@ function registerLinearTools(server) {
|
|
|
3167
3481
|
"linear_create_issue",
|
|
3168
3482
|
'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.',
|
|
3169
3483
|
{
|
|
3170
|
-
team:
|
|
3171
|
-
title:
|
|
3172
|
-
description:
|
|
3173
|
-
state:
|
|
3174
|
-
assignee:
|
|
3484
|
+
team: z5.string(),
|
|
3485
|
+
title: z5.string(),
|
|
3486
|
+
description: z5.string().optional(),
|
|
3487
|
+
state: z5.string().optional(),
|
|
3488
|
+
assignee: z5.string().nullable().optional(),
|
|
3175
3489
|
priority: prioritySchema,
|
|
3176
|
-
parent:
|
|
3490
|
+
parent: z5.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
3177
3491
|
},
|
|
3178
3492
|
async (args) => {
|
|
3179
3493
|
try {
|
|
@@ -3187,11 +3501,11 @@ function registerLinearTools(server) {
|
|
|
3187
3501
|
"linear_update_issue",
|
|
3188
3502
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
3189
3503
|
{
|
|
3190
|
-
id:
|
|
3191
|
-
title:
|
|
3192
|
-
description:
|
|
3193
|
-
state:
|
|
3194
|
-
assignee:
|
|
3504
|
+
id: z5.string(),
|
|
3505
|
+
title: z5.string().optional(),
|
|
3506
|
+
description: z5.string().optional(),
|
|
3507
|
+
state: z5.string().optional(),
|
|
3508
|
+
assignee: z5.string().nullable().optional(),
|
|
3195
3509
|
priority: prioritySchema
|
|
3196
3510
|
},
|
|
3197
3511
|
async (args) => {
|
|
@@ -3206,8 +3520,8 @@ function registerLinearTools(server) {
|
|
|
3206
3520
|
"linear_comment",
|
|
3207
3521
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
3208
3522
|
{
|
|
3209
|
-
id:
|
|
3210
|
-
body:
|
|
3523
|
+
id: z5.string(),
|
|
3524
|
+
body: z5.string()
|
|
3211
3525
|
},
|
|
3212
3526
|
async (args) => {
|
|
3213
3527
|
try {
|
|
@@ -3227,18 +3541,21 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
|
|
|
3227
3541
|
}
|
|
3228
3542
|
|
|
3229
3543
|
// src/mcp/viewer-context-tools.ts
|
|
3230
|
-
import { z as
|
|
3544
|
+
import { z as z6 } from "zod";
|
|
3231
3545
|
function text5(payload, isError = false) {
|
|
3232
3546
|
return mcpJson(payload, isError);
|
|
3233
3547
|
}
|
|
3234
3548
|
function matchRegisteredWorkspace(repoPath) {
|
|
3235
3549
|
const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
|
|
3236
3550
|
if (!hint) return null;
|
|
3237
|
-
|
|
3551
|
+
let best = null;
|
|
3552
|
+
for (const w of listWorkspaces()) {
|
|
3238
3553
|
const stored = w.path.replace(/\/+$/, "");
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3554
|
+
if (stored === hint || hint.startsWith(`${stored}/`)) {
|
|
3555
|
+
if (!best || stored.length > best.length) best = stored;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return best;
|
|
3242
3559
|
}
|
|
3243
3560
|
async function resolveProjectRepoPath(repoPath) {
|
|
3244
3561
|
const hint = repoPath?.trim() || process.cwd();
|
|
@@ -3256,7 +3573,7 @@ function registerViewerContextTools(server) {
|
|
|
3256
3573
|
"get_viewer_context",
|
|
3257
3574
|
"Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_workspaces for project context. Worktree turns may omit repoPath (uses cwd).",
|
|
3258
3575
|
{
|
|
3259
|
-
repoPath:
|
|
3576
|
+
repoPath: z6.string().optional().describe(
|
|
3260
3577
|
"Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
|
|
3261
3578
|
)
|
|
3262
3579
|
},
|
|
@@ -3276,10 +3593,10 @@ function registerViewerContextTools(server) {
|
|
|
3276
3593
|
"update_viewer_context",
|
|
3277
3594
|
"Replace account or project user context (Settings \u2192 Agents / Projects). Never call until the user confirmed. Show the proposed text, ask_user (Save this context / Do not save), wait, then pass confirmed=true. Empty context clears that field.",
|
|
3278
3595
|
{
|
|
3279
|
-
scope:
|
|
3280
|
-
context:
|
|
3281
|
-
confirmed:
|
|
3282
|
-
repoPath:
|
|
3596
|
+
scope: z6.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
|
|
3597
|
+
context: z6.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
|
|
3598
|
+
confirmed: z6.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
|
|
3599
|
+
repoPath: z6.string().optional().describe(
|
|
3283
3600
|
"Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
|
|
3284
3601
|
)
|
|
3285
3602
|
},
|
|
@@ -3356,12 +3673,9 @@ function formatMcpPrList(input) {
|
|
|
3356
3673
|
}
|
|
3357
3674
|
|
|
3358
3675
|
// src/mcp/schedule-tools.ts
|
|
3359
|
-
import { z as
|
|
3676
|
+
import { z as z7 } from "zod";
|
|
3360
3677
|
function text6(payload, isError = false) {
|
|
3361
|
-
return
|
|
3362
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
3363
|
-
...isError ? { isError: true } : {}
|
|
3364
|
-
};
|
|
3678
|
+
return mcpJson(payload, isError);
|
|
3365
3679
|
}
|
|
3366
3680
|
function fail5(err) {
|
|
3367
3681
|
return text6(
|
|
@@ -3399,17 +3713,17 @@ function registerScheduleTools(server) {
|
|
|
3399
3713
|
"create_schedule",
|
|
3400
3714
|
"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.",
|
|
3401
3715
|
{
|
|
3402
|
-
prompt:
|
|
3403
|
-
name:
|
|
3404
|
-
at:
|
|
3405
|
-
every:
|
|
3406
|
-
cron:
|
|
3407
|
-
tz:
|
|
3408
|
-
threadId:
|
|
3716
|
+
prompt: z7.string().describe("User message / goal queued when the schedule fires"),
|
|
3717
|
+
name: z7.string().optional(),
|
|
3718
|
+
at: z7.string().optional().describe("ISO datetime for a one-shot"),
|
|
3719
|
+
every: z7.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
3720
|
+
cron: z7.string().optional().describe("5-field cron expression"),
|
|
3721
|
+
tz: z7.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
3722
|
+
threadId: z7.string().optional().describe(
|
|
3409
3723
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
3410
3724
|
),
|
|
3411
|
-
agent:
|
|
3412
|
-
model:
|
|
3725
|
+
agent: z7.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
3726
|
+
model: z7.string().optional()
|
|
3413
3727
|
},
|
|
3414
3728
|
async (args) => {
|
|
3415
3729
|
try {
|
|
@@ -3442,17 +3756,17 @@ function registerScheduleTools(server) {
|
|
|
3442
3756
|
"update_schedule",
|
|
3443
3757
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
3444
3758
|
{
|
|
3445
|
-
id:
|
|
3446
|
-
prompt:
|
|
3447
|
-
name:
|
|
3448
|
-
at:
|
|
3449
|
-
every:
|
|
3450
|
-
cron:
|
|
3451
|
-
tz:
|
|
3452
|
-
threadId:
|
|
3453
|
-
agent:
|
|
3454
|
-
model:
|
|
3455
|
-
enabled:
|
|
3759
|
+
id: z7.string(),
|
|
3760
|
+
prompt: z7.string().optional(),
|
|
3761
|
+
name: z7.string().optional(),
|
|
3762
|
+
at: z7.string().optional(),
|
|
3763
|
+
every: z7.string().optional(),
|
|
3764
|
+
cron: z7.string().optional(),
|
|
3765
|
+
tz: z7.string().optional(),
|
|
3766
|
+
threadId: z7.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
3767
|
+
agent: z7.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
3768
|
+
model: z7.string().optional(),
|
|
3769
|
+
enabled: z7.boolean().optional()
|
|
3456
3770
|
},
|
|
3457
3771
|
async (args) => {
|
|
3458
3772
|
try {
|
|
@@ -3490,7 +3804,7 @@ function registerScheduleTools(server) {
|
|
|
3490
3804
|
server.tool(
|
|
3491
3805
|
"delete_schedule",
|
|
3492
3806
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
3493
|
-
{ id:
|
|
3807
|
+
{ id: z7.string() },
|
|
3494
3808
|
async ({ id }) => {
|
|
3495
3809
|
try {
|
|
3496
3810
|
deleteSchedule(id);
|
|
@@ -3503,7 +3817,7 @@ function registerScheduleTools(server) {
|
|
|
3503
3817
|
server.tool(
|
|
3504
3818
|
"run_schedule",
|
|
3505
3819
|
"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.",
|
|
3506
|
-
{ id:
|
|
3820
|
+
{ id: z7.string() },
|
|
3507
3821
|
async ({ id }) => {
|
|
3508
3822
|
try {
|
|
3509
3823
|
if (!getSchedule(id)) {
|
|
@@ -3913,7 +4227,7 @@ async function startMcpServer() {
|
|
|
3913
4227
|
);
|
|
3914
4228
|
}
|
|
3915
4229
|
try {
|
|
3916
|
-
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-
|
|
4230
|
+
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-JJQDWPEK.js");
|
|
3917
4231
|
orch.setMaxConcurrent(maxConcurrentAgents2());
|
|
3918
4232
|
} catch {
|
|
3919
4233
|
}
|
|
@@ -3986,13 +4300,13 @@ async function startMcpServer() {
|
|
|
3986
4300
|
"list_board",
|
|
3987
4301
|
"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.",
|
|
3988
4302
|
{
|
|
3989
|
-
query:
|
|
3990
|
-
repoPath:
|
|
3991
|
-
kind:
|
|
3992
|
-
column:
|
|
4303
|
+
query: z8.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
4304
|
+
repoPath: z8.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
4305
|
+
kind: z8.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
4306
|
+
column: z8.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
3993
4307
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
3994
4308
|
),
|
|
3995
|
-
limit:
|
|
4309
|
+
limit: z8.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
3996
4310
|
},
|
|
3997
4311
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
3998
4312
|
const workspaces = orch.listWorkspaces();
|
|
@@ -4007,30 +4321,19 @@ async function startMcpServer() {
|
|
|
4007
4321
|
limit,
|
|
4008
4322
|
workspaceName: (path2) => names.get(path2) ?? ""
|
|
4009
4323
|
});
|
|
4010
|
-
return {
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
hidden: snap.hidden,
|
|
4018
|
-
totals: snap.totals,
|
|
4019
|
-
columnDefs: BOARD_COLUMN_DEFS,
|
|
4020
|
-
hint: HOME_BOARD_AGENT_HINT
|
|
4021
|
-
},
|
|
4022
|
-
null,
|
|
4023
|
-
2
|
|
4024
|
-
)
|
|
4025
|
-
}
|
|
4026
|
-
]
|
|
4027
|
-
};
|
|
4324
|
+
return mcpJson({
|
|
4325
|
+
columns: snap.columns,
|
|
4326
|
+
hidden: snap.hidden,
|
|
4327
|
+
totals: snap.totals,
|
|
4328
|
+
columnDefs: BOARD_COLUMN_DEFS,
|
|
4329
|
+
hint: HOME_BOARD_AGENT_HINT
|
|
4330
|
+
});
|
|
4028
4331
|
}
|
|
4029
4332
|
);
|
|
4030
4333
|
server.tool(
|
|
4031
4334
|
"get_thread",
|
|
4032
4335
|
"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.",
|
|
4033
|
-
{ ref:
|
|
4336
|
+
{ ref: z8.string() },
|
|
4034
4337
|
async ({ ref }) => {
|
|
4035
4338
|
const t = orch.getThread(ref);
|
|
4036
4339
|
if (!t) {
|
|
@@ -4064,7 +4367,7 @@ async function startMcpServer() {
|
|
|
4064
4367
|
usage: spend.usage,
|
|
4065
4368
|
lastTurnUsage: spend.lastTurnUsage
|
|
4066
4369
|
};
|
|
4067
|
-
return
|
|
4370
|
+
return mcpJson(summary);
|
|
4068
4371
|
}
|
|
4069
4372
|
);
|
|
4070
4373
|
}
|
|
@@ -4072,17 +4375,17 @@ async function startMcpServer() {
|
|
|
4072
4375
|
"present_artifact",
|
|
4073
4376
|
"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.",
|
|
4074
4377
|
{
|
|
4075
|
-
title:
|
|
4076
|
-
type:
|
|
4378
|
+
title: z8.string().describe("Short title shown in the artifact pane header"),
|
|
4379
|
+
type: z8.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
4077
4380
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
4078
4381
|
),
|
|
4079
|
-
content:
|
|
4382
|
+
content: z8.string().describe(
|
|
4080
4383
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
4081
4384
|
),
|
|
4082
|
-
artifact_id:
|
|
4083
|
-
status:
|
|
4084
|
-
phase:
|
|
4085
|
-
mode:
|
|
4385
|
+
artifact_id: z8.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
4386
|
+
status: z8.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
4387
|
+
phase: z8.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
4388
|
+
mode: z8.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
4086
4389
|
},
|
|
4087
4390
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
4088
4391
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -4096,22 +4399,22 @@ async function startMcpServer() {
|
|
|
4096
4399
|
mode: type === "log" ? mode ?? "append" : void 0,
|
|
4097
4400
|
message: type === "log" ? "Log accepted. Same artifact_id appends; send only new lines next time." : "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
|
|
4098
4401
|
};
|
|
4099
|
-
return
|
|
4402
|
+
return mcpJson(payload);
|
|
4100
4403
|
}
|
|
4101
4404
|
);
|
|
4102
4405
|
server.tool(
|
|
4103
4406
|
"ask_user",
|
|
4104
4407
|
"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.",
|
|
4105
4408
|
{
|
|
4106
|
-
questions:
|
|
4107
|
-
|
|
4108
|
-
question:
|
|
4109
|
-
header:
|
|
4110
|
-
multiSelect:
|
|
4111
|
-
options:
|
|
4112
|
-
|
|
4113
|
-
label:
|
|
4114
|
-
description:
|
|
4409
|
+
questions: z8.array(
|
|
4410
|
+
z8.object({
|
|
4411
|
+
question: z8.string().describe("Full question text ending with ?"),
|
|
4412
|
+
header: z8.string().max(24).optional().describe("Short label shown above the question"),
|
|
4413
|
+
multiSelect: z8.boolean().optional().describe("Allow selecting multiple options"),
|
|
4414
|
+
options: z8.array(
|
|
4415
|
+
z8.object({
|
|
4416
|
+
label: z8.string(),
|
|
4417
|
+
description: z8.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
4115
4418
|
})
|
|
4116
4419
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
4117
4420
|
})
|
|
@@ -4123,16 +4426,16 @@ async function startMcpServer() {
|
|
|
4123
4426
|
questions,
|
|
4124
4427
|
message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
|
|
4125
4428
|
};
|
|
4126
|
-
return
|
|
4429
|
+
return mcpJson(payload);
|
|
4127
4430
|
}
|
|
4128
4431
|
);
|
|
4129
4432
|
server.tool(
|
|
4130
4433
|
"present_plan",
|
|
4131
4434
|
"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.",
|
|
4132
4435
|
{
|
|
4133
|
-
title:
|
|
4134
|
-
content:
|
|
4135
|
-
thread_id:
|
|
4436
|
+
title: z8.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
4437
|
+
content: z8.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
4438
|
+
thread_id: z8.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
4136
4439
|
},
|
|
4137
4440
|
async ({ title, content, thread_id }) => {
|
|
4138
4441
|
const { writePlanFile: writePlanFile2 } = await import("./plan-file-LSSSQGTQ.js");
|
|
@@ -4148,22 +4451,22 @@ async function startMcpServer() {
|
|
|
4148
4451
|
title: title?.trim() || "Plan",
|
|
4149
4452
|
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
4150
4453
|
};
|
|
4151
|
-
return
|
|
4454
|
+
return mcpJson(payload);
|
|
4152
4455
|
}
|
|
4153
4456
|
);
|
|
4154
4457
|
server.tool(
|
|
4155
4458
|
"present_schema",
|
|
4156
4459
|
"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.",
|
|
4157
4460
|
{
|
|
4158
|
-
title:
|
|
4159
|
-
mode:
|
|
4160
|
-
datasource:
|
|
4161
|
-
resource_id:
|
|
4162
|
-
record_id:
|
|
4163
|
-
resource:
|
|
4164
|
-
record:
|
|
4165
|
-
records:
|
|
4166
|
-
pane_id:
|
|
4461
|
+
title: z8.string().describe("Pane title"),
|
|
4462
|
+
mode: z8.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
4463
|
+
datasource: z8.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
4464
|
+
resource_id: z8.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
4465
|
+
record_id: z8.string().optional().describe("Record id when opening form mode"),
|
|
4466
|
+
resource: z8.record(z8.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
4467
|
+
record: z8.record(z8.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
4468
|
+
records: z8.array(z8.record(z8.unknown())).optional().describe("Inline records for table mode"),
|
|
4469
|
+
pane_id: z8.string().optional().describe("Stable pane id across updates")
|
|
4167
4470
|
},
|
|
4168
4471
|
async (args) => {
|
|
4169
4472
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -4177,17 +4480,17 @@ async function startMcpServer() {
|
|
|
4177
4480
|
record_id: args.record_id,
|
|
4178
4481
|
message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
|
|
4179
4482
|
};
|
|
4180
|
-
return
|
|
4483
|
+
return mcpJson(payload);
|
|
4181
4484
|
}
|
|
4182
4485
|
);
|
|
4183
4486
|
server.tool(
|
|
4184
4487
|
"present_files",
|
|
4185
4488
|
"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.",
|
|
4186
4489
|
{
|
|
4187
|
-
title:
|
|
4188
|
-
datasource:
|
|
4189
|
-
path:
|
|
4190
|
-
pane_id:
|
|
4490
|
+
title: z8.string().optional().describe("Pane title (default: Files)"),
|
|
4491
|
+
datasource: z8.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
4492
|
+
path: z8.string().optional().describe("Initial folder path (e.g. public)"),
|
|
4493
|
+
pane_id: z8.string().optional().describe("Stable pane id across updates")
|
|
4191
4494
|
},
|
|
4192
4495
|
async (args) => {
|
|
4193
4496
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -4199,21 +4502,21 @@ async function startMcpServer() {
|
|
|
4199
4502
|
path: args.path,
|
|
4200
4503
|
message: "Files pane accepted. Sideboard desktop opens the Files column beside chat."
|
|
4201
4504
|
};
|
|
4202
|
-
return
|
|
4505
|
+
return mcpJson(payload);
|
|
4203
4506
|
}
|
|
4204
4507
|
);
|
|
4205
4508
|
server.tool(
|
|
4206
4509
|
"wait_for_job",
|
|
4207
4510
|
"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.",
|
|
4208
4511
|
{
|
|
4209
|
-
id:
|
|
4210
|
-
timeoutMs:
|
|
4512
|
+
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
|
|
4513
|
+
timeoutMs: z8.number().optional()
|
|
4211
4514
|
},
|
|
4212
4515
|
async ({ id, timeoutMs }) => {
|
|
4213
4516
|
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
4214
4517
|
timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
|
|
4215
4518
|
});
|
|
4216
|
-
return
|
|
4519
|
+
return mcpJson(result);
|
|
4217
4520
|
}
|
|
4218
4521
|
);
|
|
4219
4522
|
if (!worktreeProfile) {
|
|
@@ -4221,63 +4524,51 @@ async function startMcpServer() {
|
|
|
4221
4524
|
registerConnectedIssueVendorTools(server);
|
|
4222
4525
|
registerScheduleTools(server);
|
|
4223
4526
|
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-SK2QISPE.js");
|
|
4224
|
-
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-
|
|
4527
|
+
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-JJQDWPEK.js");
|
|
4225
4528
|
const accountDefaults = resolveThreadDefaults2();
|
|
4226
4529
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
4227
4530
|
server.tool(
|
|
4228
4531
|
"set_caffeinate",
|
|
4229
4532
|
"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.",
|
|
4230
4533
|
{
|
|
4231
|
-
enabled:
|
|
4534
|
+
enabled: z8.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
4232
4535
|
},
|
|
4233
4536
|
async ({ enabled }) => {
|
|
4234
4537
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
4235
4538
|
const state = setCaffeinateHold2(enabled, { threadId });
|
|
4236
4539
|
if (enabled && !state.held) {
|
|
4237
|
-
return
|
|
4238
|
-
content: [
|
|
4239
|
-
{
|
|
4240
|
-
type: "text",
|
|
4241
|
-
text: JSON.stringify({
|
|
4242
|
-
...state,
|
|
4243
|
-
ok: false,
|
|
4244
|
-
message: state.platform === "darwin" ? "Could not start caffeinate." : "Caffeinate is macOS only."
|
|
4245
|
-
})
|
|
4246
|
-
}
|
|
4247
|
-
],
|
|
4248
|
-
isError: true
|
|
4249
|
-
};
|
|
4250
|
-
}
|
|
4251
|
-
return {
|
|
4252
|
-
content: [
|
|
4540
|
+
return mcpJson(
|
|
4253
4541
|
{
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4542
|
+
...state,
|
|
4543
|
+
ok: false,
|
|
4544
|
+
message: state.platform === "darwin" ? "Could not start caffeinate." : "Caffeinate is macOS only."
|
|
4545
|
+
},
|
|
4546
|
+
true
|
|
4547
|
+
);
|
|
4548
|
+
}
|
|
4549
|
+
return mcpJson({
|
|
4550
|
+
...getCaffeinateHold2(),
|
|
4551
|
+
ok: true,
|
|
4552
|
+
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)."
|
|
4553
|
+
});
|
|
4263
4554
|
}
|
|
4264
4555
|
);
|
|
4265
4556
|
server.tool(
|
|
4266
4557
|
"create_thread",
|
|
4267
4558
|
`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.`,
|
|
4268
4559
|
{
|
|
4269
|
-
sourceType:
|
|
4270
|
-
sourceRef:
|
|
4271
|
-
agent:
|
|
4272
|
-
model:
|
|
4560
|
+
sourceType: z8.enum(["branch", "pr", "ticket"]),
|
|
4561
|
+
sourceRef: z8.string(),
|
|
4562
|
+
agent: z8.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
4563
|
+
model: z8.string().nullable().optional().describe(
|
|
4273
4564
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
4274
4565
|
),
|
|
4275
|
-
repoPath:
|
|
4276
|
-
title:
|
|
4277
|
-
cowboy:
|
|
4566
|
+
repoPath: z8.string(),
|
|
4567
|
+
title: z8.string().optional(),
|
|
4568
|
+
cowboy: z8.boolean().optional().describe(
|
|
4278
4569
|
"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."
|
|
4279
4570
|
),
|
|
4280
|
-
parentThreadId:
|
|
4571
|
+
parentThreadId: z8.string().optional().describe(
|
|
4281
4572
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
4282
4573
|
)
|
|
4283
4574
|
},
|
|
@@ -4293,10 +4584,10 @@ async function startMcpServer() {
|
|
|
4293
4584
|
"start_board_card",
|
|
4294
4585
|
"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.",
|
|
4295
4586
|
{
|
|
4296
|
-
kind:
|
|
4297
|
-
ref:
|
|
4298
|
-
repoPath:
|
|
4299
|
-
title:
|
|
4587
|
+
kind: z8.enum(["ticket", "pr", "branch"]),
|
|
4588
|
+
ref: z8.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
4589
|
+
repoPath: z8.string().describe("Workspace path from list_workspaces / list_board"),
|
|
4590
|
+
title: z8.string().optional()
|
|
4300
4591
|
},
|
|
4301
4592
|
async ({ kind, ref, repoPath, title }) => {
|
|
4302
4593
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4313,20 +4604,13 @@ async function startMcpServer() {
|
|
|
4313
4604
|
}))
|
|
4314
4605
|
);
|
|
4315
4606
|
if (existing) {
|
|
4316
|
-
return {
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
title: existing.title,
|
|
4324
|
-
status: existing.status,
|
|
4325
|
-
link: `sideboard://thread/${existing.id}`
|
|
4326
|
-
})
|
|
4327
|
-
}
|
|
4328
|
-
]
|
|
4329
|
-
};
|
|
4607
|
+
return mcpJson({
|
|
4608
|
+
alreadyStarted: true,
|
|
4609
|
+
id: existing.id,
|
|
4610
|
+
title: existing.title,
|
|
4611
|
+
status: existing.status,
|
|
4612
|
+
link: `sideboard://thread/${existing.id}`
|
|
4613
|
+
});
|
|
4330
4614
|
}
|
|
4331
4615
|
const workspaces = orch.listWorkspaces();
|
|
4332
4616
|
const loaded = await getHomeBoardInputs(workspaces);
|
|
@@ -4400,9 +4684,9 @@ async function startMcpServer() {
|
|
|
4400
4684
|
"send_to_thread",
|
|
4401
4685
|
'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.',
|
|
4402
4686
|
{
|
|
4403
|
-
ref:
|
|
4404
|
-
prompt:
|
|
4405
|
-
force_stop:
|
|
4687
|
+
ref: z8.string(),
|
|
4688
|
+
prompt: z8.string(),
|
|
4689
|
+
force_stop: z8.boolean().optional()
|
|
4406
4690
|
},
|
|
4407
4691
|
async ({ ref, prompt, force_stop }) => {
|
|
4408
4692
|
if (force_stop) {
|
|
@@ -4412,79 +4696,58 @@ async function startMcpServer() {
|
|
|
4412
4696
|
}
|
|
4413
4697
|
}
|
|
4414
4698
|
const thread = await orch.send(ref, prompt);
|
|
4415
|
-
return {
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
status: thread.status,
|
|
4422
|
-
queueLength: thread.queue.length,
|
|
4423
|
-
forceStopped: Boolean(force_stop)
|
|
4424
|
-
})
|
|
4425
|
-
}
|
|
4426
|
-
]
|
|
4427
|
-
};
|
|
4699
|
+
return mcpJson({
|
|
4700
|
+
id: thread.id,
|
|
4701
|
+
status: thread.status,
|
|
4702
|
+
queueLength: thread.queue.length,
|
|
4703
|
+
forceStopped: Boolean(force_stop)
|
|
4704
|
+
});
|
|
4428
4705
|
}
|
|
4429
4706
|
);
|
|
4430
4707
|
server.tool(
|
|
4431
4708
|
"wait_for_turn",
|
|
4432
4709
|
"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).",
|
|
4433
4710
|
{
|
|
4434
|
-
ref:
|
|
4435
|
-
timeoutMs:
|
|
4711
|
+
ref: z8.string(),
|
|
4712
|
+
timeoutMs: z8.number().optional()
|
|
4436
4713
|
},
|
|
4437
4714
|
async ({ ref, timeoutMs }) => {
|
|
4438
4715
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
4439
4716
|
resolveIfStillRunning: true
|
|
4440
4717
|
});
|
|
4441
4718
|
const result = orch.getTurnResult(thread.id);
|
|
4442
|
-
return {
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
lastActivityAt: result.lastActivityAt,
|
|
4454
|
-
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
4455
|
-
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
4456
|
-
})
|
|
4457
|
-
}
|
|
4458
|
-
]
|
|
4459
|
-
};
|
|
4719
|
+
return mcpJson({
|
|
4720
|
+
id: thread.id,
|
|
4721
|
+
status: result.status,
|
|
4722
|
+
text: result.text,
|
|
4723
|
+
lastError: result.lastError,
|
|
4724
|
+
stillRunning: result.stillRunning,
|
|
4725
|
+
progress: result.progress,
|
|
4726
|
+
lastActivityAt: result.lastActivityAt,
|
|
4727
|
+
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
4728
|
+
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
4729
|
+
});
|
|
4460
4730
|
}
|
|
4461
4731
|
);
|
|
4462
4732
|
server.tool(
|
|
4463
4733
|
"get_turn_result",
|
|
4464
4734
|
"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).",
|
|
4465
|
-
{ ref:
|
|
4735
|
+
{ ref: z8.string() },
|
|
4466
4736
|
async ({ ref }) => {
|
|
4467
4737
|
const result = orch.getTurnResult(ref);
|
|
4468
|
-
return {
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
...result,
|
|
4474
|
-
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
4475
|
-
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
4476
|
-
})
|
|
4477
|
-
}
|
|
4478
|
-
]
|
|
4479
|
-
};
|
|
4738
|
+
return mcpJson({
|
|
4739
|
+
...result,
|
|
4740
|
+
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
4741
|
+
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
4742
|
+
});
|
|
4480
4743
|
}
|
|
4481
4744
|
);
|
|
4482
4745
|
server.tool(
|
|
4483
4746
|
"stop_thread",
|
|
4484
4747
|
"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.",
|
|
4485
4748
|
{
|
|
4486
|
-
ref:
|
|
4487
|
-
force:
|
|
4749
|
+
ref: z8.string(),
|
|
4750
|
+
force: z8.boolean().optional()
|
|
4488
4751
|
},
|
|
4489
4752
|
async ({ ref, force }) => {
|
|
4490
4753
|
const t = orch.getThread(ref);
|
|
@@ -4497,24 +4760,17 @@ async function startMcpServer() {
|
|
|
4497
4760
|
const clearQueue = force !== false;
|
|
4498
4761
|
const hadQueued = t.queue.length > 0;
|
|
4499
4762
|
const stopped = orch.stop(ref, { clearQueue, notifyParent: false });
|
|
4500
|
-
return {
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
id: stopped.id,
|
|
4506
|
-
status: stopped.status,
|
|
4507
|
-
clearedQueue: clearQueue && hadQueued
|
|
4508
|
-
})
|
|
4509
|
-
}
|
|
4510
|
-
]
|
|
4511
|
-
};
|
|
4763
|
+
return mcpJson({
|
|
4764
|
+
id: stopped.id,
|
|
4765
|
+
status: stopped.status,
|
|
4766
|
+
clearedQueue: clearQueue && hadQueued
|
|
4767
|
+
});
|
|
4512
4768
|
}
|
|
4513
4769
|
);
|
|
4514
4770
|
server.tool(
|
|
4515
4771
|
"archive_thread",
|
|
4516
4772
|
"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.",
|
|
4517
|
-
{ ref:
|
|
4773
|
+
{ ref: z8.string() },
|
|
4518
4774
|
async ({ ref }) => {
|
|
4519
4775
|
const t = orch.getThread(ref);
|
|
4520
4776
|
if (!t) {
|
|
@@ -4531,38 +4787,24 @@ async function startMcpServer() {
|
|
|
4531
4787
|
};
|
|
4532
4788
|
}
|
|
4533
4789
|
const archived = await orch.archive(ref);
|
|
4534
|
-
return {
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
text: JSON.stringify({
|
|
4539
|
-
id: archived.id,
|
|
4540
|
-
status: archived.status
|
|
4541
|
-
})
|
|
4542
|
-
}
|
|
4543
|
-
]
|
|
4544
|
-
};
|
|
4790
|
+
return mcpJson({
|
|
4791
|
+
id: archived.id,
|
|
4792
|
+
status: archived.status
|
|
4793
|
+
});
|
|
4545
4794
|
}
|
|
4546
4795
|
);
|
|
4547
4796
|
server.tool(
|
|
4548
4797
|
"restore_thread",
|
|
4549
4798
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
4550
|
-
{ ref:
|
|
4799
|
+
{ ref: z8.string() },
|
|
4551
4800
|
async ({ ref }) => {
|
|
4552
4801
|
try {
|
|
4553
4802
|
const restored = await orch.restore(ref);
|
|
4554
|
-
return {
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
id: restored.id,
|
|
4560
|
-
status: restored.status,
|
|
4561
|
-
worktreePath: restored.worktreePath
|
|
4562
|
-
})
|
|
4563
|
-
}
|
|
4564
|
-
]
|
|
4565
|
-
};
|
|
4803
|
+
return mcpJson({
|
|
4804
|
+
id: restored.id,
|
|
4805
|
+
status: restored.status,
|
|
4806
|
+
worktreePath: restored.worktreePath
|
|
4807
|
+
});
|
|
4566
4808
|
} catch (err) {
|
|
4567
4809
|
const message = err instanceof Error ? err.message : String(err);
|
|
4568
4810
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -4573,34 +4815,25 @@ async function startMcpServer() {
|
|
|
4573
4815
|
"get_diff",
|
|
4574
4816
|
"Compact diff summary (capped hunks, paginated)",
|
|
4575
4817
|
{
|
|
4576
|
-
ref:
|
|
4577
|
-
maxFiles:
|
|
4818
|
+
ref: z8.string(),
|
|
4819
|
+
maxFiles: z8.number().optional()
|
|
4578
4820
|
},
|
|
4579
4821
|
async ({ ref, maxFiles }) => {
|
|
4580
4822
|
const summary = await orch.diffSummary(ref);
|
|
4581
|
-
return {
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
text: JSON.stringify({
|
|
4586
|
-
...summary,
|
|
4587
|
-
files: summary.files.slice(0, maxFiles ?? 10)
|
|
4588
|
-
})
|
|
4589
|
-
}
|
|
4590
|
-
]
|
|
4591
|
-
};
|
|
4823
|
+
return mcpJson({
|
|
4824
|
+
...summary,
|
|
4825
|
+
files: summary.files.slice(0, maxFiles ?? 10)
|
|
4826
|
+
});
|
|
4592
4827
|
}
|
|
4593
4828
|
);
|
|
4594
4829
|
server.tool(
|
|
4595
4830
|
"get_pr_checks",
|
|
4596
4831
|
"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.",
|
|
4597
|
-
{ ref:
|
|
4832
|
+
{ ref: z8.string().describe("Worktree thread id/ref") },
|
|
4598
4833
|
async ({ ref }) => {
|
|
4599
4834
|
try {
|
|
4600
4835
|
const checks = await orch.getPrChecks(ref);
|
|
4601
|
-
return
|
|
4602
|
-
content: [{ type: "text", text: JSON.stringify(checks) }]
|
|
4603
|
-
};
|
|
4836
|
+
return mcpJson(checks);
|
|
4604
4837
|
} catch (err) {
|
|
4605
4838
|
const message = err instanceof Error ? err.message : String(err);
|
|
4606
4839
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -4610,25 +4843,18 @@ async function startMcpServer() {
|
|
|
4610
4843
|
server.tool(
|
|
4611
4844
|
"request_review",
|
|
4612
4845
|
'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.',
|
|
4613
|
-
{ ref:
|
|
4846
|
+
{ ref: z8.string().describe("Worktree thread id/ref to review") },
|
|
4614
4847
|
async ({ ref }) => {
|
|
4615
4848
|
try {
|
|
4616
4849
|
const tab = await orch.requestReview(ref);
|
|
4617
4850
|
const from = orch.getThread(ref);
|
|
4618
|
-
return {
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
status: tab.status,
|
|
4626
|
-
fromThreadId: from?.id ?? ref,
|
|
4627
|
-
link: `sideboard://thread/${tab.id}`
|
|
4628
|
-
})
|
|
4629
|
-
}
|
|
4630
|
-
]
|
|
4631
|
-
};
|
|
4851
|
+
return mcpJson({
|
|
4852
|
+
id: tab.id,
|
|
4853
|
+
title: tab.title,
|
|
4854
|
+
status: tab.status,
|
|
4855
|
+
fromThreadId: from?.id ?? ref,
|
|
4856
|
+
link: `sideboard://thread/${tab.id}`
|
|
4857
|
+
});
|
|
4632
4858
|
} catch (err) {
|
|
4633
4859
|
const message = err instanceof Error ? err.message : String(err);
|
|
4634
4860
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -4639,53 +4865,37 @@ async function startMcpServer() {
|
|
|
4639
4865
|
"ask_git",
|
|
4640
4866
|
"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.",
|
|
4641
4867
|
{
|
|
4642
|
-
ref:
|
|
4643
|
-
action:
|
|
4868
|
+
ref: z8.string().describe("Worktree thread id/ref"),
|
|
4869
|
+
action: z8.enum(AGENT_GIT_ACTIONS).describe(
|
|
4644
4870
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
4645
4871
|
)
|
|
4646
4872
|
},
|
|
4647
4873
|
async ({ ref, action }) => {
|
|
4648
4874
|
try {
|
|
4649
4875
|
const thread = await orch.askGit(ref, action);
|
|
4650
|
-
return {
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
queueLength: thread.queue.length,
|
|
4658
|
-
action,
|
|
4659
|
-
link: `sideboard://thread/${thread.id}`
|
|
4660
|
-
})
|
|
4661
|
-
}
|
|
4662
|
-
]
|
|
4663
|
-
};
|
|
4876
|
+
return mcpJson({
|
|
4877
|
+
id: thread.id,
|
|
4878
|
+
status: thread.status,
|
|
4879
|
+
queueLength: thread.queue.length,
|
|
4880
|
+
action,
|
|
4881
|
+
link: `sideboard://thread/${thread.id}`
|
|
4882
|
+
});
|
|
4664
4883
|
} catch (err) {
|
|
4665
4884
|
const raw = err instanceof Error ? err.message : String(err);
|
|
4666
4885
|
const message = formatGhLandError(raw);
|
|
4667
|
-
return
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
hint: /body is too long/i.test(message) ? "Branch is already pushed. send_to_thread so the worktree agent runs gh pr create --draft -R <origin> --body-file <short.md>. Keep the description short." : void 0
|
|
4677
|
-
},
|
|
4678
|
-
null,
|
|
4679
|
-
2
|
|
4680
|
-
)
|
|
4681
|
-
}
|
|
4682
|
-
],
|
|
4683
|
-
isError: true
|
|
4684
|
-
};
|
|
4886
|
+
return mcpJson(
|
|
4887
|
+
{
|
|
4888
|
+
error: true,
|
|
4889
|
+
action,
|
|
4890
|
+
lastError: message,
|
|
4891
|
+
hint: /body is too long/i.test(message) ? "Branch is already pushed. send_to_thread so the worktree agent runs gh pr create --draft -R <origin> --body-file <short.md>. Keep the description short." : void 0
|
|
4892
|
+
},
|
|
4893
|
+
true
|
|
4894
|
+
);
|
|
4685
4895
|
}
|
|
4686
4896
|
}
|
|
4687
4897
|
);
|
|
4688
|
-
const agentEnum =
|
|
4898
|
+
const agentEnum = z8.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
4689
4899
|
server.tool(
|
|
4690
4900
|
"list_models",
|
|
4691
4901
|
"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.",
|
|
@@ -4695,9 +4905,7 @@ async function startMcpServer() {
|
|
|
4695
4905
|
async ({ agent }) => {
|
|
4696
4906
|
try {
|
|
4697
4907
|
const catalogs = await listModelsForAgent(agent);
|
|
4698
|
-
return
|
|
4699
|
-
content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
|
|
4700
|
-
};
|
|
4908
|
+
return mcpJson(catalogs);
|
|
4701
4909
|
} catch (err) {
|
|
4702
4910
|
const message = err instanceof Error ? err.message : String(err);
|
|
4703
4911
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -4708,11 +4916,11 @@ async function startMcpServer() {
|
|
|
4708
4916
|
"fork_worktree",
|
|
4709
4917
|
"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.",
|
|
4710
4918
|
{
|
|
4711
|
-
ref:
|
|
4712
|
-
through_index:
|
|
4919
|
+
ref: z8.string().describe("Worktree thread id/ref to fork"),
|
|
4920
|
+
through_index: z8.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
4713
4921
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4714
|
-
model:
|
|
4715
|
-
title:
|
|
4922
|
+
model: z8.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
4923
|
+
title: z8.string().optional()
|
|
4716
4924
|
},
|
|
4717
4925
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4718
4926
|
try {
|
|
@@ -4725,24 +4933,17 @@ async function startMcpServer() {
|
|
|
4725
4933
|
model,
|
|
4726
4934
|
title
|
|
4727
4935
|
});
|
|
4728
|
-
return {
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
worktreePath: thread.worktreePath,
|
|
4740
|
-
fromThreadId: source?.id ?? ref,
|
|
4741
|
-
link: `sideboard://thread/${thread.id}`
|
|
4742
|
-
})
|
|
4743
|
-
}
|
|
4744
|
-
]
|
|
4745
|
-
};
|
|
4936
|
+
return mcpJson({
|
|
4937
|
+
id: thread.id,
|
|
4938
|
+
title: thread.title,
|
|
4939
|
+
status: thread.status,
|
|
4940
|
+
agent: thread.agent,
|
|
4941
|
+
model: thread.model,
|
|
4942
|
+
branchName: thread.branchName,
|
|
4943
|
+
worktreePath: thread.worktreePath,
|
|
4944
|
+
fromThreadId: source?.id ?? ref,
|
|
4945
|
+
link: `sideboard://thread/${thread.id}`
|
|
4946
|
+
});
|
|
4746
4947
|
} catch (err) {
|
|
4747
4948
|
const message = err instanceof Error ? err.message : String(err);
|
|
4748
4949
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -4753,11 +4954,11 @@ async function startMcpServer() {
|
|
|
4753
4954
|
"fork_chat",
|
|
4754
4955
|
"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.",
|
|
4755
4956
|
{
|
|
4756
|
-
ref:
|
|
4757
|
-
through_index:
|
|
4957
|
+
ref: z8.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
4958
|
+
through_index: z8.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
4758
4959
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
4759
|
-
model:
|
|
4760
|
-
title:
|
|
4960
|
+
model: z8.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
4961
|
+
title: z8.string().optional()
|
|
4761
4962
|
},
|
|
4762
4963
|
async ({ ref, through_index, agent, model, title }) => {
|
|
4763
4964
|
try {
|
|
@@ -4775,24 +4976,17 @@ async function startMcpServer() {
|
|
|
4775
4976
|
model,
|
|
4776
4977
|
title
|
|
4777
4978
|
});
|
|
4778
|
-
return {
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
worktreePath: tab.worktreePath,
|
|
4790
|
-
fromThreadId: source.id,
|
|
4791
|
-
link: `sideboard://thread/${tab.id}`
|
|
4792
|
-
})
|
|
4793
|
-
}
|
|
4794
|
-
]
|
|
4795
|
-
};
|
|
4979
|
+
return mcpJson({
|
|
4980
|
+
id: tab.id,
|
|
4981
|
+
title: tab.title,
|
|
4982
|
+
status: tab.status,
|
|
4983
|
+
agent: tab.agent,
|
|
4984
|
+
model: tab.model,
|
|
4985
|
+
sourceType: tab.sourceType,
|
|
4986
|
+
worktreePath: tab.worktreePath,
|
|
4987
|
+
fromThreadId: source.id,
|
|
4988
|
+
link: `sideboard://thread/${tab.id}`
|
|
4989
|
+
});
|
|
4796
4990
|
} catch (err) {
|
|
4797
4991
|
const message = err instanceof Error ? err.message : String(err);
|
|
4798
4992
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -4803,49 +4997,35 @@ async function startMcpServer() {
|
|
|
4803
4997
|
"run_dev_script",
|
|
4804
4998
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
4805
4999
|
{
|
|
4806
|
-
ref:
|
|
4807
|
-
name:
|
|
5000
|
+
ref: z8.string(),
|
|
5001
|
+
name: z8.string().optional()
|
|
4808
5002
|
},
|
|
4809
5003
|
async ({ ref, name }) => {
|
|
4810
5004
|
const result = await orch.startDev(ref, name);
|
|
4811
|
-
return {
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
scriptName: result.scriptName,
|
|
4818
|
-
ports: result.ports,
|
|
4819
|
-
url: `http://localhost:${result.port}`
|
|
4820
|
-
})
|
|
4821
|
-
}
|
|
4822
|
-
]
|
|
4823
|
-
};
|
|
5005
|
+
return mcpJson({
|
|
5006
|
+
port: result.port,
|
|
5007
|
+
scriptName: result.scriptName,
|
|
5008
|
+
ports: result.ports,
|
|
5009
|
+
url: `http://localhost:${result.port}`
|
|
5010
|
+
});
|
|
4824
5011
|
}
|
|
4825
5012
|
);
|
|
4826
5013
|
server.tool(
|
|
4827
5014
|
"list_run_scripts",
|
|
4828
5015
|
"List named run scripts available for a thread",
|
|
4829
|
-
{ ref:
|
|
5016
|
+
{ ref: z8.string() },
|
|
4830
5017
|
async ({ ref }) => {
|
|
4831
5018
|
const scripts = orch.listThreadRunScripts(ref);
|
|
4832
5019
|
const active = orch.getActiveRuns(ref);
|
|
4833
|
-
return {
|
|
4834
|
-
content: [
|
|
4835
|
-
{
|
|
4836
|
-
type: "text",
|
|
4837
|
-
text: JSON.stringify({ scripts, active }, null, 2)
|
|
4838
|
-
}
|
|
4839
|
-
]
|
|
4840
|
-
};
|
|
5020
|
+
return mcpJson({ scripts, active });
|
|
4841
5021
|
}
|
|
4842
5022
|
);
|
|
4843
5023
|
server.tool(
|
|
4844
5024
|
"stop_dev_script",
|
|
4845
5025
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
4846
5026
|
{
|
|
4847
|
-
ref:
|
|
4848
|
-
name:
|
|
5027
|
+
ref: z8.string(),
|
|
5028
|
+
name: z8.string().optional()
|
|
4849
5029
|
},
|
|
4850
5030
|
async ({ ref, name }) => {
|
|
4851
5031
|
orch.stopDev(ref, name);
|
|
@@ -4855,27 +5035,25 @@ async function startMcpServer() {
|
|
|
4855
5035
|
server.tool(
|
|
4856
5036
|
"run_setup",
|
|
4857
5037
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
4858
|
-
{ ref:
|
|
5038
|
+
{ ref: z8.string() },
|
|
4859
5039
|
async ({ ref }) => {
|
|
4860
5040
|
const result = await orch.runSetup(ref);
|
|
4861
|
-
return
|
|
4862
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
4863
|
-
};
|
|
5041
|
+
return mcpJson(result);
|
|
4864
5042
|
}
|
|
4865
5043
|
);
|
|
4866
5044
|
server.tool(
|
|
4867
5045
|
"add_workspace",
|
|
4868
5046
|
"Register a git repo as a Sideboard workspace",
|
|
4869
|
-
{ repoPath:
|
|
5047
|
+
{ repoPath: z8.string() },
|
|
4870
5048
|
async ({ repoPath }) => {
|
|
4871
5049
|
const ws = await orch.addWorkspace(repoPath);
|
|
4872
|
-
return
|
|
5050
|
+
return mcpJson(ws);
|
|
4873
5051
|
}
|
|
4874
5052
|
);
|
|
4875
5053
|
server.tool(
|
|
4876
5054
|
"remove_workspace",
|
|
4877
5055
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
4878
|
-
{ repoPath:
|
|
5056
|
+
{ repoPath: z8.string() },
|
|
4879
5057
|
async ({ repoPath }) => {
|
|
4880
5058
|
orch.removeWorkspace(repoPath);
|
|
4881
5059
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -4885,40 +5063,31 @@ async function startMcpServer() {
|
|
|
4885
5063
|
"fanout",
|
|
4886
5064
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
4887
5065
|
{
|
|
4888
|
-
prompt:
|
|
4889
|
-
agents:
|
|
4890
|
-
repoPath:
|
|
4891
|
-
sourceType:
|
|
4892
|
-
sourceRef:
|
|
4893
|
-
title:
|
|
5066
|
+
prompt: z8.string(),
|
|
5067
|
+
agents: z8.array(z8.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
5068
|
+
repoPath: z8.string(),
|
|
5069
|
+
sourceType: z8.enum(["branch", "pr", "ticket"]).optional(),
|
|
5070
|
+
sourceRef: z8.string().optional(),
|
|
5071
|
+
title: z8.string().optional()
|
|
4894
5072
|
},
|
|
4895
5073
|
async (args) => {
|
|
4896
5074
|
const threads = await orch.bestOfN(args);
|
|
4897
|
-
return
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
branchName: t.branchName,
|
|
4906
|
-
worktreePath: t.worktreePath
|
|
4907
|
-
})),
|
|
4908
|
-
null,
|
|
4909
|
-
2
|
|
4910
|
-
)
|
|
4911
|
-
}
|
|
4912
|
-
]
|
|
4913
|
-
};
|
|
5075
|
+
return mcpJson(
|
|
5076
|
+
threads.map((t) => ({
|
|
5077
|
+
id: t.id,
|
|
5078
|
+
agent: t.agent,
|
|
5079
|
+
branchName: t.branchName,
|
|
5080
|
+
worktreePath: t.worktreePath
|
|
5081
|
+
}))
|
|
5082
|
+
);
|
|
4914
5083
|
}
|
|
4915
5084
|
);
|
|
4916
5085
|
server.tool(
|
|
4917
5086
|
"list_branches",
|
|
4918
5087
|
"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).",
|
|
4919
5088
|
{
|
|
4920
|
-
repoPath:
|
|
4921
|
-
unmergedOnly:
|
|
5089
|
+
repoPath: z8.string(),
|
|
5090
|
+
unmergedOnly: z8.boolean().optional()
|
|
4922
5091
|
},
|
|
4923
5092
|
async ({ repoPath, unmergedOnly }) => {
|
|
4924
5093
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4939,19 +5108,19 @@ async function startMcpServer() {
|
|
|
4939
5108
|
"list_prs",
|
|
4940
5109
|
'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.',
|
|
4941
5110
|
{
|
|
4942
|
-
repoPath:
|
|
4943
|
-
query:
|
|
4944
|
-
queue:
|
|
5111
|
+
repoPath: z8.string(),
|
|
5112
|
+
query: z8.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
5113
|
+
queue: z8.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
4945
5114
|
'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
|
|
4946
5115
|
),
|
|
4947
|
-
state:
|
|
4948
|
-
label:
|
|
5116
|
+
state: z8.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
5117
|
+
label: z8.string().optional().describe(
|
|
4949
5118
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
4950
5119
|
),
|
|
4951
|
-
reviewer:
|
|
5120
|
+
reviewer: z8.string().optional().describe(
|
|
4952
5121
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
4953
5122
|
),
|
|
4954
|
-
limit:
|
|
5123
|
+
limit: z8.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
4955
5124
|
},
|
|
4956
5125
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
4957
5126
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -4983,89 +5152,65 @@ async function startMcpServer() {
|
|
|
4983
5152
|
server.tool(
|
|
4984
5153
|
"get_pr_stack",
|
|
4985
5154
|
"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).",
|
|
4986
|
-
{ ref:
|
|
5155
|
+
{ ref: z8.string() },
|
|
4987
5156
|
async ({ ref }) => {
|
|
4988
5157
|
const stack = await orch.getPrStack(ref);
|
|
4989
|
-
return
|
|
4990
|
-
content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
|
|
4991
|
-
};
|
|
5158
|
+
return mcpJson(stack);
|
|
4992
5159
|
}
|
|
4993
5160
|
);
|
|
4994
5161
|
server.tool(
|
|
4995
5162
|
"open_pr_stack_layers",
|
|
4996
5163
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
4997
5164
|
{
|
|
4998
|
-
ref:
|
|
4999
|
-
layer:
|
|
5165
|
+
ref: z8.string(),
|
|
5166
|
+
layer: z8.number().int().positive().optional()
|
|
5000
5167
|
},
|
|
5001
5168
|
async ({ ref, layer }) => {
|
|
5002
5169
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
5003
|
-
return {
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
worktreePath: t.worktreePath,
|
|
5017
|
-
prUrl: t.prUrl,
|
|
5018
|
-
link: `sideboard://thread/${t.id}`
|
|
5019
|
-
}))
|
|
5020
|
-
},
|
|
5021
|
-
null,
|
|
5022
|
-
2
|
|
5023
|
-
)
|
|
5024
|
-
}
|
|
5025
|
-
]
|
|
5026
|
-
};
|
|
5170
|
+
return mcpJson({
|
|
5171
|
+
stackNumber: result.stack.stackNumber,
|
|
5172
|
+
trunk: result.stack.trunk,
|
|
5173
|
+
threads: result.threads.map((t) => ({
|
|
5174
|
+
id: t.id,
|
|
5175
|
+
title: t.title,
|
|
5176
|
+
branchName: t.branchName,
|
|
5177
|
+
stackLayer: t.stackLayer,
|
|
5178
|
+
worktreePath: t.worktreePath,
|
|
5179
|
+
prUrl: t.prUrl,
|
|
5180
|
+
link: `sideboard://thread/${t.id}`
|
|
5181
|
+
}))
|
|
5182
|
+
});
|
|
5027
5183
|
}
|
|
5028
5184
|
);
|
|
5029
5185
|
server.tool(
|
|
5030
5186
|
"add_stack_layer",
|
|
5031
5187
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
5032
5188
|
{
|
|
5033
|
-
ref:
|
|
5034
|
-
branchName:
|
|
5035
|
-
title:
|
|
5189
|
+
ref: z8.string(),
|
|
5190
|
+
branchName: z8.string(),
|
|
5191
|
+
title: z8.string().optional()
|
|
5036
5192
|
},
|
|
5037
5193
|
async ({ ref, branchName, title }) => {
|
|
5038
5194
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
5039
|
-
return {
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
branchName: result.thread.branchName,
|
|
5048
|
-
stackLayer: result.thread.stackLayer,
|
|
5049
|
-
worktreePath: result.thread.worktreePath,
|
|
5050
|
-
link: `sideboard://thread/${result.thread.id}`
|
|
5051
|
-
},
|
|
5052
|
-
null,
|
|
5053
|
-
2
|
|
5054
|
-
)
|
|
5055
|
-
}
|
|
5056
|
-
]
|
|
5057
|
-
};
|
|
5195
|
+
return mcpJson({
|
|
5196
|
+
id: result.thread.id,
|
|
5197
|
+
title: result.thread.title,
|
|
5198
|
+
branchName: result.thread.branchName,
|
|
5199
|
+
stackLayer: result.thread.stackLayer,
|
|
5200
|
+
worktreePath: result.thread.worktreePath,
|
|
5201
|
+
link: `sideboard://thread/${result.thread.id}`
|
|
5202
|
+
});
|
|
5058
5203
|
}
|
|
5059
5204
|
);
|
|
5060
5205
|
server.tool(
|
|
5061
5206
|
"create_pr_stack",
|
|
5062
5207
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
5063
5208
|
{
|
|
5064
|
-
repoPath:
|
|
5065
|
-
branches:
|
|
5066
|
-
agent:
|
|
5067
|
-
base:
|
|
5068
|
-
title:
|
|
5209
|
+
repoPath: z8.string(),
|
|
5210
|
+
branches: z8.array(z8.string()).min(1),
|
|
5211
|
+
agent: z8.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
5212
|
+
base: z8.string().optional(),
|
|
5213
|
+
title: z8.string().optional()
|
|
5069
5214
|
},
|
|
5070
5215
|
async (args) => {
|
|
5071
5216
|
const result = await orch.createPrStack({
|
|
@@ -5075,39 +5220,28 @@ async function startMcpServer() {
|
|
|
5075
5220
|
base: args.base,
|
|
5076
5221
|
title: args.title
|
|
5077
5222
|
});
|
|
5078
|
-
return {
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
stackLayer: t.stackLayer,
|
|
5091
|
-
worktreePath: t.worktreePath,
|
|
5092
|
-
link: `sideboard://thread/${t.id}`
|
|
5093
|
-
}))
|
|
5094
|
-
},
|
|
5095
|
-
null,
|
|
5096
|
-
2
|
|
5097
|
-
)
|
|
5098
|
-
}
|
|
5099
|
-
]
|
|
5100
|
-
};
|
|
5223
|
+
return mcpJson({
|
|
5224
|
+
stackNumber: result.stack.stackNumber,
|
|
5225
|
+
trunk: result.stack.trunk,
|
|
5226
|
+
threads: result.threads.map((t) => ({
|
|
5227
|
+
id: t.id,
|
|
5228
|
+
title: t.title,
|
|
5229
|
+
branchName: t.branchName,
|
|
5230
|
+
stackLayer: t.stackLayer,
|
|
5231
|
+
worktreePath: t.worktreePath,
|
|
5232
|
+
link: `sideboard://thread/${t.id}`
|
|
5233
|
+
}))
|
|
5234
|
+
});
|
|
5101
5235
|
}
|
|
5102
5236
|
);
|
|
5103
5237
|
server.tool(
|
|
5104
5238
|
"list_issues",
|
|
5105
5239
|
"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.",
|
|
5106
5240
|
{
|
|
5107
|
-
repoPath:
|
|
5108
|
-
query:
|
|
5109
|
-
assignee:
|
|
5110
|
-
limit:
|
|
5241
|
+
repoPath: z8.string(),
|
|
5242
|
+
query: z8.string().optional().describe("Search title, identifier, or description"),
|
|
5243
|
+
assignee: z8.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
5244
|
+
limit: z8.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
5111
5245
|
},
|
|
5112
5246
|
async ({ repoPath, query, assignee, limit }) => {
|
|
5113
5247
|
const root = await resolveRepoRoot(repoPath);
|