@sideboard-ai/core 0.1.125 → 0.1.130
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-GKY2GR2J.js → chunk-FMBLOOAO.js} +484 -37
- 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-UAVZ2JSO.js → chunk-UDQI3N47.js} +14 -6
- package/dist/{chunk-HZLE6LJJ.js → chunk-VE22NWBD.js} +2 -2
- package/dist/{chunk-ED27HCPV.js → chunk-YALVX3UE.js} +496 -37
- 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 +1233 -170
- package/dist/index.d.cts +233 -10
- package/dist/index.d.ts +233 -10
- package/dist/index.js +695 -131
- package/dist/mcp/run-stdio.cjs +1081 -149
- package/dist/mcp/run-stdio.js +579 -123
- package/dist/{orchestrator-FZYM7I42.js → orchestrator-3ZSFTG72.js} +8 -8
- package/dist/{orchestrator-HN3LYHFN.js → orchestrator-G6DU3AF3.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,11 +2,22 @@
|
|
|
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,
|
|
@@ -18,30 +29,32 @@ import {
|
|
|
18
29
|
resolveScheduleThreadId,
|
|
19
30
|
slackApi,
|
|
20
31
|
slackTokenFor,
|
|
32
|
+
syncBoardPins,
|
|
21
33
|
updateSchedule
|
|
22
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-FMBLOOAO.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
|
|
@@ -185,6 +201,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
185
201
|
|
|
186
202
|
// src/integrations/linear.ts
|
|
187
203
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
204
|
+
var LIST_ISSUE_FIELDS = `
|
|
205
|
+
id
|
|
206
|
+
identifier
|
|
207
|
+
title
|
|
208
|
+
url
|
|
209
|
+
assignee { id name }
|
|
210
|
+
team { id key }
|
|
211
|
+
labels(first: 10) { nodes { name } }
|
|
212
|
+
cycle { name number startsAt endsAt completedAt }
|
|
213
|
+
`;
|
|
188
214
|
var ISSUE_FIELDS = `
|
|
189
215
|
id
|
|
190
216
|
identifier
|
|
@@ -194,18 +220,21 @@ var ISSUE_FIELDS = `
|
|
|
194
220
|
priority
|
|
195
221
|
state { id name type }
|
|
196
222
|
assignee { id name }
|
|
197
|
-
team { id key name states { nodes { id name type } } }
|
|
198
|
-
labels { nodes { name } }
|
|
223
|
+
team { id key name states(first: 50) { nodes { id name type } } }
|
|
224
|
+
labels(first: 50) { nodes { name } }
|
|
225
|
+
cycle { name number startsAt endsAt completedAt }
|
|
199
226
|
`;
|
|
200
227
|
var ASSIGNED_ISSUES_QUERY = `
|
|
201
228
|
query SideboardAssignedIssues($first: Int!) {
|
|
202
229
|
viewer {
|
|
230
|
+
id
|
|
231
|
+
name
|
|
203
232
|
assignedIssues(
|
|
204
233
|
first: $first
|
|
205
234
|
orderBy: updatedAt
|
|
206
235
|
filter: { state: { type: { nin: ["completed", "canceled"] } } }
|
|
207
236
|
) {
|
|
208
|
-
nodes { ${
|
|
237
|
+
nodes { ${LIST_ISSUE_FIELDS} }
|
|
209
238
|
}
|
|
210
239
|
}
|
|
211
240
|
}
|
|
@@ -218,7 +247,7 @@ query SideboardTeams {
|
|
|
218
247
|
id
|
|
219
248
|
key
|
|
220
249
|
name
|
|
221
|
-
states { nodes { id name type } }
|
|
250
|
+
states(first: 50) { nodes { id name type } }
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
253
|
}
|
|
@@ -302,6 +331,25 @@ function mapState(node) {
|
|
|
302
331
|
type: String(node.type ?? "")
|
|
303
332
|
};
|
|
304
333
|
}
|
|
334
|
+
function linearCycleIsActive(cycle, now = Date.now()) {
|
|
335
|
+
if (!cycle) return false;
|
|
336
|
+
if (cycle.completedAt) return false;
|
|
337
|
+
const start = cycle.startsAt ? Date.parse(cycle.startsAt) : Number.NaN;
|
|
338
|
+
const end = cycle.endsAt ? Date.parse(cycle.endsAt) : Number.NaN;
|
|
339
|
+
if (Number.isFinite(start) && now < start) return false;
|
|
340
|
+
if (Number.isFinite(end) && now > end) return false;
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
function mapCycle(node) {
|
|
344
|
+
if (!node?.name && node?.number == null) return null;
|
|
345
|
+
const name = String(node.name ?? (node.number != null ? `Cycle ${node.number}` : "")).trim();
|
|
346
|
+
if (!name) return null;
|
|
347
|
+
return {
|
|
348
|
+
name,
|
|
349
|
+
number: typeof node.number === "number" ? node.number : void 0,
|
|
350
|
+
isActive: linearCycleIsActive(node)
|
|
351
|
+
};
|
|
352
|
+
}
|
|
305
353
|
function mapIssue(node) {
|
|
306
354
|
const team = node.team;
|
|
307
355
|
return {
|
|
@@ -319,7 +367,8 @@ function mapIssue(node) {
|
|
|
319
367
|
name: String(team.name ?? ""),
|
|
320
368
|
states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
321
369
|
} : void 0,
|
|
322
|
-
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
|
|
370
|
+
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n)),
|
|
371
|
+
cycle: mapCycle(node.cycle)
|
|
323
372
|
};
|
|
324
373
|
}
|
|
325
374
|
function toIssueInfo(issue) {
|
|
@@ -329,7 +378,11 @@ function toIssueInfo(issue) {
|
|
|
329
378
|
title: issue.title,
|
|
330
379
|
url: issue.url,
|
|
331
380
|
labels: issue.labels,
|
|
332
|
-
provider: "linear"
|
|
381
|
+
provider: "linear",
|
|
382
|
+
assignee: issue.assignee?.name,
|
|
383
|
+
assignees: issue.assignee?.name ? [issue.assignee.name] : void 0,
|
|
384
|
+
cycle: issue.cycle ?? null,
|
|
385
|
+
teamKey: issue.team?.key || void 0
|
|
333
386
|
};
|
|
334
387
|
}
|
|
335
388
|
function mapTeam(node) {
|
|
@@ -371,10 +424,18 @@ function normalizePriority(priority) {
|
|
|
371
424
|
}
|
|
372
425
|
return priority;
|
|
373
426
|
}
|
|
374
|
-
async function
|
|
375
|
-
const first = Math.max(1, Math.min(
|
|
427
|
+
async function listLinearAssignedIssues(opts) {
|
|
428
|
+
const first = Math.max(1, Math.min(250, opts?.limit ?? 200));
|
|
376
429
|
const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
|
|
377
|
-
return
|
|
430
|
+
return {
|
|
431
|
+
viewer: {
|
|
432
|
+
id: String(json.viewer?.id ?? ""),
|
|
433
|
+
name: String(json.viewer?.name ?? "")
|
|
434
|
+
},
|
|
435
|
+
issues: (json.viewer?.assignedIssues?.nodes ?? []).map(
|
|
436
|
+
(node) => toIssueInfo(mapIssue(node))
|
|
437
|
+
)
|
|
438
|
+
};
|
|
378
439
|
}
|
|
379
440
|
async function listLinearTeams(opts) {
|
|
380
441
|
const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
|
|
@@ -483,13 +544,13 @@ async function commentLinearIssue(input, opts) {
|
|
|
483
544
|
|
|
484
545
|
// src/integrations/issues.ts
|
|
485
546
|
async function listGitHubIssues(repoPath, opts) {
|
|
486
|
-
const limit = Math.max(1, Math.min(
|
|
547
|
+
const limit = Math.max(1, Math.min(1e3, opts?.limit ?? 200));
|
|
487
548
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
488
549
|
const args = [
|
|
489
550
|
"issue",
|
|
490
551
|
"list",
|
|
491
552
|
"--json",
|
|
492
|
-
"number,title,url,labels",
|
|
553
|
+
"number,title,url,labels,assignees",
|
|
493
554
|
"--limit",
|
|
494
555
|
String(limit),
|
|
495
556
|
"--state",
|
|
@@ -512,27 +573,55 @@ async function listGitHubIssues(repoPath, opts) {
|
|
|
512
573
|
const labels = (item.labels ?? []).map(
|
|
513
574
|
(l) => typeof l === "string" ? l : String(l?.name ?? "")
|
|
514
575
|
).filter(Boolean);
|
|
576
|
+
const assignees = (item.assignees ?? []).map((a) => typeof a === "string" ? a : String(a?.login ?? "")).map((login) => login.trim()).filter(Boolean);
|
|
515
577
|
return {
|
|
516
578
|
id: Number.isFinite(number) ? `gh-${number}` : identifier,
|
|
517
579
|
identifier,
|
|
518
580
|
title: String(item.title ?? ""),
|
|
519
581
|
url: String(item.url ?? ""),
|
|
520
582
|
labels,
|
|
521
|
-
provider: "github"
|
|
583
|
+
provider: "github",
|
|
584
|
+
assignee: assignees[0],
|
|
585
|
+
assignees
|
|
522
586
|
};
|
|
523
587
|
});
|
|
524
588
|
}
|
|
589
|
+
async function githubViewerLogin(repoPath) {
|
|
590
|
+
const { stdout, exitCode } = await gh(["api", "user", "--jq", ".login"], repoPath, {
|
|
591
|
+
reject: false
|
|
592
|
+
});
|
|
593
|
+
if (exitCode !== 0) return "";
|
|
594
|
+
return stdout.trim();
|
|
595
|
+
}
|
|
525
596
|
async function listIssues(repoPath) {
|
|
526
597
|
const settings = loadAppSettings();
|
|
527
598
|
const preferredSource = settings.integrations.issueSource ?? "github";
|
|
528
599
|
const linearConnected = isLinearConnected(settings);
|
|
529
600
|
const source = resolveEffectiveIssueSource(settings);
|
|
530
601
|
if (source === "linear") {
|
|
531
|
-
const
|
|
532
|
-
return {
|
|
602
|
+
const listed = await listLinearAssignedIssues();
|
|
603
|
+
return {
|
|
604
|
+
source,
|
|
605
|
+
preferredSource,
|
|
606
|
+
linearConnected,
|
|
607
|
+
issues: listed.issues,
|
|
608
|
+
viewer: {
|
|
609
|
+
login: listed.viewer.name,
|
|
610
|
+
name: listed.viewer.name
|
|
611
|
+
}
|
|
612
|
+
};
|
|
533
613
|
}
|
|
534
|
-
const issues = await
|
|
535
|
-
|
|
614
|
+
const [issues, login] = await Promise.all([
|
|
615
|
+
listGitHubIssues(repoPath),
|
|
616
|
+
githubViewerLogin(repoPath)
|
|
617
|
+
]);
|
|
618
|
+
return {
|
|
619
|
+
source,
|
|
620
|
+
preferredSource,
|
|
621
|
+
linearConnected,
|
|
622
|
+
issues,
|
|
623
|
+
viewer: login ? { login } : void 0
|
|
624
|
+
};
|
|
536
625
|
}
|
|
537
626
|
|
|
538
627
|
// src/mcp/archive-guard.ts
|
|
@@ -1315,6 +1404,221 @@ function registerScheduleTools(server) {
|
|
|
1315
1404
|
);
|
|
1316
1405
|
}
|
|
1317
1406
|
|
|
1407
|
+
// src/board/load-home-board.ts
|
|
1408
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1409
|
+
import { join as join2 } from "path";
|
|
1410
|
+
|
|
1411
|
+
// src/board/board-pins.ts
|
|
1412
|
+
import { randomUUID } from "crypto";
|
|
1413
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
1414
|
+
import { join } from "path";
|
|
1415
|
+
var FILE = "home-board-pins.json";
|
|
1416
|
+
var VERSION = 1;
|
|
1417
|
+
function pinsFile() {
|
|
1418
|
+
return join(appDataDir(), FILE);
|
|
1419
|
+
}
|
|
1420
|
+
function readDisk() {
|
|
1421
|
+
const path = pinsFile();
|
|
1422
|
+
if (!existsSync(path)) return [];
|
|
1423
|
+
try {
|
|
1424
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
1425
|
+
if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
|
|
1426
|
+
return raw.items.filter((item) => item?.id && item.kind && item.ref);
|
|
1427
|
+
} catch {
|
|
1428
|
+
return [];
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function writeDisk(items) {
|
|
1432
|
+
mkdirSync(appDataDir(), { recursive: true });
|
|
1433
|
+
writeFileSync(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
|
|
1434
|
+
}
|
|
1435
|
+
function listBoardPins() {
|
|
1436
|
+
return readDisk();
|
|
1437
|
+
}
|
|
1438
|
+
function replaceBoardPins(items) {
|
|
1439
|
+
writeDisk(items);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// src/board/load-home-board.ts
|
|
1443
|
+
var CACHE_VERSION = 1;
|
|
1444
|
+
var memory = null;
|
|
1445
|
+
var inflight = null;
|
|
1446
|
+
function errText(err) {
|
|
1447
|
+
return err instanceof Error ? err.message : String(err);
|
|
1448
|
+
}
|
|
1449
|
+
function homeBoardWorkspaceKey(workspaces) {
|
|
1450
|
+
return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
|
|
1451
|
+
}
|
|
1452
|
+
function cacheFile() {
|
|
1453
|
+
return join2(appDataDir(), "home-board-cache.json");
|
|
1454
|
+
}
|
|
1455
|
+
function emptyInputs() {
|
|
1456
|
+
return {
|
|
1457
|
+
issues: [],
|
|
1458
|
+
prs: [],
|
|
1459
|
+
issueSource: "github",
|
|
1460
|
+
viewerLogin: void 0,
|
|
1461
|
+
issueErrors: [],
|
|
1462
|
+
prErrors: []
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
function asLoaded(entry, fromCache) {
|
|
1466
|
+
const pins = syncBoardPins(listBoardPins(), entry.inputs.issues, entry.inputs.prs);
|
|
1467
|
+
return { ...entry.inputs, fetchedAt: entry.fetchedAt, fromCache, pins };
|
|
1468
|
+
}
|
|
1469
|
+
function cacheStillFresh(entry, key, now) {
|
|
1470
|
+
return entry.workspaceKey === key && now - entry.fetchedAt < HOME_BOARD_CACHE_TTL_MS;
|
|
1471
|
+
}
|
|
1472
|
+
function shouldCacheHomeBoardInputs(inputs) {
|
|
1473
|
+
const issuesFailed = inputs.issues.length === 0 && inputs.issueErrors.length > 0;
|
|
1474
|
+
const prsFailed = inputs.prs.length === 0 && inputs.prErrors.length > 0;
|
|
1475
|
+
return !(issuesFailed && prsFailed);
|
|
1476
|
+
}
|
|
1477
|
+
function readDiskCache() {
|
|
1478
|
+
const path = cacheFile();
|
|
1479
|
+
if (!existsSync2(path)) return null;
|
|
1480
|
+
try {
|
|
1481
|
+
const raw = JSON.parse(readFileSync2(path, "utf8"));
|
|
1482
|
+
if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
if (!raw.inputs || !Array.isArray(raw.inputs.issues) || !Array.isArray(raw.inputs.prs)) {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
return raw;
|
|
1489
|
+
} catch {
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
function writeDiskCache(entry) {
|
|
1494
|
+
try {
|
|
1495
|
+
mkdirSync2(appDataDir(), { recursive: true });
|
|
1496
|
+
writeFileSync2(cacheFile(), JSON.stringify(entry), "utf8");
|
|
1497
|
+
} catch {
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async function loadHomeBoardInputs(workspaces) {
|
|
1501
|
+
const paths = workspaces.map((w) => w.path).filter(Boolean);
|
|
1502
|
+
if (paths.length === 0) return emptyInputs();
|
|
1503
|
+
const issueErrors = [];
|
|
1504
|
+
const prErrors = [];
|
|
1505
|
+
let issueSource = "github";
|
|
1506
|
+
let viewerLogin;
|
|
1507
|
+
let issues = [];
|
|
1508
|
+
try {
|
|
1509
|
+
const first = await listIssues(paths[0]);
|
|
1510
|
+
issueSource = first.source;
|
|
1511
|
+
viewerLogin = first.viewer?.login || first.viewer?.name || void 0;
|
|
1512
|
+
if (first.source === "linear") {
|
|
1513
|
+
const repoPath = paths[0] ?? "";
|
|
1514
|
+
issues = first.issues.map((issue) => ({
|
|
1515
|
+
...issue,
|
|
1516
|
+
repoPath,
|
|
1517
|
+
needsWorkspacePick: issueNeedsWorkspacePick(
|
|
1518
|
+
issue.provider ?? first.source,
|
|
1519
|
+
paths.length
|
|
1520
|
+
)
|
|
1521
|
+
}));
|
|
1522
|
+
} else {
|
|
1523
|
+
const settled = await Promise.allSettled(
|
|
1524
|
+
paths.map(async (path) => {
|
|
1525
|
+
const result = path === paths[0] ? first : await listIssues(path);
|
|
1526
|
+
return result.issues.map((issue) => ({
|
|
1527
|
+
...issue,
|
|
1528
|
+
repoPath: path,
|
|
1529
|
+
needsWorkspacePick: issueNeedsWorkspacePick(
|
|
1530
|
+
issue.provider ?? result.source,
|
|
1531
|
+
1
|
|
1532
|
+
)
|
|
1533
|
+
}));
|
|
1534
|
+
})
|
|
1535
|
+
);
|
|
1536
|
+
const collected = [];
|
|
1537
|
+
for (const item of settled) {
|
|
1538
|
+
if (item.status === "fulfilled") collected.push(...item.value);
|
|
1539
|
+
else issueErrors.push(errText(item.reason));
|
|
1540
|
+
}
|
|
1541
|
+
issues = collected;
|
|
1542
|
+
if (collected.length === 0 && issueErrors[0]) {
|
|
1543
|
+
throw new Error(issueErrors[0]);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
issues = dedupeBoardIssues(issues);
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
issueErrors.push(errText(err));
|
|
1549
|
+
issues = [];
|
|
1550
|
+
}
|
|
1551
|
+
let prs = [];
|
|
1552
|
+
try {
|
|
1553
|
+
const settled = await Promise.allSettled(
|
|
1554
|
+
paths.map(async (path) => {
|
|
1555
|
+
const list = await listPrs(path);
|
|
1556
|
+
return list.map((pr) => ({ ...pr, repoPath: path }));
|
|
1557
|
+
})
|
|
1558
|
+
);
|
|
1559
|
+
const collected = [];
|
|
1560
|
+
for (const item of settled) {
|
|
1561
|
+
if (item.status === "fulfilled") collected.push(...item.value);
|
|
1562
|
+
else prErrors.push(errText(item.reason));
|
|
1563
|
+
}
|
|
1564
|
+
if (collected.length === 0 && prErrors[0]) {
|
|
1565
|
+
throw new Error(prErrors[0]);
|
|
1566
|
+
}
|
|
1567
|
+
prs = dedupeBoardPrs(collected);
|
|
1568
|
+
} catch (err) {
|
|
1569
|
+
prErrors.push(errText(err));
|
|
1570
|
+
prs = [];
|
|
1571
|
+
}
|
|
1572
|
+
return { issues, prs, issueSource, viewerLogin, issueErrors, prErrors };
|
|
1573
|
+
}
|
|
1574
|
+
async function getHomeBoardInputs(workspaces, opts) {
|
|
1575
|
+
const key = homeBoardWorkspaceKey(workspaces);
|
|
1576
|
+
const now = opts?.now ?? Date.now();
|
|
1577
|
+
if (!key) {
|
|
1578
|
+
return {
|
|
1579
|
+
...emptyInputs(),
|
|
1580
|
+
fetchedAt: now,
|
|
1581
|
+
fromCache: false,
|
|
1582
|
+
pins: listBoardPins()
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
if (!opts?.refresh) {
|
|
1586
|
+
const disk = readDiskCache();
|
|
1587
|
+
const best = memory && disk ? disk.fetchedAt >= memory.fetchedAt ? disk : memory : memory ?? disk;
|
|
1588
|
+
if (best && cacheStillFresh(best, key, now)) {
|
|
1589
|
+
memory = best;
|
|
1590
|
+
return asLoaded(best, true);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
if (inflight && inflight.key === key) {
|
|
1594
|
+
return inflight.promise;
|
|
1595
|
+
}
|
|
1596
|
+
const promise = (async () => {
|
|
1597
|
+
const inputs = await loadHomeBoardInputs(workspaces);
|
|
1598
|
+
const fetchedAt = opts?.now ?? Date.now();
|
|
1599
|
+
const pins = syncBoardPins(listBoardPins(), inputs.issues, inputs.prs);
|
|
1600
|
+
replaceBoardPins(pins);
|
|
1601
|
+
const loaded = { ...inputs, fetchedAt, fromCache: false, pins };
|
|
1602
|
+
if (shouldCacheHomeBoardInputs(inputs)) {
|
|
1603
|
+
const entry = {
|
|
1604
|
+
version: CACHE_VERSION,
|
|
1605
|
+
workspaceKey: key,
|
|
1606
|
+
fetchedAt,
|
|
1607
|
+
inputs
|
|
1608
|
+
};
|
|
1609
|
+
memory = entry;
|
|
1610
|
+
writeDiskCache(entry);
|
|
1611
|
+
}
|
|
1612
|
+
return loaded;
|
|
1613
|
+
})();
|
|
1614
|
+
inflight = { key, promise };
|
|
1615
|
+
try {
|
|
1616
|
+
return await promise;
|
|
1617
|
+
} finally {
|
|
1618
|
+
if (inflight?.promise === promise) inflight = null;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1318
1622
|
// src/mcp/server.ts
|
|
1319
1623
|
var MAX_ORCH_THREADS = 5;
|
|
1320
1624
|
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
@@ -1335,6 +1639,93 @@ function withTimeout(promise, ms, label) {
|
|
|
1335
1639
|
);
|
|
1336
1640
|
});
|
|
1337
1641
|
}
|
|
1642
|
+
async function createOrchChildThread(orch, args, resolveNewThreadOptions) {
|
|
1643
|
+
const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
|
|
1644
|
+
let parentId = args.parentThreadId?.trim() || "";
|
|
1645
|
+
let parent = parentId ? orch.getThread(parentId) : null;
|
|
1646
|
+
let parentCorrectedFrom;
|
|
1647
|
+
if (envParentId) {
|
|
1648
|
+
const envParent = orch.getThread(envParentId);
|
|
1649
|
+
if (envParent) {
|
|
1650
|
+
if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
|
|
1651
|
+
else if (!parentId) parentCorrectedFrom = void 0;
|
|
1652
|
+
parentId = envParentId;
|
|
1653
|
+
parent = envParent;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (parentId && !parent) {
|
|
1657
|
+
parentCorrectedFrom = parentId;
|
|
1658
|
+
parentId = "";
|
|
1659
|
+
parent = null;
|
|
1660
|
+
}
|
|
1661
|
+
if (parentId) {
|
|
1662
|
+
const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
|
|
1663
|
+
if (children.length >= MAX_ORCH_THREADS) {
|
|
1664
|
+
return {
|
|
1665
|
+
ok: false,
|
|
1666
|
+
text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
let agentArg = args.agent;
|
|
1671
|
+
let agentCoercedFrom;
|
|
1672
|
+
const resolvedProbe = resolveNewThreadOptions({ agent: agentArg }).agent;
|
|
1673
|
+
if (resolvedProbe === "codex") {
|
|
1674
|
+
agentCoercedFrom = agentArg ?? "codex";
|
|
1675
|
+
const accountAgent = resolveNewThreadOptions({}).agent;
|
|
1676
|
+
agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
|
|
1677
|
+
}
|
|
1678
|
+
const opts = resolveNewThreadOptions({
|
|
1679
|
+
agent: agentArg,
|
|
1680
|
+
model: args.model
|
|
1681
|
+
});
|
|
1682
|
+
try {
|
|
1683
|
+
const priorIds = new Set(orch.getThreads(false).map((t) => t.id));
|
|
1684
|
+
const thread = await withTimeout(
|
|
1685
|
+
orch.createThread({
|
|
1686
|
+
sourceType: args.sourceType,
|
|
1687
|
+
sourceRef: args.sourceRef,
|
|
1688
|
+
agent: opts.agent,
|
|
1689
|
+
model: opts.model,
|
|
1690
|
+
effort: opts.effort,
|
|
1691
|
+
fast: opts.fast,
|
|
1692
|
+
repoPath: args.repoPath,
|
|
1693
|
+
title: args.title,
|
|
1694
|
+
parentThreadId: parentId || null,
|
|
1695
|
+
cowboy: args.cowboy || void 0,
|
|
1696
|
+
attachments: args.attachments
|
|
1697
|
+
}),
|
|
1698
|
+
CREATE_THREAD_TIMEOUT_MS,
|
|
1699
|
+
"create_thread"
|
|
1700
|
+
);
|
|
1701
|
+
const alreadyStarted = priorIds.has(thread.id);
|
|
1702
|
+
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
1703
|
+
return {
|
|
1704
|
+
ok: true,
|
|
1705
|
+
text: JSON.stringify({
|
|
1706
|
+
id: thread.id,
|
|
1707
|
+
title: thread.title,
|
|
1708
|
+
branchName: thread.branchName,
|
|
1709
|
+
worktreePath: thread.worktreePath,
|
|
1710
|
+
agent: thread.agent,
|
|
1711
|
+
model: thread.model,
|
|
1712
|
+
status: thread.status,
|
|
1713
|
+
cowboy: Boolean(thread.cowboy),
|
|
1714
|
+
link: `sideboard://thread/${thread.id}`,
|
|
1715
|
+
parentThreadId: thread.parentThreadId,
|
|
1716
|
+
...alreadyStarted ? { alreadyStarted: true } : {},
|
|
1717
|
+
...agentCoercedFrom ? {
|
|
1718
|
+
agentCoercedFrom,
|
|
1719
|
+
note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
|
|
1720
|
+
} : {},
|
|
1721
|
+
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
} catch (err) {
|
|
1725
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1726
|
+
return { ok: false, text: `create_thread failed: ${message}` };
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1338
1729
|
async function startMcpServer() {
|
|
1339
1730
|
const orch = getOrchestrator();
|
|
1340
1731
|
try {
|
|
@@ -1346,7 +1737,7 @@ async function startMcpServer() {
|
|
|
1346
1737
|
);
|
|
1347
1738
|
}
|
|
1348
1739
|
try {
|
|
1349
|
-
const { maxConcurrentAgents } = await import("../app-settings-
|
|
1740
|
+
const { maxConcurrentAgents } = await import("../app-settings-E7NEQY7D.js");
|
|
1350
1741
|
orch.setMaxConcurrent(maxConcurrentAgents());
|
|
1351
1742
|
} catch {
|
|
1352
1743
|
}
|
|
@@ -1366,7 +1757,7 @@ async function startMcpServer() {
|
|
|
1366
1757
|
if (!worktreeProfile) {
|
|
1367
1758
|
server.tool(
|
|
1368
1759
|
"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.",
|
|
1760
|
+
"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
1761
|
{},
|
|
1371
1762
|
async () => {
|
|
1372
1763
|
const workspaces = orch.listWorkspaces();
|
|
@@ -1400,6 +1791,51 @@ async function startMcpServer() {
|
|
|
1400
1791
|
};
|
|
1401
1792
|
}
|
|
1402
1793
|
);
|
|
1794
|
+
server.tool(
|
|
1795
|
+
"list_board",
|
|
1796
|
+
"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.",
|
|
1797
|
+
{
|
|
1798
|
+
query: z4.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
1799
|
+
repoPath: z4.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
1800
|
+
kind: z4.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
1801
|
+
column: z4.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
1802
|
+
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
1803
|
+
),
|
|
1804
|
+
limit: z4.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
1805
|
+
},
|
|
1806
|
+
async ({ query, repoPath, kind, column, limit }) => {
|
|
1807
|
+
const workspaces = orch.listWorkspaces();
|
|
1808
|
+
const all = orch.getThreads(true);
|
|
1809
|
+
const names = new Map(workspaces.map((w) => [w.path, w.name]));
|
|
1810
|
+
const snap = assembleHomeBoard({
|
|
1811
|
+
threads: all.filter((t) => t.status !== "archived"),
|
|
1812
|
+
query,
|
|
1813
|
+
repoPath,
|
|
1814
|
+
kind: kind ?? "all",
|
|
1815
|
+
column: column === "needs_you" ? "new" : column,
|
|
1816
|
+
limit,
|
|
1817
|
+
workspaceName: (path) => names.get(path) ?? ""
|
|
1818
|
+
});
|
|
1819
|
+
return {
|
|
1820
|
+
content: [
|
|
1821
|
+
{
|
|
1822
|
+
type: "text",
|
|
1823
|
+
text: JSON.stringify(
|
|
1824
|
+
{
|
|
1825
|
+
columns: snap.columns,
|
|
1826
|
+
hidden: snap.hidden,
|
|
1827
|
+
totals: snap.totals,
|
|
1828
|
+
columnDefs: BOARD_COLUMN_DEFS,
|
|
1829
|
+
hint: HOME_BOARD_AGENT_HINT
|
|
1830
|
+
},
|
|
1831
|
+
null,
|
|
1832
|
+
2
|
|
1833
|
+
)
|
|
1834
|
+
}
|
|
1835
|
+
]
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
);
|
|
1403
1839
|
server.tool(
|
|
1404
1840
|
"get_thread",
|
|
1405
1841
|
"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.",
|
|
@@ -1438,25 +1874,31 @@ async function startMcpServer() {
|
|
|
1438
1874
|
}
|
|
1439
1875
|
server.tool(
|
|
1440
1876
|
"present_artifact",
|
|
1441
|
-
"Show
|
|
1877
|
+
"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.",
|
|
1442
1878
|
{
|
|
1443
1879
|
title: z4.string().describe("Short title shown in the artifact pane header"),
|
|
1444
|
-
type: z4.enum(["html", "svg", "markdown", "react"]).describe(
|
|
1445
|
-
"
|
|
1880
|
+
type: z4.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
1881
|
+
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
1446
1882
|
),
|
|
1447
1883
|
content: z4.string().describe(
|
|
1448
|
-
"
|
|
1884
|
+
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
1449
1885
|
),
|
|
1450
|
-
artifact_id: z4.string().optional().describe("Stable id
|
|
1886
|
+
artifact_id: z4.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
1887
|
+
status: z4.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
1888
|
+
phase: z4.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
1889
|
+
mode: z4.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
1451
1890
|
},
|
|
1452
|
-
async ({ title, type, artifact_id }) => {
|
|
1891
|
+
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
1453
1892
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1454
1893
|
const payload = {
|
|
1455
1894
|
ok: true,
|
|
1456
1895
|
artifact_id: id,
|
|
1457
1896
|
title,
|
|
1458
1897
|
type,
|
|
1459
|
-
|
|
1898
|
+
status,
|
|
1899
|
+
phase,
|
|
1900
|
+
mode: type === "log" ? mode ?? "append" : void 0,
|
|
1901
|
+
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."
|
|
1460
1902
|
};
|
|
1461
1903
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
1462
1904
|
}
|
|
@@ -1569,7 +2011,7 @@ async function startMcpServer() {
|
|
|
1569
2011
|
registerLinearTools(server);
|
|
1570
2012
|
registerScheduleTools(server);
|
|
1571
2013
|
const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-NBRUWYXO.js");
|
|
1572
|
-
const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-
|
|
2014
|
+
const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-E7NEQY7D.js");
|
|
1573
2015
|
const accountDefaults = resolveThreadDefaults();
|
|
1574
2016
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
1575
2017
|
server.tool(
|
|
@@ -1612,7 +2054,7 @@ async function startMcpServer() {
|
|
|
1612
2054
|
);
|
|
1613
2055
|
server.tool(
|
|
1614
2056
|
"create_thread",
|
|
1615
|
-
`Create a
|
|
2057
|
+
`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
2058
|
{
|
|
1617
2059
|
sourceType: z4.enum(["branch", "pr", "ticket"]),
|
|
1618
2060
|
sourceRef: z4.string(),
|
|
@@ -1630,104 +2072,118 @@ async function startMcpServer() {
|
|
|
1630
2072
|
)
|
|
1631
2073
|
},
|
|
1632
2074
|
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;
|
|
2075
|
+
const result = await createOrchChildThread(orch, args, resolveNewThreadOptions);
|
|
2076
|
+
return {
|
|
2077
|
+
content: [{ type: "text", text: result.text }],
|
|
2078
|
+
...result.ok ? {} : { isError: true }
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
);
|
|
2082
|
+
server.tool(
|
|
2083
|
+
"start_board_card",
|
|
2084
|
+
"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.",
|
|
2085
|
+
{
|
|
2086
|
+
kind: z4.enum(["ticket", "pr", "branch"]),
|
|
2087
|
+
ref: z4.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
2088
|
+
repoPath: z4.string().describe("Workspace path from list_workspaces / list_board"),
|
|
2089
|
+
title: z4.string().optional()
|
|
2090
|
+
},
|
|
2091
|
+
async ({ kind, ref, repoPath, title }) => {
|
|
2092
|
+
const root = await resolveRepoRoot(repoPath);
|
|
2093
|
+
const existing = findLiveThreadForCreate(
|
|
2094
|
+
{
|
|
2095
|
+
sourceType: kind,
|
|
2096
|
+
sourceRef: ref.trim(),
|
|
2097
|
+
repoPath: canonicalizeRepoPath(root),
|
|
2098
|
+
title: title?.trim()
|
|
2099
|
+
},
|
|
2100
|
+
orch.getThreads(false).map((t) => ({
|
|
2101
|
+
...t,
|
|
2102
|
+
repoPath: canonicalizeRepoPath(t.repoPath)
|
|
2103
|
+
}))
|
|
2104
|
+
);
|
|
2105
|
+
if (existing) {
|
|
1695
2106
|
return {
|
|
1696
2107
|
content: [
|
|
1697
2108
|
{
|
|
1698
2109
|
type: "text",
|
|
1699
2110
|
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 } : {}
|
|
2111
|
+
alreadyStarted: true,
|
|
2112
|
+
id: existing.id,
|
|
2113
|
+
title: existing.title,
|
|
2114
|
+
status: existing.status,
|
|
2115
|
+
link: `sideboard://thread/${existing.id}`
|
|
1715
2116
|
})
|
|
1716
2117
|
}
|
|
1717
2118
|
]
|
|
1718
2119
|
};
|
|
1719
|
-
}
|
|
1720
|
-
|
|
2120
|
+
}
|
|
2121
|
+
const workspaces = orch.listWorkspaces();
|
|
2122
|
+
const loaded = await getHomeBoardInputs(workspaces);
|
|
2123
|
+
const pin = findBoardPin(loaded.pins, kind, ref, root);
|
|
2124
|
+
if (kind === "branch") {
|
|
2125
|
+
const sourceRef = pin?.ref === "default" ? "default" : pin?.ref ?? ref.trim();
|
|
2126
|
+
const result2 = await createOrchChildThread(
|
|
2127
|
+
orch,
|
|
2128
|
+
{
|
|
2129
|
+
sourceType: "branch",
|
|
2130
|
+
sourceRef,
|
|
2131
|
+
repoPath: root,
|
|
2132
|
+
title: title?.trim() || pin?.title || (sourceRef === "default" ? void 0 : sourceRef)
|
|
2133
|
+
},
|
|
2134
|
+
resolveNewThreadOptions
|
|
2135
|
+
);
|
|
1721
2136
|
return {
|
|
1722
|
-
content: [
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
2137
|
+
content: [{ type: "text", text: result2.text }],
|
|
2138
|
+
...result2.ok ? {} : { isError: true }
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
if (kind === "ticket") {
|
|
2142
|
+
const issue = findBoardIssue(loaded.issues, ref, root);
|
|
2143
|
+
const ident = issue?.identifier ?? ref.trim();
|
|
2144
|
+
const attachments = issue ? [
|
|
2145
|
+
{
|
|
2146
|
+
id: randomUUID2(),
|
|
2147
|
+
name: issue.identifier,
|
|
2148
|
+
kind: "issue",
|
|
2149
|
+
content: [
|
|
2150
|
+
`Linked issue: ${issue.identifier} \u2014 ${issue.title}`,
|
|
2151
|
+
issue.url ? `URL: ${issue.url}` : null
|
|
2152
|
+
].filter(Boolean).join("\n")
|
|
2153
|
+
}
|
|
2154
|
+
] : void 0;
|
|
2155
|
+
const result2 = await createOrchChildThread(
|
|
2156
|
+
orch,
|
|
2157
|
+
{
|
|
2158
|
+
sourceType: "ticket",
|
|
2159
|
+
sourceRef: ident,
|
|
2160
|
+
repoPath: root,
|
|
2161
|
+
title: title?.trim() || issue?.title,
|
|
2162
|
+
attachments
|
|
2163
|
+
},
|
|
2164
|
+
resolveNewThreadOptions
|
|
2165
|
+
);
|
|
2166
|
+
return {
|
|
2167
|
+
content: [{ type: "text", text: result2.text }],
|
|
2168
|
+
...result2.ok ? {} : { isError: true }
|
|
1729
2169
|
};
|
|
1730
2170
|
}
|
|
2171
|
+
const pr = findBoardPr(loaded.prs, ref, root);
|
|
2172
|
+
const number = pr ? String(pr.number) : ref.trim().replace(/^#/, "");
|
|
2173
|
+
const result = await createOrchChildThread(
|
|
2174
|
+
orch,
|
|
2175
|
+
{
|
|
2176
|
+
sourceType: "pr",
|
|
2177
|
+
sourceRef: number,
|
|
2178
|
+
repoPath: root,
|
|
2179
|
+
title: title?.trim() || pr?.title
|
|
2180
|
+
},
|
|
2181
|
+
resolveNewThreadOptions
|
|
2182
|
+
);
|
|
2183
|
+
return {
|
|
2184
|
+
content: [{ type: "text", text: result.text }],
|
|
2185
|
+
...result.ok ? {} : { isError: true }
|
|
2186
|
+
};
|
|
1731
2187
|
}
|
|
1732
2188
|
);
|
|
1733
2189
|
server.tool(
|
|
@@ -2374,7 +2830,7 @@ async function startMcpServer() {
|
|
|
2374
2830
|
);
|
|
2375
2831
|
server.tool(
|
|
2376
2832
|
"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.",
|
|
2833
|
+
"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
2834
|
{ repoPath: z4.string() },
|
|
2379
2835
|
async ({ repoPath }) => {
|
|
2380
2836
|
const root = await resolveRepoRoot(repoPath);
|