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