@sideboard-ai/core 0.1.159 → 0.1.161
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{abletime-LK7GV556.js → abletime-AGF54TNA.js} +2 -2
- package/dist/{abletime-HOQBDQC4.js → abletime-BMS53RIZ.js} +2 -2
- package/dist/{agents-67ZBH5OE.js → agents-6UOXBORU.js} +4 -4
- package/dist/{agents-5HY76BCR.js → agents-RD774UEV.js} +4 -4
- package/dist/{app-settings-BJ5GFAE5.js → app-settings-BRLZQA7N.js} +1 -1
- package/dist/{app-settings-YWJ4MVYM.js → app-settings-JJQDWPEK.js} +1 -1
- package/dist/{chunk-DWUOKF3W.js → chunk-7OWRFJK6.js} +4 -4
- package/dist/{chunk-FLG4A5FJ.js → chunk-CVZ4KXVP.js} +8 -3
- package/dist/{chunk-TGU3LSDG.js → chunk-EZANYS5D.js} +22 -22
- package/dist/{chunk-SIMFKJIB.js → chunk-HZROPNYL.js} +2 -2
- package/dist/{chunk-AGLPYXDT.js → chunk-ILXKJ4ZD.js} +1 -1
- package/dist/{chunk-MEZXWELT.js → chunk-IO5WGZRR.js} +1 -1
- package/dist/{chunk-UBMCEZRE.js → chunk-KJFFQAMA.js} +1 -1
- package/dist/{chunk-EQIJ5MHR.js → chunk-LTXS26K5.js} +8 -3
- package/dist/{chunk-OYOXLY6B.js → chunk-MIX4R72K.js} +23 -23
- package/dist/{chunk-O7RWWPMR.js → chunk-QANLIT2E.js} +2 -2
- package/dist/{chunk-V4R6L2ZL.js → chunk-RQNXLIXO.js} +4 -4
- package/dist/{chunk-KEDHHXKW.js → chunk-UUEBNUEG.js} +1 -1
- package/dist/{coordinator-prompt-CNI6AK65.js → coordinator-prompt-X3LXE5Q4.js} +3 -3
- package/dist/{coordinator-prompt-7NKY7QRK.js → coordinator-prompt-YYUXC7EM.js} +3 -3
- package/dist/{global-workspace-FKAJZHO5.js → global-workspace-E6S5XQZF.js} +3 -3
- package/dist/{global-workspace-IJYCUO4M.js → global-workspace-SPR2Y4YQ.js} +3 -3
- package/dist/index.cjs +756 -614
- package/dist/index.js +746 -612
- package/dist/mcp/run-stdio.cjs +756 -614
- package/dist/mcp/run-stdio.js +746 -612
- package/dist/{orchestrator-NSVP7DJH.js → orchestrator-6FZBSP5B.js} +5 -5
- package/dist/{orchestrator-KFLS5BTP.js → orchestrator-QREP2CFO.js} +5 -5
- package/dist/{workspaces-D2M3AM2M.js → workspaces-MZELP4BI.js} +3 -3
- package/dist/{workspaces-OFSDJ7PZ.js → workspaces-OK2SNNQF.js} +3 -3
- package/dist/{worktree-BQE5KLRY.js → worktree-BX53JZHO.js} +2 -2
- package/dist/{worktree-EA6AD6D6.js → worktree-IY7YCEGH.js} +2 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -3544,14 +3544,19 @@ function findProjectProfileKey(projects, repoPath) {
|
|
|
3544
3544
|
const want = profileRepoKey(repoPath ?? "");
|
|
3545
3545
|
if (!want) return void 0;
|
|
3546
3546
|
if (projects[want]) return want;
|
|
3547
|
+
let best;
|
|
3548
|
+
let bestLen = 0;
|
|
3547
3549
|
for (const key of Object.keys(projects)) {
|
|
3548
3550
|
const stored = profileRepoKey(key);
|
|
3549
3551
|
if (!stored) continue;
|
|
3550
|
-
if (stored === want || want.startsWith(`${stored}/`)
|
|
3551
|
-
|
|
3552
|
+
if (stored === want || want.startsWith(`${stored}/`)) {
|
|
3553
|
+
if (stored.length > bestLen) {
|
|
3554
|
+
best = key;
|
|
3555
|
+
bestLen = stored.length;
|
|
3556
|
+
}
|
|
3552
3557
|
}
|
|
3553
3558
|
}
|
|
3554
|
-
return
|
|
3559
|
+
return best;
|
|
3555
3560
|
}
|
|
3556
3561
|
function normalizeAdvanced(raw) {
|
|
3557
3562
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -16377,13 +16382,13 @@ function formatStat(files) {
|
|
|
16377
16382
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
16378
16383
|
}
|
|
16379
16384
|
function emptyScopeStats() {
|
|
16380
|
-
const
|
|
16385
|
+
const z9 = emptyStat();
|
|
16381
16386
|
return {
|
|
16382
|
-
commits:
|
|
16383
|
-
uncommitted:
|
|
16384
|
-
staged:
|
|
16385
|
-
unstaged:
|
|
16386
|
-
last_turn:
|
|
16387
|
+
commits: z9,
|
|
16388
|
+
uncommitted: z9,
|
|
16389
|
+
staged: z9,
|
|
16390
|
+
unstaged: z9,
|
|
16391
|
+
last_turn: z9
|
|
16387
16392
|
};
|
|
16388
16393
|
}
|
|
16389
16394
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -17520,17 +17525,17 @@ function formatIssueToolsDirective(opts) {
|
|
|
17520
17525
|
];
|
|
17521
17526
|
if (opts.linear) {
|
|
17522
17527
|
lines.push(
|
|
17523
|
-
"- Linear: `linear_get_issue` (comments), `linear_comment`, `linear_update_issue` (state), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
|
|
17528
|
+
"- Linear: `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
|
|
17524
17529
|
);
|
|
17525
17530
|
}
|
|
17526
17531
|
if (github) {
|
|
17527
17532
|
lines.push(
|
|
17528
|
-
"- GitHub: `github_get_issue` (comments), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
|
|
17533
|
+
"- GitHub: `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
|
|
17529
17534
|
);
|
|
17530
17535
|
}
|
|
17531
17536
|
if (opts.abletime) {
|
|
17532
17537
|
lines.push(
|
|
17533
|
-
"- AbleTime: `abletime_get_task` (comments), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
|
|
17538
|
+
"- AbleTime: `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
|
|
17534
17539
|
);
|
|
17535
17540
|
}
|
|
17536
17541
|
lines.push(
|
|
@@ -20758,7 +20763,7 @@ init_nested_electron_env();
|
|
|
20758
20763
|
// src/mcp/server.ts
|
|
20759
20764
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
20760
20765
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
20761
|
-
var
|
|
20766
|
+
var import_zod8 = require("zod");
|
|
20762
20767
|
var import_node_crypto12 = require("crypto");
|
|
20763
20768
|
var import_node_path50 = require("path");
|
|
20764
20769
|
init_orchestrator();
|
|
@@ -21871,12 +21876,53 @@ ${link}` : link;
|
|
|
21871
21876
|
// src/mcp/slack-tools.ts
|
|
21872
21877
|
init_outbound_watch();
|
|
21873
21878
|
init_workspaces();
|
|
21874
|
-
|
|
21879
|
+
|
|
21880
|
+
// src/mcp/issue-list.ts
|
|
21881
|
+
var MCP_ISSUE_LIST_DEFAULT_LIMIT = 40;
|
|
21882
|
+
var MCP_ISSUE_LIST_MAX_LIMIT = 250;
|
|
21883
|
+
function clampMcpIssueLimit(limit) {
|
|
21884
|
+
if (limit == null || !Number.isFinite(limit)) return MCP_ISSUE_LIST_DEFAULT_LIMIT;
|
|
21885
|
+
return Math.max(1, Math.min(MCP_ISSUE_LIST_MAX_LIMIT, Math.floor(limit)));
|
|
21886
|
+
}
|
|
21887
|
+
function applyIssueListWindow(items, limit) {
|
|
21888
|
+
const truncated = items.length > limit;
|
|
21889
|
+
return {
|
|
21890
|
+
items: truncated ? items.slice(0, limit) : items,
|
|
21891
|
+
truncated
|
|
21892
|
+
};
|
|
21893
|
+
}
|
|
21894
|
+
function compactIssueRow(issue) {
|
|
21895
|
+
return {
|
|
21896
|
+
identifier: issue.identifier,
|
|
21897
|
+
title: issue.title,
|
|
21898
|
+
...issue.assignee ? { assignee: issue.assignee } : {},
|
|
21899
|
+
...issue.labels.length > 0 ? { labels: issue.labels } : {},
|
|
21900
|
+
...issue.teamKey ? { team: issue.teamKey } : {},
|
|
21901
|
+
...issue.cycle?.name ? { cycle: issue.cycle.name } : {}
|
|
21902
|
+
};
|
|
21903
|
+
}
|
|
21904
|
+
function formatMcpIssueList(input) {
|
|
21905
|
+
return {
|
|
21906
|
+
...input.source ? { source: input.source } : {},
|
|
21907
|
+
...input.viewer ? { viewer: input.viewer } : {},
|
|
21908
|
+
limit: input.limit,
|
|
21909
|
+
returned: input.issues.length,
|
|
21910
|
+
truncated: input.truncated,
|
|
21911
|
+
...input.truncated ? { hint: "More issues match. Pass a higher limit (max 250) or a tighter query." } : {},
|
|
21912
|
+
issues: input.issues.map(compactIssueRow)
|
|
21913
|
+
};
|
|
21914
|
+
}
|
|
21915
|
+
function mcpJson(payload, isError = false) {
|
|
21875
21916
|
return {
|
|
21876
|
-
content: [{ type: "text", text: JSON.stringify(payload
|
|
21917
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
21877
21918
|
...isError ? { isError: true } : {}
|
|
21878
21919
|
};
|
|
21879
21920
|
}
|
|
21921
|
+
|
|
21922
|
+
// src/mcp/slack-tools.ts
|
|
21923
|
+
function text(payload, isError = false) {
|
|
21924
|
+
return mcpJson(payload, isError);
|
|
21925
|
+
}
|
|
21880
21926
|
function fail(err) {
|
|
21881
21927
|
return text(
|
|
21882
21928
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
@@ -22145,49 +22191,331 @@ function registerSlackTools(server) {
|
|
|
22145
22191
|
init_app_settings();
|
|
22146
22192
|
|
|
22147
22193
|
// src/mcp/abletime-tools.ts
|
|
22194
|
+
var import_zod3 = require("zod");
|
|
22195
|
+
init_abletime();
|
|
22196
|
+
|
|
22197
|
+
// src/mcp/issue-payload.ts
|
|
22148
22198
|
var import_zod2 = require("zod");
|
|
22149
22199
|
init_abletime();
|
|
22150
22200
|
|
|
22151
|
-
// src/mcp/
|
|
22152
|
-
var
|
|
22153
|
-
var
|
|
22154
|
-
|
|
22155
|
-
|
|
22156
|
-
|
|
22201
|
+
// src/mcp/smart-crush.ts
|
|
22202
|
+
var MUST_KEEP_TEXT_RE = /\b(error|exception|failed|failure|fatal|critical|panic|traceback|npm err|assert)\b/i;
|
|
22203
|
+
var CRUSH_MIN_ITEMS = 5;
|
|
22204
|
+
var CRUSH_MIN_CHARS = 800;
|
|
22205
|
+
var CRUSH_COMMENT_MAX_CHARS = 16e3;
|
|
22206
|
+
var CRUSH_BODY_MIN_CHARS = 12e3;
|
|
22207
|
+
var CRUSH_BODY_MAX_CHARS = 8e3;
|
|
22208
|
+
var NEAR_DUP_JACCARD = 0.92;
|
|
22209
|
+
function looksLikeMustKeep(text7) {
|
|
22210
|
+
return MUST_KEEP_TEXT_RE.test(text7);
|
|
22211
|
+
}
|
|
22212
|
+
function normalizeText(text7) {
|
|
22213
|
+
return text7.replace(/\s+/g, " ").trim().toLowerCase();
|
|
22214
|
+
}
|
|
22215
|
+
function wordSet(text7) {
|
|
22216
|
+
return new Set(
|
|
22217
|
+
text7.toLowerCase().split(/\W+/).filter((w) => w.length > 2)
|
|
22218
|
+
);
|
|
22157
22219
|
}
|
|
22158
|
-
function
|
|
22159
|
-
|
|
22220
|
+
function jaccard(a, b) {
|
|
22221
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
22222
|
+
let inter = 0;
|
|
22223
|
+
for (const w of a) {
|
|
22224
|
+
if (b.has(w)) inter += 1;
|
|
22225
|
+
}
|
|
22226
|
+
const union = a.size + b.size - inter;
|
|
22227
|
+
return union === 0 ? 0 : inter / union;
|
|
22228
|
+
}
|
|
22229
|
+
function importance(text7) {
|
|
22230
|
+
let score = text7.length;
|
|
22231
|
+
if (/```/.test(text7)) score += 400;
|
|
22232
|
+
if (/https?:\/\//.test(text7)) score += 50;
|
|
22233
|
+
if (looksLikeMustKeep(text7)) score += 1e4;
|
|
22234
|
+
return score;
|
|
22235
|
+
}
|
|
22236
|
+
function meanStd(values) {
|
|
22237
|
+
if (values.length === 0) return { mean: 0, std: 0 };
|
|
22238
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
22239
|
+
const variance = values.reduce((a, b) => a + (b - mean) * (b - mean), 0) / values.length;
|
|
22240
|
+
return { mean, std: Math.sqrt(variance) };
|
|
22241
|
+
}
|
|
22242
|
+
function dedupTextItems(items, getText) {
|
|
22243
|
+
const kept = [];
|
|
22244
|
+
const keptNorm = [];
|
|
22245
|
+
const keptWords = [];
|
|
22246
|
+
for (const item of items) {
|
|
22247
|
+
const text7 = getText(item);
|
|
22248
|
+
const norm = normalizeText(text7);
|
|
22249
|
+
if (!norm) {
|
|
22250
|
+
kept.push(item);
|
|
22251
|
+
keptNorm.push(norm);
|
|
22252
|
+
keptWords.push(wordSet(text7));
|
|
22253
|
+
continue;
|
|
22254
|
+
}
|
|
22255
|
+
const words = wordSet(text7);
|
|
22256
|
+
let dupAt = -1;
|
|
22257
|
+
for (let i = 0; i < kept.length; i++) {
|
|
22258
|
+
if (keptNorm[i] === norm) {
|
|
22259
|
+
dupAt = i;
|
|
22260
|
+
break;
|
|
22261
|
+
}
|
|
22262
|
+
if (keptWords[i].size >= 8 && words.size >= 8 && jaccard(keptWords[i], words) >= NEAR_DUP_JACCARD) {
|
|
22263
|
+
dupAt = i;
|
|
22264
|
+
break;
|
|
22265
|
+
}
|
|
22266
|
+
}
|
|
22267
|
+
if (dupAt < 0) {
|
|
22268
|
+
kept.push(item);
|
|
22269
|
+
keptNorm.push(norm);
|
|
22270
|
+
keptWords.push(words);
|
|
22271
|
+
continue;
|
|
22272
|
+
}
|
|
22273
|
+
if (text7.length > getText(kept[dupAt]).length) {
|
|
22274
|
+
kept[dupAt] = item;
|
|
22275
|
+
keptNorm[dupAt] = norm;
|
|
22276
|
+
keptWords[dupAt] = words;
|
|
22277
|
+
}
|
|
22278
|
+
}
|
|
22279
|
+
return { items: kept, dropped: items.length - kept.length };
|
|
22280
|
+
}
|
|
22281
|
+
function crushTextItems(items, getText, opts = {}) {
|
|
22282
|
+
const minItems = opts.minItems ?? CRUSH_MIN_ITEMS;
|
|
22283
|
+
const minChars = opts.minChars ?? CRUSH_MIN_CHARS;
|
|
22284
|
+
const maxChars = opts.maxChars ?? CRUSH_COMMENT_MAX_CHARS;
|
|
22285
|
+
const original = items;
|
|
22286
|
+
const totalChars = items.reduce((n, item) => n + getText(item).length, 0);
|
|
22287
|
+
if (items.length < minItems || totalChars < minChars) {
|
|
22288
|
+
return {
|
|
22289
|
+
items,
|
|
22290
|
+
truncated: false,
|
|
22291
|
+
kept: items.length,
|
|
22292
|
+
dropped: 0,
|
|
22293
|
+
reason: "passthrough"
|
|
22294
|
+
};
|
|
22295
|
+
}
|
|
22296
|
+
const deduped = dedupTextItems(items, getText);
|
|
22297
|
+
const unique = deduped.items;
|
|
22298
|
+
const uniqueChars = unique.reduce((n, item) => n + getText(item).length, 0);
|
|
22299
|
+
if (uniqueChars <= maxChars) {
|
|
22300
|
+
const changed = unique.length < original.length;
|
|
22301
|
+
return {
|
|
22302
|
+
items: unique,
|
|
22303
|
+
truncated: changed,
|
|
22304
|
+
kept: unique.length,
|
|
22305
|
+
dropped: original.length - unique.length,
|
|
22306
|
+
reason: changed ? "dedup" : "passthrough"
|
|
22307
|
+
};
|
|
22308
|
+
}
|
|
22309
|
+
const lengths = unique.map((item) => getText(item).length);
|
|
22310
|
+
const { mean, std } = meanStd(lengths);
|
|
22311
|
+
const outlierFloor = mean + 2 * std;
|
|
22312
|
+
const mustKeep = /* @__PURE__ */ new Set();
|
|
22313
|
+
unique.forEach((item, i) => {
|
|
22314
|
+
const text7 = getText(item);
|
|
22315
|
+
if (looksLikeMustKeep(text7) || text7.length > outlierFloor) mustKeep.add(i);
|
|
22316
|
+
});
|
|
22317
|
+
const remainingIdx = unique.map((_, i) => i).filter((i) => !mustKeep.has(i));
|
|
22318
|
+
let used = [...mustKeep].reduce((n, i) => n + lengths[i], 0);
|
|
22319
|
+
const pick = new Set(mustKeep);
|
|
22320
|
+
const canTake = (i) => {
|
|
22321
|
+
if (pick.has(i)) return false;
|
|
22322
|
+
const next = used + lengths[i];
|
|
22323
|
+
if (next > maxChars && pick.size > mustKeep.size) return false;
|
|
22324
|
+
used = next;
|
|
22325
|
+
pick.add(i);
|
|
22326
|
+
return true;
|
|
22327
|
+
};
|
|
22328
|
+
const k = remainingIdx.length;
|
|
22329
|
+
const headN = Math.max(1, Math.ceil(k * 0.3));
|
|
22330
|
+
const tailN = Math.max(1, Math.ceil(k * 0.15));
|
|
22331
|
+
for (const i of remainingIdx.slice(0, headN)) canTake(i);
|
|
22332
|
+
for (const i of remainingIdx.slice(-tailN).reverse()) canTake(i);
|
|
22333
|
+
const middle = remainingIdx.filter((i) => !pick.has(i)).sort((a, b) => importance(getText(unique[b])) - importance(getText(unique[a])));
|
|
22334
|
+
for (const i of middle) {
|
|
22335
|
+
if (used >= maxChars) break;
|
|
22336
|
+
canTake(i);
|
|
22337
|
+
}
|
|
22338
|
+
const crushed = unique.filter((_, i) => pick.has(i));
|
|
22339
|
+
const crushedChars = crushed.reduce((n, item) => n + getText(item).length, 0);
|
|
22340
|
+
if (crushed.length >= original.length && crushedChars >= totalChars) {
|
|
22341
|
+
return {
|
|
22342
|
+
items: original,
|
|
22343
|
+
truncated: false,
|
|
22344
|
+
kept: original.length,
|
|
22345
|
+
dropped: 0,
|
|
22346
|
+
reason: "passthrough"
|
|
22347
|
+
};
|
|
22348
|
+
}
|
|
22160
22349
|
return {
|
|
22161
|
-
items:
|
|
22162
|
-
truncated
|
|
22350
|
+
items: crushed,
|
|
22351
|
+
truncated: crushed.length < original.length,
|
|
22352
|
+
kept: crushed.length,
|
|
22353
|
+
dropped: original.length - crushed.length,
|
|
22354
|
+
reason: "budget"
|
|
22163
22355
|
};
|
|
22164
22356
|
}
|
|
22165
|
-
function
|
|
22166
|
-
return
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22172
|
-
|
|
22357
|
+
function splitBlocks(text7) {
|
|
22358
|
+
if (/\n\n/.test(text7)) return text7.split(/\n\n+/);
|
|
22359
|
+
return text7.split("\n");
|
|
22360
|
+
}
|
|
22361
|
+
function capOversizedBlock(block, maxChars) {
|
|
22362
|
+
if (block.length <= maxChars) return block;
|
|
22363
|
+
const head = Math.floor(maxChars * 0.3);
|
|
22364
|
+
const tail = Math.floor(maxChars * 0.15);
|
|
22365
|
+
return `${block.slice(0, head)}
|
|
22366
|
+
|
|
22367
|
+
[\u2026crushed ${block.length - head - tail} chars; pass include=full to read the rest\u2026]
|
|
22368
|
+
|
|
22369
|
+
${block.slice(-tail)}`;
|
|
22370
|
+
}
|
|
22371
|
+
function crushMarkdown(text7, opts) {
|
|
22372
|
+
const original = text7 ?? "";
|
|
22373
|
+
const minChars = opts?.minChars ?? CRUSH_BODY_MIN_CHARS;
|
|
22374
|
+
const maxChars = opts?.maxChars ?? CRUSH_BODY_MAX_CHARS;
|
|
22375
|
+
if (original.length < minChars) {
|
|
22376
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
22377
|
+
}
|
|
22378
|
+
const rawBlocks = splitBlocks(original).filter((b) => b.length > 0);
|
|
22379
|
+
const blocks = rawBlocks.map((b) => capOversizedBlock(b, maxChars));
|
|
22380
|
+
const cappedABlock = blocks.some((b, i) => b !== rawBlocks[i]);
|
|
22381
|
+
if (blocks.length <= 1) {
|
|
22382
|
+
const cut = blocks[0] ?? original;
|
|
22383
|
+
if (cut.length >= original.length) {
|
|
22384
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
22385
|
+
}
|
|
22386
|
+
return { text: cut, truncated: true, originalChars: original.length };
|
|
22387
|
+
}
|
|
22388
|
+
const must = blocks.filter((b) => looksLikeMustKeep(b));
|
|
22389
|
+
const rest = blocks.filter((b) => !looksLikeMustKeep(b));
|
|
22390
|
+
const marker = `[\u2026crushed body; ${original.length} chars originally. Pass include=full to read the rest.]`;
|
|
22391
|
+
let used = must.reduce((n, b) => n + b.length, 0) + marker.length;
|
|
22392
|
+
const keptRest = [];
|
|
22393
|
+
const headN = Math.max(1, Math.ceil(rest.length * 0.3));
|
|
22394
|
+
const tailN = Math.max(1, Math.ceil(rest.length * 0.15));
|
|
22395
|
+
const head = rest.slice(0, headN);
|
|
22396
|
+
const tail = rest.slice(-tailN);
|
|
22397
|
+
const middle = rest.slice(headN, Math.max(headN, rest.length - tailN));
|
|
22398
|
+
const take = (block) => {
|
|
22399
|
+
if (used + block.length + 2 > maxChars && keptRest.length + must.length > 0) {
|
|
22400
|
+
return false;
|
|
22401
|
+
}
|
|
22402
|
+
used += block.length + 2;
|
|
22403
|
+
keptRest.push(block);
|
|
22404
|
+
return true;
|
|
22173
22405
|
};
|
|
22406
|
+
for (const b of head) take(b);
|
|
22407
|
+
for (const b of tail) take(b);
|
|
22408
|
+
const ranked = [...middle].sort((a, b) => importance(b) - importance(a));
|
|
22409
|
+
for (const b of ranked) {
|
|
22410
|
+
if (used >= maxChars) break;
|
|
22411
|
+
take(b);
|
|
22412
|
+
}
|
|
22413
|
+
const keepSet = /* @__PURE__ */ new Set([...must, ...keptRest]);
|
|
22414
|
+
const ordered = blocks.filter((b) => keepSet.has(b));
|
|
22415
|
+
const omitted = blocks.length - ordered.length;
|
|
22416
|
+
if (omitted <= 0) {
|
|
22417
|
+
const joined = blocks.join("\n\n");
|
|
22418
|
+
if (cappedABlock && joined.length < original.length) {
|
|
22419
|
+
return { text: joined, truncated: true, originalChars: original.length };
|
|
22420
|
+
}
|
|
22421
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
22422
|
+
}
|
|
22423
|
+
const out = [];
|
|
22424
|
+
let inserted = false;
|
|
22425
|
+
for (const block of blocks) {
|
|
22426
|
+
if (keepSet.has(block)) {
|
|
22427
|
+
out.push(block);
|
|
22428
|
+
continue;
|
|
22429
|
+
}
|
|
22430
|
+
if (!inserted) {
|
|
22431
|
+
out.push(marker);
|
|
22432
|
+
inserted = true;
|
|
22433
|
+
}
|
|
22434
|
+
}
|
|
22435
|
+
const textOut = out.join("\n\n");
|
|
22436
|
+
if (textOut.length >= original.length) {
|
|
22437
|
+
return { text: original, truncated: false, originalChars: original.length };
|
|
22438
|
+
}
|
|
22439
|
+
return { text: textOut, truncated: true, originalChars: original.length };
|
|
22174
22440
|
}
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22441
|
+
|
|
22442
|
+
// src/mcp/issue-payload.ts
|
|
22443
|
+
var MCP_ISSUE_INCLUDE_VALUES = ["default", "full"];
|
|
22444
|
+
var mcpIssueIncludeSchema = import_zod2.z.enum(MCP_ISSUE_INCLUDE_VALUES).optional().describe(
|
|
22445
|
+
"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."
|
|
22446
|
+
);
|
|
22447
|
+
var MCP_ISSUE_CRUSH_HINT = "Pass include=full to re-fetch the uncompressed ticket from the vendor.";
|
|
22448
|
+
function emptyMeta() {
|
|
22449
|
+
return { truncated: true, hint: MCP_ISSUE_CRUSH_HINT };
|
|
22450
|
+
}
|
|
22451
|
+
function withComments(comments, meta) {
|
|
22452
|
+
const crushed = crushTextItems(comments, (c) => c.body);
|
|
22453
|
+
if (crushed.truncated) {
|
|
22454
|
+
meta.comments = {
|
|
22455
|
+
kept: crushed.kept,
|
|
22456
|
+
dropped: crushed.dropped,
|
|
22457
|
+
reason: crushed.reason
|
|
22458
|
+
};
|
|
22459
|
+
}
|
|
22460
|
+
return { comments: crushed.items, meta };
|
|
22461
|
+
}
|
|
22462
|
+
function withBody(text7, meta) {
|
|
22463
|
+
const crushed = crushMarkdown(text7);
|
|
22464
|
+
if (crushed.truncated) {
|
|
22465
|
+
meta.body = { originalChars: crushed.originalChars };
|
|
22466
|
+
return { value: crushed.text, meta };
|
|
22467
|
+
}
|
|
22468
|
+
return { value: text7, meta };
|
|
22469
|
+
}
|
|
22470
|
+
function attachCrush(payload, meta) {
|
|
22471
|
+
if (!meta.comments && !meta.body) return payload;
|
|
22472
|
+
return { ...payload, crush: meta };
|
|
22473
|
+
}
|
|
22474
|
+
function formatLinearIssuePayload(issue, include) {
|
|
22475
|
+
if (include === "full") return issue;
|
|
22476
|
+
const meta = emptyMeta();
|
|
22477
|
+
const comments = withComments(issue.comments, meta);
|
|
22478
|
+
const description = withBody(issue.description, comments.meta);
|
|
22479
|
+
const { team, ...rest } = issue;
|
|
22480
|
+
const next = {
|
|
22481
|
+
...rest,
|
|
22482
|
+
...team ? { team: { id: team.id, key: team.key, name: team.name } } : {},
|
|
22483
|
+
description: description.value,
|
|
22484
|
+
comments: comments.comments
|
|
22184
22485
|
};
|
|
22486
|
+
return attachCrush(next, description.meta);
|
|
22185
22487
|
}
|
|
22186
|
-
function
|
|
22187
|
-
return
|
|
22188
|
-
|
|
22189
|
-
|
|
22488
|
+
function formatGitHubIssuePayload(issue, include) {
|
|
22489
|
+
if (include === "full") return issue;
|
|
22490
|
+
const meta = emptyMeta();
|
|
22491
|
+
const comments = withComments(issue.comments, meta);
|
|
22492
|
+
const body = withBody(issue.body, comments.meta);
|
|
22493
|
+
const next = {
|
|
22494
|
+
...issue,
|
|
22495
|
+
body: body.value,
|
|
22496
|
+
comments: comments.comments
|
|
22497
|
+
};
|
|
22498
|
+
return attachCrush(next, body.meta);
|
|
22499
|
+
}
|
|
22500
|
+
function formatAbleTimeTaskPayload(task, include) {
|
|
22501
|
+
const base = {
|
|
22502
|
+
...toAbleTimeIssueInfo(task),
|
|
22503
|
+
description: task.description,
|
|
22504
|
+
state: task.state,
|
|
22505
|
+
comments: task.comments
|
|
22190
22506
|
};
|
|
22507
|
+
if (include === "full") return base;
|
|
22508
|
+
const meta = emptyMeta();
|
|
22509
|
+
const comments = withComments(task.comments, meta);
|
|
22510
|
+
const description = withBody(task.description, comments.meta);
|
|
22511
|
+
return attachCrush(
|
|
22512
|
+
{
|
|
22513
|
+
...base,
|
|
22514
|
+
description: description.value,
|
|
22515
|
+
comments: comments.comments
|
|
22516
|
+
},
|
|
22517
|
+
description.meta
|
|
22518
|
+
);
|
|
22191
22519
|
}
|
|
22192
22520
|
|
|
22193
22521
|
// src/mcp/abletime-tools.ts
|
|
@@ -22233,7 +22561,7 @@ function registerAbleTimeTools(server) {
|
|
|
22233
22561
|
server.tool(
|
|
22234
22562
|
"abletime_list_tasks",
|
|
22235
22563
|
"List open AbleTime tasks (excludes done/archived). Optional projectId filter.",
|
|
22236
|
-
{ projectId:
|
|
22564
|
+
{ projectId: import_zod3.z.string().optional() },
|
|
22237
22565
|
async ({ projectId }) => {
|
|
22238
22566
|
try {
|
|
22239
22567
|
const tasks = await listAbleTimeTasks({ projectId });
|
|
@@ -22246,7 +22574,7 @@ function registerAbleTimeTools(server) {
|
|
|
22246
22574
|
server.tool(
|
|
22247
22575
|
"abletime_search_tasks",
|
|
22248
22576
|
"Search AbleTime tasks by text, reference, or tag.",
|
|
22249
|
-
{ query:
|
|
22577
|
+
{ query: import_zod3.z.string() },
|
|
22250
22578
|
async ({ query }) => {
|
|
22251
22579
|
try {
|
|
22252
22580
|
const tasks = await searchAbleTimeTasks(query);
|
|
@@ -22258,17 +22586,11 @@ function registerAbleTimeTools(server) {
|
|
|
22258
22586
|
);
|
|
22259
22587
|
server.tool(
|
|
22260
22588
|
"abletime_get_task",
|
|
22261
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments. Re-fetch to read new comments.",
|
|
22262
|
-
{ id:
|
|
22263
|
-
async ({ id }) => {
|
|
22589
|
+
"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.",
|
|
22590
|
+
{ id: import_zod3.z.string(), include: mcpIssueIncludeSchema },
|
|
22591
|
+
async ({ id, include }) => {
|
|
22264
22592
|
try {
|
|
22265
|
-
|
|
22266
|
-
return text2({
|
|
22267
|
-
...toAbleTimeIssueInfo(task),
|
|
22268
|
-
description: task.description,
|
|
22269
|
-
state: task.state,
|
|
22270
|
-
comments: task.comments
|
|
22271
|
-
});
|
|
22593
|
+
return text2(formatAbleTimeTaskPayload(await getAbleTimeTask(id), include));
|
|
22272
22594
|
} catch (err) {
|
|
22273
22595
|
return fail2(err);
|
|
22274
22596
|
}
|
|
@@ -22277,7 +22599,7 @@ function registerAbleTimeTools(server) {
|
|
|
22277
22599
|
server.tool(
|
|
22278
22600
|
"abletime_comment",
|
|
22279
22601
|
"Add a markdown comment on an AbleTime task (id or CRM-232).",
|
|
22280
|
-
{ id:
|
|
22602
|
+
{ id: import_zod3.z.string(), body: import_zod3.z.string() },
|
|
22281
22603
|
async (args) => {
|
|
22282
22604
|
try {
|
|
22283
22605
|
return text2(await commentAbleTimeTask(args));
|
|
@@ -22290,10 +22612,10 @@ function registerAbleTimeTools(server) {
|
|
|
22290
22612
|
"abletime_update_task",
|
|
22291
22613
|
"Update an AbleTime task (id or CRM-232). Pass title, description, and/or state.",
|
|
22292
22614
|
{
|
|
22293
|
-
id:
|
|
22294
|
-
title:
|
|
22295
|
-
description:
|
|
22296
|
-
state:
|
|
22615
|
+
id: import_zod3.z.string(),
|
|
22616
|
+
title: import_zod3.z.string().optional(),
|
|
22617
|
+
description: import_zod3.z.string().optional(),
|
|
22618
|
+
state: import_zod3.z.string().optional()
|
|
22297
22619
|
},
|
|
22298
22620
|
async (args) => {
|
|
22299
22621
|
try {
|
|
@@ -22312,12 +22634,12 @@ function registerAbleTimeTools(server) {
|
|
|
22312
22634
|
"abletime_create_task",
|
|
22313
22635
|
"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).",
|
|
22314
22636
|
{
|
|
22315
|
-
title:
|
|
22316
|
-
description:
|
|
22317
|
-
projectId:
|
|
22318
|
-
categoryId:
|
|
22319
|
-
state:
|
|
22320
|
-
parent:
|
|
22637
|
+
title: import_zod3.z.string(),
|
|
22638
|
+
description: import_zod3.z.string().optional(),
|
|
22639
|
+
projectId: import_zod3.z.string().optional(),
|
|
22640
|
+
categoryId: import_zod3.z.string().optional(),
|
|
22641
|
+
state: import_zod3.z.enum(["backlog", "todo"]).optional(),
|
|
22642
|
+
parent: import_zod3.z.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
22321
22643
|
},
|
|
22322
22644
|
async (args) => {
|
|
22323
22645
|
try {
|
|
@@ -22331,10 +22653,10 @@ function registerAbleTimeTools(server) {
|
|
|
22331
22653
|
"abletime_ensure_task",
|
|
22332
22654
|
"Find or create an AbleTime task for this work, then create_thread with sourceType=ticket.",
|
|
22333
22655
|
{
|
|
22334
|
-
title:
|
|
22335
|
-
description:
|
|
22336
|
-
projectId:
|
|
22337
|
-
categoryId:
|
|
22656
|
+
title: import_zod3.z.string(),
|
|
22657
|
+
description: import_zod3.z.string().optional(),
|
|
22658
|
+
projectId: import_zod3.z.string().optional(),
|
|
22659
|
+
categoryId: import_zod3.z.string().optional()
|
|
22338
22660
|
},
|
|
22339
22661
|
async (args) => {
|
|
22340
22662
|
try {
|
|
@@ -22348,7 +22670,7 @@ function registerAbleTimeTools(server) {
|
|
|
22348
22670
|
}
|
|
22349
22671
|
|
|
22350
22672
|
// src/mcp/github-tools.ts
|
|
22351
|
-
var
|
|
22673
|
+
var import_zod4 = require("zod");
|
|
22352
22674
|
|
|
22353
22675
|
// src/integrations/github-issues.ts
|
|
22354
22676
|
init_worktree();
|
|
@@ -22534,15 +22856,15 @@ function fail3(err) {
|
|
|
22534
22856
|
true
|
|
22535
22857
|
);
|
|
22536
22858
|
}
|
|
22537
|
-
var repoPathSchema =
|
|
22859
|
+
var repoPathSchema = import_zod4.z.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
22538
22860
|
function registerGithubIssueTools(server) {
|
|
22539
22861
|
server.tool(
|
|
22540
22862
|
"github_get_issue",
|
|
22541
|
-
"Get a GitHub issue (#123 or URL): body, comments, state. Re-fetch to read new comments. Uses Account gh.",
|
|
22542
|
-
{ id:
|
|
22543
|
-
async ({ id, repoPath }) => {
|
|
22863
|
+
"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.",
|
|
22864
|
+
{ id: import_zod4.z.string(), include: mcpIssueIncludeSchema, repoPath: repoPathSchema },
|
|
22865
|
+
async ({ id, include, repoPath }) => {
|
|
22544
22866
|
try {
|
|
22545
|
-
return text3(await getGitHubIssue(id, { repoPath }));
|
|
22867
|
+
return text3(formatGitHubIssuePayload(await getGitHubIssue(id, { repoPath }), include));
|
|
22546
22868
|
} catch (err) {
|
|
22547
22869
|
return fail3(err);
|
|
22548
22870
|
}
|
|
@@ -22551,7 +22873,7 @@ function registerGithubIssueTools(server) {
|
|
|
22551
22873
|
server.tool(
|
|
22552
22874
|
"github_comment",
|
|
22553
22875
|
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh.",
|
|
22554
|
-
{ id:
|
|
22876
|
+
{ id: import_zod4.z.string(), body: import_zod4.z.string(), repoPath: repoPathSchema },
|
|
22555
22877
|
async ({ id, body, repoPath }) => {
|
|
22556
22878
|
try {
|
|
22557
22879
|
return text3(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
@@ -22564,10 +22886,10 @@ function registerGithubIssueTools(server) {
|
|
|
22564
22886
|
"github_update_issue",
|
|
22565
22887
|
"Update a GitHub issue (#123). Pass title, body, and/or state (open|closed).",
|
|
22566
22888
|
{
|
|
22567
|
-
id:
|
|
22568
|
-
title:
|
|
22569
|
-
body:
|
|
22570
|
-
state:
|
|
22889
|
+
id: import_zod4.z.string(),
|
|
22890
|
+
title: import_zod4.z.string().optional(),
|
|
22891
|
+
body: import_zod4.z.string().optional(),
|
|
22892
|
+
state: import_zod4.z.string().optional().describe("open or closed"),
|
|
22571
22893
|
repoPath: repoPathSchema
|
|
22572
22894
|
},
|
|
22573
22895
|
async (args) => {
|
|
@@ -22582,9 +22904,9 @@ function registerGithubIssueTools(server) {
|
|
|
22582
22904
|
"github_create_issue",
|
|
22583
22905
|
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
22584
22906
|
{
|
|
22585
|
-
title:
|
|
22586
|
-
body:
|
|
22587
|
-
parent:
|
|
22907
|
+
title: import_zod4.z.string(),
|
|
22908
|
+
body: import_zod4.z.string().optional(),
|
|
22909
|
+
parent: import_zod4.z.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
22588
22910
|
repoPath: repoPathSchema
|
|
22589
22911
|
},
|
|
22590
22912
|
async (args) => {
|
|
@@ -22598,7 +22920,7 @@ function registerGithubIssueTools(server) {
|
|
|
22598
22920
|
}
|
|
22599
22921
|
|
|
22600
22922
|
// src/mcp/linear-tools.ts
|
|
22601
|
-
var
|
|
22923
|
+
var import_zod5 = require("zod");
|
|
22602
22924
|
function text4(payload, isError = false) {
|
|
22603
22925
|
return mcpJson(payload, isError);
|
|
22604
22926
|
}
|
|
@@ -22608,7 +22930,7 @@ function fail4(err) {
|
|
|
22608
22930
|
true
|
|
22609
22931
|
);
|
|
22610
22932
|
}
|
|
22611
|
-
var prioritySchema =
|
|
22933
|
+
var prioritySchema = import_zod5.z.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
22612
22934
|
function registerLinearTools(server) {
|
|
22613
22935
|
server.tool(
|
|
22614
22936
|
"linear_list_teams",
|
|
@@ -22626,9 +22948,9 @@ function registerLinearTools(server) {
|
|
|
22626
22948
|
"linear_search_issues",
|
|
22627
22949
|
"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.",
|
|
22628
22950
|
{
|
|
22629
|
-
query:
|
|
22630
|
-
assignee:
|
|
22631
|
-
limit:
|
|
22951
|
+
query: import_zod5.z.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
22952
|
+
assignee: import_zod5.z.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
22953
|
+
limit: import_zod5.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
22632
22954
|
},
|
|
22633
22955
|
async ({ query, assignee, limit }) => {
|
|
22634
22956
|
try {
|
|
@@ -22655,11 +22977,11 @@ function registerLinearTools(server) {
|
|
|
22655
22977
|
);
|
|
22656
22978
|
server.tool(
|
|
22657
22979
|
"linear_get_issue",
|
|
22658
|
-
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children.",
|
|
22659
|
-
{ id:
|
|
22660
|
-
async ({ id }) => {
|
|
22980
|
+
"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.",
|
|
22981
|
+
{ id: import_zod5.z.string(), include: mcpIssueIncludeSchema },
|
|
22982
|
+
async ({ id, include }) => {
|
|
22661
22983
|
try {
|
|
22662
|
-
return text4(await getLinearIssue(id));
|
|
22984
|
+
return text4(formatLinearIssuePayload(await getLinearIssue(id), include));
|
|
22663
22985
|
} catch (err) {
|
|
22664
22986
|
return fail4(err);
|
|
22665
22987
|
}
|
|
@@ -22669,13 +22991,13 @@ function registerLinearTools(server) {
|
|
|
22669
22991
|
"linear_create_issue",
|
|
22670
22992
|
'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.',
|
|
22671
22993
|
{
|
|
22672
|
-
team:
|
|
22673
|
-
title:
|
|
22674
|
-
description:
|
|
22675
|
-
state:
|
|
22676
|
-
assignee:
|
|
22994
|
+
team: import_zod5.z.string(),
|
|
22995
|
+
title: import_zod5.z.string(),
|
|
22996
|
+
description: import_zod5.z.string().optional(),
|
|
22997
|
+
state: import_zod5.z.string().optional(),
|
|
22998
|
+
assignee: import_zod5.z.string().nullable().optional(),
|
|
22677
22999
|
priority: prioritySchema,
|
|
22678
|
-
parent:
|
|
23000
|
+
parent: import_zod5.z.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
22679
23001
|
},
|
|
22680
23002
|
async (args) => {
|
|
22681
23003
|
try {
|
|
@@ -22689,11 +23011,11 @@ function registerLinearTools(server) {
|
|
|
22689
23011
|
"linear_update_issue",
|
|
22690
23012
|
"Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, and/or priority.",
|
|
22691
23013
|
{
|
|
22692
|
-
id:
|
|
22693
|
-
title:
|
|
22694
|
-
description:
|
|
22695
|
-
state:
|
|
22696
|
-
assignee:
|
|
23014
|
+
id: import_zod5.z.string(),
|
|
23015
|
+
title: import_zod5.z.string().optional(),
|
|
23016
|
+
description: import_zod5.z.string().optional(),
|
|
23017
|
+
state: import_zod5.z.string().optional(),
|
|
23018
|
+
assignee: import_zod5.z.string().nullable().optional(),
|
|
22697
23019
|
priority: prioritySchema
|
|
22698
23020
|
},
|
|
22699
23021
|
async (args) => {
|
|
@@ -22708,8 +23030,8 @@ function registerLinearTools(server) {
|
|
|
22708
23030
|
"linear_comment",
|
|
22709
23031
|
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
22710
23032
|
{
|
|
22711
|
-
id:
|
|
22712
|
-
body:
|
|
23033
|
+
id: import_zod5.z.string(),
|
|
23034
|
+
body: import_zod5.z.string()
|
|
22713
23035
|
},
|
|
22714
23036
|
async (args) => {
|
|
22715
23037
|
try {
|
|
@@ -22729,7 +23051,7 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
|
|
|
22729
23051
|
}
|
|
22730
23052
|
|
|
22731
23053
|
// src/mcp/viewer-context-tools.ts
|
|
22732
|
-
var
|
|
23054
|
+
var import_zod6 = require("zod");
|
|
22733
23055
|
init_worktree();
|
|
22734
23056
|
init_app_settings();
|
|
22735
23057
|
init_workspaces2();
|
|
@@ -22739,11 +23061,14 @@ function text5(payload, isError = false) {
|
|
|
22739
23061
|
function matchRegisteredWorkspace(repoPath) {
|
|
22740
23062
|
const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
|
|
22741
23063
|
if (!hint) return null;
|
|
22742
|
-
|
|
23064
|
+
let best = null;
|
|
23065
|
+
for (const w of listWorkspaces()) {
|
|
22743
23066
|
const stored = w.path.replace(/\/+$/, "");
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
23067
|
+
if (stored === hint || hint.startsWith(`${stored}/`)) {
|
|
23068
|
+
if (!best || stored.length > best.length) best = stored;
|
|
23069
|
+
}
|
|
23070
|
+
}
|
|
23071
|
+
return best;
|
|
22747
23072
|
}
|
|
22748
23073
|
async function resolveProjectRepoPath(repoPath) {
|
|
22749
23074
|
const hint = repoPath?.trim() || process.cwd();
|
|
@@ -22761,7 +23086,7 @@ function registerViewerContextTools(server) {
|
|
|
22761
23086
|
"get_viewer_context",
|
|
22762
23087
|
"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).",
|
|
22763
23088
|
{
|
|
22764
|
-
repoPath:
|
|
23089
|
+
repoPath: import_zod6.z.string().optional().describe(
|
|
22765
23090
|
"Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
|
|
22766
23091
|
)
|
|
22767
23092
|
},
|
|
@@ -22781,10 +23106,10 @@ function registerViewerContextTools(server) {
|
|
|
22781
23106
|
"update_viewer_context",
|
|
22782
23107
|
"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.",
|
|
22783
23108
|
{
|
|
22784
|
-
scope:
|
|
22785
|
-
context:
|
|
22786
|
-
confirmed:
|
|
22787
|
-
repoPath:
|
|
23109
|
+
scope: import_zod6.z.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
|
|
23110
|
+
context: import_zod6.z.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
|
|
23111
|
+
confirmed: import_zod6.z.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
|
|
23112
|
+
repoPath: import_zod6.z.string().optional().describe(
|
|
22788
23113
|
"Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
|
|
22789
23114
|
)
|
|
22790
23115
|
},
|
|
@@ -22865,14 +23190,11 @@ init_list_prs();
|
|
|
22865
23190
|
init_app_settings();
|
|
22866
23191
|
|
|
22867
23192
|
// src/mcp/schedule-tools.ts
|
|
22868
|
-
var
|
|
23193
|
+
var import_zod7 = require("zod");
|
|
22869
23194
|
init_schedules();
|
|
22870
23195
|
init_schedule_runner();
|
|
22871
23196
|
function text6(payload, isError = false) {
|
|
22872
|
-
return
|
|
22873
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
22874
|
-
...isError ? { isError: true } : {}
|
|
22875
|
-
};
|
|
23197
|
+
return mcpJson(payload, isError);
|
|
22876
23198
|
}
|
|
22877
23199
|
function fail5(err) {
|
|
22878
23200
|
return text6(
|
|
@@ -22910,17 +23232,17 @@ function registerScheduleTools(server) {
|
|
|
22910
23232
|
"create_schedule",
|
|
22911
23233
|
"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.",
|
|
22912
23234
|
{
|
|
22913
|
-
prompt:
|
|
22914
|
-
name:
|
|
22915
|
-
at:
|
|
22916
|
-
every:
|
|
22917
|
-
cron:
|
|
22918
|
-
tz:
|
|
22919
|
-
threadId:
|
|
23235
|
+
prompt: import_zod7.z.string().describe("User message / goal queued when the schedule fires"),
|
|
23236
|
+
name: import_zod7.z.string().optional(),
|
|
23237
|
+
at: import_zod7.z.string().optional().describe("ISO datetime for a one-shot"),
|
|
23238
|
+
every: import_zod7.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
23239
|
+
cron: import_zod7.z.string().optional().describe("5-field cron expression"),
|
|
23240
|
+
tz: import_zod7.z.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
23241
|
+
threadId: import_zod7.z.string().optional().describe(
|
|
22920
23242
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
22921
23243
|
),
|
|
22922
|
-
agent:
|
|
22923
|
-
model:
|
|
23244
|
+
agent: import_zod7.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
23245
|
+
model: import_zod7.z.string().optional()
|
|
22924
23246
|
},
|
|
22925
23247
|
async (args) => {
|
|
22926
23248
|
try {
|
|
@@ -22953,17 +23275,17 @@ function registerScheduleTools(server) {
|
|
|
22953
23275
|
"update_schedule",
|
|
22954
23276
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
22955
23277
|
{
|
|
22956
|
-
id:
|
|
22957
|
-
prompt:
|
|
22958
|
-
name:
|
|
22959
|
-
at:
|
|
22960
|
-
every:
|
|
22961
|
-
cron:
|
|
22962
|
-
tz:
|
|
22963
|
-
threadId:
|
|
22964
|
-
agent:
|
|
22965
|
-
model:
|
|
22966
|
-
enabled:
|
|
23278
|
+
id: import_zod7.z.string(),
|
|
23279
|
+
prompt: import_zod7.z.string().optional(),
|
|
23280
|
+
name: import_zod7.z.string().optional(),
|
|
23281
|
+
at: import_zod7.z.string().optional(),
|
|
23282
|
+
every: import_zod7.z.string().optional(),
|
|
23283
|
+
cron: import_zod7.z.string().optional(),
|
|
23284
|
+
tz: import_zod7.z.string().optional(),
|
|
23285
|
+
threadId: import_zod7.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
23286
|
+
agent: import_zod7.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
23287
|
+
model: import_zod7.z.string().optional(),
|
|
23288
|
+
enabled: import_zod7.z.boolean().optional()
|
|
22967
23289
|
},
|
|
22968
23290
|
async (args) => {
|
|
22969
23291
|
try {
|
|
@@ -23001,7 +23323,7 @@ function registerScheduleTools(server) {
|
|
|
23001
23323
|
server.tool(
|
|
23002
23324
|
"delete_schedule",
|
|
23003
23325
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
23004
|
-
{ id:
|
|
23326
|
+
{ id: import_zod7.z.string() },
|
|
23005
23327
|
async ({ id }) => {
|
|
23006
23328
|
try {
|
|
23007
23329
|
deleteSchedule(id);
|
|
@@ -23014,7 +23336,7 @@ function registerScheduleTools(server) {
|
|
|
23014
23336
|
server.tool(
|
|
23015
23337
|
"run_schedule",
|
|
23016
23338
|
"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.",
|
|
23017
|
-
{ id:
|
|
23339
|
+
{ id: import_zod7.z.string() },
|
|
23018
23340
|
async ({ id }) => {
|
|
23019
23341
|
try {
|
|
23020
23342
|
if (!getSchedule(id)) {
|
|
@@ -23446,13 +23768,13 @@ async function startMcpServer() {
|
|
|
23446
23768
|
"list_board",
|
|
23447
23769
|
"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.",
|
|
23448
23770
|
{
|
|
23449
|
-
query:
|
|
23450
|
-
repoPath:
|
|
23451
|
-
kind:
|
|
23452
|
-
column:
|
|
23771
|
+
query: import_zod8.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
23772
|
+
repoPath: import_zod8.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
23773
|
+
kind: import_zod8.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
23774
|
+
column: import_zod8.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
23453
23775
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
23454
23776
|
),
|
|
23455
|
-
limit:
|
|
23777
|
+
limit: import_zod8.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
23456
23778
|
},
|
|
23457
23779
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
23458
23780
|
const workspaces = orch.listWorkspaces();
|
|
@@ -23467,30 +23789,19 @@ async function startMcpServer() {
|
|
|
23467
23789
|
limit,
|
|
23468
23790
|
workspaceName: (path) => names.get(path) ?? ""
|
|
23469
23791
|
});
|
|
23470
|
-
return {
|
|
23471
|
-
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
hidden: snap.hidden,
|
|
23478
|
-
totals: snap.totals,
|
|
23479
|
-
columnDefs: BOARD_COLUMN_DEFS,
|
|
23480
|
-
hint: HOME_BOARD_AGENT_HINT
|
|
23481
|
-
},
|
|
23482
|
-
null,
|
|
23483
|
-
2
|
|
23484
|
-
)
|
|
23485
|
-
}
|
|
23486
|
-
]
|
|
23487
|
-
};
|
|
23792
|
+
return mcpJson({
|
|
23793
|
+
columns: snap.columns,
|
|
23794
|
+
hidden: snap.hidden,
|
|
23795
|
+
totals: snap.totals,
|
|
23796
|
+
columnDefs: BOARD_COLUMN_DEFS,
|
|
23797
|
+
hint: HOME_BOARD_AGENT_HINT
|
|
23798
|
+
});
|
|
23488
23799
|
}
|
|
23489
23800
|
);
|
|
23490
23801
|
server.tool(
|
|
23491
23802
|
"get_thread",
|
|
23492
23803
|
"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.",
|
|
23493
|
-
{ ref:
|
|
23804
|
+
{ ref: import_zod8.z.string() },
|
|
23494
23805
|
async ({ ref }) => {
|
|
23495
23806
|
const t = orch.getThread(ref);
|
|
23496
23807
|
if (!t) {
|
|
@@ -23524,7 +23835,7 @@ async function startMcpServer() {
|
|
|
23524
23835
|
usage: spend.usage,
|
|
23525
23836
|
lastTurnUsage: spend.lastTurnUsage
|
|
23526
23837
|
};
|
|
23527
|
-
return
|
|
23838
|
+
return mcpJson(summary);
|
|
23528
23839
|
}
|
|
23529
23840
|
);
|
|
23530
23841
|
}
|
|
@@ -23532,17 +23843,17 @@ async function startMcpServer() {
|
|
|
23532
23843
|
"present_artifact",
|
|
23533
23844
|
"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.",
|
|
23534
23845
|
{
|
|
23535
|
-
title:
|
|
23536
|
-
type:
|
|
23846
|
+
title: import_zod8.z.string().describe("Short title shown in the artifact pane header"),
|
|
23847
|
+
type: import_zod8.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
23537
23848
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
23538
23849
|
),
|
|
23539
|
-
content:
|
|
23850
|
+
content: import_zod8.z.string().describe(
|
|
23540
23851
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
23541
23852
|
),
|
|
23542
|
-
artifact_id:
|
|
23543
|
-
status:
|
|
23544
|
-
phase:
|
|
23545
|
-
mode:
|
|
23853
|
+
artifact_id: import_zod8.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
23854
|
+
status: import_zod8.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
23855
|
+
phase: import_zod8.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
23856
|
+
mode: import_zod8.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
23546
23857
|
},
|
|
23547
23858
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
23548
23859
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -23556,22 +23867,22 @@ async function startMcpServer() {
|
|
|
23556
23867
|
mode: type === "log" ? mode ?? "append" : void 0,
|
|
23557
23868
|
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."
|
|
23558
23869
|
};
|
|
23559
|
-
return
|
|
23870
|
+
return mcpJson(payload);
|
|
23560
23871
|
}
|
|
23561
23872
|
);
|
|
23562
23873
|
server.tool(
|
|
23563
23874
|
"ask_user",
|
|
23564
23875
|
"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.",
|
|
23565
23876
|
{
|
|
23566
|
-
questions:
|
|
23567
|
-
|
|
23568
|
-
question:
|
|
23569
|
-
header:
|
|
23570
|
-
multiSelect:
|
|
23571
|
-
options:
|
|
23572
|
-
|
|
23573
|
-
label:
|
|
23574
|
-
description:
|
|
23877
|
+
questions: import_zod8.z.array(
|
|
23878
|
+
import_zod8.z.object({
|
|
23879
|
+
question: import_zod8.z.string().describe("Full question text ending with ?"),
|
|
23880
|
+
header: import_zod8.z.string().max(24).optional().describe("Short label shown above the question"),
|
|
23881
|
+
multiSelect: import_zod8.z.boolean().optional().describe("Allow selecting multiple options"),
|
|
23882
|
+
options: import_zod8.z.array(
|
|
23883
|
+
import_zod8.z.object({
|
|
23884
|
+
label: import_zod8.z.string(),
|
|
23885
|
+
description: import_zod8.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
23575
23886
|
})
|
|
23576
23887
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
23577
23888
|
})
|
|
@@ -23583,16 +23894,16 @@ async function startMcpServer() {
|
|
|
23583
23894
|
questions,
|
|
23584
23895
|
message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
|
|
23585
23896
|
};
|
|
23586
|
-
return
|
|
23897
|
+
return mcpJson(payload);
|
|
23587
23898
|
}
|
|
23588
23899
|
);
|
|
23589
23900
|
server.tool(
|
|
23590
23901
|
"present_plan",
|
|
23591
23902
|
"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.",
|
|
23592
23903
|
{
|
|
23593
|
-
title:
|
|
23594
|
-
content:
|
|
23595
|
-
thread_id:
|
|
23904
|
+
title: import_zod8.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
23905
|
+
content: import_zod8.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
23906
|
+
thread_id: import_zod8.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
23596
23907
|
},
|
|
23597
23908
|
async ({ title, content, thread_id }) => {
|
|
23598
23909
|
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
@@ -23608,22 +23919,22 @@ async function startMcpServer() {
|
|
|
23608
23919
|
title: title?.trim() || "Plan",
|
|
23609
23920
|
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
23610
23921
|
};
|
|
23611
|
-
return
|
|
23922
|
+
return mcpJson(payload);
|
|
23612
23923
|
}
|
|
23613
23924
|
);
|
|
23614
23925
|
server.tool(
|
|
23615
23926
|
"present_schema",
|
|
23616
23927
|
"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.",
|
|
23617
23928
|
{
|
|
23618
|
-
title:
|
|
23619
|
-
mode:
|
|
23620
|
-
datasource:
|
|
23621
|
-
resource_id:
|
|
23622
|
-
record_id:
|
|
23623
|
-
resource:
|
|
23624
|
-
record:
|
|
23625
|
-
records:
|
|
23626
|
-
pane_id:
|
|
23929
|
+
title: import_zod8.z.string().describe("Pane title"),
|
|
23930
|
+
mode: import_zod8.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
23931
|
+
datasource: import_zod8.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
23932
|
+
resource_id: import_zod8.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
23933
|
+
record_id: import_zod8.z.string().optional().describe("Record id when opening form mode"),
|
|
23934
|
+
resource: import_zod8.z.record(import_zod8.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
23935
|
+
record: import_zod8.z.record(import_zod8.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
23936
|
+
records: import_zod8.z.array(import_zod8.z.record(import_zod8.z.unknown())).optional().describe("Inline records for table mode"),
|
|
23937
|
+
pane_id: import_zod8.z.string().optional().describe("Stable pane id across updates")
|
|
23627
23938
|
},
|
|
23628
23939
|
async (args) => {
|
|
23629
23940
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -23637,17 +23948,17 @@ async function startMcpServer() {
|
|
|
23637
23948
|
record_id: args.record_id,
|
|
23638
23949
|
message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
|
|
23639
23950
|
};
|
|
23640
|
-
return
|
|
23951
|
+
return mcpJson(payload);
|
|
23641
23952
|
}
|
|
23642
23953
|
);
|
|
23643
23954
|
server.tool(
|
|
23644
23955
|
"present_files",
|
|
23645
23956
|
"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.",
|
|
23646
23957
|
{
|
|
23647
|
-
title:
|
|
23648
|
-
datasource:
|
|
23649
|
-
path:
|
|
23650
|
-
pane_id:
|
|
23958
|
+
title: import_zod8.z.string().optional().describe("Pane title (default: Files)"),
|
|
23959
|
+
datasource: import_zod8.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
23960
|
+
path: import_zod8.z.string().optional().describe("Initial folder path (e.g. public)"),
|
|
23961
|
+
pane_id: import_zod8.z.string().optional().describe("Stable pane id across updates")
|
|
23651
23962
|
},
|
|
23652
23963
|
async (args) => {
|
|
23653
23964
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -23659,21 +23970,21 @@ async function startMcpServer() {
|
|
|
23659
23970
|
path: args.path,
|
|
23660
23971
|
message: "Files pane accepted. Sideboard desktop opens the Files column beside chat."
|
|
23661
23972
|
};
|
|
23662
|
-
return
|
|
23973
|
+
return mcpJson(payload);
|
|
23663
23974
|
}
|
|
23664
23975
|
);
|
|
23665
23976
|
server.tool(
|
|
23666
23977
|
"wait_for_job",
|
|
23667
23978
|
"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.",
|
|
23668
23979
|
{
|
|
23669
|
-
id:
|
|
23670
|
-
timeoutMs:
|
|
23980
|
+
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
|
|
23981
|
+
timeoutMs: import_zod8.z.number().optional()
|
|
23671
23982
|
},
|
|
23672
23983
|
async ({ id, timeoutMs }) => {
|
|
23673
23984
|
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
23674
23985
|
timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
|
|
23675
23986
|
});
|
|
23676
|
-
return
|
|
23987
|
+
return mcpJson(result);
|
|
23677
23988
|
}
|
|
23678
23989
|
);
|
|
23679
23990
|
if (!worktreeProfile) {
|
|
@@ -23688,56 +23999,44 @@ async function startMcpServer() {
|
|
|
23688
23999
|
"set_caffeinate",
|
|
23689
24000
|
"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.",
|
|
23690
24001
|
{
|
|
23691
|
-
enabled:
|
|
24002
|
+
enabled: import_zod8.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
23692
24003
|
},
|
|
23693
24004
|
async ({ enabled }) => {
|
|
23694
24005
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
23695
24006
|
const state = setCaffeinateHold2(enabled, { threadId });
|
|
23696
24007
|
if (enabled && !state.held) {
|
|
23697
|
-
return
|
|
23698
|
-
content: [
|
|
23699
|
-
{
|
|
23700
|
-
type: "text",
|
|
23701
|
-
text: JSON.stringify({
|
|
23702
|
-
...state,
|
|
23703
|
-
ok: false,
|
|
23704
|
-
message: state.platform === "darwin" ? "Could not start caffeinate." : "Caffeinate is macOS only."
|
|
23705
|
-
})
|
|
23706
|
-
}
|
|
23707
|
-
],
|
|
23708
|
-
isError: true
|
|
23709
|
-
};
|
|
23710
|
-
}
|
|
23711
|
-
return {
|
|
23712
|
-
content: [
|
|
24008
|
+
return mcpJson(
|
|
23713
24009
|
{
|
|
23714
|
-
|
|
23715
|
-
|
|
23716
|
-
|
|
23717
|
-
|
|
23718
|
-
|
|
23719
|
-
|
|
23720
|
-
|
|
23721
|
-
|
|
23722
|
-
|
|
24010
|
+
...state,
|
|
24011
|
+
ok: false,
|
|
24012
|
+
message: state.platform === "darwin" ? "Could not start caffeinate." : "Caffeinate is macOS only."
|
|
24013
|
+
},
|
|
24014
|
+
true
|
|
24015
|
+
);
|
|
24016
|
+
}
|
|
24017
|
+
return mcpJson({
|
|
24018
|
+
...getCaffeinateHold2(),
|
|
24019
|
+
ok: true,
|
|
24020
|
+
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)."
|
|
24021
|
+
});
|
|
23723
24022
|
}
|
|
23724
24023
|
);
|
|
23725
24024
|
server.tool(
|
|
23726
24025
|
"create_thread",
|
|
23727
24026
|
`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.`,
|
|
23728
24027
|
{
|
|
23729
|
-
sourceType:
|
|
23730
|
-
sourceRef:
|
|
23731
|
-
agent:
|
|
23732
|
-
model:
|
|
24028
|
+
sourceType: import_zod8.z.enum(["branch", "pr", "ticket"]),
|
|
24029
|
+
sourceRef: import_zod8.z.string(),
|
|
24030
|
+
agent: import_zod8.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
24031
|
+
model: import_zod8.z.string().nullable().optional().describe(
|
|
23733
24032
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
23734
24033
|
),
|
|
23735
|
-
repoPath:
|
|
23736
|
-
title:
|
|
23737
|
-
cowboy:
|
|
24034
|
+
repoPath: import_zod8.z.string(),
|
|
24035
|
+
title: import_zod8.z.string().optional(),
|
|
24036
|
+
cowboy: import_zod8.z.boolean().optional().describe(
|
|
23738
24037
|
"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."
|
|
23739
24038
|
),
|
|
23740
|
-
parentThreadId:
|
|
24039
|
+
parentThreadId: import_zod8.z.string().optional().describe(
|
|
23741
24040
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
23742
24041
|
)
|
|
23743
24042
|
},
|
|
@@ -23753,10 +24052,10 @@ async function startMcpServer() {
|
|
|
23753
24052
|
"start_board_card",
|
|
23754
24053
|
"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.",
|
|
23755
24054
|
{
|
|
23756
|
-
kind:
|
|
23757
|
-
ref:
|
|
23758
|
-
repoPath:
|
|
23759
|
-
title:
|
|
24055
|
+
kind: import_zod8.z.enum(["ticket", "pr", "branch"]),
|
|
24056
|
+
ref: import_zod8.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
24057
|
+
repoPath: import_zod8.z.string().describe("Workspace path from list_workspaces / list_board"),
|
|
24058
|
+
title: import_zod8.z.string().optional()
|
|
23760
24059
|
},
|
|
23761
24060
|
async ({ kind, ref, repoPath, title }) => {
|
|
23762
24061
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -23773,20 +24072,13 @@ async function startMcpServer() {
|
|
|
23773
24072
|
}))
|
|
23774
24073
|
);
|
|
23775
24074
|
if (existing) {
|
|
23776
|
-
return {
|
|
23777
|
-
|
|
23778
|
-
|
|
23779
|
-
|
|
23780
|
-
|
|
23781
|
-
|
|
23782
|
-
|
|
23783
|
-
title: existing.title,
|
|
23784
|
-
status: existing.status,
|
|
23785
|
-
link: `sideboard://thread/${existing.id}`
|
|
23786
|
-
})
|
|
23787
|
-
}
|
|
23788
|
-
]
|
|
23789
|
-
};
|
|
24075
|
+
return mcpJson({
|
|
24076
|
+
alreadyStarted: true,
|
|
24077
|
+
id: existing.id,
|
|
24078
|
+
title: existing.title,
|
|
24079
|
+
status: existing.status,
|
|
24080
|
+
link: `sideboard://thread/${existing.id}`
|
|
24081
|
+
});
|
|
23790
24082
|
}
|
|
23791
24083
|
const workspaces = orch.listWorkspaces();
|
|
23792
24084
|
const loaded = await getHomeBoardInputs(workspaces);
|
|
@@ -23860,9 +24152,9 @@ async function startMcpServer() {
|
|
|
23860
24152
|
"send_to_thread",
|
|
23861
24153
|
'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.',
|
|
23862
24154
|
{
|
|
23863
|
-
ref:
|
|
23864
|
-
prompt:
|
|
23865
|
-
force_stop:
|
|
24155
|
+
ref: import_zod8.z.string(),
|
|
24156
|
+
prompt: import_zod8.z.string(),
|
|
24157
|
+
force_stop: import_zod8.z.boolean().optional()
|
|
23866
24158
|
},
|
|
23867
24159
|
async ({ ref, prompt, force_stop }) => {
|
|
23868
24160
|
if (force_stop) {
|
|
@@ -23872,79 +24164,58 @@ async function startMcpServer() {
|
|
|
23872
24164
|
}
|
|
23873
24165
|
}
|
|
23874
24166
|
const thread = await orch.send(ref, prompt);
|
|
23875
|
-
return {
|
|
23876
|
-
|
|
23877
|
-
|
|
23878
|
-
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
status: thread.status,
|
|
23882
|
-
queueLength: thread.queue.length,
|
|
23883
|
-
forceStopped: Boolean(force_stop)
|
|
23884
|
-
})
|
|
23885
|
-
}
|
|
23886
|
-
]
|
|
23887
|
-
};
|
|
24167
|
+
return mcpJson({
|
|
24168
|
+
id: thread.id,
|
|
24169
|
+
status: thread.status,
|
|
24170
|
+
queueLength: thread.queue.length,
|
|
24171
|
+
forceStopped: Boolean(force_stop)
|
|
24172
|
+
});
|
|
23888
24173
|
}
|
|
23889
24174
|
);
|
|
23890
24175
|
server.tool(
|
|
23891
24176
|
"wait_for_turn",
|
|
23892
24177
|
"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).",
|
|
23893
24178
|
{
|
|
23894
|
-
ref:
|
|
23895
|
-
timeoutMs:
|
|
24179
|
+
ref: import_zod8.z.string(),
|
|
24180
|
+
timeoutMs: import_zod8.z.number().optional()
|
|
23896
24181
|
},
|
|
23897
24182
|
async ({ ref, timeoutMs }) => {
|
|
23898
24183
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
23899
24184
|
resolveIfStillRunning: true
|
|
23900
24185
|
});
|
|
23901
24186
|
const result = orch.getTurnResult(thread.id);
|
|
23902
|
-
return {
|
|
23903
|
-
|
|
23904
|
-
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
23909
|
-
|
|
23910
|
-
|
|
23911
|
-
|
|
23912
|
-
|
|
23913
|
-
lastActivityAt: result.lastActivityAt,
|
|
23914
|
-
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
23915
|
-
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
23916
|
-
})
|
|
23917
|
-
}
|
|
23918
|
-
]
|
|
23919
|
-
};
|
|
24187
|
+
return mcpJson({
|
|
24188
|
+
id: thread.id,
|
|
24189
|
+
status: result.status,
|
|
24190
|
+
text: result.text,
|
|
24191
|
+
lastError: result.lastError,
|
|
24192
|
+
stillRunning: result.stillRunning,
|
|
24193
|
+
progress: result.progress,
|
|
24194
|
+
lastActivityAt: result.lastActivityAt,
|
|
24195
|
+
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
24196
|
+
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
24197
|
+
});
|
|
23920
24198
|
}
|
|
23921
24199
|
);
|
|
23922
24200
|
server.tool(
|
|
23923
24201
|
"get_turn_result",
|
|
23924
24202
|
"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).",
|
|
23925
|
-
{ ref:
|
|
24203
|
+
{ ref: import_zod8.z.string() },
|
|
23926
24204
|
async ({ ref }) => {
|
|
23927
24205
|
const result = orch.getTurnResult(ref);
|
|
23928
|
-
return {
|
|
23929
|
-
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
|
|
23933
|
-
...result,
|
|
23934
|
-
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
23935
|
-
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
23936
|
-
})
|
|
23937
|
-
}
|
|
23938
|
-
]
|
|
23939
|
-
};
|
|
24206
|
+
return mcpJson({
|
|
24207
|
+
...result,
|
|
24208
|
+
hint: result.stillRunning ? mcpWaitStillRunningHint(result.status) : mcpWaitFinishedHint(result.status),
|
|
24209
|
+
incomplete: !result.stillRunning && Boolean(mcpWaitFinishedHint(result.status))
|
|
24210
|
+
});
|
|
23940
24211
|
}
|
|
23941
24212
|
);
|
|
23942
24213
|
server.tool(
|
|
23943
24214
|
"stop_thread",
|
|
23944
24215
|
"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.",
|
|
23945
24216
|
{
|
|
23946
|
-
ref:
|
|
23947
|
-
force:
|
|
24217
|
+
ref: import_zod8.z.string(),
|
|
24218
|
+
force: import_zod8.z.boolean().optional()
|
|
23948
24219
|
},
|
|
23949
24220
|
async ({ ref, force }) => {
|
|
23950
24221
|
const t = orch.getThread(ref);
|
|
@@ -23957,24 +24228,17 @@ async function startMcpServer() {
|
|
|
23957
24228
|
const clearQueue = force !== false;
|
|
23958
24229
|
const hadQueued = t.queue.length > 0;
|
|
23959
24230
|
const stopped = orch.stop(ref, { clearQueue, notifyParent: false });
|
|
23960
|
-
return {
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
id: stopped.id,
|
|
23966
|
-
status: stopped.status,
|
|
23967
|
-
clearedQueue: clearQueue && hadQueued
|
|
23968
|
-
})
|
|
23969
|
-
}
|
|
23970
|
-
]
|
|
23971
|
-
};
|
|
24231
|
+
return mcpJson({
|
|
24232
|
+
id: stopped.id,
|
|
24233
|
+
status: stopped.status,
|
|
24234
|
+
clearedQueue: clearQueue && hadQueued
|
|
24235
|
+
});
|
|
23972
24236
|
}
|
|
23973
24237
|
);
|
|
23974
24238
|
server.tool(
|
|
23975
24239
|
"archive_thread",
|
|
23976
24240
|
"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.",
|
|
23977
|
-
{ ref:
|
|
24241
|
+
{ ref: import_zod8.z.string() },
|
|
23978
24242
|
async ({ ref }) => {
|
|
23979
24243
|
const t = orch.getThread(ref);
|
|
23980
24244
|
if (!t) {
|
|
@@ -23991,38 +24255,24 @@ async function startMcpServer() {
|
|
|
23991
24255
|
};
|
|
23992
24256
|
}
|
|
23993
24257
|
const archived = await orch.archive(ref);
|
|
23994
|
-
return {
|
|
23995
|
-
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
text: JSON.stringify({
|
|
23999
|
-
id: archived.id,
|
|
24000
|
-
status: archived.status
|
|
24001
|
-
})
|
|
24002
|
-
}
|
|
24003
|
-
]
|
|
24004
|
-
};
|
|
24258
|
+
return mcpJson({
|
|
24259
|
+
id: archived.id,
|
|
24260
|
+
status: archived.status
|
|
24261
|
+
});
|
|
24005
24262
|
}
|
|
24006
24263
|
);
|
|
24007
24264
|
server.tool(
|
|
24008
24265
|
"restore_thread",
|
|
24009
24266
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
24010
|
-
{ ref:
|
|
24267
|
+
{ ref: import_zod8.z.string() },
|
|
24011
24268
|
async ({ ref }) => {
|
|
24012
24269
|
try {
|
|
24013
24270
|
const restored = await orch.restore(ref);
|
|
24014
|
-
return {
|
|
24015
|
-
|
|
24016
|
-
|
|
24017
|
-
|
|
24018
|
-
|
|
24019
|
-
id: restored.id,
|
|
24020
|
-
status: restored.status,
|
|
24021
|
-
worktreePath: restored.worktreePath
|
|
24022
|
-
})
|
|
24023
|
-
}
|
|
24024
|
-
]
|
|
24025
|
-
};
|
|
24271
|
+
return mcpJson({
|
|
24272
|
+
id: restored.id,
|
|
24273
|
+
status: restored.status,
|
|
24274
|
+
worktreePath: restored.worktreePath
|
|
24275
|
+
});
|
|
24026
24276
|
} catch (err) {
|
|
24027
24277
|
const message = err instanceof Error ? err.message : String(err);
|
|
24028
24278
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -24033,34 +24283,25 @@ async function startMcpServer() {
|
|
|
24033
24283
|
"get_diff",
|
|
24034
24284
|
"Compact diff summary (capped hunks, paginated)",
|
|
24035
24285
|
{
|
|
24036
|
-
ref:
|
|
24037
|
-
maxFiles:
|
|
24286
|
+
ref: import_zod8.z.string(),
|
|
24287
|
+
maxFiles: import_zod8.z.number().optional()
|
|
24038
24288
|
},
|
|
24039
24289
|
async ({ ref, maxFiles }) => {
|
|
24040
24290
|
const summary = await orch.diffSummary(ref);
|
|
24041
|
-
return {
|
|
24042
|
-
|
|
24043
|
-
|
|
24044
|
-
|
|
24045
|
-
text: JSON.stringify({
|
|
24046
|
-
...summary,
|
|
24047
|
-
files: summary.files.slice(0, maxFiles ?? 10)
|
|
24048
|
-
})
|
|
24049
|
-
}
|
|
24050
|
-
]
|
|
24051
|
-
};
|
|
24291
|
+
return mcpJson({
|
|
24292
|
+
...summary,
|
|
24293
|
+
files: summary.files.slice(0, maxFiles ?? 10)
|
|
24294
|
+
});
|
|
24052
24295
|
}
|
|
24053
24296
|
);
|
|
24054
24297
|
server.tool(
|
|
24055
24298
|
"get_pr_checks",
|
|
24056
24299
|
"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.",
|
|
24057
|
-
{ ref:
|
|
24300
|
+
{ ref: import_zod8.z.string().describe("Worktree thread id/ref") },
|
|
24058
24301
|
async ({ ref }) => {
|
|
24059
24302
|
try {
|
|
24060
24303
|
const checks = await orch.getPrChecks(ref);
|
|
24061
|
-
return
|
|
24062
|
-
content: [{ type: "text", text: JSON.stringify(checks) }]
|
|
24063
|
-
};
|
|
24304
|
+
return mcpJson(checks);
|
|
24064
24305
|
} catch (err) {
|
|
24065
24306
|
const message = err instanceof Error ? err.message : String(err);
|
|
24066
24307
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -24070,25 +24311,18 @@ async function startMcpServer() {
|
|
|
24070
24311
|
server.tool(
|
|
24071
24312
|
"request_review",
|
|
24072
24313
|
'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.',
|
|
24073
|
-
{ ref:
|
|
24314
|
+
{ ref: import_zod8.z.string().describe("Worktree thread id/ref to review") },
|
|
24074
24315
|
async ({ ref }) => {
|
|
24075
24316
|
try {
|
|
24076
24317
|
const tab = await orch.requestReview(ref);
|
|
24077
24318
|
const from = orch.getThread(ref);
|
|
24078
|
-
return {
|
|
24079
|
-
|
|
24080
|
-
|
|
24081
|
-
|
|
24082
|
-
|
|
24083
|
-
|
|
24084
|
-
|
|
24085
|
-
status: tab.status,
|
|
24086
|
-
fromThreadId: from?.id ?? ref,
|
|
24087
|
-
link: `sideboard://thread/${tab.id}`
|
|
24088
|
-
})
|
|
24089
|
-
}
|
|
24090
|
-
]
|
|
24091
|
-
};
|
|
24319
|
+
return mcpJson({
|
|
24320
|
+
id: tab.id,
|
|
24321
|
+
title: tab.title,
|
|
24322
|
+
status: tab.status,
|
|
24323
|
+
fromThreadId: from?.id ?? ref,
|
|
24324
|
+
link: `sideboard://thread/${tab.id}`
|
|
24325
|
+
});
|
|
24092
24326
|
} catch (err) {
|
|
24093
24327
|
const message = err instanceof Error ? err.message : String(err);
|
|
24094
24328
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -24099,53 +24333,37 @@ async function startMcpServer() {
|
|
|
24099
24333
|
"ask_git",
|
|
24100
24334
|
"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.",
|
|
24101
24335
|
{
|
|
24102
|
-
ref:
|
|
24103
|
-
action:
|
|
24336
|
+
ref: import_zod8.z.string().describe("Worktree thread id/ref"),
|
|
24337
|
+
action: import_zod8.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
24104
24338
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
24105
24339
|
)
|
|
24106
24340
|
},
|
|
24107
24341
|
async ({ ref, action }) => {
|
|
24108
24342
|
try {
|
|
24109
24343
|
const thread = await orch.askGit(ref, action);
|
|
24110
|
-
return {
|
|
24111
|
-
|
|
24112
|
-
|
|
24113
|
-
|
|
24114
|
-
|
|
24115
|
-
|
|
24116
|
-
|
|
24117
|
-
queueLength: thread.queue.length,
|
|
24118
|
-
action,
|
|
24119
|
-
link: `sideboard://thread/${thread.id}`
|
|
24120
|
-
})
|
|
24121
|
-
}
|
|
24122
|
-
]
|
|
24123
|
-
};
|
|
24344
|
+
return mcpJson({
|
|
24345
|
+
id: thread.id,
|
|
24346
|
+
status: thread.status,
|
|
24347
|
+
queueLength: thread.queue.length,
|
|
24348
|
+
action,
|
|
24349
|
+
link: `sideboard://thread/${thread.id}`
|
|
24350
|
+
});
|
|
24124
24351
|
} catch (err) {
|
|
24125
24352
|
const raw = err instanceof Error ? err.message : String(err);
|
|
24126
24353
|
const message = formatGhLandError(raw);
|
|
24127
|
-
return
|
|
24128
|
-
|
|
24129
|
-
|
|
24130
|
-
|
|
24131
|
-
|
|
24132
|
-
|
|
24133
|
-
|
|
24134
|
-
|
|
24135
|
-
|
|
24136
|
-
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
|
|
24137
|
-
},
|
|
24138
|
-
null,
|
|
24139
|
-
2
|
|
24140
|
-
)
|
|
24141
|
-
}
|
|
24142
|
-
],
|
|
24143
|
-
isError: true
|
|
24144
|
-
};
|
|
24354
|
+
return mcpJson(
|
|
24355
|
+
{
|
|
24356
|
+
error: true,
|
|
24357
|
+
action,
|
|
24358
|
+
lastError: message,
|
|
24359
|
+
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
|
|
24360
|
+
},
|
|
24361
|
+
true
|
|
24362
|
+
);
|
|
24145
24363
|
}
|
|
24146
24364
|
}
|
|
24147
24365
|
);
|
|
24148
|
-
const agentEnum =
|
|
24366
|
+
const agentEnum = import_zod8.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
24149
24367
|
server.tool(
|
|
24150
24368
|
"list_models",
|
|
24151
24369
|
"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.",
|
|
@@ -24155,9 +24373,7 @@ async function startMcpServer() {
|
|
|
24155
24373
|
async ({ agent }) => {
|
|
24156
24374
|
try {
|
|
24157
24375
|
const catalogs = await listModelsForAgent(agent);
|
|
24158
|
-
return
|
|
24159
|
-
content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
|
|
24160
|
-
};
|
|
24376
|
+
return mcpJson(catalogs);
|
|
24161
24377
|
} catch (err) {
|
|
24162
24378
|
const message = err instanceof Error ? err.message : String(err);
|
|
24163
24379
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -24168,11 +24384,11 @@ async function startMcpServer() {
|
|
|
24168
24384
|
"fork_worktree",
|
|
24169
24385
|
"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.",
|
|
24170
24386
|
{
|
|
24171
|
-
ref:
|
|
24172
|
-
through_index:
|
|
24387
|
+
ref: import_zod8.z.string().describe("Worktree thread id/ref to fork"),
|
|
24388
|
+
through_index: import_zod8.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
24173
24389
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
24174
|
-
model:
|
|
24175
|
-
title:
|
|
24390
|
+
model: import_zod8.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
24391
|
+
title: import_zod8.z.string().optional()
|
|
24176
24392
|
},
|
|
24177
24393
|
async ({ ref, through_index, agent, model, title }) => {
|
|
24178
24394
|
try {
|
|
@@ -24185,24 +24401,17 @@ async function startMcpServer() {
|
|
|
24185
24401
|
model,
|
|
24186
24402
|
title
|
|
24187
24403
|
});
|
|
24188
|
-
return {
|
|
24189
|
-
|
|
24190
|
-
|
|
24191
|
-
|
|
24192
|
-
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24198
|
-
|
|
24199
|
-
worktreePath: thread.worktreePath,
|
|
24200
|
-
fromThreadId: source?.id ?? ref,
|
|
24201
|
-
link: `sideboard://thread/${thread.id}`
|
|
24202
|
-
})
|
|
24203
|
-
}
|
|
24204
|
-
]
|
|
24205
|
-
};
|
|
24404
|
+
return mcpJson({
|
|
24405
|
+
id: thread.id,
|
|
24406
|
+
title: thread.title,
|
|
24407
|
+
status: thread.status,
|
|
24408
|
+
agent: thread.agent,
|
|
24409
|
+
model: thread.model,
|
|
24410
|
+
branchName: thread.branchName,
|
|
24411
|
+
worktreePath: thread.worktreePath,
|
|
24412
|
+
fromThreadId: source?.id ?? ref,
|
|
24413
|
+
link: `sideboard://thread/${thread.id}`
|
|
24414
|
+
});
|
|
24206
24415
|
} catch (err) {
|
|
24207
24416
|
const message = err instanceof Error ? err.message : String(err);
|
|
24208
24417
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -24213,11 +24422,11 @@ async function startMcpServer() {
|
|
|
24213
24422
|
"fork_chat",
|
|
24214
24423
|
"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.",
|
|
24215
24424
|
{
|
|
24216
|
-
ref:
|
|
24217
|
-
through_index:
|
|
24425
|
+
ref: import_zod8.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
24426
|
+
through_index: import_zod8.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
24218
24427
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
24219
|
-
model:
|
|
24220
|
-
title:
|
|
24428
|
+
model: import_zod8.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
24429
|
+
title: import_zod8.z.string().optional()
|
|
24221
24430
|
},
|
|
24222
24431
|
async ({ ref, through_index, agent, model, title }) => {
|
|
24223
24432
|
try {
|
|
@@ -24235,24 +24444,17 @@ async function startMcpServer() {
|
|
|
24235
24444
|
model,
|
|
24236
24445
|
title
|
|
24237
24446
|
});
|
|
24238
|
-
return {
|
|
24239
|
-
|
|
24240
|
-
|
|
24241
|
-
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
worktreePath: tab.worktreePath,
|
|
24250
|
-
fromThreadId: source.id,
|
|
24251
|
-
link: `sideboard://thread/${tab.id}`
|
|
24252
|
-
})
|
|
24253
|
-
}
|
|
24254
|
-
]
|
|
24255
|
-
};
|
|
24447
|
+
return mcpJson({
|
|
24448
|
+
id: tab.id,
|
|
24449
|
+
title: tab.title,
|
|
24450
|
+
status: tab.status,
|
|
24451
|
+
agent: tab.agent,
|
|
24452
|
+
model: tab.model,
|
|
24453
|
+
sourceType: tab.sourceType,
|
|
24454
|
+
worktreePath: tab.worktreePath,
|
|
24455
|
+
fromThreadId: source.id,
|
|
24456
|
+
link: `sideboard://thread/${tab.id}`
|
|
24457
|
+
});
|
|
24256
24458
|
} catch (err) {
|
|
24257
24459
|
const message = err instanceof Error ? err.message : String(err);
|
|
24258
24460
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
@@ -24263,49 +24465,35 @@ async function startMcpServer() {
|
|
|
24263
24465
|
"run_dev_script",
|
|
24264
24466
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
24265
24467
|
{
|
|
24266
|
-
ref:
|
|
24267
|
-
name:
|
|
24468
|
+
ref: import_zod8.z.string(),
|
|
24469
|
+
name: import_zod8.z.string().optional()
|
|
24268
24470
|
},
|
|
24269
24471
|
async ({ ref, name }) => {
|
|
24270
24472
|
const result = await orch.startDev(ref, name);
|
|
24271
|
-
return {
|
|
24272
|
-
|
|
24273
|
-
|
|
24274
|
-
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
scriptName: result.scriptName,
|
|
24278
|
-
ports: result.ports,
|
|
24279
|
-
url: `http://localhost:${result.port}`
|
|
24280
|
-
})
|
|
24281
|
-
}
|
|
24282
|
-
]
|
|
24283
|
-
};
|
|
24473
|
+
return mcpJson({
|
|
24474
|
+
port: result.port,
|
|
24475
|
+
scriptName: result.scriptName,
|
|
24476
|
+
ports: result.ports,
|
|
24477
|
+
url: `http://localhost:${result.port}`
|
|
24478
|
+
});
|
|
24284
24479
|
}
|
|
24285
24480
|
);
|
|
24286
24481
|
server.tool(
|
|
24287
24482
|
"list_run_scripts",
|
|
24288
24483
|
"List named run scripts available for a thread",
|
|
24289
|
-
{ ref:
|
|
24484
|
+
{ ref: import_zod8.z.string() },
|
|
24290
24485
|
async ({ ref }) => {
|
|
24291
24486
|
const scripts = orch.listThreadRunScripts(ref);
|
|
24292
24487
|
const active = orch.getActiveRuns(ref);
|
|
24293
|
-
return {
|
|
24294
|
-
content: [
|
|
24295
|
-
{
|
|
24296
|
-
type: "text",
|
|
24297
|
-
text: JSON.stringify({ scripts, active }, null, 2)
|
|
24298
|
-
}
|
|
24299
|
-
]
|
|
24300
|
-
};
|
|
24488
|
+
return mcpJson({ scripts, active });
|
|
24301
24489
|
}
|
|
24302
24490
|
);
|
|
24303
24491
|
server.tool(
|
|
24304
24492
|
"stop_dev_script",
|
|
24305
24493
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
24306
24494
|
{
|
|
24307
|
-
ref:
|
|
24308
|
-
name:
|
|
24495
|
+
ref: import_zod8.z.string(),
|
|
24496
|
+
name: import_zod8.z.string().optional()
|
|
24309
24497
|
},
|
|
24310
24498
|
async ({ ref, name }) => {
|
|
24311
24499
|
orch.stopDev(ref, name);
|
|
@@ -24315,27 +24503,25 @@ async function startMcpServer() {
|
|
|
24315
24503
|
server.tool(
|
|
24316
24504
|
"run_setup",
|
|
24317
24505
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
24318
|
-
{ ref:
|
|
24506
|
+
{ ref: import_zod8.z.string() },
|
|
24319
24507
|
async ({ ref }) => {
|
|
24320
24508
|
const result = await orch.runSetup(ref);
|
|
24321
|
-
return
|
|
24322
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
24323
|
-
};
|
|
24509
|
+
return mcpJson(result);
|
|
24324
24510
|
}
|
|
24325
24511
|
);
|
|
24326
24512
|
server.tool(
|
|
24327
24513
|
"add_workspace",
|
|
24328
24514
|
"Register a git repo as a Sideboard workspace",
|
|
24329
|
-
{ repoPath:
|
|
24515
|
+
{ repoPath: import_zod8.z.string() },
|
|
24330
24516
|
async ({ repoPath }) => {
|
|
24331
24517
|
const ws = await orch.addWorkspace(repoPath);
|
|
24332
|
-
return
|
|
24518
|
+
return mcpJson(ws);
|
|
24333
24519
|
}
|
|
24334
24520
|
);
|
|
24335
24521
|
server.tool(
|
|
24336
24522
|
"remove_workspace",
|
|
24337
24523
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
24338
|
-
{ repoPath:
|
|
24524
|
+
{ repoPath: import_zod8.z.string() },
|
|
24339
24525
|
async ({ repoPath }) => {
|
|
24340
24526
|
orch.removeWorkspace(repoPath);
|
|
24341
24527
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -24345,40 +24531,31 @@ async function startMcpServer() {
|
|
|
24345
24531
|
"fanout",
|
|
24346
24532
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
24347
24533
|
{
|
|
24348
|
-
prompt:
|
|
24349
|
-
agents:
|
|
24350
|
-
repoPath:
|
|
24351
|
-
sourceType:
|
|
24352
|
-
sourceRef:
|
|
24353
|
-
title:
|
|
24534
|
+
prompt: import_zod8.z.string(),
|
|
24535
|
+
agents: import_zod8.z.array(import_zod8.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
24536
|
+
repoPath: import_zod8.z.string(),
|
|
24537
|
+
sourceType: import_zod8.z.enum(["branch", "pr", "ticket"]).optional(),
|
|
24538
|
+
sourceRef: import_zod8.z.string().optional(),
|
|
24539
|
+
title: import_zod8.z.string().optional()
|
|
24354
24540
|
},
|
|
24355
24541
|
async (args) => {
|
|
24356
24542
|
const threads = await orch.bestOfN(args);
|
|
24357
|
-
return
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
24364
|
-
|
|
24365
|
-
branchName: t.branchName,
|
|
24366
|
-
worktreePath: t.worktreePath
|
|
24367
|
-
})),
|
|
24368
|
-
null,
|
|
24369
|
-
2
|
|
24370
|
-
)
|
|
24371
|
-
}
|
|
24372
|
-
]
|
|
24373
|
-
};
|
|
24543
|
+
return mcpJson(
|
|
24544
|
+
threads.map((t) => ({
|
|
24545
|
+
id: t.id,
|
|
24546
|
+
agent: t.agent,
|
|
24547
|
+
branchName: t.branchName,
|
|
24548
|
+
worktreePath: t.worktreePath
|
|
24549
|
+
}))
|
|
24550
|
+
);
|
|
24374
24551
|
}
|
|
24375
24552
|
);
|
|
24376
24553
|
server.tool(
|
|
24377
24554
|
"list_branches",
|
|
24378
24555
|
"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).",
|
|
24379
24556
|
{
|
|
24380
|
-
repoPath:
|
|
24381
|
-
unmergedOnly:
|
|
24557
|
+
repoPath: import_zod8.z.string(),
|
|
24558
|
+
unmergedOnly: import_zod8.z.boolean().optional()
|
|
24382
24559
|
},
|
|
24383
24560
|
async ({ repoPath, unmergedOnly }) => {
|
|
24384
24561
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -24399,19 +24576,19 @@ async function startMcpServer() {
|
|
|
24399
24576
|
"list_prs",
|
|
24400
24577
|
'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.',
|
|
24401
24578
|
{
|
|
24402
|
-
repoPath:
|
|
24403
|
-
query:
|
|
24404
|
-
queue:
|
|
24579
|
+
repoPath: import_zod8.z.string(),
|
|
24580
|
+
query: import_zod8.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
24581
|
+
queue: import_zod8.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
24405
24582
|
'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
|
|
24406
24583
|
),
|
|
24407
|
-
state:
|
|
24408
|
-
label:
|
|
24584
|
+
state: import_zod8.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
24585
|
+
label: import_zod8.z.string().optional().describe(
|
|
24409
24586
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
24410
24587
|
),
|
|
24411
|
-
reviewer:
|
|
24588
|
+
reviewer: import_zod8.z.string().optional().describe(
|
|
24412
24589
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
24413
24590
|
),
|
|
24414
|
-
limit:
|
|
24591
|
+
limit: import_zod8.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
24415
24592
|
},
|
|
24416
24593
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
24417
24594
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -24443,89 +24620,65 @@ async function startMcpServer() {
|
|
|
24443
24620
|
server.tool(
|
|
24444
24621
|
"get_pr_stack",
|
|
24445
24622
|
"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).",
|
|
24446
|
-
{ ref:
|
|
24623
|
+
{ ref: import_zod8.z.string() },
|
|
24447
24624
|
async ({ ref }) => {
|
|
24448
24625
|
const stack = await orch.getPrStack(ref);
|
|
24449
|
-
return
|
|
24450
|
-
content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
|
|
24451
|
-
};
|
|
24626
|
+
return mcpJson(stack);
|
|
24452
24627
|
}
|
|
24453
24628
|
);
|
|
24454
24629
|
server.tool(
|
|
24455
24630
|
"open_pr_stack_layers",
|
|
24456
24631
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
24457
24632
|
{
|
|
24458
|
-
ref:
|
|
24459
|
-
layer:
|
|
24633
|
+
ref: import_zod8.z.string(),
|
|
24634
|
+
layer: import_zod8.z.number().int().positive().optional()
|
|
24460
24635
|
},
|
|
24461
24636
|
async ({ ref, layer }) => {
|
|
24462
24637
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
24463
|
-
return {
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24472
|
-
|
|
24473
|
-
|
|
24474
|
-
|
|
24475
|
-
|
|
24476
|
-
worktreePath: t.worktreePath,
|
|
24477
|
-
prUrl: t.prUrl,
|
|
24478
|
-
link: `sideboard://thread/${t.id}`
|
|
24479
|
-
}))
|
|
24480
|
-
},
|
|
24481
|
-
null,
|
|
24482
|
-
2
|
|
24483
|
-
)
|
|
24484
|
-
}
|
|
24485
|
-
]
|
|
24486
|
-
};
|
|
24638
|
+
return mcpJson({
|
|
24639
|
+
stackNumber: result.stack.stackNumber,
|
|
24640
|
+
trunk: result.stack.trunk,
|
|
24641
|
+
threads: result.threads.map((t) => ({
|
|
24642
|
+
id: t.id,
|
|
24643
|
+
title: t.title,
|
|
24644
|
+
branchName: t.branchName,
|
|
24645
|
+
stackLayer: t.stackLayer,
|
|
24646
|
+
worktreePath: t.worktreePath,
|
|
24647
|
+
prUrl: t.prUrl,
|
|
24648
|
+
link: `sideboard://thread/${t.id}`
|
|
24649
|
+
}))
|
|
24650
|
+
});
|
|
24487
24651
|
}
|
|
24488
24652
|
);
|
|
24489
24653
|
server.tool(
|
|
24490
24654
|
"add_stack_layer",
|
|
24491
24655
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
24492
24656
|
{
|
|
24493
|
-
ref:
|
|
24494
|
-
branchName:
|
|
24495
|
-
title:
|
|
24657
|
+
ref: import_zod8.z.string(),
|
|
24658
|
+
branchName: import_zod8.z.string(),
|
|
24659
|
+
title: import_zod8.z.string().optional()
|
|
24496
24660
|
},
|
|
24497
24661
|
async ({ ref, branchName, title }) => {
|
|
24498
24662
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
24499
|
-
return {
|
|
24500
|
-
|
|
24501
|
-
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
branchName: result.thread.branchName,
|
|
24508
|
-
stackLayer: result.thread.stackLayer,
|
|
24509
|
-
worktreePath: result.thread.worktreePath,
|
|
24510
|
-
link: `sideboard://thread/${result.thread.id}`
|
|
24511
|
-
},
|
|
24512
|
-
null,
|
|
24513
|
-
2
|
|
24514
|
-
)
|
|
24515
|
-
}
|
|
24516
|
-
]
|
|
24517
|
-
};
|
|
24663
|
+
return mcpJson({
|
|
24664
|
+
id: result.thread.id,
|
|
24665
|
+
title: result.thread.title,
|
|
24666
|
+
branchName: result.thread.branchName,
|
|
24667
|
+
stackLayer: result.thread.stackLayer,
|
|
24668
|
+
worktreePath: result.thread.worktreePath,
|
|
24669
|
+
link: `sideboard://thread/${result.thread.id}`
|
|
24670
|
+
});
|
|
24518
24671
|
}
|
|
24519
24672
|
);
|
|
24520
24673
|
server.tool(
|
|
24521
24674
|
"create_pr_stack",
|
|
24522
24675
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
24523
24676
|
{
|
|
24524
|
-
repoPath:
|
|
24525
|
-
branches:
|
|
24526
|
-
agent:
|
|
24527
|
-
base:
|
|
24528
|
-
title:
|
|
24677
|
+
repoPath: import_zod8.z.string(),
|
|
24678
|
+
branches: import_zod8.z.array(import_zod8.z.string()).min(1),
|
|
24679
|
+
agent: import_zod8.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
24680
|
+
base: import_zod8.z.string().optional(),
|
|
24681
|
+
title: import_zod8.z.string().optional()
|
|
24529
24682
|
},
|
|
24530
24683
|
async (args) => {
|
|
24531
24684
|
const result = await orch.createPrStack({
|
|
@@ -24535,39 +24688,28 @@ async function startMcpServer() {
|
|
|
24535
24688
|
base: args.base,
|
|
24536
24689
|
title: args.title
|
|
24537
24690
|
});
|
|
24538
|
-
return {
|
|
24539
|
-
|
|
24540
|
-
|
|
24541
|
-
|
|
24542
|
-
|
|
24543
|
-
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24548
|
-
|
|
24549
|
-
|
|
24550
|
-
stackLayer: t.stackLayer,
|
|
24551
|
-
worktreePath: t.worktreePath,
|
|
24552
|
-
link: `sideboard://thread/${t.id}`
|
|
24553
|
-
}))
|
|
24554
|
-
},
|
|
24555
|
-
null,
|
|
24556
|
-
2
|
|
24557
|
-
)
|
|
24558
|
-
}
|
|
24559
|
-
]
|
|
24560
|
-
};
|
|
24691
|
+
return mcpJson({
|
|
24692
|
+
stackNumber: result.stack.stackNumber,
|
|
24693
|
+
trunk: result.stack.trunk,
|
|
24694
|
+
threads: result.threads.map((t) => ({
|
|
24695
|
+
id: t.id,
|
|
24696
|
+
title: t.title,
|
|
24697
|
+
branchName: t.branchName,
|
|
24698
|
+
stackLayer: t.stackLayer,
|
|
24699
|
+
worktreePath: t.worktreePath,
|
|
24700
|
+
link: `sideboard://thread/${t.id}`
|
|
24701
|
+
}))
|
|
24702
|
+
});
|
|
24561
24703
|
}
|
|
24562
24704
|
);
|
|
24563
24705
|
server.tool(
|
|
24564
24706
|
"list_issues",
|
|
24565
24707
|
"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.",
|
|
24566
24708
|
{
|
|
24567
|
-
repoPath:
|
|
24568
|
-
query:
|
|
24569
|
-
assignee:
|
|
24570
|
-
limit:
|
|
24709
|
+
repoPath: import_zod8.z.string(),
|
|
24710
|
+
query: import_zod8.z.string().optional().describe("Search title, identifier, or description"),
|
|
24711
|
+
assignee: import_zod8.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
24712
|
+
limit: import_zod8.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
24571
24713
|
},
|
|
24572
24714
|
async ({ repoPath, query, assignee, limit }) => {
|
|
24573
24715
|
const root = await resolveRepoRoot(repoPath);
|