@sideboard-ai/core 0.1.190 → 0.1.192
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-J3EGHIAV.js → abletime-53T7G667.js} +3 -1
- package/dist/{abletime-CAFQ4GEG.js → abletime-JFMBCSIQ.js} +3 -1
- package/dist/{agents-5OBZETUV.js → agents-T5VBB6X3.js} +2 -2
- package/dist/{agents-DTPVTDB5.js → agents-TXGQIK2E.js} +2 -2
- package/dist/{chunk-TEJU6UJY.js → chunk-4M2AK3O5.js} +126 -20
- package/dist/{chunk-XXEIF2IL.js → chunk-7NSXGL6L.js} +3 -3
- package/dist/{chunk-IAOXW6RI.js → chunk-FZ3G5TXM.js} +23 -16
- package/dist/{chunk-Z6TW3A5P.js → chunk-NF6ENGHS.js} +24 -17
- package/dist/{chunk-OLDZXO53.js → chunk-TWSCTJCR.js} +2 -2
- package/dist/{chunk-KEVY4GEK.js → chunk-UBQIELFS.js} +123 -20
- package/dist/{chunk-VIODP5PT.js → chunk-VV7UT3E2.js} +3 -3
- package/dist/{chunk-GBWKMWUQ.js → chunk-WHD4JNTN.js} +2 -2
- package/dist/{coordinator-prompt-LWKOL3AZ.js → coordinator-prompt-PNUDN4DW.js} +1 -1
- package/dist/{coordinator-prompt-MSC44OOM.js → coordinator-prompt-S4HHFSLI.js} +1 -1
- package/dist/{global-workspace-6G5JJH2E.js → global-workspace-2BVCJUAL.js} +1 -1
- package/dist/{global-workspace-XATJTV3N.js → global-workspace-JTBBH2K5.js} +1 -1
- package/dist/index.cjs +565 -57
- package/dist/index.d.cts +96 -6
- package/dist/index.d.ts +96 -6
- package/dist/index.js +422 -30
- package/dist/mcp/run-stdio.cjs +543 -57
- package/dist/mcp/run-stdio.js +407 -30
- package/dist/{orchestrator-BHJELEFM.js → orchestrator-4G76BOHH.js} +5 -3
- package/dist/{orchestrator-HWNGCDGW.js → orchestrator-LWEZ74RK.js} +5 -3
- package/dist/{workspaces-LMFCRXTT.js → workspaces-OY7D5RPU.js} +1 -1
- package/dist/{workspaces-FWKPWSXV.js → workspaces-ZYPME3YD.js} +1 -1
- package/package.json +1 -1
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
readTurnLive,
|
|
31
31
|
recordSlackOutboundWatch,
|
|
32
32
|
requireSlackWorkspace,
|
|
33
|
+
resolveOrchChildFollowUp,
|
|
33
34
|
resolveScheduleThreadId,
|
|
34
35
|
slackApi,
|
|
35
36
|
slackTokenFor,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
syncBoardPins,
|
|
38
39
|
updateSchedule,
|
|
39
40
|
waitForDetachedJob
|
|
40
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-FZ3G5TXM.js";
|
|
41
42
|
import {
|
|
42
43
|
applySystemCaEnv,
|
|
43
44
|
isCursorApiKeyConfigured,
|
|
@@ -46,7 +47,7 @@ import {
|
|
|
46
47
|
materializeSystemCaBundle,
|
|
47
48
|
sideboardMcpProfile,
|
|
48
49
|
trustSystemCertificates
|
|
49
|
-
} from "../chunk-
|
|
50
|
+
} from "../chunk-WHD4JNTN.js";
|
|
50
51
|
import "../chunk-HVIEYTUK.js";
|
|
51
52
|
import "../chunk-Y4MIXGPA.js";
|
|
52
53
|
import {
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
searchAbleTimeTasks,
|
|
61
62
|
toAbleTimeIssueInfo,
|
|
62
63
|
updateAbleTimeTask
|
|
63
|
-
} from "../chunk-
|
|
64
|
+
} from "../chunk-UBQIELFS.js";
|
|
64
65
|
import {
|
|
65
66
|
httpFetch
|
|
66
67
|
} from "../chunk-VKQGAWHY.js";
|
|
@@ -70,7 +71,7 @@ import {
|
|
|
70
71
|
GLOBAL_WORKSPACE_ID,
|
|
71
72
|
isCloudCoordinatorThread,
|
|
72
73
|
listWorkspaces
|
|
73
|
-
} from "../chunk-
|
|
74
|
+
} from "../chunk-7NSXGL6L.js";
|
|
74
75
|
import {
|
|
75
76
|
canonicalizeRepoPath,
|
|
76
77
|
formatGhLandError,
|
|
@@ -366,13 +367,78 @@ async function commentGitHubIssue(input, opts) {
|
|
|
366
367
|
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
367
368
|
return { body, url };
|
|
368
369
|
}
|
|
370
|
+
function isGitHubNoneToken(value) {
|
|
371
|
+
if (value == null) return true;
|
|
372
|
+
const s = value.trim();
|
|
373
|
+
return !s || /^(none|clear|unschedule|null)$/i.test(s);
|
|
374
|
+
}
|
|
375
|
+
function githubRelationEditFlag(type, remove = false) {
|
|
376
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
377
|
+
if (s === "blockedby" || s === "blocked" || s === "dependson") {
|
|
378
|
+
return remove ? "--remove-blocked-by" : "--add-blocked-by";
|
|
379
|
+
}
|
|
380
|
+
if (s === "blocks" || s === "blocking") {
|
|
381
|
+
return remove ? "--remove-blocking" : "--add-blocking";
|
|
382
|
+
}
|
|
383
|
+
throw new Error(
|
|
384
|
+
`GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
function labelsEqual(a, b) {
|
|
388
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
389
|
+
}
|
|
390
|
+
function parseProjectTitles(raw) {
|
|
391
|
+
const out = [];
|
|
392
|
+
const seen = /* @__PURE__ */ new Set();
|
|
393
|
+
const push = (name) => {
|
|
394
|
+
const title = name.trim();
|
|
395
|
+
const key = title.toLowerCase();
|
|
396
|
+
if (!title || seen.has(key)) return;
|
|
397
|
+
seen.add(key);
|
|
398
|
+
out.push(title);
|
|
399
|
+
};
|
|
400
|
+
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
401
|
+
if (Array.isArray(fromItems)) {
|
|
402
|
+
for (const item of fromItems) {
|
|
403
|
+
if (typeof item === "string") {
|
|
404
|
+
push(item);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!item || typeof item !== "object") continue;
|
|
408
|
+
const rec = item;
|
|
409
|
+
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
410
|
+
const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
411
|
+
if (title) push(title);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return out;
|
|
415
|
+
}
|
|
416
|
+
async function listGitHubIssueProjects(number, cwd, repoArgs) {
|
|
417
|
+
const result = await gh(
|
|
418
|
+
["issue", "view", String(number), ...repoArgs, "--json", "projectItems"],
|
|
419
|
+
cwd,
|
|
420
|
+
{ reject: false }
|
|
421
|
+
);
|
|
422
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
423
|
+
try {
|
|
424
|
+
const parsed = JSON.parse(result.stdout);
|
|
425
|
+
return parseProjectTitles(parsed);
|
|
426
|
+
} catch {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
369
430
|
async function updateGitHubIssue(input, opts) {
|
|
370
431
|
const number = parseGitHubIssueNumber(input.id);
|
|
371
432
|
const title = input.title?.trim();
|
|
372
433
|
const body = input.body;
|
|
373
434
|
const state = input.state?.trim().toLowerCase();
|
|
374
|
-
|
|
375
|
-
|
|
435
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
436
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
437
|
+
const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
438
|
+
if (!hasEditFields && !state) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
"github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
|
|
441
|
+
);
|
|
376
442
|
}
|
|
377
443
|
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
378
444
|
if (state === "closed" || state === "close") {
|
|
@@ -388,10 +454,46 @@ async function updateGitHubIssue(input, opts) {
|
|
|
388
454
|
} else if (state) {
|
|
389
455
|
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
390
456
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
457
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
458
|
+
if (title) args.push("--title", title);
|
|
459
|
+
if (body !== void 0) args.push("--body", body);
|
|
460
|
+
if (input.labels !== void 0) {
|
|
461
|
+
const existing = await getGitHubIssue(String(number), opts);
|
|
462
|
+
const desired = input.labels.map((name) => name.trim()).filter(Boolean);
|
|
463
|
+
const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
|
|
464
|
+
const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
|
|
465
|
+
if (add.length) args.push("--add-label", add.join(","));
|
|
466
|
+
if (remove.length) args.push("--remove-label", remove.join(","));
|
|
467
|
+
}
|
|
468
|
+
if (input.project !== void 0) {
|
|
469
|
+
const current = await listGitHubIssueProjects(number, cwd, repoArgs);
|
|
470
|
+
if (isGitHubNoneToken(input.project)) {
|
|
471
|
+
for (const name of current) args.push("--remove-project", name);
|
|
472
|
+
} else {
|
|
473
|
+
const wanted = (input.project ?? "").trim();
|
|
474
|
+
if (!current.some((name) => labelsEqual(name, wanted))) {
|
|
475
|
+
args.push("--add-project", wanted);
|
|
476
|
+
}
|
|
477
|
+
for (const name of current) {
|
|
478
|
+
if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (input.parent !== void 0) {
|
|
483
|
+
if (isGitHubNoneToken(input.parent)) {
|
|
484
|
+
args.push("--remove-parent");
|
|
485
|
+
} else {
|
|
486
|
+
args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
for (const rel of removeRelations ?? []) {
|
|
490
|
+
args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
|
|
491
|
+
}
|
|
492
|
+
for (const rel of addRelations ?? []) {
|
|
493
|
+
args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
|
|
494
|
+
}
|
|
495
|
+
const editChanged = args.length > 3 + repoArgs.length;
|
|
496
|
+
if (editChanged) {
|
|
395
497
|
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
396
498
|
}
|
|
397
499
|
return getGitHubIssue(String(number), opts);
|
|
@@ -563,12 +665,14 @@ var ISSUE_FIELDS = `
|
|
|
563
665
|
children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
|
|
564
666
|
relations(first: 25) {
|
|
565
667
|
nodes {
|
|
668
|
+
id
|
|
566
669
|
type
|
|
567
670
|
relatedIssue { ${ISSUE_REF_FIELDS} }
|
|
568
671
|
}
|
|
569
672
|
}
|
|
570
673
|
inverseRelations(first: 25) {
|
|
571
674
|
nodes {
|
|
675
|
+
id
|
|
572
676
|
type
|
|
573
677
|
issue { ${ISSUE_REF_FIELDS} }
|
|
574
678
|
}
|
|
@@ -688,6 +792,45 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
|
688
792
|
}
|
|
689
793
|
}
|
|
690
794
|
`;
|
|
795
|
+
var LABELS_QUERY = `
|
|
796
|
+
query SideboardLabels($first: Int!) {
|
|
797
|
+
issueLabels(first: $first) {
|
|
798
|
+
nodes { id name }
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
`;
|
|
802
|
+
var TEAM_LABELS_QUERY = `
|
|
803
|
+
query SideboardTeamLabels($id: String!, $first: Int!, $after: String) {
|
|
804
|
+
team(id: $id) {
|
|
805
|
+
labels(first: $first, after: $after) {
|
|
806
|
+
nodes { id name }
|
|
807
|
+
pageInfo { hasNextPage endCursor }
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
`;
|
|
812
|
+
var PROJECTS_QUERY = `
|
|
813
|
+
query SideboardProjects($first: Int!, $filter: ProjectFilter) {
|
|
814
|
+
projects(first: $first, filter: $filter) {
|
|
815
|
+
nodes { id name slugId }
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
`;
|
|
819
|
+
var ISSUE_RELATION_CREATE = `
|
|
820
|
+
mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
|
|
821
|
+
issueRelationCreate(input: $input) {
|
|
822
|
+
success
|
|
823
|
+
issueRelation { id type }
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
`;
|
|
827
|
+
var ISSUE_RELATION_DELETE = `
|
|
828
|
+
mutation SideboardIssueRelationDelete($id: String!) {
|
|
829
|
+
issueRelationDelete(id: $id) {
|
|
830
|
+
success
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
`;
|
|
691
834
|
function rewriteLinearError(message) {
|
|
692
835
|
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
693
836
|
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
@@ -807,12 +950,18 @@ function mapRelations(node) {
|
|
|
807
950
|
for (const rel of node.relations?.nodes ?? []) {
|
|
808
951
|
const issue = mapIssueRef(rel.relatedIssue);
|
|
809
952
|
if (!issue) continue;
|
|
810
|
-
|
|
953
|
+
const id = rel.id?.trim();
|
|
954
|
+
out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
|
|
811
955
|
}
|
|
812
956
|
for (const rel of node.inverseRelations?.nodes ?? []) {
|
|
813
957
|
const issue = mapIssueRef(rel.issue);
|
|
814
958
|
if (!issue) continue;
|
|
815
|
-
|
|
959
|
+
const id = rel.id?.trim();
|
|
960
|
+
out.push({
|
|
961
|
+
type: flipLinearRelationType(String(rel.type ?? "related")),
|
|
962
|
+
issue,
|
|
963
|
+
...id ? { id } : {}
|
|
964
|
+
});
|
|
816
965
|
}
|
|
817
966
|
return out;
|
|
818
967
|
}
|
|
@@ -973,6 +1122,171 @@ function resolveLinearCycle(team, cycle) {
|
|
|
973
1122
|
}
|
|
974
1123
|
return found.id;
|
|
975
1124
|
}
|
|
1125
|
+
var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
|
|
1126
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1127
|
+
function isLinearNoneToken(value) {
|
|
1128
|
+
if (value == null) return true;
|
|
1129
|
+
const s = value.trim();
|
|
1130
|
+
return !s || LINEAR_NONE_RE.test(s);
|
|
1131
|
+
}
|
|
1132
|
+
function resolveLinearLabel(labels, name) {
|
|
1133
|
+
const s = name.trim();
|
|
1134
|
+
if (!s) throw new Error("Linear label is empty");
|
|
1135
|
+
const lower = s.toLowerCase();
|
|
1136
|
+
const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
|
|
1137
|
+
if (!found) {
|
|
1138
|
+
const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1139
|
+
throw new Error(`Linear label not found: ${name}. Available: ${available}`);
|
|
1140
|
+
}
|
|
1141
|
+
return found;
|
|
1142
|
+
}
|
|
1143
|
+
function resolveLinearProject(projects, project) {
|
|
1144
|
+
if (isLinearNoneToken(project)) return null;
|
|
1145
|
+
const s = (project ?? "").trim();
|
|
1146
|
+
const lower = s.toLowerCase();
|
|
1147
|
+
const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
|
|
1148
|
+
if (!found) {
|
|
1149
|
+
const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
1150
|
+
throw new Error(`Linear project not found: ${project}. Available: ${available}`);
|
|
1151
|
+
}
|
|
1152
|
+
return found.id;
|
|
1153
|
+
}
|
|
1154
|
+
function normalizeLinearRelationType(type) {
|
|
1155
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
1156
|
+
if (s === "blocks" || s === "block") return "blocks";
|
|
1157
|
+
if (s === "blockedby" || s === "blocked") return "blockedBy";
|
|
1158
|
+
if (s === "related" || s === "relates" || s === "relatesto") return "related";
|
|
1159
|
+
if (s === "duplicate" || s === "dup") return "duplicate";
|
|
1160
|
+
if (s === "duplicateof" || s === "original") return "duplicateOf";
|
|
1161
|
+
if (s === "similar") return "similar";
|
|
1162
|
+
throw new Error(
|
|
1163
|
+
`Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
|
|
1167
|
+
const view = normalizeLinearRelationType(type);
|
|
1168
|
+
if (view === "blockedBy") {
|
|
1169
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
|
|
1170
|
+
}
|
|
1171
|
+
if (view === "duplicateOf") {
|
|
1172
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
|
|
1173
|
+
}
|
|
1174
|
+
return { issueId: thisIssueId, relatedIssueId, type: view };
|
|
1175
|
+
}
|
|
1176
|
+
function relationMatches(rel, type, issueRef) {
|
|
1177
|
+
if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
|
|
1178
|
+
const ref = issueRef.trim().toLowerCase();
|
|
1179
|
+
return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
|
|
1180
|
+
}
|
|
1181
|
+
function formatRelationList(relations) {
|
|
1182
|
+
if (!relations.length) return "(none)";
|
|
1183
|
+
return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
|
|
1184
|
+
}
|
|
1185
|
+
function mapLabelNodes(nodes) {
|
|
1186
|
+
return (nodes ?? []).flatMap((node) => {
|
|
1187
|
+
const id = String(node.id ?? "").trim();
|
|
1188
|
+
const name = String(node.name ?? "").trim();
|
|
1189
|
+
if (!id) return [];
|
|
1190
|
+
return [{ id, name }];
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
async function listLinearWorkspaceLabels(opts) {
|
|
1194
|
+
const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
|
|
1195
|
+
return mapLabelNodes(json.issueLabels?.nodes);
|
|
1196
|
+
}
|
|
1197
|
+
async function listLinearLabels(opts) {
|
|
1198
|
+
const teamId = opts?.teamId?.trim();
|
|
1199
|
+
if (teamId) {
|
|
1200
|
+
const out = [];
|
|
1201
|
+
let after;
|
|
1202
|
+
for (let page = 0; page < 4; page++) {
|
|
1203
|
+
const json = await linearGraphql(
|
|
1204
|
+
TEAM_LABELS_QUERY,
|
|
1205
|
+
after ? { id: teamId, first: 250, after } : { id: teamId, first: 250 },
|
|
1206
|
+
opts
|
|
1207
|
+
);
|
|
1208
|
+
const conn = json.team?.labels;
|
|
1209
|
+
out.push(...mapLabelNodes(conn?.nodes));
|
|
1210
|
+
if (!conn?.pageInfo?.hasNextPage || !conn.pageInfo.endCursor) break;
|
|
1211
|
+
after = conn.pageInfo.endCursor;
|
|
1212
|
+
}
|
|
1213
|
+
if (out.length) return out;
|
|
1214
|
+
}
|
|
1215
|
+
return listLinearWorkspaceLabels(opts);
|
|
1216
|
+
}
|
|
1217
|
+
async function listLinearProjects(filter, opts) {
|
|
1218
|
+
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
|
|
1219
|
+
return (json.projects?.nodes ?? []).flatMap((node) => {
|
|
1220
|
+
const id = String(node.id ?? "").trim();
|
|
1221
|
+
if (!id) return [];
|
|
1222
|
+
return [
|
|
1223
|
+
{
|
|
1224
|
+
id,
|
|
1225
|
+
name: String(node.name ?? ""),
|
|
1226
|
+
...node.slugId ? { slugId: String(node.slugId) } : {}
|
|
1227
|
+
}
|
|
1228
|
+
];
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
async function resolveLinearProjectId(project, opts) {
|
|
1232
|
+
if (isLinearNoneToken(project)) return null;
|
|
1233
|
+
const s = (project ?? "").trim();
|
|
1234
|
+
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
1235
|
+
const matched = await listLinearProjects(filter, opts);
|
|
1236
|
+
try {
|
|
1237
|
+
return resolveLinearProject(matched, s);
|
|
1238
|
+
} catch {
|
|
1239
|
+
if (UUID_RE.test(s)) return s;
|
|
1240
|
+
const listed = await listLinearProjects(void 0, opts);
|
|
1241
|
+
return resolveLinearProject(listed, s);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
async function resolveLinearLabelIds(names, opts) {
|
|
1245
|
+
if (names.length === 0) return [];
|
|
1246
|
+
const labels = await listLinearLabels(opts);
|
|
1247
|
+
return names.map((name) => {
|
|
1248
|
+
const s = name.trim();
|
|
1249
|
+
try {
|
|
1250
|
+
return resolveLinearLabel(labels, s).id;
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (UUID_RE.test(s)) return s;
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
async function applyLinearIssueRelations(issue, add, remove, opts) {
|
|
1258
|
+
const current = [...issue.relations];
|
|
1259
|
+
for (const rel of remove ?? []) {
|
|
1260
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1261
|
+
const issueRef = rel.issue.trim();
|
|
1262
|
+
if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
|
|
1263
|
+
const match = current.find((r) => relationMatches(r, type, issueRef));
|
|
1264
|
+
if (!match?.id) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
`Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
|
|
1270
|
+
if (!json.issueRelationDelete?.success) {
|
|
1271
|
+
throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
|
|
1272
|
+
}
|
|
1273
|
+
const idx = current.indexOf(match);
|
|
1274
|
+
if (idx >= 0) current.splice(idx, 1);
|
|
1275
|
+
}
|
|
1276
|
+
for (const rel of add ?? []) {
|
|
1277
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
1278
|
+
const issueRef = rel.issue.trim();
|
|
1279
|
+
if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
|
|
1280
|
+
if (current.some((r) => relationMatches(r, type, issueRef))) continue;
|
|
1281
|
+
const related = await getLinearIssue(issueRef, opts);
|
|
1282
|
+
const input = linearRelationCreateInput(issue.id, type, related.id);
|
|
1283
|
+
const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
|
|
1284
|
+
if (!json.issueRelationCreate?.success) {
|
|
1285
|
+
throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
|
|
1286
|
+
}
|
|
1287
|
+
current.push({ type, issue: related, id: void 0 });
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
976
1290
|
function normalizePriority(priority) {
|
|
977
1291
|
if (priority == null) return void 0;
|
|
978
1292
|
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
@@ -980,7 +1294,6 @@ function normalizePriority(priority) {
|
|
|
980
1294
|
}
|
|
981
1295
|
return priority;
|
|
982
1296
|
}
|
|
983
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
984
1297
|
function assigneeFilterKey(assignee) {
|
|
985
1298
|
return (assignee ?? "").trim();
|
|
986
1299
|
}
|
|
@@ -1187,8 +1500,10 @@ async function updateLinearIssue(input, opts) {
|
|
|
1187
1500
|
if (input.description !== void 0) {
|
|
1188
1501
|
mutationInput.description = input.description;
|
|
1189
1502
|
}
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1503
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
1504
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
1505
|
+
const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || input.labels !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
1506
|
+
const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
|
|
1192
1507
|
if (input.state?.trim()) {
|
|
1193
1508
|
const { teams } = await listLinearTeams(opts);
|
|
1194
1509
|
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
@@ -1221,14 +1536,47 @@ async function updateLinearIssue(input, opts) {
|
|
|
1221
1536
|
if (input.priority !== void 0) {
|
|
1222
1537
|
mutationInput.priority = normalizePriority(input.priority);
|
|
1223
1538
|
}
|
|
1224
|
-
if (
|
|
1225
|
-
|
|
1539
|
+
if (input.labels !== void 0) {
|
|
1540
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, {
|
|
1541
|
+
...opts,
|
|
1542
|
+
teamId: existing?.team?.id
|
|
1543
|
+
});
|
|
1226
1544
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
throw new Error("Linear issueUpdate failed");
|
|
1545
|
+
if (input.project !== void 0) {
|
|
1546
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
1230
1547
|
}
|
|
1231
|
-
|
|
1548
|
+
if (input.parent !== void 0) {
|
|
1549
|
+
if (isLinearNoneToken(input.parent)) {
|
|
1550
|
+
mutationInput.parentId = null;
|
|
1551
|
+
} else {
|
|
1552
|
+
const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
|
|
1553
|
+
mutationInput.parentId = parent.id;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
const hasIssueFields = Object.keys(mutationInput).length > 0;
|
|
1557
|
+
const hasRelationWrites = Boolean(addRelations || removeRelations);
|
|
1558
|
+
if (!hasIssueFields && !hasRelationWrites) {
|
|
1559
|
+
throw new Error(
|
|
1560
|
+
"linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
let updated;
|
|
1564
|
+
if (hasIssueFields) {
|
|
1565
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
1566
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
1567
|
+
throw new Error("Linear issueUpdate failed");
|
|
1568
|
+
}
|
|
1569
|
+
updated = mapIssue(json.issueUpdate.issue);
|
|
1570
|
+
}
|
|
1571
|
+
if (hasRelationWrites) {
|
|
1572
|
+
const base = updated ?? existing;
|
|
1573
|
+
if (!base) {
|
|
1574
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
1575
|
+
}
|
|
1576
|
+
await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
|
|
1577
|
+
return getLinearIssue(base.id || issueId, opts);
|
|
1578
|
+
}
|
|
1579
|
+
return updated;
|
|
1232
1580
|
}
|
|
1233
1581
|
async function commentLinearIssue(input, opts) {
|
|
1234
1582
|
const issueId = input.id.trim();
|
|
@@ -2472,14 +2820,23 @@ function registerAbleTimeTools(server) {
|
|
|
2472
2820
|
}
|
|
2473
2821
|
}
|
|
2474
2822
|
);
|
|
2823
|
+
const relationInputSchema = z4.object({
|
|
2824
|
+
type: z4.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
|
|
2825
|
+
issue: z4.string().describe("Related task id or reference (CRM-232).")
|
|
2826
|
+
});
|
|
2475
2827
|
server.tool(
|
|
2476
2828
|
"abletime_update_task",
|
|
2477
|
-
|
|
2829
|
+
'Update an AbleTime task (id or CRM-232). Pass title, description, state, labels, project, parent, and/or relations. labels replaces tags. project is name or id (tasks stay in a project). parent is CRM-232 or "none". relations sets the wait-on dependency (blockedBy/blocks); removeRelations clears it. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.',
|
|
2478
2830
|
{
|
|
2479
2831
|
id: z4.string(),
|
|
2480
2832
|
title: z4.string().optional(),
|
|
2481
2833
|
description: z4.string().optional(),
|
|
2482
|
-
state: z4.string().optional()
|
|
2834
|
+
state: z4.string().optional(),
|
|
2835
|
+
labels: z4.array(z4.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
|
|
2836
|
+
project: z4.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
|
|
2837
|
+
parent: z4.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
|
|
2838
|
+
relations: z4.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
|
|
2839
|
+
removeRelations: z4.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
|
|
2483
2840
|
},
|
|
2484
2841
|
async (args) => {
|
|
2485
2842
|
try {
|
|
@@ -2613,14 +2970,23 @@ function registerGithubIssueTools(server) {
|
|
|
2613
2970
|
}
|
|
2614
2971
|
}
|
|
2615
2972
|
);
|
|
2973
|
+
const relationInputSchema = z5.object({
|
|
2974
|
+
type: z5.string().describe("blocks or blockedBy (this issue's view)."),
|
|
2975
|
+
issue: z5.string().describe("Related issue #123 or URL.")
|
|
2976
|
+
});
|
|
2616
2977
|
server.tool(
|
|
2617
2978
|
"github_update_issue",
|
|
2618
|
-
|
|
2979
|
+
'Update a GitHub issue (#123). Pass title, body, state (open|closed), labels, project, parent, and/or relations. labels replaces all names (pass [] to clear). project is a Project title, or "none" to remove. parent is #123 or "none". relations adds blockedBy/blocks; removeRelations drops them (`gh` 2.94+). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.',
|
|
2619
2980
|
{
|
|
2620
2981
|
id: z5.string(),
|
|
2621
2982
|
title: z5.string().optional(),
|
|
2622
2983
|
body: z5.string().optional(),
|
|
2623
2984
|
state: z5.string().optional().describe("open or closed"),
|
|
2985
|
+
labels: z5.array(z5.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
|
|
2986
|
+
project: z5.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
|
|
2987
|
+
parent: z5.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
|
|
2988
|
+
relations: z5.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
|
|
2989
|
+
removeRelations: z5.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
|
|
2624
2990
|
repoPath: repoPathSchema
|
|
2625
2991
|
},
|
|
2626
2992
|
async (args) => {
|
|
@@ -2750,9 +3116,13 @@ function registerLinearTools(server) {
|
|
|
2750
3116
|
}
|
|
2751
3117
|
}
|
|
2752
3118
|
);
|
|
3119
|
+
const relationInputSchema = z6.object({
|
|
3120
|
+
type: z6.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
|
|
3121
|
+
issue: z6.string().describe("Related issue uuid or identifier (ENG-123).")
|
|
3122
|
+
});
|
|
2753
3123
|
server.tool(
|
|
2754
3124
|
"linear_update_issue",
|
|
2755
|
-
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or
|
|
3125
|
+
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, cycle, labels, project, parent, and/or relations. Cycle/project/parent: name or id, or "none" to clear. labels replaces all names (pass [] to clear; get the issue first to keep existing). relations adds; removeRelations removes by type + issue. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.',
|
|
2756
3126
|
{
|
|
2757
3127
|
id: z6.string(),
|
|
2758
3128
|
title: z6.string().optional(),
|
|
@@ -2760,7 +3130,12 @@ function registerLinearTools(server) {
|
|
|
2760
3130
|
state: z6.string().optional(),
|
|
2761
3131
|
assignee: z6.string().nullable().optional(),
|
|
2762
3132
|
priority: prioritySchema,
|
|
2763
|
-
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
|
|
3133
|
+
cycle: z6.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
|
|
3134
|
+
labels: z6.array(z6.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
|
|
3135
|
+
project: z6.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
|
|
3136
|
+
parent: z6.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
|
|
3137
|
+
relations: z6.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
|
|
3138
|
+
removeRelations: z6.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
|
|
2764
3139
|
},
|
|
2765
3140
|
async (args) => {
|
|
2766
3141
|
try {
|
|
@@ -3977,7 +4352,7 @@ async function startMcpServer() {
|
|
|
3977
4352
|
);
|
|
3978
4353
|
server.tool(
|
|
3979
4354
|
"send_to_thread",
|
|
3980
|
-
'
|
|
4355
|
+
'Steer a prompt on a worktree thread chat (Settings \u2192 Follow-up, default steer: interrupt and start now, skip the inbox). 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 send_to_thread to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that interrupts the child mid-thought. Call wait_for_turn again instead.',
|
|
3981
4356
|
{
|
|
3982
4357
|
ref: z9.string(),
|
|
3983
4358
|
prompt: z9.string(),
|
|
@@ -3990,7 +4365,9 @@ async function startMcpServer() {
|
|
|
3990
4365
|
orch.stop(ref, { clearQueue: true, notifyParent: false });
|
|
3991
4366
|
}
|
|
3992
4367
|
}
|
|
3993
|
-
const thread = await orch.send(ref, prompt
|
|
4368
|
+
const thread = await orch.send(ref, prompt, {
|
|
4369
|
+
followUp: resolveOrchChildFollowUp()
|
|
4370
|
+
});
|
|
3994
4371
|
return mcpJson({
|
|
3995
4372
|
id: thread.id,
|
|
3996
4373
|
status: thread.status,
|
|
@@ -4001,7 +4378,7 @@ async function startMcpServer() {
|
|
|
4001
4378
|
);
|
|
4002
4379
|
server.tool(
|
|
4003
4380
|
"wait_for_turn",
|
|
4004
|
-
"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
|
|
4381
|
+
"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_to_thread a check-in (that steers / interrupts), 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).",
|
|
4005
4382
|
{
|
|
4006
4383
|
ref: z9.string(),
|
|
4007
4384
|
timeoutMs: z9.number().optional()
|
|
@@ -4158,7 +4535,7 @@ async function startMcpServer() {
|
|
|
4158
4535
|
);
|
|
4159
4536
|
server.tool(
|
|
4160
4537
|
"ask_git",
|
|
4161
|
-
"Commit & push, open a draft PR, mark ready for review, 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,
|
|
4538
|
+
"Commit & push, open a draft PR, mark ready for review, 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, steers the worktree agent to commit (Settings \u2192 Follow-up, default interrupt now); 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=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). 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.",
|
|
4162
4539
|
{
|
|
4163
4540
|
ref: z9.string().describe("Worktree thread id/ref"),
|
|
4164
4541
|
action: z9.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -5,17 +5,18 @@ import {
|
|
|
5
5
|
Orchestrator,
|
|
6
6
|
getOrchestrator,
|
|
7
7
|
isPidAlive,
|
|
8
|
+
resolveOrchChildFollowUp,
|
|
8
9
|
resolveSendFollowUp,
|
|
9
10
|
startOrchestration,
|
|
10
11
|
waitForPidExit
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-FZ3G5TXM.js";
|
|
13
|
+
import "./chunk-WHD4JNTN.js";
|
|
13
14
|
import "./chunk-HVIEYTUK.js";
|
|
14
15
|
import "./chunk-Y4MIXGPA.js";
|
|
15
16
|
import "./chunk-VKQGAWHY.js";
|
|
16
17
|
import "./chunk-JFOPOMK7.js";
|
|
17
18
|
import "./chunk-OCQPTUR7.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-7NSXGL6L.js";
|
|
19
20
|
import "./chunk-XBLN52RZ.js";
|
|
20
21
|
import "./chunk-D6JWYJ4P.js";
|
|
21
22
|
import "./chunk-GOZ6T3AG.js";
|
|
@@ -30,6 +31,7 @@ export {
|
|
|
30
31
|
Orchestrator,
|
|
31
32
|
getOrchestrator,
|
|
32
33
|
isPidAlive,
|
|
34
|
+
resolveOrchChildFollowUp,
|
|
33
35
|
resolveSendFollowUp,
|
|
34
36
|
startOrchestration,
|
|
35
37
|
waitForPidExit
|
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
Orchestrator,
|
|
4
4
|
getOrchestrator,
|
|
5
5
|
isPidAlive,
|
|
6
|
+
resolveOrchChildFollowUp,
|
|
6
7
|
resolveSendFollowUp,
|
|
7
8
|
startOrchestration,
|
|
8
9
|
waitForPidExit
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NF6ENGHS.js";
|
|
10
11
|
import "./chunk-DJU54RYW.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-TWSCTJCR.js";
|
|
13
|
+
import "./chunk-VV7UT3E2.js";
|
|
13
14
|
import "./chunk-WXKXSZN4.js";
|
|
14
15
|
import "./chunk-6K64FJDD.js";
|
|
15
16
|
import "./chunk-IEEAVWAP.js";
|
|
@@ -31,6 +32,7 @@ export {
|
|
|
31
32
|
Orchestrator,
|
|
32
33
|
getOrchestrator,
|
|
33
34
|
isPidAlive,
|
|
35
|
+
resolveOrchChildFollowUp,
|
|
34
36
|
resolveSendFollowUp,
|
|
35
37
|
startOrchestration,
|
|
36
38
|
waitForPidExit
|