@sideboard-ai/core 0.1.124 → 0.1.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agents-DPR2TDNF.js → agents-7BUQY2WX.js} +6 -6
- package/dist/{agents-DCXXLTXF.js → agents-X2I3RNCY.js} +6 -6
- package/dist/{app-settings-YFWV7MOG.js → app-settings-6RPALX4J.js} +7 -1
- package/dist/{app-settings-IKFWZDUK.js → app-settings-E7NEQY7D.js} +7 -1
- package/dist/{chunk-SDCPQL27.js → chunk-DLM5LSQW.js} +484 -93
- package/dist/{chunk-RG737OWT.js → chunk-G6X6UFJO.js} +2 -0
- package/dist/{chunk-HPAWHIZ3.js → chunk-HLJUNJBF.js} +2 -0
- package/dist/{chunk-OHEFHIG3.js → chunk-HRSBZIXS.js} +14 -6
- package/dist/{chunk-KWGP6RZM.js → chunk-HULESWLI.js} +4 -4
- package/dist/{chunk-FXQPY2KU.js → chunk-HUYEU4GS.js} +26 -4
- package/dist/{chunk-2X7I6MDW.js → chunk-HZPO3SSZ.js} +11 -8
- package/dist/{chunk-O43IRQ2Y.js → chunk-MNL4FSKY.js} +4 -4
- package/dist/{chunk-7Y3AYQWT.js → chunk-NV73AO7Q.js} +4 -4
- package/dist/{chunk-TRTWH6C2.js → chunk-RSFCB23A.js} +11 -8
- package/dist/{chunk-RBVVWBVB.js → chunk-SYLTXDRF.js} +2 -2
- package/dist/{chunk-MZDAEI3Y.js → chunk-T7KJQJAX.js} +496 -109
- package/dist/{chunk-UAVZ2JSO.js → chunk-UDQI3N47.js} +14 -6
- package/dist/{chunk-HZLE6LJJ.js → chunk-VE22NWBD.js} +2 -2
- package/dist/{chunk-JNAIKICO.js → chunk-ZMUOPTKZ.js} +4 -4
- package/dist/{chunk-BMIRX64H.js → chunk-ZND6XV6J.js} +26 -4
- package/dist/{coordinator-prompt-AZ3WRDNC.js → coordinator-prompt-MEEUOEF2.js} +4 -4
- package/dist/{coordinator-prompt-LEU62W25.js → coordinator-prompt-SPJSGAQS.js} +4 -4
- package/dist/{global-workspace-U5UOVXKQ.js → global-workspace-BMWP7YZC.js} +5 -5
- package/dist/{global-workspace-4YNYDMLQ.js → global-workspace-KNESLWKG.js} +5 -5
- package/dist/index.cjs +1208 -236
- package/dist/index.d.cts +303 -103
- package/dist/index.d.ts +303 -103
- package/dist/index.js +671 -129
- package/dist/mcp/run-stdio.cjs +1054 -194
- package/dist/mcp/run-stdio.js +554 -114
- package/dist/{orchestrator-VJQ5EWZZ.js → orchestrator-3JLPPTCF.js} +8 -8
- package/dist/{orchestrator-JS3EHI4E.js → orchestrator-6GUCE4LY.js} +8 -8
- package/dist/{thread-store-JR235AWK.js → thread-store-5MLYY35S.js} +1 -1
- package/dist/{thread-store-PMH3BMB6.js → thread-store-F6BCARQG.js} +1 -1
- package/dist/{workspaces-ICK433ZV.js → workspaces-HSYRYJYV.js} +6 -6
- package/dist/{workspaces-POWKTH2D.js → workspaces-O4QZOASB.js} +6 -6
- package/dist/{worktree-CMGBTVN4.js → worktree-ERHC7Q4F.js} +5 -3
- package/dist/{worktree-T57RD7BQ.js → worktree-JVGSLSLJ.js} +5 -3
- package/package.json +1 -1
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -2,46 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
AGENT_GIT_ACTIONS,
|
|
5
|
+
BOARD_COLUMN_DEFS,
|
|
6
|
+
HOME_BOARD_AGENT_HINT,
|
|
7
|
+
HOME_BOARD_CACHE_TTL_MS,
|
|
8
|
+
assembleHomeBoard,
|
|
5
9
|
createSchedule,
|
|
10
|
+
dedupeBoardIssues,
|
|
11
|
+
dedupeBoardPrs,
|
|
6
12
|
deleteSchedule,
|
|
13
|
+
findBoardIssue,
|
|
14
|
+
findBoardPin,
|
|
15
|
+
findBoardPr,
|
|
16
|
+
findLiveThreadForCreate,
|
|
7
17
|
fireSchedule,
|
|
8
18
|
getOrchestrator,
|
|
9
19
|
getSchedule,
|
|
20
|
+
issueNeedsWorkspacePick,
|
|
10
21
|
listLinearIssues,
|
|
11
22
|
listSchedules,
|
|
12
23
|
listSlackOutboundWatches,
|
|
13
24
|
listSlackWorkspaces,
|
|
25
|
+
pollSlackOutboundWatches,
|
|
14
26
|
readTurnLive,
|
|
15
27
|
recordSlackOutboundWatch,
|
|
16
|
-
refreshSlackReplyBadges,
|
|
17
28
|
requireSlackWorkspace,
|
|
18
29
|
resolveScheduleThreadId,
|
|
19
30
|
slackApi,
|
|
20
31
|
slackTokenFor,
|
|
32
|
+
syncBoardPins,
|
|
21
33
|
updateSchedule
|
|
22
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-DLM5LSQW.js";
|
|
23
35
|
import {
|
|
24
36
|
listModelsForAgent,
|
|
25
37
|
sideboardMcpProfile
|
|
26
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-ZMUOPTKZ.js";
|
|
27
39
|
import "../chunk-YMRT2DU6.js";
|
|
28
|
-
import "../chunk-
|
|
40
|
+
import "../chunk-SYLTXDRF.js";
|
|
29
41
|
import "../chunk-N5PM7HGQ.js";
|
|
30
42
|
import "../chunk-KLBOWJ74.js";
|
|
31
43
|
import {
|
|
32
44
|
GLOBAL_WORKSPACE_ID,
|
|
33
45
|
isCloudCoordinatorThread
|
|
34
|
-
} from "../chunk-
|
|
35
|
-
import "../chunk-
|
|
46
|
+
} from "../chunk-MNL4FSKY.js";
|
|
47
|
+
import "../chunk-RSFCB23A.js";
|
|
36
48
|
import {
|
|
49
|
+
canonicalizeRepoPath,
|
|
37
50
|
listBranches,
|
|
38
51
|
listPrs,
|
|
39
52
|
resolveGithubRepoSlug,
|
|
40
53
|
resolveRepoRoot,
|
|
41
54
|
warmGithubAgentAuth
|
|
42
|
-
} from "../chunk-
|
|
55
|
+
} from "../chunk-HRSBZIXS.js";
|
|
43
56
|
import "../chunk-B3SJXYIJ.js";
|
|
44
|
-
import "../chunk-
|
|
57
|
+
import "../chunk-G6X6UFJO.js";
|
|
45
58
|
import {
|
|
46
59
|
gh
|
|
47
60
|
} from "../chunk-KPIYENTF.js";
|
|
@@ -52,15 +65,18 @@ import {
|
|
|
52
65
|
loadAppSettings,
|
|
53
66
|
resolveEffectiveIssueSource,
|
|
54
67
|
saveLinearOAuth
|
|
55
|
-
} from "../chunk-
|
|
68
|
+
} from "../chunk-ZND6XV6J.js";
|
|
56
69
|
import "../chunk-NSTQ6QKD.js";
|
|
57
70
|
import "../chunk-KGZBYWZ3.js";
|
|
58
|
-
import
|
|
71
|
+
import {
|
|
72
|
+
appDataDir
|
|
73
|
+
} from "../chunk-BD2ICC4D.js";
|
|
59
74
|
|
|
60
75
|
// src/mcp/server.ts
|
|
61
76
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
62
77
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
63
78
|
import { z as z4 } from "zod";
|
|
79
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
64
80
|
import { basename } from "path";
|
|
65
81
|
|
|
66
82
|
// src/http/fetch.ts
|
|
@@ -196,10 +212,13 @@ var ISSUE_FIELDS = `
|
|
|
196
212
|
assignee { id name }
|
|
197
213
|
team { id key name states { nodes { id name type } } }
|
|
198
214
|
labels { nodes { name } }
|
|
215
|
+
cycle { id name number startsAt endsAt completedAt }
|
|
199
216
|
`;
|
|
200
217
|
var ASSIGNED_ISSUES_QUERY = `
|
|
201
218
|
query SideboardAssignedIssues($first: Int!) {
|
|
202
219
|
viewer {
|
|
220
|
+
id
|
|
221
|
+
name
|
|
203
222
|
assignedIssues(
|
|
204
223
|
first: $first
|
|
205
224
|
orderBy: updatedAt
|
|
@@ -302,6 +321,25 @@ function mapState(node) {
|
|
|
302
321
|
type: String(node.type ?? "")
|
|
303
322
|
};
|
|
304
323
|
}
|
|
324
|
+
function linearCycleIsActive(cycle, now = Date.now()) {
|
|
325
|
+
if (!cycle) return false;
|
|
326
|
+
if (cycle.completedAt) return false;
|
|
327
|
+
const start = cycle.startsAt ? Date.parse(cycle.startsAt) : Number.NaN;
|
|
328
|
+
const end = cycle.endsAt ? Date.parse(cycle.endsAt) : Number.NaN;
|
|
329
|
+
if (Number.isFinite(start) && now < start) return false;
|
|
330
|
+
if (Number.isFinite(end) && now > end) return false;
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
function mapCycle(node) {
|
|
334
|
+
if (!node?.name && node?.number == null) return null;
|
|
335
|
+
const name = String(node.name ?? (node.number != null ? `Cycle ${node.number}` : "")).trim();
|
|
336
|
+
if (!name) return null;
|
|
337
|
+
return {
|
|
338
|
+
name,
|
|
339
|
+
number: typeof node.number === "number" ? node.number : void 0,
|
|
340
|
+
isActive: linearCycleIsActive(node)
|
|
341
|
+
};
|
|
342
|
+
}
|
|
305
343
|
function mapIssue(node) {
|
|
306
344
|
const team = node.team;
|
|
307
345
|
return {
|
|
@@ -319,7 +357,8 @@ function mapIssue(node) {
|
|
|
319
357
|
name: String(team.name ?? ""),
|
|
320
358
|
states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
321
359
|
} : void 0,
|
|
322
|
-
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
|
|
360
|
+
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n)),
|
|
361
|
+
cycle: mapCycle(node.cycle)
|
|
323
362
|
};
|
|
324
363
|
}
|
|
325
364
|
function toIssueInfo(issue) {
|
|
@@ -329,7 +368,11 @@ function toIssueInfo(issue) {
|
|
|
329
368
|
title: issue.title,
|
|
330
369
|
url: issue.url,
|
|
331
370
|
labels: issue.labels,
|
|
332
|
-
provider: "linear"
|
|
371
|
+
provider: "linear",
|
|
372
|
+
assignee: issue.assignee?.name,
|
|
373
|
+
assignees: issue.assignee?.name ? [issue.assignee.name] : void 0,
|
|
374
|
+
cycle: issue.cycle ?? null,
|
|
375
|
+
teamKey: issue.team?.key || void 0
|
|
333
376
|
};
|
|
334
377
|
}
|
|
335
378
|
function mapTeam(node) {
|
|
@@ -371,10 +414,18 @@ function normalizePriority(priority) {
|
|
|
371
414
|
}
|
|
372
415
|
return priority;
|
|
373
416
|
}
|
|
374
|
-
async function
|
|
375
|
-
const first = Math.max(1, Math.min(
|
|
417
|
+
async function listLinearAssignedIssues(opts) {
|
|
418
|
+
const first = Math.max(1, Math.min(250, opts?.limit ?? 200));
|
|
376
419
|
const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
|
|
377
|
-
return
|
|
420
|
+
return {
|
|
421
|
+
viewer: {
|
|
422
|
+
id: String(json.viewer?.id ?? ""),
|
|
423
|
+
name: String(json.viewer?.name ?? "")
|
|
424
|
+
},
|
|
425
|
+
issues: (json.viewer?.assignedIssues?.nodes ?? []).map(
|
|
426
|
+
(node) => toIssueInfo(mapIssue(node))
|
|
427
|
+
)
|
|
428
|
+
};
|
|
378
429
|
}
|
|
379
430
|
async function listLinearTeams(opts) {
|
|
380
431
|
const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
|
|
@@ -483,13 +534,13 @@ async function commentLinearIssue(input, opts) {
|
|
|
483
534
|
|
|
484
535
|
// src/integrations/issues.ts
|
|
485
536
|
async function listGitHubIssues(repoPath, opts) {
|
|
486
|
-
const limit = Math.max(1, Math.min(
|
|
537
|
+
const limit = Math.max(1, Math.min(1e3, opts?.limit ?? 200));
|
|
487
538
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
488
539
|
const args = [
|
|
489
540
|
"issue",
|
|
490
541
|
"list",
|
|
491
542
|
"--json",
|
|
492
|
-
"number,title,url,labels",
|
|
543
|
+
"number,title,url,labels,assignees",
|
|
493
544
|
"--limit",
|
|
494
545
|
String(limit),
|
|
495
546
|
"--state",
|
|
@@ -512,27 +563,55 @@ async function listGitHubIssues(repoPath, opts) {
|
|
|
512
563
|
const labels = (item.labels ?? []).map(
|
|
513
564
|
(l) => typeof l === "string" ? l : String(l?.name ?? "")
|
|
514
565
|
).filter(Boolean);
|
|
566
|
+
const assignees = (item.assignees ?? []).map((a) => typeof a === "string" ? a : String(a?.login ?? "")).map((login) => login.trim()).filter(Boolean);
|
|
515
567
|
return {
|
|
516
568
|
id: Number.isFinite(number) ? `gh-${number}` : identifier,
|
|
517
569
|
identifier,
|
|
518
570
|
title: String(item.title ?? ""),
|
|
519
571
|
url: String(item.url ?? ""),
|
|
520
572
|
labels,
|
|
521
|
-
provider: "github"
|
|
573
|
+
provider: "github",
|
|
574
|
+
assignee: assignees[0],
|
|
575
|
+
assignees
|
|
522
576
|
};
|
|
523
577
|
});
|
|
524
578
|
}
|
|
579
|
+
async function githubViewerLogin(repoPath) {
|
|
580
|
+
const { stdout, exitCode } = await gh(["api", "user", "--jq", ".login"], repoPath, {
|
|
581
|
+
reject: false
|
|
582
|
+
});
|
|
583
|
+
if (exitCode !== 0) return "";
|
|
584
|
+
return stdout.trim();
|
|
585
|
+
}
|
|
525
586
|
async function listIssues(repoPath) {
|
|
526
587
|
const settings = loadAppSettings();
|
|
527
588
|
const preferredSource = settings.integrations.issueSource ?? "github";
|
|
528
589
|
const linearConnected = isLinearConnected(settings);
|
|
529
590
|
const source = resolveEffectiveIssueSource(settings);
|
|
530
591
|
if (source === "linear") {
|
|
531
|
-
const
|
|
532
|
-
return {
|
|
592
|
+
const listed = await listLinearAssignedIssues();
|
|
593
|
+
return {
|
|
594
|
+
source,
|
|
595
|
+
preferredSource,
|
|
596
|
+
linearConnected,
|
|
597
|
+
issues: listed.issues,
|
|
598
|
+
viewer: {
|
|
599
|
+
login: listed.viewer.name,
|
|
600
|
+
name: listed.viewer.name
|
|
601
|
+
}
|
|
602
|
+
};
|
|
533
603
|
}
|
|
534
|
-
const issues = await
|
|
535
|
-
|
|
604
|
+
const [issues, login] = await Promise.all([
|
|
605
|
+
listGitHubIssues(repoPath),
|
|
606
|
+
githubViewerLogin(repoPath)
|
|
607
|
+
]);
|
|
608
|
+
return {
|
|
609
|
+
source,
|
|
610
|
+
preferredSource,
|
|
611
|
+
linearConnected,
|
|
612
|
+
issues,
|
|
613
|
+
viewer: login ? { login } : void 0
|
|
614
|
+
};
|
|
536
615
|
}
|
|
537
616
|
|
|
538
617
|
// src/mcp/archive-guard.ts
|
|
@@ -1027,7 +1106,7 @@ function registerSlackTools(server) {
|
|
|
1027
1106
|
},
|
|
1028
1107
|
async ({ team_id }) => {
|
|
1029
1108
|
try {
|
|
1030
|
-
await
|
|
1109
|
+
await pollSlackOutboundWatches({ force: true });
|
|
1031
1110
|
const team = team_id?.trim();
|
|
1032
1111
|
const watches = listSlackOutboundWatches().filter(
|
|
1033
1112
|
(w) => !team || w.teamId === team
|
|
@@ -1315,6 +1394,221 @@ function registerScheduleTools(server) {
|
|
|
1315
1394
|
);
|
|
1316
1395
|
}
|
|
1317
1396
|
|
|
1397
|
+
// src/board/load-home-board.ts
|
|
1398
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1399
|
+
import { join as join2 } from "path";
|
|
1400
|
+
|
|
1401
|
+
// src/board/board-pins.ts
|
|
1402
|
+
import { randomUUID } from "crypto";
|
|
1403
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
1404
|
+
import { join } from "path";
|
|
1405
|
+
var FILE = "home-board-pins.json";
|
|
1406
|
+
var VERSION = 1;
|
|
1407
|
+
function pinsFile() {
|
|
1408
|
+
return join(appDataDir(), FILE);
|
|
1409
|
+
}
|
|
1410
|
+
function readDisk() {
|
|
1411
|
+
const path = pinsFile();
|
|
1412
|
+
if (!existsSync(path)) return [];
|
|
1413
|
+
try {
|
|
1414
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
1415
|
+
if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
|
|
1416
|
+
return raw.items.filter((item) => item?.id && item.kind && item.ref);
|
|
1417
|
+
} catch {
|
|
1418
|
+
return [];
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function writeDisk(items) {
|
|
1422
|
+
mkdirSync(appDataDir(), { recursive: true });
|
|
1423
|
+
writeFileSync(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
|
|
1424
|
+
}
|
|
1425
|
+
function listBoardPins() {
|
|
1426
|
+
return readDisk();
|
|
1427
|
+
}
|
|
1428
|
+
function replaceBoardPins(items) {
|
|
1429
|
+
writeDisk(items);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// src/board/load-home-board.ts
|
|
1433
|
+
var CACHE_VERSION = 1;
|
|
1434
|
+
var memory = null;
|
|
1435
|
+
var inflight = null;
|
|
1436
|
+
function errText(err) {
|
|
1437
|
+
return err instanceof Error ? err.message : String(err);
|
|
1438
|
+
}
|
|
1439
|
+
function homeBoardWorkspaceKey(workspaces) {
|
|
1440
|
+
return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
|
|
1441
|
+
}
|
|
1442
|
+
function cacheFile() {
|
|
1443
|
+
return join2(appDataDir(), "home-board-cache.json");
|
|
1444
|
+
}
|
|
1445
|
+
function emptyInputs() {
|
|
1446
|
+
return {
|
|
1447
|
+
issues: [],
|
|
1448
|
+
prs: [],
|
|
1449
|
+
issueSource: "github",
|
|
1450
|
+
viewerLogin: void 0,
|
|
1451
|
+
issueErrors: [],
|
|
1452
|
+
prErrors: []
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function asLoaded(entry, fromCache) {
|
|
1456
|
+
const pins = syncBoardPins(listBoardPins(), entry.inputs.issues, entry.inputs.prs);
|
|
1457
|
+
return { ...entry.inputs, fetchedAt: entry.fetchedAt, fromCache, pins };
|
|
1458
|
+
}
|
|
1459
|
+
function cacheStillFresh(entry, key, now) {
|
|
1460
|
+
return entry.workspaceKey === key && now - entry.fetchedAt < HOME_BOARD_CACHE_TTL_MS;
|
|
1461
|
+
}
|
|
1462
|
+
function shouldCacheHomeBoardInputs(inputs) {
|
|
1463
|
+
const issuesFailed = inputs.issues.length === 0 && inputs.issueErrors.length > 0;
|
|
1464
|
+
const prsFailed = inputs.prs.length === 0 && inputs.prErrors.length > 0;
|
|
1465
|
+
return !(issuesFailed && prsFailed);
|
|
1466
|
+
}
|
|
1467
|
+
function readDiskCache() {
|
|
1468
|
+
const path = cacheFile();
|
|
1469
|
+
if (!existsSync2(path)) return null;
|
|
1470
|
+
try {
|
|
1471
|
+
const raw = JSON.parse(readFileSync2(path, "utf8"));
|
|
1472
|
+
if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
if (!raw.inputs || !Array.isArray(raw.inputs.issues) || !Array.isArray(raw.inputs.prs)) {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
return raw;
|
|
1479
|
+
} catch {
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
function writeDiskCache(entry) {
|
|
1484
|
+
try {
|
|
1485
|
+
mkdirSync2(appDataDir(), { recursive: true });
|
|
1486
|
+
writeFileSync2(cacheFile(), JSON.stringify(entry), "utf8");
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
async function loadHomeBoardInputs(workspaces) {
|
|
1491
|
+
const paths = workspaces.map((w) => w.path).filter(Boolean);
|
|
1492
|
+
if (paths.length === 0) return emptyInputs();
|
|
1493
|
+
const issueErrors = [];
|
|
1494
|
+
const prErrors = [];
|
|
1495
|
+
let issueSource = "github";
|
|
1496
|
+
let viewerLogin;
|
|
1497
|
+
let issues = [];
|
|
1498
|
+
try {
|
|
1499
|
+
const first = await listIssues(paths[0]);
|
|
1500
|
+
issueSource = first.source;
|
|
1501
|
+
viewerLogin = first.viewer?.login || first.viewer?.name || void 0;
|
|
1502
|
+
if (first.source === "linear") {
|
|
1503
|
+
const repoPath = paths[0] ?? "";
|
|
1504
|
+
issues = first.issues.map((issue) => ({
|
|
1505
|
+
...issue,
|
|
1506
|
+
repoPath,
|
|
1507
|
+
needsWorkspacePick: issueNeedsWorkspacePick(
|
|
1508
|
+
issue.provider ?? first.source,
|
|
1509
|
+
paths.length
|
|
1510
|
+
)
|
|
1511
|
+
}));
|
|
1512
|
+
} else {
|
|
1513
|
+
const settled = await Promise.allSettled(
|
|
1514
|
+
paths.map(async (path) => {
|
|
1515
|
+
const result = path === paths[0] ? first : await listIssues(path);
|
|
1516
|
+
return result.issues.map((issue) => ({
|
|
1517
|
+
...issue,
|
|
1518
|
+
repoPath: path,
|
|
1519
|
+
needsWorkspacePick: issueNeedsWorkspacePick(
|
|
1520
|
+
issue.provider ?? result.source,
|
|
1521
|
+
1
|
|
1522
|
+
)
|
|
1523
|
+
}));
|
|
1524
|
+
})
|
|
1525
|
+
);
|
|
1526
|
+
const collected = [];
|
|
1527
|
+
for (const item of settled) {
|
|
1528
|
+
if (item.status === "fulfilled") collected.push(...item.value);
|
|
1529
|
+
else issueErrors.push(errText(item.reason));
|
|
1530
|
+
}
|
|
1531
|
+
issues = collected;
|
|
1532
|
+
if (collected.length === 0 && issueErrors[0]) {
|
|
1533
|
+
throw new Error(issueErrors[0]);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
issues = dedupeBoardIssues(issues);
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
issueErrors.push(errText(err));
|
|
1539
|
+
issues = [];
|
|
1540
|
+
}
|
|
1541
|
+
let prs = [];
|
|
1542
|
+
try {
|
|
1543
|
+
const settled = await Promise.allSettled(
|
|
1544
|
+
paths.map(async (path) => {
|
|
1545
|
+
const list = await listPrs(path);
|
|
1546
|
+
return list.map((pr) => ({ ...pr, repoPath: path }));
|
|
1547
|
+
})
|
|
1548
|
+
);
|
|
1549
|
+
const collected = [];
|
|
1550
|
+
for (const item of settled) {
|
|
1551
|
+
if (item.status === "fulfilled") collected.push(...item.value);
|
|
1552
|
+
else prErrors.push(errText(item.reason));
|
|
1553
|
+
}
|
|
1554
|
+
if (collected.length === 0 && prErrors[0]) {
|
|
1555
|
+
throw new Error(prErrors[0]);
|
|
1556
|
+
}
|
|
1557
|
+
prs = dedupeBoardPrs(collected);
|
|
1558
|
+
} catch (err) {
|
|
1559
|
+
prErrors.push(errText(err));
|
|
1560
|
+
prs = [];
|
|
1561
|
+
}
|
|
1562
|
+
return { issues, prs, issueSource, viewerLogin, issueErrors, prErrors };
|
|
1563
|
+
}
|
|
1564
|
+
async function getHomeBoardInputs(workspaces, opts) {
|
|
1565
|
+
const key = homeBoardWorkspaceKey(workspaces);
|
|
1566
|
+
const now = opts?.now ?? Date.now();
|
|
1567
|
+
if (!key) {
|
|
1568
|
+
return {
|
|
1569
|
+
...emptyInputs(),
|
|
1570
|
+
fetchedAt: now,
|
|
1571
|
+
fromCache: false,
|
|
1572
|
+
pins: listBoardPins()
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
if (!opts?.refresh) {
|
|
1576
|
+
const disk = readDiskCache();
|
|
1577
|
+
const best = memory && disk ? disk.fetchedAt >= memory.fetchedAt ? disk : memory : memory ?? disk;
|
|
1578
|
+
if (best && cacheStillFresh(best, key, now)) {
|
|
1579
|
+
memory = best;
|
|
1580
|
+
return asLoaded(best, true);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (inflight && inflight.key === key) {
|
|
1584
|
+
return inflight.promise;
|
|
1585
|
+
}
|
|
1586
|
+
const promise = (async () => {
|
|
1587
|
+
const inputs = await loadHomeBoardInputs(workspaces);
|
|
1588
|
+
const fetchedAt = opts?.now ?? Date.now();
|
|
1589
|
+
const pins = syncBoardPins(listBoardPins(), inputs.issues, inputs.prs);
|
|
1590
|
+
replaceBoardPins(pins);
|
|
1591
|
+
const loaded = { ...inputs, fetchedAt, fromCache: false, pins };
|
|
1592
|
+
if (shouldCacheHomeBoardInputs(inputs)) {
|
|
1593
|
+
const entry = {
|
|
1594
|
+
version: CACHE_VERSION,
|
|
1595
|
+
workspaceKey: key,
|
|
1596
|
+
fetchedAt,
|
|
1597
|
+
inputs
|
|
1598
|
+
};
|
|
1599
|
+
memory = entry;
|
|
1600
|
+
writeDiskCache(entry);
|
|
1601
|
+
}
|
|
1602
|
+
return loaded;
|
|
1603
|
+
})();
|
|
1604
|
+
inflight = { key, promise };
|
|
1605
|
+
try {
|
|
1606
|
+
return await promise;
|
|
1607
|
+
} finally {
|
|
1608
|
+
if (inflight?.promise === promise) inflight = null;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1318
1612
|
// src/mcp/server.ts
|
|
1319
1613
|
var MAX_ORCH_THREADS = 5;
|
|
1320
1614
|
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
@@ -1335,6 +1629,93 @@ function withTimeout(promise, ms, label) {
|
|
|
1335
1629
|
);
|
|
1336
1630
|
});
|
|
1337
1631
|
}
|
|
1632
|
+
async function createOrchChildThread(orch, args, resolveNewThreadOptions) {
|
|
1633
|
+
const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
|
|
1634
|
+
let parentId = args.parentThreadId?.trim() || "";
|
|
1635
|
+
let parent = parentId ? orch.getThread(parentId) : null;
|
|
1636
|
+
let parentCorrectedFrom;
|
|
1637
|
+
if (envParentId) {
|
|
1638
|
+
const envParent = orch.getThread(envParentId);
|
|
1639
|
+
if (envParent) {
|
|
1640
|
+
if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
|
|
1641
|
+
else if (!parentId) parentCorrectedFrom = void 0;
|
|
1642
|
+
parentId = envParentId;
|
|
1643
|
+
parent = envParent;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (parentId && !parent) {
|
|
1647
|
+
parentCorrectedFrom = parentId;
|
|
1648
|
+
parentId = "";
|
|
1649
|
+
parent = null;
|
|
1650
|
+
}
|
|
1651
|
+
if (parentId) {
|
|
1652
|
+
const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
|
|
1653
|
+
if (children.length >= MAX_ORCH_THREADS) {
|
|
1654
|
+
return {
|
|
1655
|
+
ok: false,
|
|
1656
|
+
text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
let agentArg = args.agent;
|
|
1661
|
+
let agentCoercedFrom;
|
|
1662
|
+
const resolvedProbe = resolveNewThreadOptions({ agent: agentArg }).agent;
|
|
1663
|
+
if (resolvedProbe === "codex") {
|
|
1664
|
+
agentCoercedFrom = agentArg ?? "codex";
|
|
1665
|
+
const accountAgent = resolveNewThreadOptions({}).agent;
|
|
1666
|
+
agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
|
|
1667
|
+
}
|
|
1668
|
+
const opts = resolveNewThreadOptions({
|
|
1669
|
+
agent: agentArg,
|
|
1670
|
+
model: args.model
|
|
1671
|
+
});
|
|
1672
|
+
try {
|
|
1673
|
+
const priorIds = new Set(orch.getThreads(false).map((t) => t.id));
|
|
1674
|
+
const thread = await withTimeout(
|
|
1675
|
+
orch.createThread({
|
|
1676
|
+
sourceType: args.sourceType,
|
|
1677
|
+
sourceRef: args.sourceRef,
|
|
1678
|
+
agent: opts.agent,
|
|
1679
|
+
model: opts.model,
|
|
1680
|
+
effort: opts.effort,
|
|
1681
|
+
fast: opts.fast,
|
|
1682
|
+
repoPath: args.repoPath,
|
|
1683
|
+
title: args.title,
|
|
1684
|
+
parentThreadId: parentId || null,
|
|
1685
|
+
cowboy: args.cowboy || void 0,
|
|
1686
|
+
attachments: args.attachments
|
|
1687
|
+
}),
|
|
1688
|
+
CREATE_THREAD_TIMEOUT_MS,
|
|
1689
|
+
"create_thread"
|
|
1690
|
+
);
|
|
1691
|
+
const alreadyStarted = priorIds.has(thread.id);
|
|
1692
|
+
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
1693
|
+
return {
|
|
1694
|
+
ok: true,
|
|
1695
|
+
text: JSON.stringify({
|
|
1696
|
+
id: thread.id,
|
|
1697
|
+
title: thread.title,
|
|
1698
|
+
branchName: thread.branchName,
|
|
1699
|
+
worktreePath: thread.worktreePath,
|
|
1700
|
+
agent: thread.agent,
|
|
1701
|
+
model: thread.model,
|
|
1702
|
+
status: thread.status,
|
|
1703
|
+
cowboy: Boolean(thread.cowboy),
|
|
1704
|
+
link: `sideboard://thread/${thread.id}`,
|
|
1705
|
+
parentThreadId: thread.parentThreadId,
|
|
1706
|
+
...alreadyStarted ? { alreadyStarted: true } : {},
|
|
1707
|
+
...agentCoercedFrom ? {
|
|
1708
|
+
agentCoercedFrom,
|
|
1709
|
+
note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
|
|
1710
|
+
} : {},
|
|
1711
|
+
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
1712
|
+
})
|
|
1713
|
+
};
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1716
|
+
return { ok: false, text: `create_thread failed: ${message}` };
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1338
1719
|
async function startMcpServer() {
|
|
1339
1720
|
const orch = getOrchestrator();
|
|
1340
1721
|
try {
|
|
@@ -1346,7 +1727,7 @@ async function startMcpServer() {
|
|
|
1346
1727
|
);
|
|
1347
1728
|
}
|
|
1348
1729
|
try {
|
|
1349
|
-
const { maxConcurrentAgents } = await import("../app-settings-
|
|
1730
|
+
const { maxConcurrentAgents } = await import("../app-settings-E7NEQY7D.js");
|
|
1350
1731
|
orch.setMaxConcurrent(maxConcurrentAgents());
|
|
1351
1732
|
} catch {
|
|
1352
1733
|
}
|
|
@@ -1366,7 +1747,7 @@ async function startMcpServer() {
|
|
|
1366
1747
|
if (!worktreeProfile) {
|
|
1367
1748
|
server.tool(
|
|
1368
1749
|
"list_workspaces",
|
|
1369
|
-
"List registered Sideboard workspaces (repos). Each line is name, path, and github:owner/repo when resolvable \u2014 use path as repoPath for list_branches/list_prs/list_issues/create_thread.",
|
|
1750
|
+
"List registered Sideboard workspaces (repos). Each line is name, path, and github:owner/repo when resolvable \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_thread.",
|
|
1370
1751
|
{},
|
|
1371
1752
|
async () => {
|
|
1372
1753
|
const workspaces = orch.listWorkspaces();
|
|
@@ -1400,6 +1781,51 @@ async function startMcpServer() {
|
|
|
1400
1781
|
};
|
|
1401
1782
|
}
|
|
1402
1783
|
);
|
|
1784
|
+
server.tool(
|
|
1785
|
+
"list_board",
|
|
1786
|
+
"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.",
|
|
1787
|
+
{
|
|
1788
|
+
query: z4.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
1789
|
+
repoPath: z4.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
1790
|
+
kind: z4.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
1791
|
+
column: z4.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
1792
|
+
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
1793
|
+
),
|
|
1794
|
+
limit: z4.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
1795
|
+
},
|
|
1796
|
+
async ({ query, repoPath, kind, column, limit }) => {
|
|
1797
|
+
const workspaces = orch.listWorkspaces();
|
|
1798
|
+
const all = orch.getThreads(true);
|
|
1799
|
+
const names = new Map(workspaces.map((w) => [w.path, w.name]));
|
|
1800
|
+
const snap = assembleHomeBoard({
|
|
1801
|
+
threads: all.filter((t) => t.status !== "archived"),
|
|
1802
|
+
query,
|
|
1803
|
+
repoPath,
|
|
1804
|
+
kind: kind ?? "all",
|
|
1805
|
+
column: column === "needs_you" ? "new" : column,
|
|
1806
|
+
limit,
|
|
1807
|
+
workspaceName: (path) => names.get(path) ?? ""
|
|
1808
|
+
});
|
|
1809
|
+
return {
|
|
1810
|
+
content: [
|
|
1811
|
+
{
|
|
1812
|
+
type: "text",
|
|
1813
|
+
text: JSON.stringify(
|
|
1814
|
+
{
|
|
1815
|
+
columns: snap.columns,
|
|
1816
|
+
hidden: snap.hidden,
|
|
1817
|
+
totals: snap.totals,
|
|
1818
|
+
columnDefs: BOARD_COLUMN_DEFS,
|
|
1819
|
+
hint: HOME_BOARD_AGENT_HINT
|
|
1820
|
+
},
|
|
1821
|
+
null,
|
|
1822
|
+
2
|
|
1823
|
+
)
|
|
1824
|
+
}
|
|
1825
|
+
]
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
);
|
|
1403
1829
|
server.tool(
|
|
1404
1830
|
"get_thread",
|
|
1405
1831
|
"Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
|
|
@@ -1569,7 +1995,7 @@ async function startMcpServer() {
|
|
|
1569
1995
|
registerLinearTools(server);
|
|
1570
1996
|
registerScheduleTools(server);
|
|
1571
1997
|
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-NBRUWYXO.js");
|
|
1572
|
-
const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-
|
|
1998
|
+
const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-E7NEQY7D.js");
|
|
1573
1999
|
const accountDefaults = resolveThreadDefaults();
|
|
1574
2000
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
1575
2001
|
server.tool(
|
|
@@ -1612,7 +2038,7 @@ async function startMcpServer() {
|
|
|
1612
2038
|
);
|
|
1613
2039
|
server.tool(
|
|
1614
2040
|
"create_thread",
|
|
1615
|
-
`Create a
|
|
2041
|
+
`Create a worktree thread (chat) from branch, pr, or ticket. If a live worktree already matches that ticket, PR, or named branch, 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.`,
|
|
1616
2042
|
{
|
|
1617
2043
|
sourceType: z4.enum(["branch", "pr", "ticket"]),
|
|
1618
2044
|
sourceRef: z4.string(),
|
|
@@ -1630,104 +2056,118 @@ async function startMcpServer() {
|
|
|
1630
2056
|
)
|
|
1631
2057
|
},
|
|
1632
2058
|
async (args) => {
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
}
|
|
1665
|
-
let agentArg = args.agent;
|
|
1666
|
-
let agentCoercedFrom;
|
|
1667
|
-
const resolvedProbe = resolveNewThreadOptions({ agent: agentArg }).agent;
|
|
1668
|
-
if (resolvedProbe === "codex") {
|
|
1669
|
-
agentCoercedFrom = agentArg ?? "codex";
|
|
1670
|
-
const accountAgent = resolveNewThreadOptions({}).agent;
|
|
1671
|
-
agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
|
|
1672
|
-
}
|
|
1673
|
-
const opts = resolveNewThreadOptions({
|
|
1674
|
-
agent: agentArg,
|
|
1675
|
-
model: args.model
|
|
1676
|
-
});
|
|
1677
|
-
try {
|
|
1678
|
-
const thread = await withTimeout(
|
|
1679
|
-
orch.createThread({
|
|
1680
|
-
sourceType: args.sourceType,
|
|
1681
|
-
sourceRef: args.sourceRef,
|
|
1682
|
-
agent: opts.agent,
|
|
1683
|
-
model: opts.model,
|
|
1684
|
-
effort: opts.effort,
|
|
1685
|
-
fast: opts.fast,
|
|
1686
|
-
repoPath: args.repoPath,
|
|
1687
|
-
title: args.title,
|
|
1688
|
-
parentThreadId: parentId || null,
|
|
1689
|
-
cowboy: args.cowboy || void 0
|
|
1690
|
-
}),
|
|
1691
|
-
CREATE_THREAD_TIMEOUT_MS,
|
|
1692
|
-
"create_thread"
|
|
1693
|
-
);
|
|
1694
|
-
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
2059
|
+
const result = await createOrchChildThread(orch, args, resolveNewThreadOptions);
|
|
2060
|
+
return {
|
|
2061
|
+
content: [{ type: "text", text: result.text }],
|
|
2062
|
+
...result.ok ? {} : { isError: true }
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
);
|
|
2066
|
+
server.tool(
|
|
2067
|
+
"start_board_card",
|
|
2068
|
+
"Same as create_thread for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Reuses the live worktree if one already matches. Then send_to_thread.",
|
|
2069
|
+
{
|
|
2070
|
+
kind: z4.enum(["ticket", "pr", "branch"]),
|
|
2071
|
+
ref: z4.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
2072
|
+
repoPath: z4.string().describe("Workspace path from list_workspaces / list_board"),
|
|
2073
|
+
title: z4.string().optional()
|
|
2074
|
+
},
|
|
2075
|
+
async ({ kind, ref, repoPath, title }) => {
|
|
2076
|
+
const root = await resolveRepoRoot(repoPath);
|
|
2077
|
+
const existing = findLiveThreadForCreate(
|
|
2078
|
+
{
|
|
2079
|
+
sourceType: kind,
|
|
2080
|
+
sourceRef: ref.trim(),
|
|
2081
|
+
repoPath: canonicalizeRepoPath(root),
|
|
2082
|
+
title: title?.trim()
|
|
2083
|
+
},
|
|
2084
|
+
orch.getThreads(false).map((t) => ({
|
|
2085
|
+
...t,
|
|
2086
|
+
repoPath: canonicalizeRepoPath(t.repoPath)
|
|
2087
|
+
}))
|
|
2088
|
+
);
|
|
2089
|
+
if (existing) {
|
|
1695
2090
|
return {
|
|
1696
2091
|
content: [
|
|
1697
2092
|
{
|
|
1698
2093
|
type: "text",
|
|
1699
2094
|
text: JSON.stringify({
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
model: thread.model,
|
|
1706
|
-
status: thread.status,
|
|
1707
|
-
cowboy: Boolean(thread.cowboy),
|
|
1708
|
-
link: `sideboard://thread/${thread.id}`,
|
|
1709
|
-
parentThreadId: thread.parentThreadId,
|
|
1710
|
-
...agentCoercedFrom ? {
|
|
1711
|
-
agentCoercedFrom,
|
|
1712
|
-
note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
|
|
1713
|
-
} : {},
|
|
1714
|
-
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
2095
|
+
alreadyStarted: true,
|
|
2096
|
+
id: existing.id,
|
|
2097
|
+
title: existing.title,
|
|
2098
|
+
status: existing.status,
|
|
2099
|
+
link: `sideboard://thread/${existing.id}`
|
|
1715
2100
|
})
|
|
1716
2101
|
}
|
|
1717
2102
|
]
|
|
1718
2103
|
};
|
|
1719
|
-
}
|
|
1720
|
-
|
|
2104
|
+
}
|
|
2105
|
+
const workspaces = orch.listWorkspaces();
|
|
2106
|
+
const loaded = await getHomeBoardInputs(workspaces);
|
|
2107
|
+
const pin = findBoardPin(loaded.pins, kind, ref, root);
|
|
2108
|
+
if (kind === "branch") {
|
|
2109
|
+
const sourceRef = pin?.ref === "default" ? "default" : pin?.ref ?? ref.trim();
|
|
2110
|
+
const result2 = await createOrchChildThread(
|
|
2111
|
+
orch,
|
|
2112
|
+
{
|
|
2113
|
+
sourceType: "branch",
|
|
2114
|
+
sourceRef,
|
|
2115
|
+
repoPath: root,
|
|
2116
|
+
title: title?.trim() || pin?.title || (sourceRef === "default" ? void 0 : sourceRef)
|
|
2117
|
+
},
|
|
2118
|
+
resolveNewThreadOptions
|
|
2119
|
+
);
|
|
1721
2120
|
return {
|
|
1722
|
-
content: [
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
2121
|
+
content: [{ type: "text", text: result2.text }],
|
|
2122
|
+
...result2.ok ? {} : { isError: true }
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
if (kind === "ticket") {
|
|
2126
|
+
const issue = findBoardIssue(loaded.issues, ref, root);
|
|
2127
|
+
const ident = issue?.identifier ?? ref.trim();
|
|
2128
|
+
const attachments = issue ? [
|
|
2129
|
+
{
|
|
2130
|
+
id: randomUUID2(),
|
|
2131
|
+
name: issue.identifier,
|
|
2132
|
+
kind: "issue",
|
|
2133
|
+
content: [
|
|
2134
|
+
`Linked issue: ${issue.identifier} \u2014 ${issue.title}`,
|
|
2135
|
+
issue.url ? `URL: ${issue.url}` : null
|
|
2136
|
+
].filter(Boolean).join("\n")
|
|
2137
|
+
}
|
|
2138
|
+
] : void 0;
|
|
2139
|
+
const result2 = await createOrchChildThread(
|
|
2140
|
+
orch,
|
|
2141
|
+
{
|
|
2142
|
+
sourceType: "ticket",
|
|
2143
|
+
sourceRef: ident,
|
|
2144
|
+
repoPath: root,
|
|
2145
|
+
title: title?.trim() || issue?.title,
|
|
2146
|
+
attachments
|
|
2147
|
+
},
|
|
2148
|
+
resolveNewThreadOptions
|
|
2149
|
+
);
|
|
2150
|
+
return {
|
|
2151
|
+
content: [{ type: "text", text: result2.text }],
|
|
2152
|
+
...result2.ok ? {} : { isError: true }
|
|
1729
2153
|
};
|
|
1730
2154
|
}
|
|
2155
|
+
const pr = findBoardPr(loaded.prs, ref, root);
|
|
2156
|
+
const number = pr ? String(pr.number) : ref.trim().replace(/^#/, "");
|
|
2157
|
+
const result = await createOrchChildThread(
|
|
2158
|
+
orch,
|
|
2159
|
+
{
|
|
2160
|
+
sourceType: "pr",
|
|
2161
|
+
sourceRef: number,
|
|
2162
|
+
repoPath: root,
|
|
2163
|
+
title: title?.trim() || pr?.title
|
|
2164
|
+
},
|
|
2165
|
+
resolveNewThreadOptions
|
|
2166
|
+
);
|
|
2167
|
+
return {
|
|
2168
|
+
content: [{ type: "text", text: result.text }],
|
|
2169
|
+
...result.ok ? {} : { isError: true }
|
|
2170
|
+
};
|
|
1731
2171
|
}
|
|
1732
2172
|
);
|
|
1733
2173
|
server.tool(
|
|
@@ -2374,7 +2814,7 @@ async function startMcpServer() {
|
|
|
2374
2814
|
);
|
|
2375
2815
|
server.tool(
|
|
2376
2816
|
"list_issues",
|
|
2377
|
-
"List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
|
|
2817
|
+
"List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Linear returns issues assigned to you (includes cycle). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Prefer list_board for Home columns + current-cycle filter. Then create_thread with sourceType=ticket.",
|
|
2378
2818
|
{ repoPath: z4.string() },
|
|
2379
2819
|
async ({ repoPath }) => {
|
|
2380
2820
|
const root = await resolveRepoRoot(repoPath);
|