@sideboard-ai/core 0.1.280 → 0.1.282
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-VCVPSLSU.js → abletime-WLDBJJUI.js} +3 -3
- package/dist/{abletime-VRATUMDO.js → abletime-X64E3GUJ.js} +3 -3
- package/dist/{abletime-oauth-4K7MICAD.js → abletime-oauth-AXR676W5.js} +2 -2
- package/dist/{abletime-oauth-4C4G2J3R.js → abletime-oauth-UWOCEICP.js} +2 -2
- package/dist/{abletime-rest-PIBCNHF3.js → abletime-rest-LPUDJUEP.js} +2 -2
- package/dist/{abletime-rest-6N63ZVQ3.js → abletime-rest-S4XWEXV2.js} +2 -2
- package/dist/{agents-DYCFLDNL.js → agents-2IE4HKHA.js} +5 -5
- package/dist/{agents-RXP5NZBC.js → agents-XA2E64AW.js} +5 -5
- package/dist/{chunk-DK4DKQ2H.js → chunk-2KNO2GJQ.js} +722 -139
- package/dist/{chunk-TZ5JHIYK.js → chunk-2SJXQ4UX.js} +68 -21
- package/dist/{chunk-K7MWJLR7.js → chunk-5MGLNVNK.js} +6 -906
- package/dist/{chunk-RK5PIWCW.js → chunk-ADXVTKLC.js} +12 -4
- package/dist/{chunk-Y7JPL4WA.js → chunk-BDAUPNHL.js} +3 -4
- package/dist/{chunk-R7HLXD4I.js → chunk-BS3QLVPL.js} +93 -57
- package/dist/{chunk-2P75E243.js → chunk-BYZ7F2BQ.js} +707 -128
- package/dist/{chunk-2J34G3WJ.js → chunk-DGPROCE5.js} +93 -57
- package/dist/{chunk-HQXBBUQX.js → chunk-DQZPF6WL.js} +12 -11
- package/dist/{chunk-YDXNCBA2.js → chunk-EIEAR62M.js} +12 -11
- package/dist/{chunk-XKZAVGTT.js → chunk-I6HSGKMZ.js} +1 -1
- package/dist/chunk-JPH7PYSK.js +1241 -0
- package/dist/{chunk-IPPY3FG5.js → chunk-K5XSTHHP.js} +6 -906
- package/dist/chunk-N5UGX3JL.js +1239 -0
- package/dist/{chunk-HNNS25SG.js → chunk-OCUIZD4Y.js} +14 -4
- package/dist/{chunk-IJDQKC5A.js → chunk-PY4UCMRI.js} +4 -4
- package/dist/{chunk-NQ7JMVSU.js → chunk-U7HAVAGJ.js} +1 -1
- package/dist/{chunk-PZURG3XN.js → chunk-VA55JG2I.js} +4 -4
- package/dist/{chunk-CPA43FBK.js → chunk-W7HIK2A6.js} +3 -4
- package/dist/{chunk-SKSOCIJT.js → chunk-WY2IAB7J.js} +68 -21
- package/dist/{chunk-QINJKNLR.js → chunk-XTPQEZZA.js} +2 -2
- package/dist/{conductor-J6WDOXWM.js → conductor-CNSCOC54.js} +5 -5
- package/dist/{conductor-OW6NAK3N.js → conductor-U776JSXQ.js} +5 -5
- package/dist/{coordinator-prompt-Z2D32HWK.js → coordinator-prompt-4DJIHSE6.js} +3 -3
- package/dist/{coordinator-prompt-ERTHREGJ.js → coordinator-prompt-SVVGU6AZ.js} +3 -3
- package/dist/{global-workspace-5PJXO45Z.js → global-workspace-UPUDWIS6.js} +3 -3
- package/dist/{global-workspace-AK5PSPQ2.js → global-workspace-XON4YZXE.js} +3 -3
- package/dist/index.cjs +3690 -2912
- package/dist/index.d.cts +101 -28
- package/dist/index.d.ts +101 -28
- package/dist/index.js +1013 -1001
- package/dist/mcp/run-stdio.cjs +1975 -1259
- package/dist/mcp/run-stdio.js +743 -785
- package/dist/{orchestrator-XEKODYDQ.js → orchestrator-FPGVKEID.js} +7 -7
- package/dist/{orchestrator-K66OMTQO.js → orchestrator-HWJHK5NL.js} +7 -7
- package/dist/{thread-store-B3ZM4QEZ.js → thread-store-7XBOUQ5K.js} +1 -1
- package/dist/{thread-store-SHCUBWB3.js → thread-store-ZQZDREVH.js} +1 -1
- package/dist/{workspaces-UP6DQ3WL.js → workspaces-7AJ5RZ3P.js} +7 -3
- package/dist/{workspaces-WU2RNLDV.js → workspaces-F76BWCSI.js} +7 -3
- package/dist/{worktree-L57T3KH7.js → worktree-NKLNVNXX.js} +20 -15
- package/dist/{worktree-Q6UJHG25.js → worktree-YTZC3UQN.js} +14 -9
- package/package.json +1 -1
- package/dist/chunk-FWNL62VA.js +0 -308
- package/dist/chunk-JANCPF7Z.js +0 -310
package/dist/index.js
CHANGED
|
@@ -22,14 +22,17 @@ import {
|
|
|
22
22
|
agentGitPrompt,
|
|
23
23
|
alignQueueAttachments,
|
|
24
24
|
appendQueuedItem,
|
|
25
|
+
applyIssueListWindow,
|
|
25
26
|
applyPromptCacheTtlEnv,
|
|
26
27
|
applyThreadIntoMain,
|
|
28
|
+
applyWorkspaceTags,
|
|
27
29
|
armSchedules,
|
|
28
30
|
assembleHomeBoard,
|
|
29
31
|
boardPinIdentity,
|
|
30
32
|
captureTurnBaseline,
|
|
31
33
|
chatBlockedSuffix,
|
|
32
34
|
claimDesktopHost,
|
|
35
|
+
clampMcpIssueLimit,
|
|
33
36
|
classifyWorktreeColumn,
|
|
34
37
|
classifyWorktreeListBadges,
|
|
35
38
|
cleanupArchivedHistory,
|
|
@@ -68,11 +71,13 @@ import {
|
|
|
68
71
|
formatAskUserNotifyMessage,
|
|
69
72
|
formatDetachedJobInvoke,
|
|
70
73
|
formatDevAccessHint,
|
|
74
|
+
formatGitHubSearchUpdatedSince,
|
|
71
75
|
formatInjectedNoticesForTurn,
|
|
72
76
|
formatIssueToolsDirective,
|
|
73
77
|
formatIssueToolsReminder,
|
|
74
78
|
formatLinearDirective,
|
|
75
79
|
formatLinearReminder,
|
|
80
|
+
formatListedIssuesForMcp,
|
|
76
81
|
formatLongRunningDirective,
|
|
77
82
|
formatLongRunningReminder,
|
|
78
83
|
formatNotifyOrchestratorPrompt,
|
|
@@ -112,6 +117,7 @@ import {
|
|
|
112
117
|
isStaleLastErrorDuringTurn,
|
|
113
118
|
isThisProcessDesktopHost,
|
|
114
119
|
isUnclaimedRunScriptRequest,
|
|
120
|
+
issueMatchesUpdatedSince,
|
|
115
121
|
issueNeedsWorkspacePick,
|
|
116
122
|
issueTicketFromThread,
|
|
117
123
|
lastAgentReply,
|
|
@@ -124,6 +130,8 @@ import {
|
|
|
124
130
|
listWorktreeFiles,
|
|
125
131
|
localhostPreviewUrl,
|
|
126
132
|
markdownPreviewSource,
|
|
133
|
+
mcpIssueUpdatedSinceSchema,
|
|
134
|
+
mcpJson,
|
|
127
135
|
mentionsPrGoal,
|
|
128
136
|
mergeSetupOutput,
|
|
129
137
|
moveQueuedItemToFront,
|
|
@@ -135,16 +143,20 @@ import {
|
|
|
135
143
|
optionalServiceSpec,
|
|
136
144
|
packagedDetachedJobPath,
|
|
137
145
|
parseDurationMs,
|
|
146
|
+
parseIssueSince,
|
|
138
147
|
pendingInjectedNotices,
|
|
139
148
|
prependQueuedItem,
|
|
149
|
+
previewIssueCommentBody,
|
|
140
150
|
previewLand,
|
|
141
151
|
readRunLog,
|
|
142
152
|
readSetupLog,
|
|
143
153
|
readSkillBody,
|
|
144
154
|
readTurnLive,
|
|
155
|
+
readTurnLiveParts,
|
|
145
156
|
readWorktreeFile,
|
|
146
157
|
readWorktreeFileForUpload,
|
|
147
158
|
recordScheduleRun,
|
|
159
|
+
registerViewerContextTools,
|
|
148
160
|
releaseDesktopHost,
|
|
149
161
|
removeQueuedItem,
|
|
150
162
|
requireAgent,
|
|
@@ -181,11 +193,12 @@ import {
|
|
|
181
193
|
verifyOptionalService,
|
|
182
194
|
visibleAgentBlock,
|
|
183
195
|
waitForPidExit,
|
|
196
|
+
workspaceTagTargetRef,
|
|
184
197
|
worktreeAgentBlock,
|
|
185
198
|
worktreeBoardStatus,
|
|
186
199
|
worktreeCleanupSettings,
|
|
187
200
|
writeWorktreeFile
|
|
188
|
-
} from "./chunk-
|
|
201
|
+
} from "./chunk-2KNO2GJQ.js";
|
|
189
202
|
import {
|
|
190
203
|
BASE_ALLOWED_TOOLS,
|
|
191
204
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
@@ -275,7 +288,7 @@ import {
|
|
|
275
288
|
whichOnPath,
|
|
276
289
|
worktreeMcpToolNames,
|
|
277
290
|
writeInjectedMcpConfig
|
|
278
|
-
} from "./chunk-
|
|
291
|
+
} from "./chunk-OCUIZD4Y.js";
|
|
279
292
|
import {
|
|
280
293
|
brightsyMcpServerName,
|
|
281
294
|
connectBrightsyTeam,
|
|
@@ -402,7 +415,7 @@ import {
|
|
|
402
415
|
workspaceAgentChatSummaries,
|
|
403
416
|
worktreeRunPortsPath,
|
|
404
417
|
wrapReviewSkillMarkdown
|
|
405
|
-
} from "./chunk-
|
|
418
|
+
} from "./chunk-DQZPF6WL.js";
|
|
406
419
|
import {
|
|
407
420
|
STDERR_EVENT_MAX_CHARS,
|
|
408
421
|
STDOUT_EVENT_MAX_CHARS,
|
|
@@ -444,7 +457,7 @@ import {
|
|
|
444
457
|
visibleToolRowDetail,
|
|
445
458
|
withEventParentId,
|
|
446
459
|
withEventsParentId
|
|
447
|
-
} from "./chunk-
|
|
460
|
+
} from "./chunk-BDAUPNHL.js";
|
|
448
461
|
import "./chunk-7H655NEW.js";
|
|
449
462
|
import {
|
|
450
463
|
ABLETIME_OAUTH_CALLBACK_PATH,
|
|
@@ -468,7 +481,7 @@ import {
|
|
|
468
481
|
parseAbleTimeOAuthCallbackUrl,
|
|
469
482
|
resolveAbleTimeOAuthHost,
|
|
470
483
|
startAbleTimeOAuth
|
|
471
|
-
} from "./chunk-
|
|
484
|
+
} from "./chunk-XTPQEZZA.js";
|
|
472
485
|
import {
|
|
473
486
|
ableTimeDependencyTarget,
|
|
474
487
|
commentAbleTimeTask,
|
|
@@ -493,8 +506,8 @@ import {
|
|
|
493
506
|
toAbleTimeIssueInfo,
|
|
494
507
|
updateAbleTimeTask,
|
|
495
508
|
verifyAbleTimeConnection
|
|
496
|
-
} from "./chunk-
|
|
497
|
-
import "./chunk-
|
|
509
|
+
} from "./chunk-DGPROCE5.js";
|
|
510
|
+
import "./chunk-U7HAVAGJ.js";
|
|
498
511
|
import {
|
|
499
512
|
ABLETIME_MCP_PATH,
|
|
500
513
|
ABLETIME_MCP_PM_PATH,
|
|
@@ -515,7 +528,7 @@ import {
|
|
|
515
528
|
normalizeAbleTimeCredential,
|
|
516
529
|
normalizeAbleTimeHost,
|
|
517
530
|
rewriteAbleTimeError
|
|
518
|
-
} from "./chunk-
|
|
531
|
+
} from "./chunk-VA55JG2I.js";
|
|
519
532
|
import {
|
|
520
533
|
LEGACY_PLAN_FILE_REL,
|
|
521
534
|
PLAN_FILE_NAME,
|
|
@@ -564,6 +577,7 @@ import {
|
|
|
564
577
|
isGlobalThread,
|
|
565
578
|
isImageFilePath,
|
|
566
579
|
isOrchestratorThread,
|
|
580
|
+
isRemovedWorkspace,
|
|
567
581
|
isSlackCoordinatorThread,
|
|
568
582
|
listGlobalThreads,
|
|
569
583
|
listWorkspaces,
|
|
@@ -574,23 +588,21 @@ import {
|
|
|
574
588
|
primaryCheckoutFromLinkedWorktree,
|
|
575
589
|
remoteCoordinatorSourceRef,
|
|
576
590
|
removeWorkspace,
|
|
591
|
+
sameWorkspacePath,
|
|
577
592
|
slackCoordinatorSourceRef,
|
|
578
593
|
stageAbsolutePathsAsAttachments,
|
|
579
594
|
stageBuffersAsAttachments,
|
|
580
595
|
syncWorkspacesFromThreads,
|
|
581
596
|
takenTeamSlugsForOrchestration
|
|
582
|
-
} from "./chunk-
|
|
597
|
+
} from "./chunk-WY2IAB7J.js";
|
|
583
598
|
import {
|
|
584
|
-
FAMOUS_SOCCER_TEAMS,
|
|
585
599
|
GITHUB_PR_BODY_MAX_CHARS,
|
|
586
600
|
GITHUB_PR_BODY_SAFE_CHARS,
|
|
587
601
|
PR_DETAILS_JSON_FIELDS,
|
|
588
602
|
addPrStackLayer,
|
|
589
|
-
allocateTeamName,
|
|
590
603
|
allocateTeamSlug,
|
|
591
604
|
appendIndexedGitConfig,
|
|
592
605
|
applyGithubGitAuthEnv,
|
|
593
|
-
branchDisplayLabel,
|
|
594
606
|
canonicalizeRepoPath,
|
|
595
607
|
checkoutPrStackLayer,
|
|
596
608
|
clampGithubPrBody,
|
|
@@ -633,19 +645,16 @@ import {
|
|
|
633
645
|
isGhPrBodyTooLongError,
|
|
634
646
|
isGhRateLimitError,
|
|
635
647
|
isInPrStack,
|
|
636
|
-
isPlaceholderBranch,
|
|
637
648
|
isPrNotMergeableError,
|
|
638
649
|
isSideboardScratchPath,
|
|
639
650
|
listBranches,
|
|
640
651
|
listPrs,
|
|
641
652
|
listWorktrees,
|
|
642
|
-
lookupSoccerTeam,
|
|
643
653
|
markPrReady,
|
|
644
654
|
mergeAgentGitAuthEnv,
|
|
645
655
|
mergePr,
|
|
646
656
|
mergePrStack,
|
|
647
657
|
nonInteractiveGitProcessEnv,
|
|
648
|
-
normalizeWorktreePath,
|
|
649
658
|
originFetchBranch,
|
|
650
659
|
originGhRepoEnv,
|
|
651
660
|
parseGhStackViewJson,
|
|
@@ -672,32 +681,40 @@ import {
|
|
|
672
681
|
submitPrStack,
|
|
673
682
|
suggestSlug,
|
|
674
683
|
switchWorktreeBranch,
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
warmGithubAgentAuth,
|
|
678
|
-
worktreeDisplayLabel,
|
|
679
|
-
worktreeDisplayLabelForGroup,
|
|
680
|
-
worktreeNameFromPath,
|
|
681
|
-
worktreeSlugForTicket
|
|
682
|
-
} from "./chunk-IPPY3FG5.js";
|
|
684
|
+
warmGithubAgentAuth
|
|
685
|
+
} from "./chunk-K5XSTHHP.js";
|
|
683
686
|
import {
|
|
687
|
+
FAMOUS_SOCCER_TEAMS,
|
|
688
|
+
allocateTeamName,
|
|
684
689
|
appendMessage,
|
|
690
|
+
branchDisplayLabel,
|
|
685
691
|
createEmptyThread,
|
|
686
692
|
deleteThreadRecord,
|
|
687
693
|
findThreadByRef,
|
|
688
694
|
invalidateThreadListCache,
|
|
689
695
|
invalidateThreadRecord,
|
|
696
|
+
isPlaceholderBranch,
|
|
690
697
|
isSelfWrittenRecord,
|
|
691
698
|
isThreadRecordFile,
|
|
692
699
|
listThreads,
|
|
700
|
+
lookupSoccerTeam,
|
|
693
701
|
normalizeThread,
|
|
702
|
+
normalizeWorkspaceTags,
|
|
703
|
+
normalizeWorktreePath,
|
|
694
704
|
readThread,
|
|
695
705
|
resolveThreadEffort,
|
|
696
706
|
setStatus,
|
|
707
|
+
threadDisplayLabel,
|
|
708
|
+
ticketSlugForBranch,
|
|
697
709
|
updateThread,
|
|
698
710
|
withThreadLock,
|
|
711
|
+
workspaceTagsFromGroup,
|
|
712
|
+
worktreeDisplayLabel,
|
|
713
|
+
worktreeDisplayLabelForGroup,
|
|
714
|
+
worktreeNameFromPath,
|
|
715
|
+
worktreeSlugForTicket,
|
|
699
716
|
writeThread
|
|
700
|
-
} from "./chunk-
|
|
717
|
+
} from "./chunk-N5UGX3JL.js";
|
|
701
718
|
import "./chunk-2P4XKWV2.js";
|
|
702
719
|
import {
|
|
703
720
|
BRIGHTSY_OAUTH_CLIENT_ID,
|
|
@@ -1189,18 +1206,18 @@ function lastVisibleMessage(messages) {
|
|
|
1189
1206
|
const m = messages[i];
|
|
1190
1207
|
if (!m) continue;
|
|
1191
1208
|
if (m.role === "agent") {
|
|
1192
|
-
const
|
|
1193
|
-
if (
|
|
1209
|
+
const text5 = (lastAssistantMessageText(m.parts) || m.text || "").trim();
|
|
1210
|
+
if (text5 && !isInternalAgentStatusText(text5)) return m;
|
|
1194
1211
|
continue;
|
|
1195
1212
|
}
|
|
1196
1213
|
if (m.role === "user" && m.text?.trim()) return m;
|
|
1197
1214
|
}
|
|
1198
1215
|
return void 0;
|
|
1199
1216
|
}
|
|
1200
|
-
function agentStub(source,
|
|
1217
|
+
function agentStub(source, text5, usage) {
|
|
1201
1218
|
return {
|
|
1202
1219
|
role: "agent",
|
|
1203
|
-
text:
|
|
1220
|
+
text: text5,
|
|
1204
1221
|
ts: source.ts,
|
|
1205
1222
|
...usage ? { usage } : {}
|
|
1206
1223
|
};
|
|
@@ -1231,9 +1248,9 @@ async function getGitHubStatus() {
|
|
|
1231
1248
|
};
|
|
1232
1249
|
}
|
|
1233
1250
|
const status = await run("gh", ["auth", "status"], { reject: false });
|
|
1234
|
-
const
|
|
1251
|
+
const text5 = `${status.stdout}
|
|
1235
1252
|
${status.stderr}`;
|
|
1236
|
-
const loginMatch =
|
|
1253
|
+
const loginMatch = text5.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text5.match(/Logged in to ([^\s]+) as (\S+)/i);
|
|
1237
1254
|
if (loginMatch) {
|
|
1238
1255
|
return {
|
|
1239
1256
|
connected: true,
|
|
@@ -1253,76 +1270,6 @@ async function refreshGitHubAuth() {
|
|
|
1253
1270
|
return getGitHubStatus();
|
|
1254
1271
|
}
|
|
1255
1272
|
|
|
1256
|
-
// src/integrations/issue-since.ts
|
|
1257
|
-
var ISSUE_SINCE_ERROR = "updatedSince must be an ISO datetime, YYYY-MM-DD, or relative time (yesterday, 2d, 3 hours ago)";
|
|
1258
|
-
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
1259
|
-
var DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
1260
|
-
var RELATIVE_RE = /^(\d+)\s*(hours?|hrs?|h|days?|d|weeks?|wks?|w)(?:\s+ago)?$/i;
|
|
1261
|
-
function startOfLocalDay(date) {
|
|
1262
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
1263
|
-
}
|
|
1264
|
-
function addLocalDays(date, days) {
|
|
1265
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
|
|
1266
|
-
}
|
|
1267
|
-
function parseIssueSince(input, now = /* @__PURE__ */ new Date()) {
|
|
1268
|
-
const raw = input.trim();
|
|
1269
|
-
if (!raw) throw new Error(ISSUE_SINCE_ERROR);
|
|
1270
|
-
const dateOnly = raw.match(DATE_ONLY_RE);
|
|
1271
|
-
if (dateOnly) {
|
|
1272
|
-
const year = Number(dateOnly[1]);
|
|
1273
|
-
const month = Number(dateOnly[2]);
|
|
1274
|
-
const day = Number(dateOnly[3]);
|
|
1275
|
-
const local = new Date(year, month - 1, day);
|
|
1276
|
-
if (local.getFullYear() !== year || local.getMonth() !== month - 1 || local.getDate() !== day) {
|
|
1277
|
-
throw new Error(ISSUE_SINCE_ERROR);
|
|
1278
|
-
}
|
|
1279
|
-
return startOfLocalDay(local).toISOString();
|
|
1280
|
-
}
|
|
1281
|
-
if (DATE_TIME_RE.test(raw)) {
|
|
1282
|
-
const ms = Date.parse(raw);
|
|
1283
|
-
if (!Number.isFinite(ms)) throw new Error(ISSUE_SINCE_ERROR);
|
|
1284
|
-
return new Date(ms).toISOString();
|
|
1285
|
-
}
|
|
1286
|
-
const lower = raw.toLowerCase();
|
|
1287
|
-
if (lower === "today") return startOfLocalDay(now).toISOString();
|
|
1288
|
-
if (lower === "yesterday") return addLocalDays(startOfLocalDay(now), -1).toISOString();
|
|
1289
|
-
if (lower === "last week" || lower === "lastweek") {
|
|
1290
|
-
return addLocalDays(startOfLocalDay(now), -7).toISOString();
|
|
1291
|
-
}
|
|
1292
|
-
const relative = lower.match(RELATIVE_RE);
|
|
1293
|
-
if (relative) {
|
|
1294
|
-
const n = Number(relative[1]);
|
|
1295
|
-
if (!Number.isInteger(n) || n < 1) throw new Error(ISSUE_SINCE_ERROR);
|
|
1296
|
-
const unit = relative[2];
|
|
1297
|
-
if (unit.startsWith("h")) return new Date(now.getTime() - n * 36e5).toISOString();
|
|
1298
|
-
if (unit.startsWith("w")) return addLocalDays(startOfLocalDay(now), -7 * n).toISOString();
|
|
1299
|
-
return addLocalDays(startOfLocalDay(now), -n).toISOString();
|
|
1300
|
-
}
|
|
1301
|
-
throw new Error(ISSUE_SINCE_ERROR);
|
|
1302
|
-
}
|
|
1303
|
-
function formatGitHubSearchUpdatedSince(iso) {
|
|
1304
|
-
return iso.replace(/\.\d{3}Z$/, "Z");
|
|
1305
|
-
}
|
|
1306
|
-
function issueMatchesUpdatedSince(issue, sinceIso) {
|
|
1307
|
-
const ts = issue.updatedAt || issue.createdAt;
|
|
1308
|
-
if (!ts) return false;
|
|
1309
|
-
const value = Date.parse(ts);
|
|
1310
|
-
const since = Date.parse(sinceIso);
|
|
1311
|
-
return Number.isFinite(value) && Number.isFinite(since) && value >= since;
|
|
1312
|
-
}
|
|
1313
|
-
var ISSUE_COMMENT_PREVIEW_CHARS = 240;
|
|
1314
|
-
function previewIssueCommentBody(body) {
|
|
1315
|
-
const flat = body.replace(/\s+/g, " ").trim();
|
|
1316
|
-
if (flat.length <= ISSUE_COMMENT_PREVIEW_CHARS) return flat;
|
|
1317
|
-
return `${flat.slice(0, ISSUE_COMMENT_PREVIEW_CHARS - 1).trimEnd()}\u2026`;
|
|
1318
|
-
}
|
|
1319
|
-
function issueActivityKind(issue, sinceIso) {
|
|
1320
|
-
const created = issue.createdAt ? Date.parse(issue.createdAt) : Number.NaN;
|
|
1321
|
-
const since = Date.parse(sinceIso);
|
|
1322
|
-
if (Number.isFinite(created) && Number.isFinite(since) && created >= since) return "created";
|
|
1323
|
-
return "updated";
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
1273
|
// src/integrations/linear-oauth.ts
|
|
1327
1274
|
import { createHash, randomBytes } from "crypto";
|
|
1328
1275
|
import { createServer } from "http";
|
|
@@ -1737,6 +1684,7 @@ var LIST_ISSUE_FIELDS = `
|
|
|
1737
1684
|
team { id key }
|
|
1738
1685
|
labels(first: 10) { nodes { name } }
|
|
1739
1686
|
cycle { id name number startsAt endsAt completedAt }
|
|
1687
|
+
project { id name }
|
|
1740
1688
|
`;
|
|
1741
1689
|
var ISSUE_REF_FIELDS = `
|
|
1742
1690
|
id
|
|
@@ -1913,9 +1861,10 @@ query SideboardTeamLabels($id: String!, $first: Int!, $after: String) {
|
|
|
1913
1861
|
}
|
|
1914
1862
|
`;
|
|
1915
1863
|
var PROJECTS_QUERY = `
|
|
1916
|
-
query SideboardProjects($first: Int!, $filter: ProjectFilter) {
|
|
1917
|
-
projects(first: $first, filter: $filter) {
|
|
1864
|
+
query SideboardProjects($first: Int!, $filter: ProjectFilter, $after: String) {
|
|
1865
|
+
projects(first: $first, filter: $filter, after: $after) {
|
|
1918
1866
|
nodes { id name slugId }
|
|
1867
|
+
pageInfo { hasNextPage endCursor }
|
|
1919
1868
|
}
|
|
1920
1869
|
}
|
|
1921
1870
|
`;
|
|
@@ -2137,6 +2086,7 @@ function toIssueInfo(issue) {
|
|
|
2137
2086
|
assignees: issue.assignee?.name ? [issue.assignee.name] : void 0,
|
|
2138
2087
|
cycle: issue.cycle ?? null,
|
|
2139
2088
|
teamKey: issue.team?.key || void 0,
|
|
2089
|
+
...issue.project ? { projects: [issue.project] } : {},
|
|
2140
2090
|
...issue.createdAt ? { createdAt: issue.createdAt } : {},
|
|
2141
2091
|
...issue.updatedAt ? { updatedAt: issue.updatedAt } : {}
|
|
2142
2092
|
};
|
|
@@ -2302,30 +2252,35 @@ async function listLinearLabels(opts) {
|
|
|
2302
2252
|
}
|
|
2303
2253
|
return listLinearWorkspaceLabels(opts);
|
|
2304
2254
|
}
|
|
2305
|
-
async function
|
|
2306
|
-
const
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2255
|
+
async function queryLinearProjects(filter, opts) {
|
|
2256
|
+
const out = [];
|
|
2257
|
+
let after;
|
|
2258
|
+
for (let page = 0; page < 4; page++) {
|
|
2259
|
+
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {}, ...after ? { after } : {} }, opts);
|
|
2260
|
+
for (const node of json.projects?.nodes ?? []) {
|
|
2261
|
+
const id = String(node.id ?? "").trim();
|
|
2262
|
+
if (!id) continue;
|
|
2263
|
+
out.push({ id, name: String(node.name ?? ""), ...node.slugId ? { slugId: String(node.slugId) } : {} });
|
|
2264
|
+
}
|
|
2265
|
+
const pageInfo = json.projects?.pageInfo;
|
|
2266
|
+
if (!pageInfo?.hasNextPage || !pageInfo.endCursor) break;
|
|
2267
|
+
after = pageInfo.endCursor;
|
|
2268
|
+
}
|
|
2269
|
+
return out;
|
|
2270
|
+
}
|
|
2271
|
+
function listLinearProjects(opts) {
|
|
2272
|
+
return queryLinearProjects(void 0, opts);
|
|
2318
2273
|
}
|
|
2319
2274
|
async function resolveLinearProjectId(project, opts) {
|
|
2320
2275
|
if (isLinearNoneToken(project)) return null;
|
|
2321
2276
|
const s = (project ?? "").trim();
|
|
2322
2277
|
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
2323
|
-
const matched = await
|
|
2278
|
+
const matched = await queryLinearProjects(filter, opts);
|
|
2324
2279
|
try {
|
|
2325
2280
|
return resolveLinearProject(matched, s);
|
|
2326
2281
|
} catch {
|
|
2327
2282
|
if (UUID_RE.test(s)) return s;
|
|
2328
|
-
const listed = await
|
|
2283
|
+
const listed = await queryLinearProjects(void 0, opts);
|
|
2329
2284
|
return resolveLinearProject(listed, s);
|
|
2330
2285
|
}
|
|
2331
2286
|
}
|
|
@@ -2718,7 +2673,7 @@ function extractGitHubIssueAttachments(issue) {
|
|
|
2718
2673
|
const texts = [issue.body ?? "", ...issue.comments.map((comment) => comment.body)];
|
|
2719
2674
|
return mergeIssueAttachments(
|
|
2720
2675
|
texts.flatMap(
|
|
2721
|
-
(
|
|
2676
|
+
(text5) => extractHttpUrls(text5).filter(isGitHubIssueAttachmentUrl).map((url) => ({
|
|
2722
2677
|
id: url,
|
|
2723
2678
|
name: url.split("/").pop() || "attachment",
|
|
2724
2679
|
url
|
|
@@ -2747,6 +2702,129 @@ async function downloadGitHubIssueAttachment(input, opts) {
|
|
|
2747
2702
|
});
|
|
2748
2703
|
}
|
|
2749
2704
|
|
|
2705
|
+
// src/integrations/github-projects.ts
|
|
2706
|
+
function rewriteGitHubProjectReadError(detail) {
|
|
2707
|
+
const text5 = detail.trim() || "gh could not read GitHub Projects";
|
|
2708
|
+
if (/read:project|project scope|missing required scopes/i.test(text5)) {
|
|
2709
|
+
return `${text5} \u2014 run \`gh auth refresh -s read:project\` so Sideboard can read GitHub Projects.`;
|
|
2710
|
+
}
|
|
2711
|
+
return text5;
|
|
2712
|
+
}
|
|
2713
|
+
function githubProjectsUnreadable(result) {
|
|
2714
|
+
return /project|scope|unknown json field/i.test(`${result.stderr}
|
|
2715
|
+
${result.stdout}`);
|
|
2716
|
+
}
|
|
2717
|
+
function githubProjectsOnIssue(raw) {
|
|
2718
|
+
const out = [];
|
|
2719
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2720
|
+
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
2721
|
+
if (!Array.isArray(fromItems)) return out;
|
|
2722
|
+
for (const item of fromItems) {
|
|
2723
|
+
if (typeof item === "string") {
|
|
2724
|
+
const name2 = item.trim();
|
|
2725
|
+
const key2 = name2.toLowerCase();
|
|
2726
|
+
if (!name2 || seen.has(key2)) continue;
|
|
2727
|
+
seen.add(key2);
|
|
2728
|
+
out.push({ name: name2 });
|
|
2729
|
+
continue;
|
|
2730
|
+
}
|
|
2731
|
+
if (!item || typeof item !== "object") continue;
|
|
2732
|
+
const rec = item;
|
|
2733
|
+
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
2734
|
+
const id = String(nested.id ?? rec.id ?? "").trim();
|
|
2735
|
+
const name = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
2736
|
+
const label = name || id;
|
|
2737
|
+
const key = label.toLowerCase();
|
|
2738
|
+
if (!label || seen.has(key)) continue;
|
|
2739
|
+
seen.add(key);
|
|
2740
|
+
out.push({ ...id ? { id } : {}, name: label });
|
|
2741
|
+
}
|
|
2742
|
+
return out;
|
|
2743
|
+
}
|
|
2744
|
+
async function readGitHubIssueProjects(number, cwd, repoArgs) {
|
|
2745
|
+
const result = await gh(
|
|
2746
|
+
["issue", "view", String(number), ...repoArgs, "--json", "projectItems"],
|
|
2747
|
+
cwd,
|
|
2748
|
+
{ reject: false }
|
|
2749
|
+
);
|
|
2750
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
2751
|
+
const detail = (result.stderr || result.stdout).trim();
|
|
2752
|
+
if (detail && githubProjectsUnreadable(result)) {
|
|
2753
|
+
return { projects: [], error: rewriteGitHubProjectReadError(detail) };
|
|
2754
|
+
}
|
|
2755
|
+
return { projects: [] };
|
|
2756
|
+
}
|
|
2757
|
+
try {
|
|
2758
|
+
const parsed = JSON.parse(result.stdout);
|
|
2759
|
+
return { projects: githubProjectsOnIssue(parsed) };
|
|
2760
|
+
} catch {
|
|
2761
|
+
return { projects: [] };
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
async function listGitHubIssueProjectNames(number, cwd, repoArgs) {
|
|
2765
|
+
const read = await readGitHubIssueProjects(number, cwd, repoArgs);
|
|
2766
|
+
return read.projects.map((project) => project.name).filter(Boolean);
|
|
2767
|
+
}
|
|
2768
|
+
function parseGitHubProjectList(stdout) {
|
|
2769
|
+
let parsed;
|
|
2770
|
+
try {
|
|
2771
|
+
parsed = JSON.parse(stdout);
|
|
2772
|
+
} catch {
|
|
2773
|
+
return [];
|
|
2774
|
+
}
|
|
2775
|
+
const rows = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.projects) ? parsed.projects ?? [] : [];
|
|
2776
|
+
const out = [];
|
|
2777
|
+
for (const row2 of rows) {
|
|
2778
|
+
if (!row2 || typeof row2 !== "object") continue;
|
|
2779
|
+
const rec = row2;
|
|
2780
|
+
const title = String(rec.title ?? rec.name ?? "").trim();
|
|
2781
|
+
if (!title) continue;
|
|
2782
|
+
const ownerRec = rec.owner && typeof rec.owner === "object" ? rec.owner : null;
|
|
2783
|
+
const owner = ownerRec?.login?.trim() || (typeof rec.owner === "string" ? rec.owner.trim() : "");
|
|
2784
|
+
const number = Number(rec.number);
|
|
2785
|
+
out.push({
|
|
2786
|
+
...typeof rec.id === "string" && rec.id.trim() ? { id: rec.id.trim() } : {},
|
|
2787
|
+
...Number.isFinite(number) ? { number } : {},
|
|
2788
|
+
title,
|
|
2789
|
+
...typeof rec.url === "string" && rec.url.trim() ? { url: rec.url.trim() } : {},
|
|
2790
|
+
...typeof rec.closed === "boolean" ? { closed: rec.closed } : {},
|
|
2791
|
+
...owner ? { owner } : {}
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
return out;
|
|
2795
|
+
}
|
|
2796
|
+
async function listGitHubProjects(opts) {
|
|
2797
|
+
const cwd = await resolveRepoRoot((opts?.repoPath ?? "").trim() || process.cwd());
|
|
2798
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
2799
|
+
const owners = /* @__PURE__ */ new Set([""]);
|
|
2800
|
+
const repoOwner = slug?.split("/")[0]?.trim();
|
|
2801
|
+
if (repoOwner) owners.add(repoOwner);
|
|
2802
|
+
const out = [];
|
|
2803
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2804
|
+
let lastError = "";
|
|
2805
|
+
let anyOk = false;
|
|
2806
|
+
for (const owner of owners) {
|
|
2807
|
+
const args = ["project", "list", "--limit", "100", "--format", "json"];
|
|
2808
|
+
if (owner) args.push("--owner", owner);
|
|
2809
|
+
const result = await gh(args, cwd, { reject: false });
|
|
2810
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
2811
|
+
lastError = (result.stderr || result.stdout).trim() || lastError;
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
anyOk = true;
|
|
2815
|
+
for (const project of parseGitHubProjectList(result.stdout)) {
|
|
2816
|
+
const key = (project.id || `${project.owner ?? ""}:${project.number ?? ""}:${project.title}`).toLowerCase();
|
|
2817
|
+
if (seen.has(key)) continue;
|
|
2818
|
+
seen.add(key);
|
|
2819
|
+
out.push(project);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
if (!anyOk) {
|
|
2823
|
+
throw new Error(rewriteGitHubProjectReadError(lastError || "gh project list failed"));
|
|
2824
|
+
}
|
|
2825
|
+
return out;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2750
2828
|
// src/integrations/github-issues.ts
|
|
2751
2829
|
function requireGhOk(result, label) {
|
|
2752
2830
|
if (result.exitCode !== 0) {
|
|
@@ -2830,7 +2908,8 @@ function toGitHubIssueInfo(issue) {
|
|
|
2830
2908
|
labels: issue.labels,
|
|
2831
2909
|
provider: "github",
|
|
2832
2910
|
assignee: issue.assignees[0],
|
|
2833
|
-
assignees: issue.assignees.length ? issue.assignees : void 0
|
|
2911
|
+
assignees: issue.assignees.length ? issue.assignees : void 0,
|
|
2912
|
+
...issue.projects?.length ? { projects: issue.projects } : {}
|
|
2834
2913
|
};
|
|
2835
2914
|
}
|
|
2836
2915
|
function issueNumberFromApiUrl(url) {
|
|
@@ -2925,7 +3004,11 @@ async function getGitHubIssue(id, opts) {
|
|
|
2925
3004
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2926
3005
|
throw new Error(`GitHub issue not found: #${number}`);
|
|
2927
3006
|
}
|
|
2928
|
-
|
|
3007
|
+
const issue = toGitHubIssue(parsed);
|
|
3008
|
+
const projects = await readGitHubIssueProjects(number, cwd, repoArgs);
|
|
3009
|
+
issue.projects = projects.projects;
|
|
3010
|
+
if (projects.error) issue.projectsError = projects.error;
|
|
3011
|
+
return issue;
|
|
2929
3012
|
}
|
|
2930
3013
|
async function commentGitHubIssue(input, opts) {
|
|
2931
3014
|
const number = parseGitHubIssueNumber(input.id);
|
|
@@ -2963,46 +3046,6 @@ function githubRelationEditFlag(type, remove = false) {
|
|
|
2963
3046
|
function labelsEqual(a, b) {
|
|
2964
3047
|
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
2965
3048
|
}
|
|
2966
|
-
function parseProjectTitles(raw) {
|
|
2967
|
-
const out = [];
|
|
2968
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2969
|
-
const push = (name) => {
|
|
2970
|
-
const title = name.trim();
|
|
2971
|
-
const key = title.toLowerCase();
|
|
2972
|
-
if (!title || seen.has(key)) return;
|
|
2973
|
-
seen.add(key);
|
|
2974
|
-
out.push(title);
|
|
2975
|
-
};
|
|
2976
|
-
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
2977
|
-
if (Array.isArray(fromItems)) {
|
|
2978
|
-
for (const item of fromItems) {
|
|
2979
|
-
if (typeof item === "string") {
|
|
2980
|
-
push(item);
|
|
2981
|
-
continue;
|
|
2982
|
-
}
|
|
2983
|
-
if (!item || typeof item !== "object") continue;
|
|
2984
|
-
const rec = item;
|
|
2985
|
-
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
2986
|
-
const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
2987
|
-
if (title) push(title);
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
return out;
|
|
2991
|
-
}
|
|
2992
|
-
async function listGitHubIssueProjects(number, cwd, repoArgs) {
|
|
2993
|
-
const result = await gh(
|
|
2994
|
-
["issue", "view", String(number), ...repoArgs, "--json", "projectItems"],
|
|
2995
|
-
cwd,
|
|
2996
|
-
{ reject: false }
|
|
2997
|
-
);
|
|
2998
|
-
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
2999
|
-
try {
|
|
3000
|
-
const parsed = JSON.parse(result.stdout);
|
|
3001
|
-
return parseProjectTitles(parsed);
|
|
3002
|
-
} catch {
|
|
3003
|
-
return [];
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
3049
|
async function updateGitHubIssue(input, opts) {
|
|
3007
3050
|
const number = parseGitHubIssueNumber(input.id);
|
|
3008
3051
|
const title = input.title?.trim();
|
|
@@ -3042,7 +3085,7 @@ async function updateGitHubIssue(input, opts) {
|
|
|
3042
3085
|
if (remove.length) args.push("--remove-label", remove.join(","));
|
|
3043
3086
|
}
|
|
3044
3087
|
if (input.project !== void 0) {
|
|
3045
|
-
const current = await
|
|
3088
|
+
const current = await listGitHubIssueProjectNames(number, cwd, repoArgs);
|
|
3046
3089
|
if (isGitHubNoneToken(input.project)) {
|
|
3047
3090
|
for (const name of current) args.push("--remove-project", name);
|
|
3048
3091
|
} else {
|
|
@@ -3126,32 +3169,43 @@ function issueMatchesAssignee(issue, assignee, viewerName = "") {
|
|
|
3126
3169
|
async function listGitHubIssues(repoPath, opts) {
|
|
3127
3170
|
const limit = Math.max(1, Math.min(1e3, opts?.limit ?? 200));
|
|
3128
3171
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
3129
|
-
const
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
searchParts.push(
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3172
|
+
const plainFields = "number,title,url,labels,assignees,createdAt,updatedAt";
|
|
3173
|
+
const buildArgs = (jsonFields) => {
|
|
3174
|
+
const args = [
|
|
3175
|
+
"issue",
|
|
3176
|
+
"list",
|
|
3177
|
+
"--json",
|
|
3178
|
+
jsonFields,
|
|
3179
|
+
"--limit",
|
|
3180
|
+
String(limit),
|
|
3181
|
+
"--state",
|
|
3182
|
+
"open"
|
|
3183
|
+
];
|
|
3184
|
+
const query = opts?.query?.trim() ?? "";
|
|
3185
|
+
const assignee = opts?.assignee?.trim() ?? "";
|
|
3186
|
+
const key = assignee.toLowerCase();
|
|
3187
|
+
const searchParts = [];
|
|
3188
|
+
if (query) searchParts.push(query);
|
|
3189
|
+
if (opts?.updatedSince) {
|
|
3190
|
+
searchParts.push(`updated:>=${formatGitHubSearchUpdatedSince(opts.updatedSince)}`);
|
|
3191
|
+
}
|
|
3192
|
+
if (key === "unassigned" || key === "none" || key === "null") {
|
|
3193
|
+
searchParts.push("no:assignee");
|
|
3194
|
+
} else if (key && key !== "all" && key !== "*") {
|
|
3195
|
+
args.push("--assignee", key === "me" || key === "@me" ? "@me" : assignee);
|
|
3196
|
+
}
|
|
3197
|
+
if (searchParts.length) args.push("--search", searchParts.join(" "));
|
|
3198
|
+
if (slug) args.push("--repo", slug);
|
|
3199
|
+
return args;
|
|
3200
|
+
};
|
|
3201
|
+
let { stdout, exitCode } = await gh(
|
|
3202
|
+
buildArgs(`${plainFields},projectItems`),
|
|
3203
|
+
repoPath,
|
|
3204
|
+
{ reject: false }
|
|
3205
|
+
);
|
|
3206
|
+
if (exitCode !== 0) {
|
|
3207
|
+
({ stdout, exitCode } = await gh(buildArgs(plainFields), repoPath, { reject: false }));
|
|
3151
3208
|
}
|
|
3152
|
-
if (searchParts.length) args.push("--search", searchParts.join(" "));
|
|
3153
|
-
if (slug) args.push("--repo", slug);
|
|
3154
|
-
const { stdout, exitCode } = await gh(args, repoPath, { reject: false });
|
|
3155
3209
|
if (exitCode !== 0 || !stdout.trim()) return [];
|
|
3156
3210
|
let parsed;
|
|
3157
3211
|
try {
|
|
@@ -3170,6 +3224,9 @@ async function listGitHubIssues(repoPath, opts) {
|
|
|
3170
3224
|
const assignees = (item.assignees ?? []).map((a) => typeof a === "string" ? a : String(a?.login ?? "")).map((login) => login.trim()).filter(Boolean);
|
|
3171
3225
|
const createdAt = item.createdAt?.trim() || void 0;
|
|
3172
3226
|
const updatedAt = item.updatedAt?.trim() || void 0;
|
|
3227
|
+
const record = raw;
|
|
3228
|
+
const projects = githubProjectsOnIssue(record);
|
|
3229
|
+
const readProjects = "projectItems" in record || "projects" in record || "projectItemsV2" in record;
|
|
3173
3230
|
return {
|
|
3174
3231
|
id: Number.isFinite(number) ? `gh-${number}` : identifier,
|
|
3175
3232
|
identifier,
|
|
@@ -3179,6 +3236,7 @@ async function listGitHubIssues(repoPath, opts) {
|
|
|
3179
3236
|
provider: "github",
|
|
3180
3237
|
assignee: assignees[0],
|
|
3181
3238
|
assignees,
|
|
3239
|
+
...readProjects ? { projects } : {},
|
|
3182
3240
|
...createdAt ? { createdAt } : {},
|
|
3183
3241
|
...updatedAt ? { updatedAt } : {}
|
|
3184
3242
|
};
|
|
@@ -3411,11 +3469,11 @@ function fenceLanguage(path2, language) {
|
|
|
3411
3469
|
}
|
|
3412
3470
|
function buildCodeRefAttachment(input) {
|
|
3413
3471
|
const path2 = input.path.trim();
|
|
3414
|
-
const
|
|
3472
|
+
const text5 = input.text.replace(/\n$/, "");
|
|
3415
3473
|
if (!path2) {
|
|
3416
3474
|
throw new Error("code reference requires a file path");
|
|
3417
3475
|
}
|
|
3418
|
-
if (!
|
|
3476
|
+
if (!text5.trim()) {
|
|
3419
3477
|
throw new Error("code reference requires selected text");
|
|
3420
3478
|
}
|
|
3421
3479
|
if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
|
|
@@ -3428,7 +3486,7 @@ function buildCodeRefAttachment(input) {
|
|
|
3428
3486
|
`Referenced code from \`${path2}\` (${range}).`,
|
|
3429
3487
|
"",
|
|
3430
3488
|
fence,
|
|
3431
|
-
|
|
3489
|
+
text5,
|
|
3432
3490
|
"```"
|
|
3433
3491
|
].join("\n");
|
|
3434
3492
|
return {
|
|
@@ -3482,16 +3540,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
|
3482
3540
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
3483
3541
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
3484
3542
|
var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
|
|
3485
|
-
function pastedTextStats(
|
|
3486
|
-
const chars =
|
|
3543
|
+
function pastedTextStats(text5) {
|
|
3544
|
+
const chars = text5.length;
|
|
3487
3545
|
if (chars === 0) return { chars: 0, lines: 0 };
|
|
3488
|
-
const lines =
|
|
3546
|
+
const lines = text5.split(/\r\n|\r|\n/).length;
|
|
3489
3547
|
return { chars, lines };
|
|
3490
3548
|
}
|
|
3491
|
-
function shouldAttachPastedText(
|
|
3492
|
-
const trimmed =
|
|
3549
|
+
function shouldAttachPastedText(text5) {
|
|
3550
|
+
const trimmed = text5.trim();
|
|
3493
3551
|
if (!trimmed) return false;
|
|
3494
|
-
const { chars, lines } = pastedTextStats(
|
|
3552
|
+
const { chars, lines } = pastedTextStats(text5);
|
|
3495
3553
|
return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
|
|
3496
3554
|
}
|
|
3497
3555
|
function nextPastedTextName(existing) {
|
|
@@ -3502,13 +3560,13 @@ function nextPastedTextName(existing) {
|
|
|
3502
3560
|
}
|
|
3503
3561
|
return `Pasted text #${max + 1}.txt`;
|
|
3504
3562
|
}
|
|
3505
|
-
function buildPastedTextAttachment(
|
|
3563
|
+
function buildPastedTextAttachment(text5, opts) {
|
|
3506
3564
|
return {
|
|
3507
3565
|
id: opts?.id ?? randomUUID2(),
|
|
3508
3566
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
3509
3567
|
kind: "file",
|
|
3510
3568
|
path: opts?.path,
|
|
3511
|
-
content:
|
|
3569
|
+
content: text5
|
|
3512
3570
|
};
|
|
3513
3571
|
}
|
|
3514
3572
|
|
|
@@ -3528,8 +3586,8 @@ function parseChatLink(href) {
|
|
|
3528
3586
|
const match = CHAT_LINK_RE.exec(href.trim());
|
|
3529
3587
|
return match?.[1] ?? null;
|
|
3530
3588
|
}
|
|
3531
|
-
function linkifyChatUrls(
|
|
3532
|
-
return
|
|
3589
|
+
function linkifyChatUrls(text5) {
|
|
3590
|
+
return text5.replace(BARE_CHAT_LINK_RE, (url) => {
|
|
3533
3591
|
const id = parseChatLink(url);
|
|
3534
3592
|
if (!id) return url;
|
|
3535
3593
|
const label = id.length > 8 ? id.slice(0, 8) : id;
|
|
@@ -3552,8 +3610,8 @@ function outboundReplyFromTurn(result, copy) {
|
|
|
3552
3610
|
case "working":
|
|
3553
3611
|
return copy.failed(`turn still ${result.taskState}`);
|
|
3554
3612
|
default: {
|
|
3555
|
-
const
|
|
3556
|
-
return
|
|
3613
|
+
const text5 = result.text.trim();
|
|
3614
|
+
return text5 || copy.completedEmpty(result.status);
|
|
3557
3615
|
}
|
|
3558
3616
|
}
|
|
3559
3617
|
}
|
|
@@ -3596,7 +3654,7 @@ function createClippedMcpServer() {
|
|
|
3596
3654
|
}
|
|
3597
3655
|
|
|
3598
3656
|
// src/mcp/server.ts
|
|
3599
|
-
import { z as
|
|
3657
|
+
import { z as z6 } from "zod";
|
|
3600
3658
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3601
3659
|
import { basename as basename2 } from "path";
|
|
3602
3660
|
|
|
@@ -3622,13 +3680,164 @@ function resolveRunScriptThreadRef(ref) {
|
|
|
3622
3680
|
return thread.id;
|
|
3623
3681
|
}
|
|
3624
3682
|
|
|
3683
|
+
// src/mcp/create-thread-agent.ts
|
|
3684
|
+
function resolveOrchCreateThreadOptions(input) {
|
|
3685
|
+
const { resolveNewThreadOptions: resolveNewThreadOptions2 } = input;
|
|
3686
|
+
const account = resolveNewThreadOptions2({});
|
|
3687
|
+
const requested = input.requestedAgent;
|
|
3688
|
+
const parent = input.parentAgent ?? void 0;
|
|
3689
|
+
const cursorReady = input.cursorReady ?? isCursorApiKeyConfigured();
|
|
3690
|
+
let agent = account.agent;
|
|
3691
|
+
let ignoredAgent;
|
|
3692
|
+
let honorRequestedModel = false;
|
|
3693
|
+
if (requested) {
|
|
3694
|
+
const parentEcho = Boolean(parent) && requested === parent && requested !== account.agent;
|
|
3695
|
+
const cursorAutofill = requested === "cursor" && account.agent !== "cursor";
|
|
3696
|
+
if (parentEcho || cursorAutofill) {
|
|
3697
|
+
ignoredAgent = requested;
|
|
3698
|
+
} else {
|
|
3699
|
+
agent = requested;
|
|
3700
|
+
honorRequestedModel = input.requestedModel !== void 0;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
let coercedFrom;
|
|
3704
|
+
if (agent === "codex" && parent === "codex" && cursorReady) {
|
|
3705
|
+
coercedFrom = requested ?? "codex";
|
|
3706
|
+
agent = "cursor";
|
|
3707
|
+
}
|
|
3708
|
+
const opts = resolveNewThreadOptions2({
|
|
3709
|
+
agent,
|
|
3710
|
+
model: honorRequestedModel ? input.requestedModel : void 0
|
|
3711
|
+
});
|
|
3712
|
+
return {
|
|
3713
|
+
...opts,
|
|
3714
|
+
...ignoredAgent ? { ignoredAgent } : {},
|
|
3715
|
+
...coercedFrom ? { coercedFrom } : {}
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
function orchCreateThreadAgentNote(input) {
|
|
3719
|
+
if (input.coercedFrom) {
|
|
3720
|
+
return `Avoid nested Codex under a Codex orchestrator \u2014 used agent=${input.agent}`;
|
|
3721
|
+
}
|
|
3722
|
+
if (input.ignoredAgent) {
|
|
3723
|
+
return `Ignored agent=${input.ignoredAgent} \u2014 used Account default agent=${input.agent}`;
|
|
3724
|
+
}
|
|
3725
|
+
return void 0;
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
// src/mcp/create-orch-child.ts
|
|
3729
|
+
var MAX_ORCH_THREADS = 5;
|
|
3730
|
+
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
3731
|
+
function withTimeout(promise, ms, label) {
|
|
3732
|
+
return new Promise((resolve, reject) => {
|
|
3733
|
+
const timer = setTimeout(() => {
|
|
3734
|
+
reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
|
|
3735
|
+
}, ms);
|
|
3736
|
+
promise.then(
|
|
3737
|
+
(value) => {
|
|
3738
|
+
clearTimeout(timer);
|
|
3739
|
+
resolve(value);
|
|
3740
|
+
},
|
|
3741
|
+
(err) => {
|
|
3742
|
+
clearTimeout(timer);
|
|
3743
|
+
reject(err);
|
|
3744
|
+
}
|
|
3745
|
+
);
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
|
|
3749
|
+
const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
|
|
3750
|
+
let parentId = args.parentChatId?.trim() || args.parentThreadId?.trim() || "";
|
|
3751
|
+
let parent = parentId ? orch.getThread(parentId) : null;
|
|
3752
|
+
let parentCorrectedFrom;
|
|
3753
|
+
if (envParentId) {
|
|
3754
|
+
const envParent = orch.getThread(envParentId);
|
|
3755
|
+
if (envParent) {
|
|
3756
|
+
if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
|
|
3757
|
+
else if (!parentId) parentCorrectedFrom = void 0;
|
|
3758
|
+
parentId = envParentId;
|
|
3759
|
+
parent = envParent;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
if (parentId && !parent) {
|
|
3763
|
+
parentCorrectedFrom = parentId;
|
|
3764
|
+
parentId = "";
|
|
3765
|
+
parent = null;
|
|
3766
|
+
}
|
|
3767
|
+
if (parentId) {
|
|
3768
|
+
const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
|
|
3769
|
+
if (children.length >= MAX_ORCH_THREADS) {
|
|
3770
|
+
return {
|
|
3771
|
+
ok: false,
|
|
3772
|
+
text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
const opts = resolveOrchCreateThreadOptions({
|
|
3777
|
+
requestedAgent: args.agent,
|
|
3778
|
+
requestedModel: args.model,
|
|
3779
|
+
parentAgent: parent?.agent,
|
|
3780
|
+
resolveNewThreadOptions: resolveNewThreadOptions2
|
|
3781
|
+
});
|
|
3782
|
+
const agentNote = orchCreateThreadAgentNote(opts);
|
|
3783
|
+
try {
|
|
3784
|
+
const priorIds = new Set(orch.getThreads(false).map((t) => t.id));
|
|
3785
|
+
const thread = await withTimeout(
|
|
3786
|
+
orch.createThread({
|
|
3787
|
+
sourceType: args.sourceType,
|
|
3788
|
+
sourceRef: args.sourceRef,
|
|
3789
|
+
agent: opts.agent,
|
|
3790
|
+
model: opts.model,
|
|
3791
|
+
effort: opts.effort,
|
|
3792
|
+
fast: opts.fast,
|
|
3793
|
+
repoPath: args.repoPath,
|
|
3794
|
+
title: args.title,
|
|
3795
|
+
parentThreadId: parentId || null,
|
|
3796
|
+
cowboy: args.cowboy || void 0,
|
|
3797
|
+
attachments: args.attachments,
|
|
3798
|
+
tags: args.tags === void 0 ? void 0 : normalizeWorkspaceTags(args.tags)
|
|
3799
|
+
}),
|
|
3800
|
+
CREATE_THREAD_TIMEOUT_MS,
|
|
3801
|
+
"create_workspace"
|
|
3802
|
+
);
|
|
3803
|
+
const alreadyStarted = priorIds.has(thread.id);
|
|
3804
|
+
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
3805
|
+
return {
|
|
3806
|
+
ok: true,
|
|
3807
|
+
text: JSON.stringify({
|
|
3808
|
+
id: thread.id,
|
|
3809
|
+
title: thread.title,
|
|
3810
|
+
branchName: thread.branchName,
|
|
3811
|
+
worktreePath: thread.worktreePath,
|
|
3812
|
+
agent: thread.agent,
|
|
3813
|
+
model: thread.model,
|
|
3814
|
+
status: thread.status,
|
|
3815
|
+
cowboy: Boolean(thread.cowboy),
|
|
3816
|
+
link: `sideboard://chat/${thread.id}`,
|
|
3817
|
+
parentChatId: thread.parentThreadId,
|
|
3818
|
+
parentThreadId: thread.parentThreadId,
|
|
3819
|
+
tags: thread.tags ?? [],
|
|
3820
|
+
chats: workspaceAgentChatSummaries(thread.worktreePath),
|
|
3821
|
+
...alreadyStarted ? { alreadyStarted: true } : {},
|
|
3822
|
+
...opts.coercedFrom ? { agentCoercedFrom: opts.coercedFrom } : {},
|
|
3823
|
+
...opts.ignoredAgent ? { agentIgnored: opts.ignoredAgent } : {},
|
|
3824
|
+
...agentNote ? { note: agentNote } : {},
|
|
3825
|
+
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
3826
|
+
})
|
|
3827
|
+
};
|
|
3828
|
+
} catch (err) {
|
|
3829
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3830
|
+
return { ok: false, text: `create_workspace failed: ${message}` };
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3625
3834
|
// src/mcp/thread-visibility.ts
|
|
3626
3835
|
function lastMessagePreview(messages, max = 160) {
|
|
3627
3836
|
if (!messages?.length) return null;
|
|
3628
3837
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3629
|
-
const
|
|
3630
|
-
if (!
|
|
3631
|
-
const flat =
|
|
3838
|
+
const text5 = messages[i]?.text?.trim();
|
|
3839
|
+
if (!text5 || isInternalAgentStatusText(text5) || isInjectedNoticeText(text5)) continue;
|
|
3840
|
+
const flat = text5.replace(/\s+/g, " ");
|
|
3632
3841
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
3633
3842
|
}
|
|
3634
3843
|
return null;
|
|
@@ -3645,79 +3854,10 @@ function childThreadRefs(parentId, threads) {
|
|
|
3645
3854
|
}
|
|
3646
3855
|
|
|
3647
3856
|
// src/mcp/abletime-tools.ts
|
|
3648
|
-
import { z as
|
|
3649
|
-
|
|
3650
|
-
// src/mcp/issue-list.ts
|
|
3651
|
-
import { z } from "zod";
|
|
3652
|
-
var MCP_ISSUE_LIST_DEFAULT_LIMIT = 40;
|
|
3653
|
-
var MCP_ISSUE_LIST_MAX_LIMIT = 250;
|
|
3654
|
-
function clampMcpIssueLimit(limit) {
|
|
3655
|
-
if (limit == null || !Number.isFinite(limit)) return MCP_ISSUE_LIST_DEFAULT_LIMIT;
|
|
3656
|
-
return Math.max(1, Math.min(MCP_ISSUE_LIST_MAX_LIMIT, Math.floor(limit)));
|
|
3657
|
-
}
|
|
3658
|
-
function applyIssueListWindow(items, limit) {
|
|
3659
|
-
const truncated = items.length > limit;
|
|
3660
|
-
return {
|
|
3661
|
-
items: truncated ? items.slice(0, limit) : items,
|
|
3662
|
-
truncated
|
|
3663
|
-
};
|
|
3664
|
-
}
|
|
3665
|
-
var MCP_ISSUE_UPDATED_SINCE_DESCRIPTION = "ISO datetime, YYYY-MM-DD, or relative (yesterday, 2d, 3 hours ago). Tickets updated since then plus new comments. Use for \u201Cany updates since yesterday?\u201D";
|
|
3666
|
-
var mcpIssueUpdatedSinceSchema = z.string().min(1).optional().describe(MCP_ISSUE_UPDATED_SINCE_DESCRIPTION);
|
|
3667
|
-
function compactIssueRow(issue, opts) {
|
|
3668
|
-
return {
|
|
3669
|
-
identifier: issue.identifier,
|
|
3670
|
-
title: issue.title,
|
|
3671
|
-
...issue.assignee ? { assignee: issue.assignee } : {},
|
|
3672
|
-
...issue.labels.length > 0 ? { labels: issue.labels } : {},
|
|
3673
|
-
...issue.teamKey ? { team: issue.teamKey } : {},
|
|
3674
|
-
...issue.cycle?.name ? { cycle: issue.cycle.name } : {},
|
|
3675
|
-
...issue.createdAt ? { createdAt: issue.createdAt } : {},
|
|
3676
|
-
...issue.updatedAt ? { updatedAt: issue.updatedAt } : {},
|
|
3677
|
-
...opts?.since ? { kind: issueActivityKind(issue, opts.since) } : {}
|
|
3678
|
-
};
|
|
3679
|
-
}
|
|
3680
|
-
function formatMcpIssueList(input) {
|
|
3681
|
-
const truncated = input.truncated || Boolean(input.commentsTruncated);
|
|
3682
|
-
const comments = input.comments ?? [];
|
|
3683
|
-
return {
|
|
3684
|
-
...input.source ? { source: input.source } : {},
|
|
3685
|
-
...input.viewer ? { viewer: input.viewer } : {},
|
|
3686
|
-
limit: input.limit,
|
|
3687
|
-
returned: input.issues.length,
|
|
3688
|
-
truncated,
|
|
3689
|
-
...input.since ? { since: input.since } : {},
|
|
3690
|
-
...truncated ? {
|
|
3691
|
-
hint: input.since ? "More issues or comments match. Pass a higher limit (max 250) or a tighter query." : "More issues match. Pass a higher limit (max 250) or a tighter query."
|
|
3692
|
-
} : {},
|
|
3693
|
-
issues: input.issues.map((issue) => compactIssueRow(issue, { since: input.since })),
|
|
3694
|
-
...input.since || comments.length > 0 ? { comments } : {}
|
|
3695
|
-
};
|
|
3696
|
-
}
|
|
3697
|
-
function formatListedIssuesForMcp(result, page) {
|
|
3698
|
-
const windowed = applyIssueListWindow(result.issues, page);
|
|
3699
|
-
const comments = result.comments ?? [];
|
|
3700
|
-
const commentWindow = applyIssueListWindow(comments, page);
|
|
3701
|
-
return formatMcpIssueList({
|
|
3702
|
-
source: result.source,
|
|
3703
|
-
viewer: result.viewer?.name || result.viewer?.login,
|
|
3704
|
-
limit: page,
|
|
3705
|
-
issues: windowed.items,
|
|
3706
|
-
truncated: windowed.truncated,
|
|
3707
|
-
since: result.since,
|
|
3708
|
-
comments: commentWindow.items,
|
|
3709
|
-
commentsTruncated: commentWindow.truncated
|
|
3710
|
-
});
|
|
3711
|
-
}
|
|
3712
|
-
function mcpJson(payload, isError = false) {
|
|
3713
|
-
return {
|
|
3714
|
-
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
3715
|
-
...isError ? { isError: true } : {}
|
|
3716
|
-
};
|
|
3717
|
-
}
|
|
3857
|
+
import { z as z2 } from "zod";
|
|
3718
3858
|
|
|
3719
3859
|
// src/mcp/issue-payload.ts
|
|
3720
|
-
import { z
|
|
3860
|
+
import { z } from "zod";
|
|
3721
3861
|
|
|
3722
3862
|
// src/mcp/smart-crush.ts
|
|
3723
3863
|
var MUST_KEEP_TEXT_RE = /\b(error|exception|failed|failure|fatal|critical|panic|traceback|npm err|assert)\b/i;
|
|
@@ -3727,15 +3867,15 @@ var CRUSH_COMMENT_MAX_CHARS = 16e3;
|
|
|
3727
3867
|
var CRUSH_BODY_MIN_CHARS = 12e3;
|
|
3728
3868
|
var CRUSH_BODY_MAX_CHARS = 8e3;
|
|
3729
3869
|
var NEAR_DUP_JACCARD = 0.92;
|
|
3730
|
-
function looksLikeMustKeep(
|
|
3731
|
-
return MUST_KEEP_TEXT_RE.test(
|
|
3870
|
+
function looksLikeMustKeep(text5) {
|
|
3871
|
+
return MUST_KEEP_TEXT_RE.test(text5);
|
|
3732
3872
|
}
|
|
3733
|
-
function normalizeText(
|
|
3734
|
-
return
|
|
3873
|
+
function normalizeText(text5) {
|
|
3874
|
+
return text5.replace(/\s+/g, " ").trim().toLowerCase();
|
|
3735
3875
|
}
|
|
3736
|
-
function wordSet(
|
|
3876
|
+
function wordSet(text5) {
|
|
3737
3877
|
return new Set(
|
|
3738
|
-
|
|
3878
|
+
text5.toLowerCase().split(/\W+/).filter((w) => w.length > 2)
|
|
3739
3879
|
);
|
|
3740
3880
|
}
|
|
3741
3881
|
function jaccard(a, b) {
|
|
@@ -3747,11 +3887,11 @@ function jaccard(a, b) {
|
|
|
3747
3887
|
const union = a.size + b.size - inter;
|
|
3748
3888
|
return union === 0 ? 0 : inter / union;
|
|
3749
3889
|
}
|
|
3750
|
-
function importance(
|
|
3751
|
-
let score =
|
|
3752
|
-
if (/```/.test(
|
|
3753
|
-
if (/https?:\/\//.test(
|
|
3754
|
-
if (looksLikeMustKeep(
|
|
3890
|
+
function importance(text5) {
|
|
3891
|
+
let score = text5.length;
|
|
3892
|
+
if (/```/.test(text5)) score += 400;
|
|
3893
|
+
if (/https?:\/\//.test(text5)) score += 50;
|
|
3894
|
+
if (looksLikeMustKeep(text5)) score += 1e4;
|
|
3755
3895
|
return score;
|
|
3756
3896
|
}
|
|
3757
3897
|
function meanStd(values) {
|
|
@@ -3765,15 +3905,15 @@ function dedupTextItems(items, getText) {
|
|
|
3765
3905
|
const keptNorm = [];
|
|
3766
3906
|
const keptWords = [];
|
|
3767
3907
|
for (const item of items) {
|
|
3768
|
-
const
|
|
3769
|
-
const norm = normalizeText(
|
|
3908
|
+
const text5 = getText(item);
|
|
3909
|
+
const norm = normalizeText(text5);
|
|
3770
3910
|
if (!norm) {
|
|
3771
3911
|
kept.push(item);
|
|
3772
3912
|
keptNorm.push(norm);
|
|
3773
|
-
keptWords.push(wordSet(
|
|
3913
|
+
keptWords.push(wordSet(text5));
|
|
3774
3914
|
continue;
|
|
3775
3915
|
}
|
|
3776
|
-
const words = wordSet(
|
|
3916
|
+
const words = wordSet(text5);
|
|
3777
3917
|
let dupAt = -1;
|
|
3778
3918
|
for (let i = 0; i < kept.length; i++) {
|
|
3779
3919
|
if (keptNorm[i] === norm) {
|
|
@@ -3791,7 +3931,7 @@ function dedupTextItems(items, getText) {
|
|
|
3791
3931
|
keptWords.push(words);
|
|
3792
3932
|
continue;
|
|
3793
3933
|
}
|
|
3794
|
-
if (
|
|
3934
|
+
if (text5.length > getText(kept[dupAt]).length) {
|
|
3795
3935
|
kept[dupAt] = item;
|
|
3796
3936
|
keptNorm[dupAt] = norm;
|
|
3797
3937
|
keptWords[dupAt] = words;
|
|
@@ -3832,8 +3972,8 @@ function crushTextItems(items, getText, opts = {}) {
|
|
|
3832
3972
|
const outlierFloor = mean + 2 * std;
|
|
3833
3973
|
const mustKeep = /* @__PURE__ */ new Set();
|
|
3834
3974
|
unique.forEach((item, i) => {
|
|
3835
|
-
const
|
|
3836
|
-
if (looksLikeMustKeep(
|
|
3975
|
+
const text5 = getText(item);
|
|
3976
|
+
if (looksLikeMustKeep(text5) || text5.length > outlierFloor) mustKeep.add(i);
|
|
3837
3977
|
});
|
|
3838
3978
|
const remainingIdx = unique.map((_, i) => i).filter((i) => !mustKeep.has(i));
|
|
3839
3979
|
let used = [...mustKeep].reduce((n, i) => n + lengths[i], 0);
|
|
@@ -3875,9 +4015,9 @@ function crushTextItems(items, getText, opts = {}) {
|
|
|
3875
4015
|
reason: "budget"
|
|
3876
4016
|
};
|
|
3877
4017
|
}
|
|
3878
|
-
function splitBlocks(
|
|
3879
|
-
if (/\n\n/.test(
|
|
3880
|
-
return
|
|
4018
|
+
function splitBlocks(text5) {
|
|
4019
|
+
if (/\n\n/.test(text5)) return text5.split(/\n\n+/);
|
|
4020
|
+
return text5.split("\n");
|
|
3881
4021
|
}
|
|
3882
4022
|
function capOversizedBlock(block, maxChars) {
|
|
3883
4023
|
if (block.length <= maxChars) return block;
|
|
@@ -3889,8 +4029,8 @@ function capOversizedBlock(block, maxChars) {
|
|
|
3889
4029
|
|
|
3890
4030
|
${block.slice(-tail)}`;
|
|
3891
4031
|
}
|
|
3892
|
-
function crushMarkdown(
|
|
3893
|
-
const original =
|
|
4032
|
+
function crushMarkdown(text5, opts) {
|
|
4033
|
+
const original = text5 ?? "";
|
|
3894
4034
|
const minChars = opts?.minChars ?? CRUSH_BODY_MIN_CHARS;
|
|
3895
4035
|
const maxChars = opts?.maxChars ?? CRUSH_BODY_MAX_CHARS;
|
|
3896
4036
|
if (original.length < minChars) {
|
|
@@ -3962,7 +4102,7 @@ function crushMarkdown(text6, opts) {
|
|
|
3962
4102
|
|
|
3963
4103
|
// src/mcp/issue-payload.ts
|
|
3964
4104
|
var MCP_ISSUE_INCLUDE_VALUES = ["default", "full"];
|
|
3965
|
-
var mcpIssueIncludeSchema =
|
|
4105
|
+
var mcpIssueIncludeSchema = z.enum(MCP_ISSUE_INCLUDE_VALUES).optional().describe(
|
|
3966
4106
|
"default applies SmartCrusher-style crush (dedup + budget; small unique tickets pass through). full returns the uncompressed vendor payload. Re-call with include=full if crush.truncated."
|
|
3967
4107
|
);
|
|
3968
4108
|
var MCP_ISSUE_CRUSH_HINT = "Pass include=full to re-fetch the uncompressed ticket from the vendor.";
|
|
@@ -3980,13 +4120,13 @@ function withComments(comments, meta) {
|
|
|
3980
4120
|
}
|
|
3981
4121
|
return { comments: crushed.items, meta };
|
|
3982
4122
|
}
|
|
3983
|
-
function withBody(
|
|
3984
|
-
const crushed = crushMarkdown(
|
|
4123
|
+
function withBody(text5, meta) {
|
|
4124
|
+
const crushed = crushMarkdown(text5);
|
|
3985
4125
|
if (crushed.truncated) {
|
|
3986
4126
|
meta.body = { originalChars: crushed.originalChars };
|
|
3987
4127
|
return { value: crushed.text, meta };
|
|
3988
4128
|
}
|
|
3989
|
-
return { value:
|
|
4129
|
+
return { value: text5, meta };
|
|
3990
4130
|
}
|
|
3991
4131
|
function attachCrush(payload, meta) {
|
|
3992
4132
|
if (!meta.comments && !meta.body) return payload;
|
|
@@ -4108,7 +4248,7 @@ function registerAbleTimeTools(server) {
|
|
|
4108
4248
|
server.tool(
|
|
4109
4249
|
"abletime_list_tasks",
|
|
4110
4250
|
"List open AbleTime tasks (excludes done/archived). Optional projectId filter. Pass updatedSince for tasks updated since then and any comments on that page.",
|
|
4111
|
-
{ projectId:
|
|
4251
|
+
{ projectId: z2.string().optional(), updatedSince: mcpIssueUpdatedSinceSchema },
|
|
4112
4252
|
async ({ projectId, updatedSince }) => {
|
|
4113
4253
|
try {
|
|
4114
4254
|
const tasks = await listAbleTimeTasks({ projectId });
|
|
@@ -4121,7 +4261,7 @@ function registerAbleTimeTools(server) {
|
|
|
4121
4261
|
server.tool(
|
|
4122
4262
|
"abletime_search_tasks",
|
|
4123
4263
|
"Search AbleTime tasks by text, reference, or tag. Pass updatedSince for tasks updated since then and any comments on that page.",
|
|
4124
|
-
{ query:
|
|
4264
|
+
{ query: z2.string(), updatedSince: mcpIssueUpdatedSinceSchema },
|
|
4125
4265
|
async ({ query, updatedSince }) => {
|
|
4126
4266
|
try {
|
|
4127
4267
|
const tasks = await searchAbleTimeTasks(query);
|
|
@@ -4133,8 +4273,8 @@ function registerAbleTimeTools(server) {
|
|
|
4133
4273
|
);
|
|
4134
4274
|
server.tool(
|
|
4135
4275
|
"abletime_get_task",
|
|
4136
|
-
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, comments, attachments. Re-fetch to read new comments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. To save a file, call abletime_download_attachment with the attachment id or url (writes `.context/attachments/`; do not dump bytes into the tool result).",
|
|
4137
|
-
{ id:
|
|
4276
|
+
"Get one AbleTime task by id or reference (e.g. CRM-232): description, state, project, comments, attachments. Re-fetch to read new comments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. To save a file, call abletime_download_attachment with the attachment id or url (writes `.context/attachments/`; do not dump bytes into the tool result).",
|
|
4277
|
+
{ id: z2.string(), include: mcpIssueIncludeSchema },
|
|
4138
4278
|
async ({ id, include }) => {
|
|
4139
4279
|
try {
|
|
4140
4280
|
return text(formatAbleTimeTaskPayload(await getAbleTimeTask(id), include));
|
|
@@ -4147,9 +4287,9 @@ function registerAbleTimeTools(server) {
|
|
|
4147
4287
|
"abletime_download_attachment",
|
|
4148
4288
|
"Download an AbleTime task file into `.context/attachments/` using Account AbleTime auth. Pass the attachment id or URL from abletime_get_task. Returns the worktree-relative path \u2014 Read that file; do not dump bytes into chat.",
|
|
4149
4289
|
{
|
|
4150
|
-
id:
|
|
4151
|
-
name:
|
|
4152
|
-
repoPath:
|
|
4290
|
+
id: z2.string().describe("Attachment id or URL from abletime_get_task."),
|
|
4291
|
+
name: z2.string().optional().describe("Optional filename override."),
|
|
4292
|
+
repoPath: z2.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).")
|
|
4153
4293
|
},
|
|
4154
4294
|
async ({ id, name, repoPath }) => {
|
|
4155
4295
|
try {
|
|
@@ -4162,7 +4302,7 @@ function registerAbleTimeTools(server) {
|
|
|
4162
4302
|
server.tool(
|
|
4163
4303
|
"abletime_comment",
|
|
4164
4304
|
"Add a markdown comment on an AbleTime task (id or CRM-232). When reviewing a PR or ticket, show the draft in chat and wait until they type a next step \u2014 do not ask_user after the review. The author sees this immediately.",
|
|
4165
|
-
{ id:
|
|
4305
|
+
{ id: z2.string(), body: z2.string() },
|
|
4166
4306
|
async (args) => {
|
|
4167
4307
|
try {
|
|
4168
4308
|
return text(await commentAbleTimeTask(args));
|
|
@@ -4171,23 +4311,23 @@ function registerAbleTimeTools(server) {
|
|
|
4171
4311
|
}
|
|
4172
4312
|
}
|
|
4173
4313
|
);
|
|
4174
|
-
const relationInputSchema =
|
|
4175
|
-
type:
|
|
4176
|
-
issue:
|
|
4314
|
+
const relationInputSchema = z2.object({
|
|
4315
|
+
type: z2.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
|
|
4316
|
+
issue: z2.string().describe("Related task id or reference (CRM-232).")
|
|
4177
4317
|
});
|
|
4178
4318
|
server.tool(
|
|
4179
4319
|
"abletime_update_task",
|
|
4180
4320
|
'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.',
|
|
4181
4321
|
{
|
|
4182
|
-
id:
|
|
4183
|
-
title:
|
|
4184
|
-
description:
|
|
4185
|
-
state:
|
|
4186
|
-
labels:
|
|
4187
|
-
project:
|
|
4188
|
-
parent:
|
|
4189
|
-
relations:
|
|
4190
|
-
removeRelations:
|
|
4322
|
+
id: z2.string(),
|
|
4323
|
+
title: z2.string().optional(),
|
|
4324
|
+
description: z2.string().optional(),
|
|
4325
|
+
state: z2.string().optional(),
|
|
4326
|
+
labels: z2.array(z2.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
|
|
4327
|
+
project: z2.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
|
|
4328
|
+
parent: z2.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
|
|
4329
|
+
relations: z2.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
|
|
4330
|
+
removeRelations: z2.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
|
|
4191
4331
|
},
|
|
4192
4332
|
async (args) => {
|
|
4193
4333
|
try {
|
|
@@ -4206,12 +4346,12 @@ function registerAbleTimeTools(server) {
|
|
|
4206
4346
|
"abletime_create_task",
|
|
4207
4347
|
"Create an AbleTime task in a project. Call abletime_list_projects if you do not have a project id. Pass parent (CRM-232) for a spin-off. New tasks start in todo (or backlog).",
|
|
4208
4348
|
{
|
|
4209
|
-
title:
|
|
4210
|
-
description:
|
|
4211
|
-
projectId:
|
|
4212
|
-
categoryId:
|
|
4213
|
-
state:
|
|
4214
|
-
parent:
|
|
4349
|
+
title: z2.string(),
|
|
4350
|
+
description: z2.string().optional(),
|
|
4351
|
+
projectId: z2.string().optional(),
|
|
4352
|
+
categoryId: z2.string().optional(),
|
|
4353
|
+
state: z2.enum(["backlog", "todo"]).optional(),
|
|
4354
|
+
parent: z2.string().optional().describe("Parent task id or reference (CRM-232) for a spin-off.")
|
|
4215
4355
|
},
|
|
4216
4356
|
async (args) => {
|
|
4217
4357
|
try {
|
|
@@ -4225,10 +4365,10 @@ function registerAbleTimeTools(server) {
|
|
|
4225
4365
|
"abletime_ensure_task",
|
|
4226
4366
|
"Find or create an AbleTime task for this work, then create_workspace with sourceType=ticket.",
|
|
4227
4367
|
{
|
|
4228
|
-
title:
|
|
4229
|
-
description:
|
|
4230
|
-
projectId:
|
|
4231
|
-
categoryId:
|
|
4368
|
+
title: z2.string(),
|
|
4369
|
+
description: z2.string().optional(),
|
|
4370
|
+
projectId: z2.string().optional(),
|
|
4371
|
+
categoryId: z2.string().optional()
|
|
4232
4372
|
},
|
|
4233
4373
|
async (args) => {
|
|
4234
4374
|
try {
|
|
@@ -4242,7 +4382,7 @@ function registerAbleTimeTools(server) {
|
|
|
4242
4382
|
}
|
|
4243
4383
|
|
|
4244
4384
|
// src/mcp/github-tools.ts
|
|
4245
|
-
import { z as
|
|
4385
|
+
import { z as z3 } from "zod";
|
|
4246
4386
|
function text2(payload, isError = false) {
|
|
4247
4387
|
return mcpJson(payload, isError);
|
|
4248
4388
|
}
|
|
@@ -4252,15 +4392,15 @@ function fail2(err) {
|
|
|
4252
4392
|
true
|
|
4253
4393
|
);
|
|
4254
4394
|
}
|
|
4255
|
-
var repoPathSchema =
|
|
4395
|
+
var repoPathSchema = z3.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).");
|
|
4256
4396
|
function registerGithubIssueTools(server) {
|
|
4257
4397
|
server.tool(
|
|
4258
4398
|
"github_search_issues",
|
|
4259
4399
|
"Search or list open GitHub issues in this repo via Account gh. Default 40; pass query and/or limit (max 250) when truncated. assignee: me, unassigned, all, or a login. Pass updatedSince for new/updated tickets and new comments (e.g. \u201Cany updates since yesterday?\u201D).",
|
|
4260
4400
|
{
|
|
4261
|
-
query:
|
|
4262
|
-
assignee:
|
|
4263
|
-
limit:
|
|
4401
|
+
query: z3.string().optional().describe("Search text (title, body, identifiers). Omit to list by assignee only."),
|
|
4402
|
+
assignee: z3.string().optional().describe("me, unassigned, all, or a GitHub login. Default: all when query is set, otherwise me."),
|
|
4403
|
+
limit: z3.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true."),
|
|
4264
4404
|
updatedSince: mcpIssueUpdatedSinceSchema,
|
|
4265
4405
|
repoPath: repoPathSchema
|
|
4266
4406
|
},
|
|
@@ -4297,10 +4437,22 @@ function registerGithubIssueTools(server) {
|
|
|
4297
4437
|
}
|
|
4298
4438
|
}
|
|
4299
4439
|
);
|
|
4440
|
+
server.tool(
|
|
4441
|
+
"github_list_projects",
|
|
4442
|
+
"List open GitHub Projects for the signed-in user and this repo owner (title, number, id). Pass a title as project on github_update_issue. If GitHub says the token is missing read:project, run `gh auth refresh -s read:project`.",
|
|
4443
|
+
{ repoPath: repoPathSchema },
|
|
4444
|
+
async ({ repoPath }) => {
|
|
4445
|
+
try {
|
|
4446
|
+
return text2({ projects: await listGitHubProjects({ repoPath }) });
|
|
4447
|
+
} catch (err) {
|
|
4448
|
+
return fail2(err);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
);
|
|
4300
4452
|
server.tool(
|
|
4301
4453
|
"github_get_issue",
|
|
4302
|
-
"Get a GitHub issue (#123 or URL): body, comments, state, attachments. Re-fetch to read new comments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. Uses Account gh. To save a file, call github_download_attachment with the attachment URL (writes `.context/attachments/`; do not dump bytes into the tool result).",
|
|
4303
|
-
{ id:
|
|
4454
|
+
"Get a GitHub issue (#123 or URL): body, comments, state, projects, attachments. Re-fetch to read new comments. projectsError means GitHub refused Projects (usually missing read:project \u2014 run `gh auth refresh -s read:project`). Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. Uses Account gh. To save a file, call github_download_attachment with the attachment URL (writes `.context/attachments/`; do not dump bytes into the tool result).",
|
|
4455
|
+
{ id: z3.string(), include: mcpIssueIncludeSchema, repoPath: repoPathSchema },
|
|
4304
4456
|
async ({ id, include, repoPath }) => {
|
|
4305
4457
|
try {
|
|
4306
4458
|
return text2(formatGitHubIssuePayload(await getGitHubIssue(id, { repoPath }), include));
|
|
@@ -4313,8 +4465,8 @@ function registerGithubIssueTools(server) {
|
|
|
4313
4465
|
"github_download_attachment",
|
|
4314
4466
|
"Download a GitHub issue file (github.com/user-attachments/\u2026 or githubusercontent.com) into `.context/attachments/` using Account gh. Pass the URL from github_get_issue attachments. Returns the worktree-relative path \u2014 Read that file; do not dump bytes into chat.",
|
|
4315
4467
|
{
|
|
4316
|
-
url:
|
|
4317
|
-
name:
|
|
4468
|
+
url: z3.string().describe("Attachment URL from github_get_issue."),
|
|
4469
|
+
name: z3.string().optional().describe("Optional filename override."),
|
|
4318
4470
|
repoPath: repoPathSchema
|
|
4319
4471
|
},
|
|
4320
4472
|
async ({ url, name, repoPath }) => {
|
|
@@ -4328,7 +4480,7 @@ function registerGithubIssueTools(server) {
|
|
|
4328
4480
|
server.tool(
|
|
4329
4481
|
"github_comment",
|
|
4330
4482
|
"Add a markdown comment on a GitHub issue (#123 or URL). Uses Account gh. When reviewing a PR or ticket, show the draft in chat and wait until they type a next step \u2014 do not ask_user after the review. The author sees this immediately.",
|
|
4331
|
-
{ id:
|
|
4483
|
+
{ id: z3.string(), body: z3.string(), repoPath: repoPathSchema },
|
|
4332
4484
|
async ({ id, body, repoPath }) => {
|
|
4333
4485
|
try {
|
|
4334
4486
|
return text2(await commentGitHubIssue({ id, body }, { repoPath }));
|
|
@@ -4337,23 +4489,23 @@ function registerGithubIssueTools(server) {
|
|
|
4337
4489
|
}
|
|
4338
4490
|
}
|
|
4339
4491
|
);
|
|
4340
|
-
const relationInputSchema =
|
|
4341
|
-
type:
|
|
4342
|
-
issue:
|
|
4492
|
+
const relationInputSchema = z3.object({
|
|
4493
|
+
type: z3.string().describe("blocks or blockedBy (this issue's view)."),
|
|
4494
|
+
issue: z3.string().describe("Related issue #123 or URL.")
|
|
4343
4495
|
});
|
|
4344
4496
|
server.tool(
|
|
4345
4497
|
"github_update_issue",
|
|
4346
4498
|
'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.',
|
|
4347
4499
|
{
|
|
4348
|
-
id:
|
|
4349
|
-
title:
|
|
4350
|
-
body:
|
|
4351
|
-
state:
|
|
4352
|
-
labels:
|
|
4353
|
-
project:
|
|
4354
|
-
parent:
|
|
4355
|
-
relations:
|
|
4356
|
-
removeRelations:
|
|
4500
|
+
id: z3.string(),
|
|
4501
|
+
title: z3.string().optional(),
|
|
4502
|
+
body: z3.string().optional(),
|
|
4503
|
+
state: z3.string().optional().describe("open or closed"),
|
|
4504
|
+
labels: z3.array(z3.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
|
|
4505
|
+
project: z3.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
|
|
4506
|
+
parent: z3.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
|
|
4507
|
+
relations: z3.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
|
|
4508
|
+
removeRelations: z3.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
|
|
4357
4509
|
repoPath: repoPathSchema
|
|
4358
4510
|
},
|
|
4359
4511
|
async (args) => {
|
|
@@ -4368,9 +4520,9 @@ function registerGithubIssueTools(server) {
|
|
|
4368
4520
|
"github_create_issue",
|
|
4369
4521
|
"Create a GitHub issue. Pass parent (#123) to mark a spin-off in the body.",
|
|
4370
4522
|
{
|
|
4371
|
-
title:
|
|
4372
|
-
body:
|
|
4373
|
-
parent:
|
|
4523
|
+
title: z3.string(),
|
|
4524
|
+
body: z3.string().optional(),
|
|
4525
|
+
parent: z3.string().optional().describe("Parent issue #123 or URL for a spin-off."),
|
|
4374
4526
|
repoPath: repoPathSchema
|
|
4375
4527
|
},
|
|
4376
4528
|
async (args) => {
|
|
@@ -4384,7 +4536,7 @@ function registerGithubIssueTools(server) {
|
|
|
4384
4536
|
}
|
|
4385
4537
|
|
|
4386
4538
|
// src/mcp/linear-tools.ts
|
|
4387
|
-
import { z as
|
|
4539
|
+
import { z as z4 } from "zod";
|
|
4388
4540
|
function text3(payload, isError = false) {
|
|
4389
4541
|
return mcpJson(payload, isError);
|
|
4390
4542
|
}
|
|
@@ -4394,7 +4546,7 @@ function fail3(err) {
|
|
|
4394
4546
|
true
|
|
4395
4547
|
);
|
|
4396
4548
|
}
|
|
4397
|
-
var prioritySchema =
|
|
4549
|
+
var prioritySchema = z4.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
4398
4550
|
function registerLinearTools(server) {
|
|
4399
4551
|
server.tool(
|
|
4400
4552
|
"linear_list_teams",
|
|
@@ -4408,13 +4560,25 @@ function registerLinearTools(server) {
|
|
|
4408
4560
|
}
|
|
4409
4561
|
}
|
|
4410
4562
|
);
|
|
4563
|
+
server.tool(
|
|
4564
|
+
"linear_list_projects",
|
|
4565
|
+
"List Linear projects (id, name, slugId). Pass a name or id as project on linear_update_issue. linear_get_issue and search rows also include the project a ticket is on.",
|
|
4566
|
+
{},
|
|
4567
|
+
async () => {
|
|
4568
|
+
try {
|
|
4569
|
+
return text3({ projects: await listLinearProjects() });
|
|
4570
|
+
} catch (err) {
|
|
4571
|
+
return fail3(err);
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
);
|
|
4411
4575
|
server.tool(
|
|
4412
4576
|
"linear_search_issues",
|
|
4413
4577
|
"Search or list open Linear issues. Default 40; pass query and/or limit (max 250) when truncated. assignee: me, unassigned, all (default with query), or a user id/name. Pass updatedSince for new/updated tickets and new comments (e.g. \u201Cany updates since yesterday?\u201D).",
|
|
4414
4578
|
{
|
|
4415
|
-
query:
|
|
4416
|
-
assignee:
|
|
4417
|
-
limit:
|
|
4579
|
+
query: z4.string().optional().describe("Search text (identifier, title, description). Omit to list by assignee only."),
|
|
4580
|
+
assignee: z4.string().optional().describe("me, unassigned, all, a Linear user id, or a display name. Default: all when query is set, otherwise me."),
|
|
4581
|
+
limit: z4.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true."),
|
|
4418
4582
|
updatedSince: mcpIssueUpdatedSinceSchema
|
|
4419
4583
|
},
|
|
4420
4584
|
async ({ query, assignee, limit, updatedSince }) => {
|
|
@@ -4453,8 +4617,8 @@ function registerLinearTools(server) {
|
|
|
4453
4617
|
);
|
|
4454
4618
|
server.tool(
|
|
4455
4619
|
"linear_get_issue",
|
|
4456
|
-
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children, attachments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. To save a file, call linear_download_attachment with the attachment id or url (writes `.context/attachments/`; do not dump bytes into the tool result).",
|
|
4457
|
-
{ id:
|
|
4620
|
+
"Get a Linear issue by uuid or identifier (ENG-123): description, comments, relations, parent/children, project, attachments. Default crushes redundant comments and huge pasted bodies (SmartCrusher-style; small unique tickets pass through). Pass include=full for the uncompressed vendor payload. To save a file, call linear_download_attachment with the attachment id or url (writes `.context/attachments/`; do not dump bytes into the tool result).",
|
|
4621
|
+
{ id: z4.string(), include: mcpIssueIncludeSchema },
|
|
4458
4622
|
async ({ id, include }) => {
|
|
4459
4623
|
try {
|
|
4460
4624
|
return text3(formatLinearIssuePayload(await getLinearIssue(id), include));
|
|
@@ -4467,9 +4631,9 @@ function registerLinearTools(server) {
|
|
|
4467
4631
|
"linear_download_attachment",
|
|
4468
4632
|
"Download a Linear file attachment into `.context/attachments/` using Account Linear auth. Pass the attachment uuid or uploads.linear.app URL from linear_get_issue. Returns the worktree-relative path \u2014 Read that file; do not dump bytes into chat.",
|
|
4469
4633
|
{
|
|
4470
|
-
id:
|
|
4471
|
-
name:
|
|
4472
|
-
repoPath:
|
|
4634
|
+
id: z4.string().describe("Attachment uuid or upload URL from linear_get_issue."),
|
|
4635
|
+
name: z4.string().optional().describe("Optional filename override."),
|
|
4636
|
+
repoPath: z4.string().optional().describe("Workspace / worktree path. Omit on a worktree turn (uses cwd).")
|
|
4473
4637
|
},
|
|
4474
4638
|
async ({ id, name, repoPath }) => {
|
|
4475
4639
|
try {
|
|
@@ -4483,13 +4647,13 @@ function registerLinearTools(server) {
|
|
|
4483
4647
|
"linear_create_issue",
|
|
4484
4648
|
'Create a Linear issue. Call linear_list_teams first. team is id/key/name; state is name/type/id; assignee is "me" or a user id. Pass parent (ENG-123 or uuid) to nest a spin-off under the current ticket.',
|
|
4485
4649
|
{
|
|
4486
|
-
team:
|
|
4487
|
-
title:
|
|
4488
|
-
description:
|
|
4489
|
-
state:
|
|
4490
|
-
assignee:
|
|
4650
|
+
team: z4.string(),
|
|
4651
|
+
title: z4.string(),
|
|
4652
|
+
description: z4.string().optional(),
|
|
4653
|
+
state: z4.string().optional(),
|
|
4654
|
+
assignee: z4.string().nullable().optional(),
|
|
4491
4655
|
priority: prioritySchema,
|
|
4492
|
-
parent:
|
|
4656
|
+
parent: z4.string().optional().describe("Parent issue uuid or identifier (ENG-123) for a spin-off / sub-issue.")
|
|
4493
4657
|
},
|
|
4494
4658
|
async (args) => {
|
|
4495
4659
|
try {
|
|
@@ -4499,26 +4663,26 @@ function registerLinearTools(server) {
|
|
|
4499
4663
|
}
|
|
4500
4664
|
}
|
|
4501
4665
|
);
|
|
4502
|
-
const relationInputSchema =
|
|
4503
|
-
type:
|
|
4504
|
-
issue:
|
|
4666
|
+
const relationInputSchema = z4.object({
|
|
4667
|
+
type: z4.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
|
|
4668
|
+
issue: z4.string().describe("Related issue uuid or identifier (ENG-123).")
|
|
4505
4669
|
});
|
|
4506
4670
|
server.tool(
|
|
4507
4671
|
"linear_update_issue",
|
|
4508
4672
|
'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.',
|
|
4509
4673
|
{
|
|
4510
|
-
id:
|
|
4511
|
-
title:
|
|
4512
|
-
description:
|
|
4513
|
-
state:
|
|
4514
|
-
assignee:
|
|
4674
|
+
id: z4.string(),
|
|
4675
|
+
title: z4.string().optional(),
|
|
4676
|
+
description: z4.string().optional(),
|
|
4677
|
+
state: z4.string().optional(),
|
|
4678
|
+
assignee: z4.string().nullable().optional(),
|
|
4515
4679
|
priority: prioritySchema,
|
|
4516
|
-
cycle:
|
|
4517
|
-
labels:
|
|
4518
|
-
project:
|
|
4519
|
-
parent:
|
|
4520
|
-
relations:
|
|
4521
|
-
removeRelations:
|
|
4680
|
+
cycle: z4.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
|
|
4681
|
+
labels: z4.array(z4.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
|
|
4682
|
+
project: z4.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
|
|
4683
|
+
parent: z4.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
|
|
4684
|
+
relations: z4.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
|
|
4685
|
+
removeRelations: z4.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
|
|
4522
4686
|
},
|
|
4523
4687
|
async (args) => {
|
|
4524
4688
|
try {
|
|
@@ -4532,8 +4696,8 @@ function registerLinearTools(server) {
|
|
|
4532
4696
|
"linear_comment",
|
|
4533
4697
|
"Add a markdown comment on a Linear issue (uuid or ENG-123). When reviewing a PR or ticket, show the draft in chat and wait until they type a next step \u2014 do not ask_user after the review. The author sees this immediately.",
|
|
4534
4698
|
{
|
|
4535
|
-
id:
|
|
4536
|
-
body:
|
|
4699
|
+
id: z4.string(),
|
|
4700
|
+
body: z4.string()
|
|
4537
4701
|
},
|
|
4538
4702
|
async (args) => {
|
|
4539
4703
|
try {
|
|
@@ -4545,109 +4709,11 @@ function registerLinearTools(server) {
|
|
|
4545
4709
|
);
|
|
4546
4710
|
}
|
|
4547
4711
|
|
|
4548
|
-
// src/mcp/issue-vendor-tools.ts
|
|
4549
|
-
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
4550
|
-
registerGithubIssueTools(server);
|
|
4551
|
-
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
4552
|
-
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
|
-
// src/mcp/viewer-context-tools.ts
|
|
4556
|
-
import { z as z6 } from "zod";
|
|
4557
|
-
function text4(payload, isError = false) {
|
|
4558
|
-
return mcpJson(payload, isError);
|
|
4559
|
-
}
|
|
4560
|
-
function matchRegisteredWorkspace(repoPath) {
|
|
4561
|
-
const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
|
|
4562
|
-
if (!hint) return null;
|
|
4563
|
-
let best = null;
|
|
4564
|
-
for (const w of listWorkspaces()) {
|
|
4565
|
-
const stored = w.path.replace(/\/+$/, "");
|
|
4566
|
-
if (stored === hint || hint.startsWith(`${stored}/`)) {
|
|
4567
|
-
if (!best || stored.length > best.length) best = stored;
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
return best;
|
|
4571
|
-
}
|
|
4572
|
-
async function resolveProjectRepoPath(repoPath) {
|
|
4573
|
-
const hint = repoPath?.trim() || process.cwd();
|
|
4574
|
-
let root = hint;
|
|
4575
|
-
try {
|
|
4576
|
-
root = await resolveRepoRoot(hint);
|
|
4577
|
-
} catch {
|
|
4578
|
-
root = hint.replace(/\/+$/, "");
|
|
4579
|
-
}
|
|
4580
|
-
return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
|
|
4581
|
-
}
|
|
4582
|
-
var PROJECT_PATH_HINT = "Pass repoPath from list_projects. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
|
|
4583
|
-
function registerViewerContextTools(server) {
|
|
4584
|
-
server.tool(
|
|
4585
|
-
"get_viewer_context",
|
|
4586
|
-
"Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_projects for project context. Worktree turns may omit repoPath (uses cwd).",
|
|
4587
|
-
{
|
|
4588
|
-
repoPath: z6.string().optional().describe(
|
|
4589
|
-
"Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
|
|
4590
|
-
)
|
|
4591
|
-
},
|
|
4592
|
-
async ({ repoPath }) => {
|
|
4593
|
-
try {
|
|
4594
|
-
const path2 = await resolveProjectRepoPath(repoPath);
|
|
4595
|
-
return text4(readViewerContext(path2 ?? void 0));
|
|
4596
|
-
} catch (err) {
|
|
4597
|
-
return text4(
|
|
4598
|
-
{ error: err instanceof Error ? err.message : String(err) },
|
|
4599
|
-
true
|
|
4600
|
-
);
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
|
-
);
|
|
4604
|
-
server.tool(
|
|
4605
|
-
"update_viewer_context",
|
|
4606
|
-
"Replace account or project user context (Settings \u2192 Agents / Projects). Never call until the user confirmed. Show the proposed text, ask_user (Save this context / Do not save), wait, then pass confirmed=true. Empty context clears that field.",
|
|
4607
|
-
{
|
|
4608
|
-
scope: z6.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
|
|
4609
|
-
context: z6.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
|
|
4610
|
-
confirmed: z6.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
|
|
4611
|
-
repoPath: z6.string().optional().describe(
|
|
4612
|
-
"Required for scope=project from orchestration (path from list_projects). Worktree turns may omit it (cwd)."
|
|
4613
|
-
)
|
|
4614
|
-
},
|
|
4615
|
-
async ({ scope, context, confirmed, repoPath }) => {
|
|
4616
|
-
try {
|
|
4617
|
-
if (scope === "project") {
|
|
4618
|
-
const path2 = await resolveProjectRepoPath(repoPath);
|
|
4619
|
-
if (!path2) {
|
|
4620
|
-
return text4(
|
|
4621
|
-
{
|
|
4622
|
-
ok: false,
|
|
4623
|
-
error: "repoPath required",
|
|
4624
|
-
message: PROJECT_PATH_HINT
|
|
4625
|
-
},
|
|
4626
|
-
true
|
|
4627
|
-
);
|
|
4628
|
-
}
|
|
4629
|
-
const result2 = writeViewerContext({
|
|
4630
|
-
scope,
|
|
4631
|
-
context,
|
|
4632
|
-
repoPath: path2,
|
|
4633
|
-
confirmed: confirmed === true
|
|
4634
|
-
});
|
|
4635
|
-
return text4(result2, !result2.ok);
|
|
4636
|
-
}
|
|
4637
|
-
const result = writeViewerContext({
|
|
4638
|
-
scope,
|
|
4639
|
-
context,
|
|
4640
|
-
confirmed: confirmed === true
|
|
4641
|
-
});
|
|
4642
|
-
return text4(result, !result.ok);
|
|
4643
|
-
} catch (err) {
|
|
4644
|
-
return text4(
|
|
4645
|
-
{ error: err instanceof Error ? err.message : String(err) },
|
|
4646
|
-
true
|
|
4647
|
-
);
|
|
4648
|
-
}
|
|
4649
|
-
}
|
|
4650
|
-
);
|
|
4712
|
+
// src/mcp/issue-vendor-tools.ts
|
|
4713
|
+
function registerConnectedIssueVendorTools(server, settings = loadAppSettings()) {
|
|
4714
|
+
registerGithubIssueTools(server);
|
|
4715
|
+
if (isLinearConnected(settings)) registerLinearTools(server);
|
|
4716
|
+
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
4651
4717
|
}
|
|
4652
4718
|
|
|
4653
4719
|
// src/mcp/pr-list.ts
|
|
@@ -4685,12 +4751,12 @@ function formatMcpPrList(input) {
|
|
|
4685
4751
|
}
|
|
4686
4752
|
|
|
4687
4753
|
// src/mcp/schedule-tools.ts
|
|
4688
|
-
import { z as
|
|
4689
|
-
function
|
|
4754
|
+
import { z as z5 } from "zod";
|
|
4755
|
+
function text4(payload, isError = false) {
|
|
4690
4756
|
return mcpJson(payload, isError);
|
|
4691
4757
|
}
|
|
4692
4758
|
function fail4(err) {
|
|
4693
|
-
return
|
|
4759
|
+
return text4(
|
|
4694
4760
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
4695
4761
|
true
|
|
4696
4762
|
);
|
|
@@ -4715,7 +4781,7 @@ function registerScheduleTools(server) {
|
|
|
4715
4781
|
{},
|
|
4716
4782
|
async () => {
|
|
4717
4783
|
try {
|
|
4718
|
-
return
|
|
4784
|
+
return text4({ schedules: listSchedules() });
|
|
4719
4785
|
} catch (err) {
|
|
4720
4786
|
return fail4(err);
|
|
4721
4787
|
}
|
|
@@ -4725,18 +4791,18 @@ function registerScheduleTools(server) {
|
|
|
4725
4791
|
"create_schedule",
|
|
4726
4792
|
"Create a local schedule that, when due, sends a prompt to an existing chat (chatId) or starts a new Global orchestration chat (omit chatId). Pass chatId=self to continue this chat. If that chat is missing or archived when the job fires, Sideboard starts a new Global chat (recurring jobs then continue that chat). Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without chatId open a new Global chat each run. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
|
|
4727
4793
|
{
|
|
4728
|
-
prompt:
|
|
4729
|
-
name:
|
|
4730
|
-
at:
|
|
4731
|
-
every:
|
|
4732
|
-
cron:
|
|
4733
|
-
tz:
|
|
4734
|
-
chatId:
|
|
4794
|
+
prompt: z5.string().describe("User message / goal queued when the schedule fires"),
|
|
4795
|
+
name: z5.string().optional(),
|
|
4796
|
+
at: z5.string().optional().describe("ISO datetime for a one-shot"),
|
|
4797
|
+
every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
4798
|
+
cron: z5.string().optional().describe("5-field cron expression"),
|
|
4799
|
+
tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
4800
|
+
chatId: z5.string().optional().describe(
|
|
4735
4801
|
'Existing chat id (orchestration or workspace), or "self" for this chat. Omit to create a new Global chat on fire.'
|
|
4736
4802
|
),
|
|
4737
|
-
threadId:
|
|
4738
|
-
agent:
|
|
4739
|
-
model:
|
|
4803
|
+
threadId: z5.string().optional().describe("Legacy alias for chatId."),
|
|
4804
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
4805
|
+
model: z5.string().optional()
|
|
4740
4806
|
},
|
|
4741
4807
|
async (args) => {
|
|
4742
4808
|
try {
|
|
@@ -4759,7 +4825,7 @@ function registerScheduleTools(server) {
|
|
|
4759
4825
|
model: args.model,
|
|
4760
4826
|
createdBy: "mcp"
|
|
4761
4827
|
});
|
|
4762
|
-
return
|
|
4828
|
+
return text4({
|
|
4763
4829
|
schedule,
|
|
4764
4830
|
hint: scheduleNeedsCaffeinate(schedule) ? "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run. Caffeinate while schedules are enabled is now on so the Mac can stay awake until this job is due." : "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run."
|
|
4765
4831
|
});
|
|
@@ -4772,20 +4838,20 @@ function registerScheduleTools(server) {
|
|
|
4772
4838
|
"update_schedule",
|
|
4773
4839
|
"Update a local schedule (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
|
|
4774
4840
|
{
|
|
4775
|
-
id:
|
|
4776
|
-
prompt:
|
|
4777
|
-
name:
|
|
4778
|
-
at:
|
|
4779
|
-
every:
|
|
4780
|
-
cron:
|
|
4781
|
-
tz:
|
|
4782
|
-
chatId:
|
|
4841
|
+
id: z5.string(),
|
|
4842
|
+
prompt: z5.string().optional(),
|
|
4843
|
+
name: z5.string().optional(),
|
|
4844
|
+
at: z5.string().optional(),
|
|
4845
|
+
every: z5.string().optional(),
|
|
4846
|
+
cron: z5.string().optional(),
|
|
4847
|
+
tz: z5.string().optional(),
|
|
4848
|
+
chatId: z5.string().optional().describe(
|
|
4783
4849
|
'Existing chat (orchestration or workspace), "self", or empty string to create a new Global chat each run'
|
|
4784
4850
|
),
|
|
4785
|
-
threadId:
|
|
4786
|
-
agent:
|
|
4787
|
-
model:
|
|
4788
|
-
enabled:
|
|
4851
|
+
threadId: z5.string().optional().describe("Legacy alias for chatId."),
|
|
4852
|
+
agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
4853
|
+
model: z5.string().optional(),
|
|
4854
|
+
enabled: z5.boolean().optional()
|
|
4789
4855
|
},
|
|
4790
4856
|
async (args) => {
|
|
4791
4857
|
try {
|
|
@@ -4817,7 +4883,7 @@ function registerScheduleTools(server) {
|
|
|
4817
4883
|
enabled: args.enabled,
|
|
4818
4884
|
model: args.model
|
|
4819
4885
|
});
|
|
4820
|
-
return
|
|
4886
|
+
return text4({ schedule });
|
|
4821
4887
|
} catch (err) {
|
|
4822
4888
|
return fail4(err);
|
|
4823
4889
|
}
|
|
@@ -4826,11 +4892,11 @@ function registerScheduleTools(server) {
|
|
|
4826
4892
|
server.tool(
|
|
4827
4893
|
"delete_schedule",
|
|
4828
4894
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
4829
|
-
{ id:
|
|
4895
|
+
{ id: z5.string() },
|
|
4830
4896
|
async ({ id }) => {
|
|
4831
4897
|
try {
|
|
4832
4898
|
deleteSchedule(id);
|
|
4833
|
-
return
|
|
4899
|
+
return text4({ ok: true, id });
|
|
4834
4900
|
} catch (err) {
|
|
4835
4901
|
return fail4(err);
|
|
4836
4902
|
}
|
|
@@ -4839,14 +4905,14 @@ function registerScheduleTools(server) {
|
|
|
4839
4905
|
server.tool(
|
|
4840
4906
|
"run_schedule",
|
|
4841
4907
|
"Fire a schedule now (does not wait for nextRunAt). Queues the prompt on the target chat or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
|
|
4842
|
-
{ id:
|
|
4908
|
+
{ id: z5.string() },
|
|
4843
4909
|
async ({ id }) => {
|
|
4844
4910
|
try {
|
|
4845
4911
|
if (!getSchedule(id)) {
|
|
4846
4912
|
return fail4(new Error(`Schedule not found: ${id}`));
|
|
4847
4913
|
}
|
|
4848
4914
|
const schedule = await fireSchedule(id);
|
|
4849
|
-
return
|
|
4915
|
+
return text4({ schedule });
|
|
4850
4916
|
} catch (err) {
|
|
4851
4917
|
return fail4(err);
|
|
4852
4918
|
}
|
|
@@ -4949,7 +5015,7 @@ async function handleJobStop(params, _ctx) {
|
|
|
4949
5015
|
return stopDetachedJob(jobCwd(rec), id, reason ? { reason } : void 0);
|
|
4950
5016
|
}
|
|
4951
5017
|
async function defaultTurnWait(ref, timeoutMs) {
|
|
4952
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
5018
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-FPGVKEID.js");
|
|
4953
5019
|
const orch = getOrchestrator2();
|
|
4954
5020
|
const thread = await orch.waitForTurn(ref, timeoutMs, {
|
|
4955
5021
|
resolveIfStillRunning: true
|
|
@@ -5286,154 +5352,7 @@ async function getHomeBoardInputs(workspaces, opts) {
|
|
|
5286
5352
|
}
|
|
5287
5353
|
}
|
|
5288
5354
|
|
|
5289
|
-
// src/mcp/create-thread-agent.ts
|
|
5290
|
-
function resolveOrchCreateThreadOptions(input) {
|
|
5291
|
-
const { resolveNewThreadOptions: resolveNewThreadOptions2 } = input;
|
|
5292
|
-
const account = resolveNewThreadOptions2({});
|
|
5293
|
-
const requested = input.requestedAgent;
|
|
5294
|
-
const parent = input.parentAgent ?? void 0;
|
|
5295
|
-
const cursorReady = input.cursorReady ?? isCursorApiKeyConfigured();
|
|
5296
|
-
let agent = account.agent;
|
|
5297
|
-
let ignoredAgent;
|
|
5298
|
-
let honorRequestedModel = false;
|
|
5299
|
-
if (requested) {
|
|
5300
|
-
const parentEcho = Boolean(parent) && requested === parent && requested !== account.agent;
|
|
5301
|
-
const cursorAutofill = requested === "cursor" && account.agent !== "cursor";
|
|
5302
|
-
if (parentEcho || cursorAutofill) {
|
|
5303
|
-
ignoredAgent = requested;
|
|
5304
|
-
} else {
|
|
5305
|
-
agent = requested;
|
|
5306
|
-
honorRequestedModel = input.requestedModel !== void 0;
|
|
5307
|
-
}
|
|
5308
|
-
}
|
|
5309
|
-
let coercedFrom;
|
|
5310
|
-
if (agent === "codex" && parent === "codex" && cursorReady) {
|
|
5311
|
-
coercedFrom = requested ?? "codex";
|
|
5312
|
-
agent = "cursor";
|
|
5313
|
-
}
|
|
5314
|
-
const opts = resolveNewThreadOptions2({
|
|
5315
|
-
agent,
|
|
5316
|
-
model: honorRequestedModel ? input.requestedModel : void 0
|
|
5317
|
-
});
|
|
5318
|
-
return {
|
|
5319
|
-
...opts,
|
|
5320
|
-
...ignoredAgent ? { ignoredAgent } : {},
|
|
5321
|
-
...coercedFrom ? { coercedFrom } : {}
|
|
5322
|
-
};
|
|
5323
|
-
}
|
|
5324
|
-
function orchCreateThreadAgentNote(input) {
|
|
5325
|
-
if (input.coercedFrom) {
|
|
5326
|
-
return `Avoid nested Codex under a Codex orchestrator \u2014 used agent=${input.agent}`;
|
|
5327
|
-
}
|
|
5328
|
-
if (input.ignoredAgent) {
|
|
5329
|
-
return `Ignored agent=${input.ignoredAgent} \u2014 used Account default agent=${input.agent}`;
|
|
5330
|
-
}
|
|
5331
|
-
return void 0;
|
|
5332
|
-
}
|
|
5333
|
-
|
|
5334
5355
|
// src/mcp/server.ts
|
|
5335
|
-
var MAX_ORCH_THREADS = 5;
|
|
5336
|
-
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
5337
|
-
function withTimeout(promise, ms, label) {
|
|
5338
|
-
return new Promise((resolve, reject) => {
|
|
5339
|
-
const timer = setTimeout(() => {
|
|
5340
|
-
reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
|
|
5341
|
-
}, ms);
|
|
5342
|
-
promise.then(
|
|
5343
|
-
(value) => {
|
|
5344
|
-
clearTimeout(timer);
|
|
5345
|
-
resolve(value);
|
|
5346
|
-
},
|
|
5347
|
-
(err) => {
|
|
5348
|
-
clearTimeout(timer);
|
|
5349
|
-
reject(err);
|
|
5350
|
-
}
|
|
5351
|
-
);
|
|
5352
|
-
});
|
|
5353
|
-
}
|
|
5354
|
-
async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
|
|
5355
|
-
const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
|
|
5356
|
-
let parentId = args.parentChatId?.trim() || args.parentThreadId?.trim() || "";
|
|
5357
|
-
let parent = parentId ? orch.getThread(parentId) : null;
|
|
5358
|
-
let parentCorrectedFrom;
|
|
5359
|
-
if (envParentId) {
|
|
5360
|
-
const envParent = orch.getThread(envParentId);
|
|
5361
|
-
if (envParent) {
|
|
5362
|
-
if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
|
|
5363
|
-
else if (!parentId) parentCorrectedFrom = void 0;
|
|
5364
|
-
parentId = envParentId;
|
|
5365
|
-
parent = envParent;
|
|
5366
|
-
}
|
|
5367
|
-
}
|
|
5368
|
-
if (parentId && !parent) {
|
|
5369
|
-
parentCorrectedFrom = parentId;
|
|
5370
|
-
parentId = "";
|
|
5371
|
-
parent = null;
|
|
5372
|
-
}
|
|
5373
|
-
if (parentId) {
|
|
5374
|
-
const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
|
|
5375
|
-
if (children.length >= MAX_ORCH_THREADS) {
|
|
5376
|
-
return {
|
|
5377
|
-
ok: false,
|
|
5378
|
-
text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
|
|
5379
|
-
};
|
|
5380
|
-
}
|
|
5381
|
-
}
|
|
5382
|
-
const opts = resolveOrchCreateThreadOptions({
|
|
5383
|
-
requestedAgent: args.agent,
|
|
5384
|
-
requestedModel: args.model,
|
|
5385
|
-
parentAgent: parent?.agent,
|
|
5386
|
-
resolveNewThreadOptions: resolveNewThreadOptions2
|
|
5387
|
-
});
|
|
5388
|
-
const agentNote = orchCreateThreadAgentNote(opts);
|
|
5389
|
-
try {
|
|
5390
|
-
const priorIds = new Set(orch.getThreads(false).map((t) => t.id));
|
|
5391
|
-
const thread = await withTimeout(
|
|
5392
|
-
orch.createThread({
|
|
5393
|
-
sourceType: args.sourceType,
|
|
5394
|
-
sourceRef: args.sourceRef,
|
|
5395
|
-
agent: opts.agent,
|
|
5396
|
-
model: opts.model,
|
|
5397
|
-
effort: opts.effort,
|
|
5398
|
-
fast: opts.fast,
|
|
5399
|
-
repoPath: args.repoPath,
|
|
5400
|
-
title: args.title,
|
|
5401
|
-
parentThreadId: parentId || null,
|
|
5402
|
-
cowboy: args.cowboy || void 0,
|
|
5403
|
-
attachments: args.attachments
|
|
5404
|
-
}),
|
|
5405
|
-
CREATE_THREAD_TIMEOUT_MS,
|
|
5406
|
-
"create_workspace"
|
|
5407
|
-
);
|
|
5408
|
-
const alreadyStarted = priorIds.has(thread.id);
|
|
5409
|
-
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
5410
|
-
return {
|
|
5411
|
-
ok: true,
|
|
5412
|
-
text: JSON.stringify({
|
|
5413
|
-
id: thread.id,
|
|
5414
|
-
title: thread.title,
|
|
5415
|
-
branchName: thread.branchName,
|
|
5416
|
-
worktreePath: thread.worktreePath,
|
|
5417
|
-
agent: thread.agent,
|
|
5418
|
-
model: thread.model,
|
|
5419
|
-
status: thread.status,
|
|
5420
|
-
cowboy: Boolean(thread.cowboy),
|
|
5421
|
-
link: `sideboard://chat/${thread.id}`,
|
|
5422
|
-
parentChatId: thread.parentThreadId,
|
|
5423
|
-
parentThreadId: thread.parentThreadId,
|
|
5424
|
-
chats: workspaceAgentChatSummaries(thread.worktreePath),
|
|
5425
|
-
...alreadyStarted ? { alreadyStarted: true } : {},
|
|
5426
|
-
...opts.coercedFrom ? { agentCoercedFrom: opts.coercedFrom } : {},
|
|
5427
|
-
...opts.ignoredAgent ? { agentIgnored: opts.ignoredAgent } : {},
|
|
5428
|
-
...agentNote ? { note: agentNote } : {},
|
|
5429
|
-
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
5430
|
-
})
|
|
5431
|
-
};
|
|
5432
|
-
} catch (err) {
|
|
5433
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5434
|
-
return { ok: false, text: `create_workspace failed: ${message}` };
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
5356
|
function previewHint(preview) {
|
|
5438
5357
|
return preview === "window" ? "Electron: the native window is the app. url is the renderer/HMR origin only \u2014 do not open it in a browser (no preload/IPC)." : "Open url in a browser, curl, or Playwright to use the app.";
|
|
5439
5358
|
}
|
|
@@ -5531,16 +5450,16 @@ async function startMcpServer() {
|
|
|
5531
5450
|
"list_board",
|
|
5532
5451
|
"Home Kanban of workspaces (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chats on that card are separate agents. 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), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live checkout if that ticket/PR/named branch is already checked out. That reuse is one checkout, not one agent \u2014 fork_chat to add another.",
|
|
5533
5452
|
{
|
|
5534
|
-
query:
|
|
5535
|
-
repoPath:
|
|
5536
|
-
kind:
|
|
5537
|
-
ownership:
|
|
5453
|
+
query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, workspace tags, repo"),
|
|
5454
|
+
repoPath: z6.string().optional().describe("Limit to one project path from list_projects"),
|
|
5455
|
+
kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
5456
|
+
ownership: z6.enum(["all", "mine", "reviewing"]).optional().describe(
|
|
5538
5457
|
"Mine = PRs you authored or local WIP; reviewing = someone else's PR. Uses the gh login."
|
|
5539
5458
|
),
|
|
5540
|
-
column:
|
|
5459
|
+
column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
5541
5460
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
5542
5461
|
),
|
|
5543
|
-
limit:
|
|
5462
|
+
limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
5544
5463
|
},
|
|
5545
5464
|
async ({ query, repoPath, kind, ownership, column, limit }) => {
|
|
5546
5465
|
const workspaces = orch.listWorkspaces();
|
|
@@ -5570,7 +5489,7 @@ async function startMcpServer() {
|
|
|
5570
5489
|
server.tool(
|
|
5571
5490
|
"get_chat",
|
|
5572
5491
|
"Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage. blockedReason is why that agent is waiting on a person (question, plan approval, or a reported block).",
|
|
5573
|
-
{ ref:
|
|
5492
|
+
{ ref: z6.string() },
|
|
5574
5493
|
async ({ ref }) => {
|
|
5575
5494
|
const t = orch.getThread(ref);
|
|
5576
5495
|
if (!t) {
|
|
@@ -5592,6 +5511,7 @@ async function startMcpServer() {
|
|
|
5592
5511
|
sessionId: t.sessionId,
|
|
5593
5512
|
parentChatId: t.parentThreadId,
|
|
5594
5513
|
parentThreadId: t.parentThreadId,
|
|
5514
|
+
tags: t.tags ?? [],
|
|
5595
5515
|
children: childThreadRefs(t.id, orch.getThreads(false)),
|
|
5596
5516
|
queueLength: t.queue.length,
|
|
5597
5517
|
messageCount: t.messages.length,
|
|
@@ -5616,17 +5536,17 @@ async function startMcpServer() {
|
|
|
5616
5536
|
"present_artifact",
|
|
5617
5537
|
"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.",
|
|
5618
5538
|
{
|
|
5619
|
-
title:
|
|
5620
|
-
type:
|
|
5539
|
+
title: z6.string().describe("Short title shown in the artifact pane header"),
|
|
5540
|
+
type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
5621
5541
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
5622
5542
|
),
|
|
5623
|
-
content:
|
|
5543
|
+
content: z6.string().describe(
|
|
5624
5544
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
5625
5545
|
),
|
|
5626
|
-
artifact_id:
|
|
5627
|
-
status:
|
|
5628
|
-
phase:
|
|
5629
|
-
mode:
|
|
5546
|
+
artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
5547
|
+
status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
5548
|
+
phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
5549
|
+
mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
5630
5550
|
},
|
|
5631
5551
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
5632
5552
|
return mcpJson(
|
|
@@ -5639,15 +5559,15 @@ async function startMcpServer() {
|
|
|
5639
5559
|
"ask_user",
|
|
5640
5560
|
"Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
|
|
5641
5561
|
{
|
|
5642
|
-
questions:
|
|
5643
|
-
|
|
5644
|
-
question:
|
|
5645
|
-
header:
|
|
5646
|
-
multiSelect:
|
|
5647
|
-
options:
|
|
5648
|
-
|
|
5649
|
-
label:
|
|
5650
|
-
description:
|
|
5562
|
+
questions: z6.array(
|
|
5563
|
+
z6.object({
|
|
5564
|
+
question: z6.string().describe("Full question text ending with ?"),
|
|
5565
|
+
header: z6.string().max(24).optional().describe("Short label shown above the question"),
|
|
5566
|
+
multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
|
|
5567
|
+
options: z6.array(
|
|
5568
|
+
z6.object({
|
|
5569
|
+
label: z6.string(),
|
|
5570
|
+
description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
5651
5571
|
})
|
|
5652
5572
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
5653
5573
|
})
|
|
@@ -5673,9 +5593,9 @@ async function startMcpServer() {
|
|
|
5673
5593
|
"present_plan",
|
|
5674
5594
|
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
5675
5595
|
{
|
|
5676
|
-
title:
|
|
5677
|
-
content:
|
|
5678
|
-
thread_id:
|
|
5596
|
+
title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
5597
|
+
content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
5598
|
+
thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
5679
5599
|
},
|
|
5680
5600
|
async ({ title, content, thread_id }) => {
|
|
5681
5601
|
const { writePlanFile: writePlanFile2 } = await import("./plan-file-7MMIPV7Y.js");
|
|
@@ -5704,15 +5624,15 @@ async function startMcpServer() {
|
|
|
5704
5624
|
"present_schema",
|
|
5705
5625
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
|
|
5706
5626
|
{
|
|
5707
|
-
title:
|
|
5708
|
-
mode:
|
|
5709
|
-
datasource:
|
|
5710
|
-
resource_id:
|
|
5711
|
-
record_id:
|
|
5712
|
-
resource:
|
|
5713
|
-
record:
|
|
5714
|
-
records:
|
|
5715
|
-
pane_id:
|
|
5627
|
+
title: z6.string().describe("Pane title"),
|
|
5628
|
+
mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
5629
|
+
datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
5630
|
+
resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
5631
|
+
record_id: z6.string().optional().describe("Record id when opening form mode"),
|
|
5632
|
+
resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
5633
|
+
record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
5634
|
+
records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
|
|
5635
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
5716
5636
|
},
|
|
5717
5637
|
async (args) => {
|
|
5718
5638
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -5733,10 +5653,10 @@ async function startMcpServer() {
|
|
|
5733
5653
|
"present_files",
|
|
5734
5654
|
"Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
|
|
5735
5655
|
{
|
|
5736
|
-
title:
|
|
5737
|
-
datasource:
|
|
5738
|
-
path:
|
|
5739
|
-
pane_id:
|
|
5656
|
+
title: z6.string().optional().describe("Pane title (default: Files)"),
|
|
5657
|
+
datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
5658
|
+
path: z6.string().optional().describe("Initial folder path (e.g. public)"),
|
|
5659
|
+
pane_id: z6.string().optional().describe("Stable pane id across updates")
|
|
5740
5660
|
},
|
|
5741
5661
|
async (args) => {
|
|
5742
5662
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -5756,8 +5676,8 @@ async function startMcpServer() {
|
|
|
5756
5676
|
"wait_for_job",
|
|
5757
5677
|
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
5758
5678
|
{
|
|
5759
|
-
id:
|
|
5760
|
-
timeoutMs:
|
|
5679
|
+
id: z6.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
5680
|
+
timeoutMs: z6.number().optional()
|
|
5761
5681
|
},
|
|
5762
5682
|
async ({ id, timeoutMs }) => {
|
|
5763
5683
|
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
@@ -5770,8 +5690,8 @@ async function startMcpServer() {
|
|
|
5770
5690
|
"stop_job",
|
|
5771
5691
|
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
|
|
5772
5692
|
{
|
|
5773
|
-
id:
|
|
5774
|
-
reason:
|
|
5693
|
+
id: z6.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
5694
|
+
reason: z6.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the answer)")
|
|
5775
5695
|
},
|
|
5776
5696
|
async ({ id, reason }) => {
|
|
5777
5697
|
const result = await stopDetachedJob(process.cwd(), id, { reason });
|
|
@@ -5783,7 +5703,7 @@ async function startMcpServer() {
|
|
|
5783
5703
|
"list_run_scripts",
|
|
5784
5704
|
"List .sideboard/.conductor run scripts for this thread (same menu as the desktop Dev button) and which are active. Each script and active run includes preview: url (open http://localhost:<SIDEBOARD_PORT>) or window (Electron: native window is the app; url is renderer/HMR only). Never guess :3000. Worktree turns may omit ref (uses cwd).",
|
|
5785
5705
|
{
|
|
5786
|
-
ref:
|
|
5706
|
+
ref: z6.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd).")
|
|
5787
5707
|
},
|
|
5788
5708
|
async ({ ref }) => {
|
|
5789
5709
|
try {
|
|
@@ -5813,8 +5733,8 @@ async function startMcpServer() {
|
|
|
5813
5733
|
"run_dev_script",
|
|
5814
5734
|
"Start a .sideboard/.conductor run script (same as the desktop Dev / play button). Several named scripts can run at once; each gets its own port, and starting one does not stop the others. Logs and Stop appear in the thread UI \u2014 do not detached-job or shell-spawn the same command. Returns {port, ports, url, preview}. preview=url \u2192 open url (browser, curl, Playwright). preview=window \u2192 Electron: the native window is the app; url is renderer/HMR only (no preload/IPC in a browser). The port is allocated SIDEBOARD_PORT, not 3000. Worktree turns may omit ref (uses cwd). Omit name for the default script.",
|
|
5815
5735
|
{
|
|
5816
|
-
ref:
|
|
5817
|
-
name:
|
|
5736
|
+
ref: z6.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
|
|
5737
|
+
name: z6.string().optional().describe("Script name from list_run_scripts (default script if omitted).")
|
|
5818
5738
|
},
|
|
5819
5739
|
async ({ ref, name }) => {
|
|
5820
5740
|
try {
|
|
@@ -5842,8 +5762,8 @@ async function startMcpServer() {
|
|
|
5842
5762
|
"stop_dev_script",
|
|
5843
5763
|
"Stop a run script started with run_dev_script or the desktop Dev button (same as UI Stop). Omit name to stop all scripts on this worktree. Worktree turns may omit ref (uses cwd).",
|
|
5844
5764
|
{
|
|
5845
|
-
ref:
|
|
5846
|
-
name:
|
|
5765
|
+
ref: z6.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
|
|
5766
|
+
name: z6.string().optional().describe("Script name to stop; omit to stop all active scripts.")
|
|
5847
5767
|
},
|
|
5848
5768
|
async ({ ref, name }) => {
|
|
5849
5769
|
try {
|
|
@@ -5860,9 +5780,9 @@ async function startMcpServer() {
|
|
|
5860
5780
|
"get_run_log",
|
|
5861
5781
|
"Read the Run-tab terminal for a worktree script (same stdout/stderr as the desktop Dev pane). Default is a tail \u2014 raise `tail` only if the error is missing. Worktree turns may omit ref (uses cwd). Omit name for the default script.",
|
|
5862
5782
|
{
|
|
5863
|
-
ref:
|
|
5864
|
-
name:
|
|
5865
|
-
tail:
|
|
5783
|
+
ref: z6.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
|
|
5784
|
+
name: z6.string().optional().describe("Script name from list_run_scripts (default script if omitted)."),
|
|
5785
|
+
tail: z6.number().int().min(1).max(MAX_RUN_LOG_CHARS).optional().describe(
|
|
5866
5786
|
`Max characters to return from the end (default ${DEFAULT_RUN_LOG_TAIL_CHARS}). Do not request the full buffer unless the tail is truncated and you still need more.`
|
|
5867
5787
|
)
|
|
5868
5788
|
},
|
|
@@ -5900,8 +5820,8 @@ async function startMcpServer() {
|
|
|
5900
5820
|
"notify_orchestrator",
|
|
5901
5821
|
"Wake the parent Global orchestrator with a short status so it does not have to poll wait_for_turn. Use when you are blocked and the parent may have moved on (missing access, waiting on something the coordinator must handle). The message is shown on the sidebar and board until the next user message in this chat. ask_user already notifies for input-required \u2014 do not call this for the same question. Information only: never a git command. Do not use this to steer other worktrees.",
|
|
5902
5822
|
{
|
|
5903
|
-
message:
|
|
5904
|
-
reason:
|
|
5823
|
+
message: z6.string().min(1).max(1500).describe("Why you are blocked / what the parent should know (short)."),
|
|
5824
|
+
reason: z6.enum(["input-required", "blocked"]).optional().describe(
|
|
5905
5825
|
"input-required = waiting on the user in this chat; blocked = need the coordinator (default)."
|
|
5906
5826
|
)
|
|
5907
5827
|
},
|
|
@@ -5927,13 +5847,45 @@ async function startMcpServer() {
|
|
|
5927
5847
|
...result.deduped ? { deduped: true } : {}
|
|
5928
5848
|
});
|
|
5929
5849
|
} catch (err) {
|
|
5930
|
-
const
|
|
5931
|
-
return { content: [{ type: "text", text:
|
|
5850
|
+
const text5 = err instanceof Error ? err.message : String(err);
|
|
5851
|
+
return { content: [{ type: "text", text: text5 }], isError: true };
|
|
5932
5852
|
}
|
|
5933
5853
|
}
|
|
5934
5854
|
);
|
|
5935
5855
|
}
|
|
5936
5856
|
registerScheduleTools(server);
|
|
5857
|
+
server.tool(
|
|
5858
|
+
"set_workspace_tags",
|
|
5859
|
+
worktreeProfile ? "Update tags on this workspace (the checkout you are in). Omit ref to use this chat. mode=replace (default), add, or remove. Short labels \u2014 the same ones the orchestrator used for this task. You cannot retag a different checkout." : "Label a project workspace so related checkouts stay recognizable in the sidebar and on the phone. Pass any chat id on that workspace. Use the same tags on every workspace from one task. mode=replace is the default; add and remove change the set. create_workspace tags= sets them at create time. Not for orchestration chats.",
|
|
5860
|
+
{
|
|
5861
|
+
ref: z6.string().optional().describe(
|
|
5862
|
+
worktreeProfile ? "Omit for this chat. Another chat id only if it is on this same checkout." : "Chat id on the workspace to label."
|
|
5863
|
+
),
|
|
5864
|
+
tags: z6.union([z6.string(), z6.array(z6.string())]).describe("Labels to set. A string may be comma-separated."),
|
|
5865
|
+
mode: z6.enum(["replace", "add", "remove"]).optional()
|
|
5866
|
+
},
|
|
5867
|
+
async ({ ref, tags, mode }) => {
|
|
5868
|
+
try {
|
|
5869
|
+
const callerId = process.env[SIDEBOARD_THREAD_ID_ENV]?.trim() || "";
|
|
5870
|
+
const target = workspaceTagTargetRef({
|
|
5871
|
+
ownWorktreeOnly: worktreeProfile,
|
|
5872
|
+
caller: callerId ? orch.getThread(callerId) : null,
|
|
5873
|
+
requestedRef: ref,
|
|
5874
|
+
find: (id) => orch.getThread(id)
|
|
5875
|
+
});
|
|
5876
|
+
const thread = applyWorkspaceTags(target, tags, mode ?? "replace");
|
|
5877
|
+
return mcpJson({
|
|
5878
|
+
id: thread.id,
|
|
5879
|
+
worktreePath: thread.worktreePath,
|
|
5880
|
+
tags: thread.tags ?? [],
|
|
5881
|
+
chats: workspaceAgentChatSummaries(thread.worktreePath)
|
|
5882
|
+
});
|
|
5883
|
+
} catch (err) {
|
|
5884
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5885
|
+
return mcpJson({ ok: false, message }, true);
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5888
|
+
);
|
|
5937
5889
|
if (!worktreeProfile) {
|
|
5938
5890
|
registerConnectedIssueVendorTools(server);
|
|
5939
5891
|
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-DYJGWK6Y.js");
|
|
@@ -5944,7 +5896,7 @@ async function startMcpServer() {
|
|
|
5944
5896
|
"set_caffeinate",
|
|
5945
5897
|
"Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. Closing or archiving this orchestration chat also releases the hold. macOS only.",
|
|
5946
5898
|
{
|
|
5947
|
-
enabled:
|
|
5899
|
+
enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
5948
5900
|
},
|
|
5949
5901
|
async ({ enabled }) => {
|
|
5950
5902
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -5968,25 +5920,28 @@ async function startMcpServer() {
|
|
|
5968
5920
|
);
|
|
5969
5921
|
server.tool(
|
|
5970
5922
|
"create_workspace",
|
|
5971
|
-
`Create a workspace (isolated git checkout + first chat) from branch, pr, or ticket. A ticket, PR, or named branch has one checkout \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. That is not one agent: chats lists every agent on that checkout. Add another with fork_chat; send_to_chat only the chat that should do this job. Creating from the default branch still opens a new isolated checkout. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy).`,
|
|
5923
|
+
`Create a workspace (isolated git checkout + first chat) from branch, pr, or ticket. A ticket, PR, or named branch has one checkout \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. That is not one agent: chats lists every agent on that checkout. Add another with fork_chat; send_to_chat only the chat that should do this job. Creating from the default branch still opens a new isolated checkout. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Pass the same tags on every workspace you open for one task (short labels such as "phone-sync") so they still match in the sidebar the next day. Reusing a checkout adds those tags.`,
|
|
5972
5924
|
{
|
|
5973
|
-
sourceType:
|
|
5974
|
-
sourceRef:
|
|
5975
|
-
agent:
|
|
5925
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]),
|
|
5926
|
+
sourceRef: z6.string(),
|
|
5927
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(
|
|
5976
5928
|
`Usually omit. Sideboard uses Account default agent (${accountDefaults.agent}). Passing your own agent or cursor is ignored.`
|
|
5977
5929
|
),
|
|
5978
|
-
model:
|
|
5930
|
+
model: z6.string().nullable().optional().describe(
|
|
5979
5931
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
5980
5932
|
),
|
|
5981
|
-
repoPath:
|
|
5982
|
-
title:
|
|
5983
|
-
cowboy:
|
|
5933
|
+
repoPath: z6.string(),
|
|
5934
|
+
title: z6.string().optional(),
|
|
5935
|
+
cowboy: z6.boolean().optional().describe(
|
|
5984
5936
|
"If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
|
|
5985
5937
|
),
|
|
5986
|
-
parentChatId:
|
|
5938
|
+
parentChatId: z6.string().optional().describe(
|
|
5987
5939
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
5988
5940
|
),
|
|
5989
|
-
parentThreadId:
|
|
5941
|
+
parentThreadId: z6.string().optional().describe("Legacy alias for parentChatId."),
|
|
5942
|
+
tags: z6.union([z6.string(), z6.array(z6.string())]).optional().describe(
|
|
5943
|
+
"Short labels for this workspace (or a comma-separated string). Use the same tags on every checkout for one task. Added when the checkout already exists."
|
|
5944
|
+
)
|
|
5990
5945
|
},
|
|
5991
5946
|
async (args) => {
|
|
5992
5947
|
const result = await createOrchChildThread(orch, args, resolveNewThreadOptions2);
|
|
@@ -6000,12 +5955,15 @@ async function startMcpServer() {
|
|
|
6000
5955
|
"start_board_card",
|
|
6001
5956
|
"Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second checkout when one already matches \u2014 returns that workspace (alreadyStarted) and its chats. A workspace can have many agents. fork_chat to add one; send_to_chat only the chat that should do this job.",
|
|
6002
5957
|
{
|
|
6003
|
-
kind:
|
|
6004
|
-
ref:
|
|
6005
|
-
repoPath:
|
|
6006
|
-
title:
|
|
5958
|
+
kind: z6.enum(["ticket", "pr", "branch"]),
|
|
5959
|
+
ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
5960
|
+
repoPath: z6.string().describe("Project path from list_projects / list_board"),
|
|
5961
|
+
title: z6.string().optional(),
|
|
5962
|
+
tags: z6.union([z6.string(), z6.array(z6.string())]).optional().describe(
|
|
5963
|
+
"Short labels for this workspace. Same tags on every checkout for one task. Added when the checkout already exists."
|
|
5964
|
+
)
|
|
6007
5965
|
},
|
|
6008
|
-
async ({ kind, ref, repoPath, title }) => {
|
|
5966
|
+
async ({ kind, ref, repoPath, title, tags }) => {
|
|
6009
5967
|
const root = await resolveRepoRoot(repoPath);
|
|
6010
5968
|
const existing = findLiveThreadForCreate(
|
|
6011
5969
|
{
|
|
@@ -6020,13 +5978,15 @@ async function startMcpServer() {
|
|
|
6020
5978
|
}))
|
|
6021
5979
|
);
|
|
6022
5980
|
if (existing) {
|
|
5981
|
+
const tagged = tags === void 0 ? orch.getThread(existing.id) : applyWorkspaceTags(existing.id, tags, "add");
|
|
6023
5982
|
return mcpJson({
|
|
6024
5983
|
alreadyStarted: true,
|
|
6025
5984
|
id: existing.id,
|
|
6026
5985
|
title: existing.title,
|
|
6027
5986
|
status: existing.status,
|
|
5987
|
+
tags: tagged?.tags ?? [],
|
|
6028
5988
|
link: `sideboard://chat/${existing.id}`,
|
|
6029
|
-
chats: workspaceAgentChatSummaries(
|
|
5989
|
+
chats: workspaceAgentChatSummaries(tagged?.worktreePath)
|
|
6030
5990
|
});
|
|
6031
5991
|
}
|
|
6032
5992
|
const workspaces = orch.listWorkspaces();
|
|
@@ -6040,7 +6000,8 @@ async function startMcpServer() {
|
|
|
6040
6000
|
sourceType: "branch",
|
|
6041
6001
|
sourceRef,
|
|
6042
6002
|
repoPath: root,
|
|
6043
|
-
title: title?.trim() || pin?.title || (sourceRef === "default" ? void 0 : sourceRef)
|
|
6003
|
+
title: title?.trim() || pin?.title || (sourceRef === "default" ? void 0 : sourceRef),
|
|
6004
|
+
tags
|
|
6044
6005
|
},
|
|
6045
6006
|
resolveNewThreadOptions2
|
|
6046
6007
|
);
|
|
@@ -6070,7 +6031,8 @@ async function startMcpServer() {
|
|
|
6070
6031
|
sourceRef: ident,
|
|
6071
6032
|
repoPath: root,
|
|
6072
6033
|
title: title?.trim() || issue?.title,
|
|
6073
|
-
attachments
|
|
6034
|
+
attachments,
|
|
6035
|
+
tags
|
|
6074
6036
|
},
|
|
6075
6037
|
resolveNewThreadOptions2
|
|
6076
6038
|
);
|
|
@@ -6087,7 +6049,8 @@ async function startMcpServer() {
|
|
|
6087
6049
|
sourceType: "pr",
|
|
6088
6050
|
sourceRef: number,
|
|
6089
6051
|
repoPath: root,
|
|
6090
|
-
title: title?.trim() || pr?.title
|
|
6052
|
+
title: title?.trim() || pr?.title,
|
|
6053
|
+
tags
|
|
6091
6054
|
},
|
|
6092
6055
|
resolveNewThreadOptions2
|
|
6093
6056
|
);
|
|
@@ -6099,11 +6062,11 @@ async function startMcpServer() {
|
|
|
6099
6062
|
);
|
|
6100
6063
|
server.tool(
|
|
6101
6064
|
"send_to_chat",
|
|
6102
|
-
'Steer a prompt on a workspace chat (Settings \u2192 Follow-up, default steer:
|
|
6065
|
+
'Steer a prompt on a workspace chat (Settings \u2192 Follow-up, default steer: interrupt the in-flight turn and start now, same as the workspace composer). Use after create_workspace 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_chat 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.',
|
|
6103
6066
|
{
|
|
6104
|
-
ref:
|
|
6105
|
-
prompt:
|
|
6106
|
-
force_stop:
|
|
6067
|
+
ref: z6.string(),
|
|
6068
|
+
prompt: z6.string(),
|
|
6069
|
+
force_stop: z6.boolean().optional()
|
|
6107
6070
|
},
|
|
6108
6071
|
async ({ ref, prompt, force_stop }) => {
|
|
6109
6072
|
if (force_stop) {
|
|
@@ -6128,8 +6091,8 @@ async function startMcpServer() {
|
|
|
6128
6091
|
"wait_for_turn",
|
|
6129
6092
|
"Wait until the chat 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. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. blockedReason is why the child is waiting on a person (the question, plan approval, or a reported block) \u2014 read it and do not send_to_chat a check-in. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
|
|
6130
6093
|
{
|
|
6131
|
-
ref:
|
|
6132
|
-
timeoutMs:
|
|
6094
|
+
ref: z6.string(),
|
|
6095
|
+
timeoutMs: z6.number().optional()
|
|
6133
6096
|
},
|
|
6134
6097
|
async ({ ref, timeoutMs }) => {
|
|
6135
6098
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -6157,7 +6120,7 @@ async function startMcpServer() {
|
|
|
6157
6120
|
server.tool(
|
|
6158
6121
|
"get_turn_result",
|
|
6159
6122
|
"Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle) and blockedReason (why the agent is waiting on a person).",
|
|
6160
|
-
{ ref:
|
|
6123
|
+
{ ref: z6.string() },
|
|
6161
6124
|
async ({ ref }) => {
|
|
6162
6125
|
const result = orch.getTurnResult(ref);
|
|
6163
6126
|
const hint = mcpWaitTaskHint(result.taskState, result.status, result.blockedReason);
|
|
@@ -6171,12 +6134,13 @@ async function startMcpServer() {
|
|
|
6171
6134
|
);
|
|
6172
6135
|
server.tool(
|
|
6173
6136
|
"stop_chat",
|
|
6174
|
-
"Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
|
|
6137
|
+
"Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true. Pass reason \u2014 a short note on why this chat is stopping. It is stored on the thread and kept when the turn unwinds. If you omit it, Sideboard still records that the queue was cleared.",
|
|
6175
6138
|
{
|
|
6176
|
-
ref:
|
|
6177
|
-
force:
|
|
6139
|
+
ref: z6.string(),
|
|
6140
|
+
force: z6.boolean().optional(),
|
|
6141
|
+
reason: z6.string().optional().describe("Why this chat is stopping. Stored on the thread for the next session.")
|
|
6178
6142
|
},
|
|
6179
|
-
async ({ ref, force }) => {
|
|
6143
|
+
async ({ ref, force, reason }) => {
|
|
6180
6144
|
const t = orch.getThread(ref);
|
|
6181
6145
|
if (!t) {
|
|
6182
6146
|
return {
|
|
@@ -6186,18 +6150,19 @@ async function startMcpServer() {
|
|
|
6186
6150
|
}
|
|
6187
6151
|
const clearQueue = force !== false;
|
|
6188
6152
|
const hadQueued = t.queue.length > 0;
|
|
6189
|
-
const stopped = orch.stop(ref, { clearQueue, notifyParent: false });
|
|
6153
|
+
const stopped = orch.stop(ref, { clearQueue, notifyParent: false, reason });
|
|
6190
6154
|
return mcpJson({
|
|
6191
6155
|
id: stopped.id,
|
|
6192
6156
|
status: stopped.status,
|
|
6193
|
-
clearedQueue: clearQueue && hadQueued
|
|
6157
|
+
clearedQueue: clearQueue && hadQueued,
|
|
6158
|
+
lastError: stopped.lastError ?? null
|
|
6194
6159
|
});
|
|
6195
6160
|
}
|
|
6196
6161
|
);
|
|
6197
6162
|
server.tool(
|
|
6198
6163
|
"archive_chat",
|
|
6199
6164
|
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
|
|
6200
|
-
{ ref:
|
|
6165
|
+
{ ref: z6.string() },
|
|
6201
6166
|
async ({ ref }) => {
|
|
6202
6167
|
const t = orch.getThread(ref);
|
|
6203
6168
|
if (!t) {
|
|
@@ -6223,7 +6188,7 @@ async function startMcpServer() {
|
|
|
6223
6188
|
server.tool(
|
|
6224
6189
|
"restore_chat",
|
|
6225
6190
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
6226
|
-
{ ref:
|
|
6191
|
+
{ ref: z6.string() },
|
|
6227
6192
|
async ({ ref }) => {
|
|
6228
6193
|
try {
|
|
6229
6194
|
const restored = await orch.restore(ref);
|
|
@@ -6242,8 +6207,8 @@ async function startMcpServer() {
|
|
|
6242
6207
|
"get_diff",
|
|
6243
6208
|
"Compact diff summary (capped hunks, paginated)",
|
|
6244
6209
|
{
|
|
6245
|
-
ref:
|
|
6246
|
-
maxFiles:
|
|
6210
|
+
ref: z6.string(),
|
|
6211
|
+
maxFiles: z6.number().optional()
|
|
6247
6212
|
},
|
|
6248
6213
|
async ({ ref, maxFiles }) => {
|
|
6249
6214
|
const summary = await orch.diffSummary(ref);
|
|
@@ -6256,7 +6221,7 @@ async function startMcpServer() {
|
|
|
6256
6221
|
server.tool(
|
|
6257
6222
|
"get_pr_checks",
|
|
6258
6223
|
"Snapshot of GitHub PR checks for a worktree thread (`gh pr checks` plus merge/review gates). null = no PR. If the user gave a goal (Greptile 5/5, CI green), the worktree agent watches with `gh pr checks --watch` via /long-running \u2014 this tool is a snapshot, not a waiter. Coordinators: do not run gh from the orchestration cwd.",
|
|
6259
|
-
{ ref:
|
|
6224
|
+
{ ref: z6.string().describe("Worktree thread id/ref") },
|
|
6260
6225
|
async ({ ref }) => {
|
|
6261
6226
|
try {
|
|
6262
6227
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -6270,7 +6235,7 @@ async function startMcpServer() {
|
|
|
6270
6235
|
server.tool(
|
|
6271
6236
|
"request_review",
|
|
6272
6237
|
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). If that worktree has a single unused idle agent (no messages), runs the review there; otherwise opens a new Review chat tab. Attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information in that chat. The review stays there so the user can read it and type next steps \u2014 do not comment on or update the PR or ticket, and do not ask_user after the review, until they ask. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review thread id.',
|
|
6273
|
-
{ ref:
|
|
6238
|
+
{ ref: z6.string().describe("Worktree thread id/ref to review") },
|
|
6274
6239
|
async ({ ref }) => {
|
|
6275
6240
|
try {
|
|
6276
6241
|
const tab = await orch.requestReview(ref);
|
|
@@ -6292,8 +6257,8 @@ async function startMcpServer() {
|
|
|
6292
6257
|
"ask_git",
|
|
6293
6258
|
"Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (including Resolve). Always steers the worktree agent; does not push or open the PR locally. 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.",
|
|
6294
6259
|
{
|
|
6295
|
-
ref:
|
|
6296
|
-
action:
|
|
6260
|
+
ref: z6.string().describe("Worktree thread id/ref"),
|
|
6261
|
+
action: z6.enum(AGENT_GIT_ACTIONS).describe(
|
|
6297
6262
|
"commit-push | create-draft | create-web | resolve-conflicts | ready-for-review | merge"
|
|
6298
6263
|
)
|
|
6299
6264
|
},
|
|
@@ -6322,7 +6287,7 @@ async function startMcpServer() {
|
|
|
6322
6287
|
}
|
|
6323
6288
|
}
|
|
6324
6289
|
);
|
|
6325
|
-
const agentEnum =
|
|
6290
|
+
const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
6326
6291
|
server.tool(
|
|
6327
6292
|
"list_models",
|
|
6328
6293
|
"List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
|
|
@@ -6343,11 +6308,11 @@ async function startMcpServer() {
|
|
|
6343
6308
|
"fork_workspace",
|
|
6344
6309
|
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id.",
|
|
6345
6310
|
{
|
|
6346
|
-
ref:
|
|
6347
|
-
through_index:
|
|
6311
|
+
ref: z6.string().describe("Worktree thread id/ref to fork"),
|
|
6312
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
6348
6313
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
6349
|
-
model:
|
|
6350
|
-
title:
|
|
6314
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
6315
|
+
title: z6.string().optional()
|
|
6351
6316
|
},
|
|
6352
6317
|
async ({ ref, through_index, agent, model, title }) => {
|
|
6353
6318
|
try {
|
|
@@ -6381,11 +6346,11 @@ async function startMcpServer() {
|
|
|
6381
6346
|
"fork_chat",
|
|
6382
6347
|
"Add another agent chat on the SAME workspace (same files, Run, and git). Also forks a Global orchestration chat into a new orchestration tab. Pass agent when the new chat should use a different harness. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Use this for a second agent on a checkout (review, another harness, parallel work on the same branch). Do not create_workspace again for that \u2014 it returns the existing checkout. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id. Use fork_workspace only when the work needs its own git checkout.",
|
|
6383
6348
|
{
|
|
6384
|
-
ref:
|
|
6385
|
-
through_index:
|
|
6349
|
+
ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
6350
|
+
through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
6386
6351
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
6387
|
-
model:
|
|
6388
|
-
title:
|
|
6352
|
+
model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
6353
|
+
title: z6.string().optional()
|
|
6389
6354
|
},
|
|
6390
6355
|
async ({ ref, through_index, agent, model, title }) => {
|
|
6391
6356
|
try {
|
|
@@ -6423,7 +6388,7 @@ async function startMcpServer() {
|
|
|
6423
6388
|
server.tool(
|
|
6424
6389
|
"run_setup",
|
|
6425
6390
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
6426
|
-
{ ref:
|
|
6391
|
+
{ ref: z6.string() },
|
|
6427
6392
|
async ({ ref }) => {
|
|
6428
6393
|
const result = await orch.runSetup(ref);
|
|
6429
6394
|
return mcpJson(result);
|
|
@@ -6432,7 +6397,7 @@ async function startMcpServer() {
|
|
|
6432
6397
|
server.tool(
|
|
6433
6398
|
"add_project",
|
|
6434
6399
|
"Register a git repo as a Sideboard workspace",
|
|
6435
|
-
{ repoPath:
|
|
6400
|
+
{ repoPath: z6.string() },
|
|
6436
6401
|
async ({ repoPath }) => {
|
|
6437
6402
|
const ws = await orch.addWorkspace(repoPath);
|
|
6438
6403
|
return mcpJson(ws);
|
|
@@ -6440,10 +6405,10 @@ async function startMcpServer() {
|
|
|
6440
6405
|
);
|
|
6441
6406
|
server.tool(
|
|
6442
6407
|
"remove_project",
|
|
6443
|
-
"Unregister a Sideboard workspace
|
|
6444
|
-
{ repoPath:
|
|
6408
|
+
"Unregister a Sideboard workspace and archive its chats",
|
|
6409
|
+
{ repoPath: z6.string() },
|
|
6445
6410
|
async ({ repoPath }) => {
|
|
6446
|
-
orch.removeWorkspace(repoPath);
|
|
6411
|
+
await orch.removeWorkspace(repoPath);
|
|
6447
6412
|
return { content: [{ type: "text", text: "ok" }] };
|
|
6448
6413
|
}
|
|
6449
6414
|
);
|
|
@@ -6451,12 +6416,12 @@ async function startMcpServer() {
|
|
|
6451
6416
|
"fanout",
|
|
6452
6417
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
6453
6418
|
{
|
|
6454
|
-
prompt:
|
|
6455
|
-
agents:
|
|
6456
|
-
repoPath:
|
|
6457
|
-
sourceType:
|
|
6458
|
-
sourceRef:
|
|
6459
|
-
title:
|
|
6419
|
+
prompt: z6.string(),
|
|
6420
|
+
agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
6421
|
+
repoPath: z6.string(),
|
|
6422
|
+
sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
|
|
6423
|
+
sourceRef: z6.string().optional(),
|
|
6424
|
+
title: z6.string().optional()
|
|
6460
6425
|
},
|
|
6461
6426
|
async (args) => {
|
|
6462
6427
|
const threads = await orch.bestOfN(args);
|
|
@@ -6474,8 +6439,8 @@ async function startMcpServer() {
|
|
|
6474
6439
|
"list_branches",
|
|
6475
6440
|
"List git branches in a registered workspace. Pass repoPath from list_projects (unmerged into the default branch by default \u2014 for create_workspace sourceType=branch).",
|
|
6476
6441
|
{
|
|
6477
|
-
repoPath:
|
|
6478
|
-
unmergedOnly:
|
|
6442
|
+
repoPath: z6.string(),
|
|
6443
|
+
unmergedOnly: z6.boolean().optional()
|
|
6479
6444
|
},
|
|
6480
6445
|
async ({ repoPath, unmergedOnly }) => {
|
|
6481
6446
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -6496,19 +6461,19 @@ async function startMcpServer() {
|
|
|
6496
6461
|
"list_prs",
|
|
6497
6462
|
`List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_projects. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled with this project's ready-for-review GitHub label (Settings \u2192 Projects; default eng-review) with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_workspace with sourceType=pr.`,
|
|
6498
6463
|
{
|
|
6499
|
-
repoPath:
|
|
6500
|
-
query:
|
|
6501
|
-
queue:
|
|
6464
|
+
repoPath: z6.string(),
|
|
6465
|
+
query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
6466
|
+
queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
6502
6467
|
"review = unclaimed ready-for-review inbox (Settings \u2192 Projects label, default eng-review). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes."
|
|
6503
6468
|
),
|
|
6504
|
-
state:
|
|
6505
|
-
label:
|
|
6469
|
+
state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
6470
|
+
label: z6.string().optional().describe(
|
|
6506
6471
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
6507
6472
|
),
|
|
6508
|
-
reviewer:
|
|
6473
|
+
reviewer: z6.string().optional().describe(
|
|
6509
6474
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
6510
6475
|
),
|
|
6511
|
-
limit:
|
|
6476
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
6512
6477
|
},
|
|
6513
6478
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
6514
6479
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -6541,7 +6506,7 @@ async function startMcpServer() {
|
|
|
6541
6506
|
server.tool(
|
|
6542
6507
|
"get_pr_stack",
|
|
6543
6508
|
"Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
|
|
6544
|
-
{ ref:
|
|
6509
|
+
{ ref: z6.string() },
|
|
6545
6510
|
async ({ ref }) => {
|
|
6546
6511
|
const stack = await orch.getPrStack(ref);
|
|
6547
6512
|
return mcpJson(stack);
|
|
@@ -6551,8 +6516,8 @@ async function startMcpServer() {
|
|
|
6551
6516
|
"open_pr_stack_layers",
|
|
6552
6517
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
6553
6518
|
{
|
|
6554
|
-
ref:
|
|
6555
|
-
layer:
|
|
6519
|
+
ref: z6.string(),
|
|
6520
|
+
layer: z6.number().int().positive().optional()
|
|
6556
6521
|
},
|
|
6557
6522
|
async ({ ref, layer }) => {
|
|
6558
6523
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -6575,9 +6540,9 @@ async function startMcpServer() {
|
|
|
6575
6540
|
"add_stack_layer",
|
|
6576
6541
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
6577
6542
|
{
|
|
6578
|
-
ref:
|
|
6579
|
-
branchName:
|
|
6580
|
-
title:
|
|
6543
|
+
ref: z6.string(),
|
|
6544
|
+
branchName: z6.string(),
|
|
6545
|
+
title: z6.string().optional()
|
|
6581
6546
|
},
|
|
6582
6547
|
async ({ ref, branchName, title }) => {
|
|
6583
6548
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -6595,11 +6560,11 @@ async function startMcpServer() {
|
|
|
6595
6560
|
"create_pr_stack",
|
|
6596
6561
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
6597
6562
|
{
|
|
6598
|
-
repoPath:
|
|
6599
|
-
branches:
|
|
6600
|
-
agent:
|
|
6601
|
-
base:
|
|
6602
|
-
title:
|
|
6563
|
+
repoPath: z6.string(),
|
|
6564
|
+
branches: z6.array(z6.string()).min(1),
|
|
6565
|
+
agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
6566
|
+
base: z6.string().optional(),
|
|
6567
|
+
title: z6.string().optional()
|
|
6603
6568
|
},
|
|
6604
6569
|
async (args) => {
|
|
6605
6570
|
const result = await orch.createPrStack({
|
|
@@ -6627,10 +6592,10 @@ async function startMcpServer() {
|
|
|
6627
6592
|
"list_issues",
|
|
6628
6593
|
"List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Pass updatedSince for new/updated tickets and new comments (\u201Cany updates since yesterday?\u201D). Then create_workspace with sourceType=ticket.",
|
|
6629
6594
|
{
|
|
6630
|
-
repoPath:
|
|
6631
|
-
query:
|
|
6632
|
-
assignee:
|
|
6633
|
-
limit:
|
|
6595
|
+
repoPath: z6.string(),
|
|
6596
|
+
query: z6.string().optional().describe("Search title, identifier, or description"),
|
|
6597
|
+
assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
6598
|
+
limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true."),
|
|
6634
6599
|
updatedSince: mcpIssueUpdatedSinceSchema
|
|
6635
6600
|
},
|
|
6636
6601
|
async ({ repoPath, query, assignee, limit, updatedSince }) => {
|
|
@@ -6950,8 +6915,8 @@ var BrightsySideboardApi = class {
|
|
|
6950
6915
|
};
|
|
6951
6916
|
function taskMessageText(task) {
|
|
6952
6917
|
const parts = task.message?.parts ?? [];
|
|
6953
|
-
const
|
|
6954
|
-
return
|
|
6918
|
+
const text5 = parts.find((p) => p.kind === "text")?.text ?? "";
|
|
6919
|
+
return text5.trim();
|
|
6955
6920
|
}
|
|
6956
6921
|
|
|
6957
6922
|
// src/brightsy/cloud-connect.ts
|
|
@@ -7361,15 +7326,15 @@ var SlackOAuthPendingStore = class {
|
|
|
7361
7326
|
const id = state.trim();
|
|
7362
7327
|
if (!id) return null;
|
|
7363
7328
|
this.sweep();
|
|
7364
|
-
const
|
|
7365
|
-
if (!
|
|
7329
|
+
const row2 = this.items.get(id);
|
|
7330
|
+
if (!row2) return null;
|
|
7366
7331
|
this.items.delete(id);
|
|
7367
|
-
return
|
|
7332
|
+
return row2.value;
|
|
7368
7333
|
}
|
|
7369
7334
|
sweep() {
|
|
7370
7335
|
const now = Date.now();
|
|
7371
|
-
for (const [key,
|
|
7372
|
-
if (
|
|
7336
|
+
for (const [key, row2] of this.items) {
|
|
7337
|
+
if (row2.expiresAt <= now) this.items.delete(key);
|
|
7373
7338
|
}
|
|
7374
7339
|
}
|
|
7375
7340
|
};
|
|
@@ -7568,11 +7533,11 @@ async function startSlackOAuth(opts) {
|
|
|
7568
7533
|
import { WebSocket as WsWebSocket } from "ws";
|
|
7569
7534
|
var BACKOFF_START_MS = 1e3;
|
|
7570
7535
|
var BACKOFF_MAX_MS = 3e4;
|
|
7571
|
-
function stripSlackMentions(
|
|
7572
|
-
return
|
|
7536
|
+
function stripSlackMentions(text5) {
|
|
7537
|
+
return text5.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
7573
7538
|
}
|
|
7574
|
-
function isSlackStopCommand(
|
|
7575
|
-
const t = stripSlackMentions(
|
|
7539
|
+
function isSlackStopCommand(text5) {
|
|
7540
|
+
const t = stripSlackMentions(text5).toLowerCase();
|
|
7576
7541
|
return t === "stop" || t === "sideboard_force_stop";
|
|
7577
7542
|
}
|
|
7578
7543
|
function parseSlackSocketFrame(raw) {
|
|
@@ -7594,8 +7559,8 @@ function inboundFromSocketFrame(frame) {
|
|
|
7594
7559
|
const ts = event.ts?.trim();
|
|
7595
7560
|
if (!channelId || !ts) return null;
|
|
7596
7561
|
const rawText = event.text?.trim() ?? "";
|
|
7597
|
-
const
|
|
7598
|
-
if (!
|
|
7562
|
+
const text5 = stripSlackMentions(rawText);
|
|
7563
|
+
if (!text5) return null;
|
|
7599
7564
|
const teamId = (frame.payload?.team_id || event.team || "").trim();
|
|
7600
7565
|
if (!teamId) return null;
|
|
7601
7566
|
const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
|
|
@@ -7607,7 +7572,7 @@ function inboundFromSocketFrame(frame) {
|
|
|
7607
7572
|
ts,
|
|
7608
7573
|
threadTs: event.thread_ts?.trim() || void 0,
|
|
7609
7574
|
userId: event.user?.trim(),
|
|
7610
|
-
text:
|
|
7575
|
+
text: text5,
|
|
7611
7576
|
kind: isMention ? "mention" : "dm"
|
|
7612
7577
|
};
|
|
7613
7578
|
}
|
|
@@ -8240,12 +8205,12 @@ function formatSlackInboundPrompt(msg) {
|
|
|
8240
8205
|
|
|
8241
8206
|
${msg.text}`;
|
|
8242
8207
|
}
|
|
8243
|
-
function isSlackInboundUserPrompt(
|
|
8244
|
-
return
|
|
8208
|
+
function isSlackInboundUserPrompt(text5) {
|
|
8209
|
+
return text5.startsWith("Slack DM\n") || text5.startsWith("Slack @mention\n");
|
|
8245
8210
|
}
|
|
8246
|
-
function formatSlackSignedReply(deviceLabel,
|
|
8211
|
+
function formatSlackSignedReply(deviceLabel, text5) {
|
|
8247
8212
|
const label = deviceLabel.trim();
|
|
8248
|
-
const body =
|
|
8213
|
+
const body = text5.trim();
|
|
8249
8214
|
if (!body) return body;
|
|
8250
8215
|
if (!label) return body;
|
|
8251
8216
|
const head = `${label}:`;
|
|
@@ -8254,8 +8219,8 @@ function formatSlackSignedReply(deviceLabel, text6) {
|
|
|
8254
8219
|
}
|
|
8255
8220
|
return `${label}: ${body}`;
|
|
8256
8221
|
}
|
|
8257
|
-
function signForThisMac(
|
|
8258
|
-
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel,
|
|
8222
|
+
function signForThisMac(text5) {
|
|
8223
|
+
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text5);
|
|
8259
8224
|
}
|
|
8260
8225
|
function slackReplyThreadTs(msg) {
|
|
8261
8226
|
if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
|
|
@@ -8316,9 +8281,9 @@ function replyStub(target) {
|
|
|
8316
8281
|
kind: "dm"
|
|
8317
8282
|
};
|
|
8318
8283
|
}
|
|
8319
|
-
async function postSlackText(target,
|
|
8284
|
+
async function postSlackText(target, text5, opts) {
|
|
8320
8285
|
if (opts.postReply) {
|
|
8321
|
-
const result = await opts.postReply(replyStub(target),
|
|
8286
|
+
const result = await opts.postReply(replyStub(target), text5);
|
|
8322
8287
|
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
8323
8288
|
return ts2 ? { ts: ts2 } : null;
|
|
8324
8289
|
}
|
|
@@ -8328,7 +8293,7 @@ async function postSlackText(target, text6, opts) {
|
|
|
8328
8293
|
"chat.postMessage",
|
|
8329
8294
|
{
|
|
8330
8295
|
channel: target.channelId,
|
|
8331
|
-
text:
|
|
8296
|
+
text: text5,
|
|
8332
8297
|
thread_ts: target.threadTs
|
|
8333
8298
|
},
|
|
8334
8299
|
opts.fetchImpl
|
|
@@ -8336,9 +8301,9 @@ async function postSlackText(target, text6, opts) {
|
|
|
8336
8301
|
const ts = json.ts?.trim();
|
|
8337
8302
|
return ts ? { ts } : null;
|
|
8338
8303
|
}
|
|
8339
|
-
async function updateSlackText(target, ts,
|
|
8304
|
+
async function updateSlackText(target, ts, text5, opts) {
|
|
8340
8305
|
if (opts.updateReply) {
|
|
8341
|
-
await opts.updateReply(replyStub(target), ts,
|
|
8306
|
+
await opts.updateReply(replyStub(target), ts, text5);
|
|
8342
8307
|
return;
|
|
8343
8308
|
}
|
|
8344
8309
|
const token = writeTokenForTeam(target.teamId);
|
|
@@ -8348,7 +8313,7 @@ async function updateSlackText(target, ts, text6, opts) {
|
|
|
8348
8313
|
{
|
|
8349
8314
|
channel: target.channelId,
|
|
8350
8315
|
ts,
|
|
8351
|
-
text:
|
|
8316
|
+
text: text5
|
|
8352
8317
|
},
|
|
8353
8318
|
opts.fetchImpl
|
|
8354
8319
|
);
|
|
@@ -8376,8 +8341,8 @@ function replyTargetOf(msg) {
|
|
|
8376
8341
|
threadTs: slackReplyThreadTs(msg)
|
|
8377
8342
|
};
|
|
8378
8343
|
}
|
|
8379
|
-
async function postSlackReply(msg,
|
|
8380
|
-
await postSlackText(replyTargetOf(msg), signForThisMac(
|
|
8344
|
+
async function postSlackReply(msg, text5, opts) {
|
|
8345
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text5), opts);
|
|
8381
8346
|
}
|
|
8382
8347
|
function startSlackTurnProgress(msg, threadId, opts) {
|
|
8383
8348
|
const log = opts.onLog ?? (() => void 0);
|
|
@@ -8460,10 +8425,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
8460
8425
|
}
|
|
8461
8426
|
});
|
|
8462
8427
|
},
|
|
8463
|
-
finishWith: (
|
|
8428
|
+
finishWith: (text5) => {
|
|
8464
8429
|
stop();
|
|
8465
8430
|
return run2(async () => {
|
|
8466
|
-
const signed = signForThisMac(
|
|
8431
|
+
const signed = signForThisMac(text5.trim());
|
|
8467
8432
|
if (!signed) {
|
|
8468
8433
|
if (!postedTs) return;
|
|
8469
8434
|
const ts = postedTs;
|
|
@@ -8491,11 +8456,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
8491
8456
|
};
|
|
8492
8457
|
}
|
|
8493
8458
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
8494
|
-
function markRelayed(threadId,
|
|
8495
|
-
lastRelayed.set(threadId, `${threadId}:${
|
|
8459
|
+
function markRelayed(threadId, text5) {
|
|
8460
|
+
lastRelayed.set(threadId, `${threadId}:${text5}`);
|
|
8496
8461
|
}
|
|
8497
|
-
function alreadyRelayed(threadId,
|
|
8498
|
-
return lastRelayed.get(threadId) === `${threadId}:${
|
|
8462
|
+
function alreadyRelayed(threadId, text5) {
|
|
8463
|
+
return lastRelayed.get(threadId) === `${threadId}:${text5}`;
|
|
8499
8464
|
}
|
|
8500
8465
|
async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
8501
8466
|
const target = getSlackReplyTarget(threadId);
|
|
@@ -8504,14 +8469,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
|
8504
8469
|
const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
|
|
8505
8470
|
if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
|
|
8506
8471
|
const result = getOrchestrator().getTurnResult(threadId);
|
|
8507
|
-
const
|
|
8508
|
-
if (!
|
|
8509
|
-
if (alreadyRelayed(threadId,
|
|
8510
|
-
markRelayed(threadId,
|
|
8472
|
+
const text5 = formatSlackTurnReply(result);
|
|
8473
|
+
if (!text5) return;
|
|
8474
|
+
if (alreadyRelayed(threadId, text5)) return;
|
|
8475
|
+
markRelayed(threadId, text5);
|
|
8511
8476
|
const log = opts.onLog ?? (() => void 0);
|
|
8512
8477
|
try {
|
|
8513
|
-
await postSlackText(target, signForThisMac(
|
|
8514
|
-
log(`replied ${target.threadTs ?? target.channelId} (${
|
|
8478
|
+
await postSlackText(target, signForThisMac(text5), opts);
|
|
8479
|
+
log(`replied ${target.threadTs ?? target.channelId} (${text5.length} chars)`);
|
|
8515
8480
|
} catch (err) {
|
|
8516
8481
|
lastRelayed.delete(threadId);
|
|
8517
8482
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -8779,11 +8744,11 @@ function questions(value) {
|
|
|
8779
8744
|
const out = [];
|
|
8780
8745
|
for (const item of value) {
|
|
8781
8746
|
if (!item || typeof item !== "object") return null;
|
|
8782
|
-
const
|
|
8783
|
-
const question = str2(
|
|
8784
|
-
if (!question || !Array.isArray(
|
|
8747
|
+
const row2 = item;
|
|
8748
|
+
const question = str2(row2.question);
|
|
8749
|
+
if (!question || !Array.isArray(row2.options)) return null;
|
|
8785
8750
|
const options = [];
|
|
8786
|
-
for (const opt of
|
|
8751
|
+
for (const opt of row2.options) {
|
|
8787
8752
|
if (!opt || typeof opt !== "object") return null;
|
|
8788
8753
|
const label = str2(opt.label);
|
|
8789
8754
|
if (!label) return null;
|
|
@@ -8838,9 +8803,9 @@ function parseRemoteClientMessage(raw) {
|
|
|
8838
8803
|
return { type: "list_hosts", sessionTokens };
|
|
8839
8804
|
}
|
|
8840
8805
|
if (msg.type === "prompt") {
|
|
8841
|
-
const
|
|
8842
|
-
if (!
|
|
8843
|
-
return { type: "prompt", text:
|
|
8806
|
+
const text5 = str2(parsed.text);
|
|
8807
|
+
if (!text5) return null;
|
|
8808
|
+
return { type: "prompt", text: text5 };
|
|
8844
8809
|
}
|
|
8845
8810
|
if (msg.type === "assistant") {
|
|
8846
8811
|
return { type: "assistant", text: str2(parsed.text) };
|
|
@@ -8875,15 +8840,15 @@ function parseRemoteServerMessage(raw) {
|
|
|
8875
8840
|
const hosts = [];
|
|
8876
8841
|
for (const item of raw2) {
|
|
8877
8842
|
if (!item || typeof item !== "object") return null;
|
|
8878
|
-
const
|
|
8879
|
-
const sessionToken = str2(
|
|
8880
|
-
if (!sessionToken || typeof
|
|
8881
|
-
if (typeof
|
|
8843
|
+
const row2 = item;
|
|
8844
|
+
const sessionToken = str2(row2.sessionToken);
|
|
8845
|
+
if (!sessionToken || typeof row2.deviceId !== "string" || typeof row2.deviceLabel !== "string") return null;
|
|
8846
|
+
if (typeof row2.online !== "boolean") return null;
|
|
8882
8847
|
hosts.push({
|
|
8883
8848
|
sessionToken,
|
|
8884
|
-
deviceId:
|
|
8885
|
-
deviceLabel:
|
|
8886
|
-
online:
|
|
8849
|
+
deviceId: row2.deviceId.trim(),
|
|
8850
|
+
deviceLabel: row2.deviceLabel.trim(),
|
|
8851
|
+
online: row2.online
|
|
8887
8852
|
});
|
|
8888
8853
|
}
|
|
8889
8854
|
return { type: "hosts", hosts };
|
|
@@ -8902,9 +8867,9 @@ function parseRemoteServerMessage(raw) {
|
|
|
8902
8867
|
return { type: "pairing_code", code, expiresAt };
|
|
8903
8868
|
}
|
|
8904
8869
|
if (type === "inbound") {
|
|
8905
|
-
const
|
|
8906
|
-
if (!
|
|
8907
|
-
return { type: "inbound", text:
|
|
8870
|
+
const text5 = str2(parsed.text);
|
|
8871
|
+
if (!text5) return null;
|
|
8872
|
+
return { type: "inbound", text: text5 };
|
|
8908
8873
|
}
|
|
8909
8874
|
if (type === "paired") {
|
|
8910
8875
|
const deviceId = str2(parsed.deviceId);
|
|
@@ -9065,8 +9030,8 @@ var RemoteHub = class {
|
|
|
9065
9030
|
send2(socket, { type: "error", message: "register this Mac before pairing" });
|
|
9066
9031
|
return;
|
|
9067
9032
|
}
|
|
9068
|
-
for (const [code2,
|
|
9069
|
-
if (
|
|
9033
|
+
for (const [code2, row2] of this.codes) {
|
|
9034
|
+
if (row2.deviceId === host.deviceId) this.codes.delete(code2);
|
|
9070
9035
|
}
|
|
9071
9036
|
const code = newCode();
|
|
9072
9037
|
const expiresAt = Date.now() + CODE_TTL_MS;
|
|
@@ -9074,13 +9039,13 @@ var RemoteHub = class {
|
|
|
9074
9039
|
send2(socket, { type: "pairing_code", code, expiresAt });
|
|
9075
9040
|
}
|
|
9076
9041
|
pairPhone(socket, code) {
|
|
9077
|
-
const
|
|
9078
|
-
if (!
|
|
9042
|
+
const row2 = this.codes.get(code);
|
|
9043
|
+
if (!row2 || row2.expiresAt < Date.now()) {
|
|
9079
9044
|
this.codes.delete(code);
|
|
9080
9045
|
send2(socket, { type: "error", message: "pairing code expired" });
|
|
9081
9046
|
return;
|
|
9082
9047
|
}
|
|
9083
|
-
const host = this.hosts.get(
|
|
9048
|
+
const host = this.hosts.get(row2.deviceId);
|
|
9084
9049
|
if (!host) {
|
|
9085
9050
|
send2(socket, { type: "error", message: "Mac is offline" });
|
|
9086
9051
|
return;
|
|
@@ -9206,26 +9171,26 @@ var IMAGE_MIME = {
|
|
|
9206
9171
|
bmp: "image/bmp"
|
|
9207
9172
|
};
|
|
9208
9173
|
function phoneWorktreeRelative(worktreePath, raw) {
|
|
9209
|
-
let
|
|
9210
|
-
if (!
|
|
9211
|
-
if (/^file:\/\//i.test(
|
|
9174
|
+
let text5 = raw.trim();
|
|
9175
|
+
if (!text5) return null;
|
|
9176
|
+
if (/^file:\/\//i.test(text5)) {
|
|
9212
9177
|
try {
|
|
9213
|
-
|
|
9178
|
+
text5 = decodeURIComponent(new URL(text5).pathname);
|
|
9214
9179
|
} catch {
|
|
9215
9180
|
return null;
|
|
9216
9181
|
}
|
|
9217
9182
|
}
|
|
9218
|
-
|
|
9219
|
-
if (!
|
|
9183
|
+
text5 = (text5.split("#")[0] ?? text5).replace(/\\/g, "/");
|
|
9184
|
+
if (!text5 || text5.includes("\0") || text5.split("/").includes("..")) return null;
|
|
9220
9185
|
const root = worktreePath.replace(/\/+$/, "");
|
|
9221
9186
|
if (!root || root === "/") return null;
|
|
9222
|
-
if (
|
|
9223
|
-
if (
|
|
9224
|
-
|
|
9187
|
+
if (text5.startsWith("/")) {
|
|
9188
|
+
if (text5 !== root && !text5.startsWith(`${root}/`)) return null;
|
|
9189
|
+
text5 = text5 === root ? "" : text5.slice(root.length + 1);
|
|
9225
9190
|
}
|
|
9226
|
-
|
|
9227
|
-
if (!
|
|
9228
|
-
return
|
|
9191
|
+
text5 = text5.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
9192
|
+
if (!text5 || text5 === "." || text5.startsWith("/")) return null;
|
|
9193
|
+
return text5;
|
|
9229
9194
|
}
|
|
9230
9195
|
function phoneImageMime(relativePath) {
|
|
9231
9196
|
const ext = relativePath.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -9344,9 +9309,9 @@ function isArtifactTool(name) {
|
|
|
9344
9309
|
const short = name.split("__").pop() ?? name;
|
|
9345
9310
|
return /^(present_artifact|create_artifact|update_artifact)$/i.test(short);
|
|
9346
9311
|
}
|
|
9347
|
-
function phoneArtifactText(
|
|
9312
|
+
function phoneArtifactText(text5, parts, limit = Number.POSITIVE_INFINITY) {
|
|
9348
9313
|
const hints = /* @__PURE__ */ new Set();
|
|
9349
|
-
const body =
|
|
9314
|
+
const body = text5.replace(new RegExp(FENCE_RE.source, "g"), (full, language, content) => {
|
|
9350
9315
|
const kind = kindFor(language, content);
|
|
9351
9316
|
if (!kind) return full;
|
|
9352
9317
|
const hint = artifactHint(content);
|
|
@@ -9383,11 +9348,84 @@ function parseOpenArtifact(value, chatId) {
|
|
|
9383
9348
|
return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
|
|
9384
9349
|
}
|
|
9385
9350
|
|
|
9386
|
-
// src/remote/phone-
|
|
9387
|
-
|
|
9351
|
+
// src/remote/phone-defaults.ts
|
|
9352
|
+
function row(resolved) {
|
|
9353
|
+
return {
|
|
9354
|
+
agent: resolved.agent,
|
|
9355
|
+
model: resolved.model,
|
|
9356
|
+
effort: resolved.effort,
|
|
9357
|
+
fast: resolved.fast
|
|
9358
|
+
};
|
|
9359
|
+
}
|
|
9360
|
+
function phoneAccountDefaults() {
|
|
9361
|
+
return {
|
|
9362
|
+
orchestration: row(resolveOrchestratorDefaults()),
|
|
9363
|
+
worktree: row(resolveThreadDefaults())
|
|
9364
|
+
};
|
|
9365
|
+
}
|
|
9366
|
+
|
|
9367
|
+
// src/remote/phone-stream.ts
|
|
9368
|
+
var PHONE_STREAM_FLUSH_MS = 80;
|
|
9369
|
+
function phoneStreamFrame(parts) {
|
|
9370
|
+
return {
|
|
9371
|
+
text: phoneArtifactText(visibleAssistantText(void 0, parts), parts),
|
|
9372
|
+
activity: liveActivitySummary(parts)
|
|
9373
|
+
};
|
|
9374
|
+
}
|
|
9375
|
+
var SKIPPED = /* @__PURE__ */ new Set(["session_id", "usage", "stderr", "exit"]);
|
|
9376
|
+
function phoneStreamParts(parts, event) {
|
|
9377
|
+
if (SKIPPED.has(event.type)) return null;
|
|
9378
|
+
return applyAgentEvent(parts, event);
|
|
9379
|
+
}
|
|
9380
|
+
function phoneStreamFlushNow(event) {
|
|
9381
|
+
return event.type === "tool_use" || event.type === "tool_result" && !event.partial;
|
|
9382
|
+
}
|
|
9383
|
+
|
|
9384
|
+
// src/remote/phone-transcript.ts
|
|
9388
9385
|
var PHONE_USER_PREFIX = "Phone\n\n";
|
|
9389
9386
|
var TRANSCRIPT_LIMIT = 40;
|
|
9390
9387
|
var MESSAGE_LIMIT = 4e3;
|
|
9388
|
+
function phoneVisibleText(role, text5) {
|
|
9389
|
+
const trimmed = text5.trim();
|
|
9390
|
+
if (role === "user" && trimmed.startsWith(PHONE_USER_PREFIX)) {
|
|
9391
|
+
return trimmed.slice(PHONE_USER_PREFIX.length).trim();
|
|
9392
|
+
}
|
|
9393
|
+
return trimmed;
|
|
9394
|
+
}
|
|
9395
|
+
function clip(text5, limit) {
|
|
9396
|
+
if (text5.length <= limit) return text5;
|
|
9397
|
+
return `${text5.slice(0, limit - 1)}\u2026`;
|
|
9398
|
+
}
|
|
9399
|
+
function withLiveTail(thread, messages) {
|
|
9400
|
+
if (thread.status !== "running" && thread.status !== "queued") return messages;
|
|
9401
|
+
const text5 = clip(phoneStreamFrame(readTurnLiveParts(thread.id)).text.trim(), MESSAGE_LIMIT);
|
|
9402
|
+
if (!text5) return messages;
|
|
9403
|
+
const last = messages[messages.length - 1];
|
|
9404
|
+
if (last?.role === "agent" && last.text === text5) {
|
|
9405
|
+
return last.streaming ? messages : [...messages.slice(0, -1), { ...last, streaming: true }];
|
|
9406
|
+
}
|
|
9407
|
+
return [...messages, { role: "agent", text: text5, streaming: true }];
|
|
9408
|
+
}
|
|
9409
|
+
function listPhoneMessages(thread) {
|
|
9410
|
+
const out = [];
|
|
9411
|
+
for (const message of thread.messages) {
|
|
9412
|
+
if (message.role !== "user" && message.role !== "agent") continue;
|
|
9413
|
+
const names = (message.attachments ?? []).map((item) => item.name.trim()).filter(Boolean);
|
|
9414
|
+
const visible = [phoneVisibleText(message.role, message.text), names.length ? names.join(", ") : ""].filter(Boolean).join("\n");
|
|
9415
|
+
const text5 = message.role === "agent" ? phoneArtifactText(visible, message.parts, MESSAGE_LIMIT) : clip(visible, MESSAGE_LIMIT);
|
|
9416
|
+
if (!text5) continue;
|
|
9417
|
+
out.push({ role: message.role, text: text5 });
|
|
9418
|
+
}
|
|
9419
|
+
const live = withLiveTail(thread, out);
|
|
9420
|
+
for (const prompt of thread.queue) {
|
|
9421
|
+
const text5 = clip(phoneVisibleText("user", prompt), MESSAGE_LIMIT);
|
|
9422
|
+
if (text5) live.push({ role: "user", text: text5 });
|
|
9423
|
+
}
|
|
9424
|
+
return live.slice(-TRANSCRIPT_LIMIT);
|
|
9425
|
+
}
|
|
9426
|
+
|
|
9427
|
+
// src/remote/phone-chats.ts
|
|
9428
|
+
var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
|
|
9391
9429
|
var PREVIEW_LIMIT = 90;
|
|
9392
9430
|
var HISTORY_PAGE_SIZE = 40;
|
|
9393
9431
|
var AGENT_KINDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
@@ -9398,11 +9436,11 @@ var MAX_DICTATION_CHARS = Math.ceil(12 * 1024 * 1024 * 4 / 3);
|
|
|
9398
9436
|
function encodePhoneControl(payload) {
|
|
9399
9437
|
return PHONE_CONTROL_PREFIX + JSON.stringify(payload);
|
|
9400
9438
|
}
|
|
9401
|
-
function takePhoneControl(
|
|
9402
|
-
if (!
|
|
9439
|
+
function takePhoneControl(text5) {
|
|
9440
|
+
if (!text5.startsWith(PHONE_CONTROL_PREFIX)) return null;
|
|
9403
9441
|
let parsed;
|
|
9404
9442
|
try {
|
|
9405
|
-
parsed = JSON.parse(
|
|
9443
|
+
parsed = JSON.parse(text5.slice(PHONE_CONTROL_PREFIX.length));
|
|
9406
9444
|
} catch {
|
|
9407
9445
|
return "invalid";
|
|
9408
9446
|
}
|
|
@@ -9457,10 +9495,10 @@ function parsePhoneControl(value) {
|
|
|
9457
9495
|
if (draft === "invalid") return "invalid";
|
|
9458
9496
|
if (op === "options") return { op: "options", chatId, ...draft };
|
|
9459
9497
|
if (op === "prompt") {
|
|
9460
|
-
const
|
|
9461
|
-
const hasPayload = Boolean(
|
|
9498
|
+
const text5 = typeof value.text === "string" ? value.text.trim() : "";
|
|
9499
|
+
const hasPayload = Boolean(text5 || draft.files?.length || draft.links?.length);
|
|
9462
9500
|
if (!hasPayload) return "invalid";
|
|
9463
|
-
return { op: "prompt", chatId, text:
|
|
9501
|
+
return { op: "prompt", chatId, text: text5, ...draft };
|
|
9464
9502
|
}
|
|
9465
9503
|
return "invalid";
|
|
9466
9504
|
}
|
|
@@ -9599,17 +9637,6 @@ function parsePhoneCreate(value) {
|
|
|
9599
9637
|
}
|
|
9600
9638
|
return "invalid";
|
|
9601
9639
|
}
|
|
9602
|
-
function phoneVisibleText(role, text6) {
|
|
9603
|
-
const trimmed = text6.trim();
|
|
9604
|
-
if (role === "user" && trimmed.startsWith(PHONE_USER_PREFIX)) {
|
|
9605
|
-
return trimmed.slice(PHONE_USER_PREFIX.length).trim();
|
|
9606
|
-
}
|
|
9607
|
-
return trimmed;
|
|
9608
|
-
}
|
|
9609
|
-
function clip(text6, limit) {
|
|
9610
|
-
if (text6.length <= limit) return text6;
|
|
9611
|
-
return `${text6.slice(0, limit - 1)}\u2026`;
|
|
9612
|
-
}
|
|
9613
9640
|
function summarize(thread) {
|
|
9614
9641
|
return {
|
|
9615
9642
|
id: thread.id,
|
|
@@ -9626,22 +9653,6 @@ function previewOf(thread) {
|
|
|
9626
9653
|
if (!last) return "";
|
|
9627
9654
|
return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
|
|
9628
9655
|
}
|
|
9629
|
-
function listPhoneMessages(thread) {
|
|
9630
|
-
const out = [];
|
|
9631
|
-
for (const message of thread.messages) {
|
|
9632
|
-
if (message.role !== "user" && message.role !== "agent") continue;
|
|
9633
|
-
const names = (message.attachments ?? []).map((item) => item.name.trim()).filter(Boolean);
|
|
9634
|
-
const visible = [phoneVisibleText(message.role, message.text), names.length ? names.join(", ") : ""].filter(Boolean).join("\n");
|
|
9635
|
-
const text6 = message.role === "agent" ? phoneArtifactText(visible, message.parts, MESSAGE_LIMIT) : clip(visible, MESSAGE_LIMIT);
|
|
9636
|
-
if (!text6) continue;
|
|
9637
|
-
out.push({ role: message.role, text: text6 });
|
|
9638
|
-
}
|
|
9639
|
-
for (const prompt of thread.queue) {
|
|
9640
|
-
const text6 = clip(phoneVisibleText("user", prompt), MESSAGE_LIMIT);
|
|
9641
|
-
if (text6) out.push({ role: "user", text: text6 });
|
|
9642
|
-
}
|
|
9643
|
-
return out.slice(-TRANSCRIPT_LIMIT);
|
|
9644
|
-
}
|
|
9645
9656
|
function repoName(repoPath) {
|
|
9646
9657
|
const parts = repoPath.replace(/\/$/, "").split("/");
|
|
9647
9658
|
return parts[parts.length - 1] || repoPath;
|
|
@@ -9657,7 +9668,7 @@ function listPhoneSidebar() {
|
|
|
9657
9668
|
const threads = listThreads();
|
|
9658
9669
|
const byRepo = /* @__PURE__ */ new Map();
|
|
9659
9670
|
for (const thread of threads) {
|
|
9660
|
-
if (!isProjectPath(thread.repoPath)) continue;
|
|
9671
|
+
if (!isProjectPath(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
|
|
9661
9672
|
const list = byRepo.get(thread.repoPath) ?? [];
|
|
9662
9673
|
list.push(thread);
|
|
9663
9674
|
byRepo.set(thread.repoPath, list);
|
|
@@ -9669,12 +9680,16 @@ function listPhoneSidebar() {
|
|
|
9669
9680
|
const projects = [...byRepo.entries()].sort(([a], [b]) => repoName(a).localeCompare(repoName(b))).map(([path2, repoThreads]) => ({
|
|
9670
9681
|
name: repoName(path2),
|
|
9671
9682
|
path: path2,
|
|
9672
|
-
worktrees: groupHomeBoardWorktrees(repoThreads).map((group) =>
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9683
|
+
worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => {
|
|
9684
|
+
const tags = workspaceTagsFromGroup(group);
|
|
9685
|
+
return {
|
|
9686
|
+
label: worktreeDisplayLabelForGroup(group),
|
|
9687
|
+
chats: group.map(summarize),
|
|
9688
|
+
...tags.length > 0 ? { tags } : {}
|
|
9689
|
+
};
|
|
9690
|
+
})
|
|
9676
9691
|
}));
|
|
9677
|
-
return { orchestration: listPhoneChats(), projects };
|
|
9692
|
+
return { orchestration: listPhoneChats(), projects, defaults: phoneAccountDefaults() };
|
|
9678
9693
|
}
|
|
9679
9694
|
function historyStamp(thread) {
|
|
9680
9695
|
return thread.archivedAt || thread.updatedAt;
|
|
@@ -9936,23 +9951,6 @@ async function listPhoneSources(repoPath, query) {
|
|
|
9936
9951
|
};
|
|
9937
9952
|
}
|
|
9938
9953
|
|
|
9939
|
-
// src/remote/phone-stream.ts
|
|
9940
|
-
var PHONE_STREAM_FLUSH_MS = 80;
|
|
9941
|
-
function phoneStreamFrame(parts) {
|
|
9942
|
-
return {
|
|
9943
|
-
text: phoneArtifactText(visibleAssistantText(void 0, parts), parts),
|
|
9944
|
-
activity: liveActivitySummary(parts)
|
|
9945
|
-
};
|
|
9946
|
-
}
|
|
9947
|
-
var SKIPPED = /* @__PURE__ */ new Set(["session_id", "usage", "stderr", "exit"]);
|
|
9948
|
-
function phoneStreamParts(parts, event) {
|
|
9949
|
-
if (SKIPPED.has(event.type)) return null;
|
|
9950
|
-
return applyAgentEvent(parts, event);
|
|
9951
|
-
}
|
|
9952
|
-
function phoneStreamFlushNow(event) {
|
|
9953
|
-
return event.type === "tool_use" || event.type === "tool_result" && !event.partial;
|
|
9954
|
-
}
|
|
9955
|
-
|
|
9956
9954
|
// src/remote/phone-live.ts
|
|
9957
9955
|
var REMOTE_STOPPED_REPLY = "Sideboard stopped the in-progress turn.";
|
|
9958
9956
|
var chatChains = /* @__PURE__ */ new Map();
|
|
@@ -9987,8 +9985,8 @@ function enqueueChat(chatId, fn) {
|
|
|
9987
9985
|
function emitControl(opts, reply) {
|
|
9988
9986
|
opts.onOutbound({ type: "assistant", text: encodePhoneControl(reply) });
|
|
9989
9987
|
}
|
|
9990
|
-
function watchPhoneStream(opts, chatId, threadId, generation) {
|
|
9991
|
-
let parts =
|
|
9988
|
+
function watchPhoneStream(opts, chatId, threadId, generation, seed = []) {
|
|
9989
|
+
let parts = seed.slice();
|
|
9992
9990
|
let timer = null;
|
|
9993
9991
|
let lastKey = "";
|
|
9994
9992
|
let closed = false;
|
|
@@ -10024,6 +10022,7 @@ ${frame.text}`;
|
|
|
10024
10022
|
parts = next;
|
|
10025
10023
|
schedule(event.event);
|
|
10026
10024
|
});
|
|
10025
|
+
if (parts.length) flush();
|
|
10027
10026
|
return () => {
|
|
10028
10027
|
closed = true;
|
|
10029
10028
|
if (timer) clearTimeout(timer);
|
|
@@ -10097,7 +10096,7 @@ function deliverPhoneTurn(opts, chatId) {
|
|
|
10097
10096
|
phoneTurnGen.set(id, generation);
|
|
10098
10097
|
enqueueChat(id, async () => {
|
|
10099
10098
|
if (chatGeneration.get(id) !== generation) return;
|
|
10100
|
-
const stopStream = watchPhoneStream(opts, id, id, generation);
|
|
10099
|
+
const stopStream = watchPhoneStream(opts, id, id, generation, readTurnLiveParts(id));
|
|
10101
10100
|
try {
|
|
10102
10101
|
await getOrchestrator().waitForTurn(id, 14 * 60 * 1e3);
|
|
10103
10102
|
} catch (err) {
|
|
@@ -10115,13 +10114,13 @@ function deliverPhoneTurn(opts, chatId) {
|
|
|
10115
10114
|
}
|
|
10116
10115
|
|
|
10117
10116
|
// src/remote/listen.ts
|
|
10118
|
-
function formatRemotePrompt(
|
|
10117
|
+
function formatRemotePrompt(text5) {
|
|
10119
10118
|
return `Phone
|
|
10120
10119
|
|
|
10121
|
-
${
|
|
10120
|
+
${text5.trim()}`;
|
|
10122
10121
|
}
|
|
10123
|
-
function isRemoteStopCommand(
|
|
10124
|
-
return
|
|
10122
|
+
function isRemoteStopCommand(text5) {
|
|
10123
|
+
return text5.trim().toLowerCase() === "stop";
|
|
10125
10124
|
}
|
|
10126
10125
|
var handleChain2 = Promise.resolve();
|
|
10127
10126
|
var inboundGeneration = 0;
|
|
@@ -10228,7 +10227,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10228
10227
|
emitControl(opts, { op: "error", message: "Speech is not available on this Mac." });
|
|
10229
10228
|
return;
|
|
10230
10229
|
}
|
|
10231
|
-
void transcribe(cmd.audioBase64).then((
|
|
10230
|
+
void transcribe(cmd.audioBase64).then((text5) => emitControl(opts, { op: "dictated", id, text: text5.trim() })).catch((err) => {
|
|
10232
10231
|
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
10233
10232
|
});
|
|
10234
10233
|
return;
|
|
@@ -10441,8 +10440,8 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10441
10440
|
}
|
|
10442
10441
|
const live = readPhoneThread(thread.id) ?? thread;
|
|
10443
10442
|
const staged = stagePhoneTurnFiles(live.worktreePath, cmd);
|
|
10444
|
-
const
|
|
10445
|
-
const formatted = formatRemotePrompt(
|
|
10443
|
+
const text5 = cmd.text.trim() || (staged.length ? "See the attached files." : "");
|
|
10444
|
+
const formatted = formatRemotePrompt(text5);
|
|
10446
10445
|
await getOrchestrator().send(thread.id, formatted, { attachments: staged });
|
|
10447
10446
|
await getOrchestrator().waitForTurn(thread.id, 14 * 60 * 1e3);
|
|
10448
10447
|
} catch (err) {
|
|
@@ -10458,9 +10457,9 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10458
10457
|
emitChatTurn(opts, chatId, thread.id);
|
|
10459
10458
|
});
|
|
10460
10459
|
}
|
|
10461
|
-
function handleRemoteInbound(
|
|
10460
|
+
function handleRemoteInbound(text5, opts) {
|
|
10462
10461
|
const log = opts.onLog ?? (() => void 0);
|
|
10463
|
-
const body =
|
|
10462
|
+
const body = text5.trim();
|
|
10464
10463
|
const control = takePhoneControl(body);
|
|
10465
10464
|
if (control) {
|
|
10466
10465
|
if (control === "invalid") {
|
|
@@ -10534,19 +10533,23 @@ function handleRemoteInbound(text6, opts) {
|
|
|
10534
10533
|
import { basename as basename3 } from "path";
|
|
10535
10534
|
import { watch } from "chokidar";
|
|
10536
10535
|
var PHONE_BOARD_SYNC_MS = 300;
|
|
10537
|
-
function chatKey(
|
|
10538
|
-
return [
|
|
10536
|
+
function chatKey(row2) {
|
|
10537
|
+
return [row2.id, row2.title, row2.status, row2.agent, row2.preview].join(" ");
|
|
10539
10538
|
}
|
|
10540
10539
|
function phoneBoardKey(sidebar) {
|
|
10541
10540
|
const orchestration = sidebar.orchestration.map(chatKey).join("\n");
|
|
10542
10541
|
const projects = sidebar.projects.map((project) => {
|
|
10543
|
-
const trees = project.worktrees.map((tree) => `${tree.label} ${tree.chats.map(chatKey).join("\n")}`).join("\n");
|
|
10542
|
+
const trees = project.worktrees.map((tree) => `${tree.label} ${(tree.tags ?? []).join(",")} ${tree.chats.map(chatKey).join("\n")}`).join("\n");
|
|
10544
10543
|
return `${project.path} ${project.name}
|
|
10545
10544
|
${trees}`;
|
|
10546
10545
|
}).join("\n---\n");
|
|
10546
|
+
const defaults = sidebar.defaults;
|
|
10547
|
+
const defaultsKey = defaults ? [defaults.orchestration, defaults.worktree].map((row2) => [row2.agent, row2.model ?? "", row2.effort, row2.fast ? "1" : "0"].join(" ")).join("\n") : "";
|
|
10547
10548
|
return `${orchestration}
|
|
10548
10549
|
===
|
|
10549
|
-
${projects}
|
|
10550
|
+
${projects}
|
|
10551
|
+
===
|
|
10552
|
+
${defaultsKey}`;
|
|
10550
10553
|
}
|
|
10551
10554
|
function isPhoneBoardEvent(event) {
|
|
10552
10555
|
return event.type === "status_changed" || event.type === "turn_started" || event.type === "turn_finished" || event.type === "queue_changed" || event.type === "error" || event.type === "context_compacted" || event.type === "quota_failover";
|
|
@@ -10568,7 +10571,7 @@ function subscribePhoneBoard(notify) {
|
|
|
10568
10571
|
const data = watch(appDataDir(), { ignoreInitial: true, depth: 0 });
|
|
10569
10572
|
const onData = (changed) => {
|
|
10570
10573
|
const name = basename3(changed);
|
|
10571
|
-
if (name !== "workspaces.json" && name !== "removed-workspaces.json") return;
|
|
10574
|
+
if (name !== "workspaces.json" && name !== "removed-workspaces.json" && name !== "settings.json") return;
|
|
10572
10575
|
notify();
|
|
10573
10576
|
};
|
|
10574
10577
|
data.on("add", onData);
|
|
@@ -11003,8 +11006,8 @@ var RelayAccountStore = class {
|
|
|
11003
11006
|
verifyCredential(token) {
|
|
11004
11007
|
const trimmed = token.trim();
|
|
11005
11008
|
if (!trimmed) return null;
|
|
11006
|
-
const
|
|
11007
|
-
return
|
|
11009
|
+
const row2 = this.db.prepare("SELECT account_id FROM credentials WHERE token_hash = ?").get(sha256(trimmed));
|
|
11010
|
+
return row2?.account_id ?? null;
|
|
11008
11011
|
}
|
|
11009
11012
|
/** Drop one Mac credential. The account and its other Macs stay. */
|
|
11010
11013
|
logout(token) {
|
|
@@ -11078,14 +11081,14 @@ var RelayAccountStore = class {
|
|
|
11078
11081
|
}
|
|
11079
11082
|
}
|
|
11080
11083
|
requireAccount(accountId) {
|
|
11081
|
-
const
|
|
11082
|
-
if (!
|
|
11084
|
+
const row2 = this.db.prepare("SELECT id FROM accounts WHERE id = ?").get(accountId);
|
|
11085
|
+
if (!row2) throw new Error("That Sideboard account is no longer on the relay.");
|
|
11083
11086
|
}
|
|
11084
11087
|
accountIdForVerifiedEmail(email) {
|
|
11085
|
-
const
|
|
11088
|
+
const row2 = this.db.prepare(
|
|
11086
11089
|
"SELECT account_id FROM identities WHERE email_verified = 1 AND email = ? LIMIT 1"
|
|
11087
11090
|
).get(email);
|
|
11088
|
-
return
|
|
11091
|
+
return row2?.account_id;
|
|
11089
11092
|
}
|
|
11090
11093
|
insertIdentity(issuer, subject, accountId, email, verified) {
|
|
11091
11094
|
this.db.prepare(
|
|
@@ -11116,11 +11119,11 @@ var RelayLoginSessions = class {
|
|
|
11116
11119
|
}
|
|
11117
11120
|
takeLogin(state) {
|
|
11118
11121
|
this.sweep();
|
|
11119
|
-
const
|
|
11120
|
-
if (!
|
|
11122
|
+
const row2 = this.waiting.get(state);
|
|
11123
|
+
if (!row2) return null;
|
|
11121
11124
|
this.waiting.delete(state);
|
|
11122
|
-
if (
|
|
11123
|
-
return
|
|
11125
|
+
if (row2.expiresAt < Date.now()) return null;
|
|
11126
|
+
return row2;
|
|
11124
11127
|
}
|
|
11125
11128
|
putResult(state, value) {
|
|
11126
11129
|
this.results.set(state, { value, expiresAt: Date.now() + LOGIN_TTL_MS });
|
|
@@ -11128,19 +11131,19 @@ var RelayLoginSessions = class {
|
|
|
11128
11131
|
/** `undefined` means the browser flow has not finished. */
|
|
11129
11132
|
takeResult(state) {
|
|
11130
11133
|
this.sweep();
|
|
11131
|
-
const
|
|
11132
|
-
if (!
|
|
11134
|
+
const row2 = this.results.get(state);
|
|
11135
|
+
if (!row2) return this.waiting.has(state) ? void 0 : null;
|
|
11133
11136
|
this.results.delete(state);
|
|
11134
|
-
if (
|
|
11135
|
-
return
|
|
11137
|
+
if (row2.expiresAt < Date.now()) return null;
|
|
11138
|
+
return row2.value;
|
|
11136
11139
|
}
|
|
11137
11140
|
sweep() {
|
|
11138
11141
|
const now = Date.now();
|
|
11139
|
-
for (const [state,
|
|
11140
|
-
if (
|
|
11142
|
+
for (const [state, row2] of this.waiting) {
|
|
11143
|
+
if (row2.expiresAt < now) this.waiting.delete(state);
|
|
11141
11144
|
}
|
|
11142
|
-
for (const [state,
|
|
11143
|
-
if (
|
|
11145
|
+
for (const [state, row2] of this.results) {
|
|
11146
|
+
if (row2.expiresAt < now) this.results.delete(state);
|
|
11144
11147
|
}
|
|
11145
11148
|
}
|
|
11146
11149
|
};
|
|
@@ -11165,10 +11168,10 @@ function relayAuthorizeUrl(provider, client, redirectUri, state) {
|
|
|
11165
11168
|
return url.toString();
|
|
11166
11169
|
}
|
|
11167
11170
|
async function readJson(res) {
|
|
11168
|
-
const
|
|
11169
|
-
if (!
|
|
11171
|
+
const text5 = await res.text();
|
|
11172
|
+
if (!text5) return {};
|
|
11170
11173
|
try {
|
|
11171
|
-
return JSON.parse(
|
|
11174
|
+
return JSON.parse(text5);
|
|
11172
11175
|
} catch {
|
|
11173
11176
|
throw new Error("Sign-in provider returned a non-JSON response.");
|
|
11174
11177
|
}
|
|
@@ -11204,8 +11207,8 @@ async function fetchGithubIdentity(client, code, redirectUri, fetchImpl) {
|
|
|
11204
11207
|
});
|
|
11205
11208
|
const emails = await readJson(emailRes);
|
|
11206
11209
|
const list = Array.isArray(emails) ? emails : [];
|
|
11207
|
-
const primary = list.find((
|
|
11208
|
-
const anyVerified = list.find((
|
|
11210
|
+
const primary = list.find((row2) => row2.primary === true && row2.verified === true);
|
|
11211
|
+
const anyVerified = list.find((row2) => row2.verified === true);
|
|
11209
11212
|
const chosen = primary ?? anyVerified;
|
|
11210
11213
|
const email = chosen && typeof chosen.email === "string" ? chosen.email : null;
|
|
11211
11214
|
return { issuer: GITHUB_ACCOUNT_ISSUER, subject, email, emailVerified: Boolean(email) };
|
|
@@ -11222,9 +11225,9 @@ async function readBody(req) {
|
|
|
11222
11225
|
if (size > 8192) throw new Error("body too large");
|
|
11223
11226
|
chunks.push(buf);
|
|
11224
11227
|
}
|
|
11225
|
-
const
|
|
11226
|
-
if (!
|
|
11227
|
-
return JSON.parse(
|
|
11228
|
+
const text5 = Buffer.concat(chunks).toString("utf8").trim();
|
|
11229
|
+
if (!text5) return {};
|
|
11230
|
+
return JSON.parse(text5);
|
|
11228
11231
|
}
|
|
11229
11232
|
function sendJson2(res, status, body) {
|
|
11230
11233
|
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
@@ -11605,10 +11608,10 @@ async function publishSlackAppHome(opts) {
|
|
|
11605
11608
|
}
|
|
11606
11609
|
|
|
11607
11610
|
// src/slack/relay-hub.ts
|
|
11608
|
-
function parseSlackDeviceDestination(
|
|
11609
|
-
const m =
|
|
11610
|
-
if (!m) return { label: null, rest:
|
|
11611
|
-
return { label: m[1], rest:
|
|
11611
|
+
function parseSlackDeviceDestination(text5) {
|
|
11612
|
+
const m = text5.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
|
|
11613
|
+
if (!m) return { label: null, rest: text5 };
|
|
11614
|
+
return { label: m[1], rest: text5.slice(m[0].length) };
|
|
11612
11615
|
}
|
|
11613
11616
|
var SlackRelayHub = class {
|
|
11614
11617
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -12386,8 +12389,8 @@ ${input.permalink}` : "";
|
|
|
12386
12389
|
|
|
12387
12390
|
${body}${link}`;
|
|
12388
12391
|
}
|
|
12389
|
-
function isSlackExternalReplyPrompt(
|
|
12390
|
-
return isSlackExternalReplyText(
|
|
12392
|
+
function isSlackExternalReplyPrompt(text5) {
|
|
12393
|
+
return isSlackExternalReplyText(text5);
|
|
12391
12394
|
}
|
|
12392
12395
|
function pendingSlackExternalReplies(messages) {
|
|
12393
12396
|
return pendingInjectedNotices(messages, isSlackExternalReplyText);
|
|
@@ -12408,7 +12411,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
12408
12411
|
await continueOnReply(threadId, prompt);
|
|
12409
12412
|
return;
|
|
12410
12413
|
}
|
|
12411
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
12414
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-FPGVKEID.js");
|
|
12412
12415
|
await getOrchestrator2().send(threadId, prompt);
|
|
12413
12416
|
} catch {
|
|
12414
12417
|
}
|
|
@@ -12416,9 +12419,9 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
12416
12419
|
function listSlackOutboundWatches() {
|
|
12417
12420
|
return pruneWatches(readStore3());
|
|
12418
12421
|
}
|
|
12419
|
-
function formatOwnerSlackFyi(userName,
|
|
12422
|
+
function formatOwnerSlackFyi(userName, text5) {
|
|
12420
12423
|
const who = userName.trim() || "Someone";
|
|
12421
|
-
const body =
|
|
12424
|
+
const body = text5.trim() || "(no text)";
|
|
12422
12425
|
return `${who} replied in Slack:
|
|
12423
12426
|
${body}`;
|
|
12424
12427
|
}
|
|
@@ -12433,7 +12436,7 @@ async function relayExternalReply(opts) {
|
|
|
12433
12436
|
const thread = readThread(threadId);
|
|
12434
12437
|
if (!thread || thread.status === "archived") return "ignored";
|
|
12435
12438
|
try {
|
|
12436
|
-
const
|
|
12439
|
+
const text5 = formatSlackExternalReplyPrompt({
|
|
12437
12440
|
userName: opts.reply.userName,
|
|
12438
12441
|
kind: opts.watch.kind,
|
|
12439
12442
|
toLabel: opts.watch.toLabel,
|
|
@@ -12442,7 +12445,7 @@ async function relayExternalReply(opts) {
|
|
|
12442
12445
|
});
|
|
12443
12446
|
appendMessage(threadId, {
|
|
12444
12447
|
role: "agent",
|
|
12445
|
-
text:
|
|
12448
|
+
text: text5,
|
|
12446
12449
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
12447
12450
|
});
|
|
12448
12451
|
} catch {
|
|
@@ -12866,6 +12869,7 @@ export {
|
|
|
12866
12869
|
applySystemCaEnv,
|
|
12867
12870
|
applyThreadIntoMain,
|
|
12868
12871
|
applyTurnUsage,
|
|
12872
|
+
applyWorkspaceTags,
|
|
12869
12873
|
armSchedules,
|
|
12870
12874
|
assertAbleTimeCredential,
|
|
12871
12875
|
assertAbleTimeMcpCredential,
|
|
@@ -13173,6 +13177,7 @@ export {
|
|
|
13173
13177
|
gitBranchPrefixSetting,
|
|
13174
13178
|
githubAgentGitEnv,
|
|
13175
13179
|
githubHttpsInsteadOfEntries,
|
|
13180
|
+
githubProjectsOnIssue,
|
|
13176
13181
|
githubRelationEditFlag,
|
|
13177
13182
|
globalAgentCwd,
|
|
13178
13183
|
groupHomeBoardWorktrees,
|
|
@@ -13259,6 +13264,7 @@ export {
|
|
|
13259
13264
|
isPrimaryCheckoutThread,
|
|
13260
13265
|
isRemoteAccountLoginCancelled,
|
|
13261
13266
|
isRemoteStopCommand,
|
|
13267
|
+
isRemovedWorkspace,
|
|
13262
13268
|
isSelfWrittenRecord,
|
|
13263
13269
|
isSessionQuotaLimit,
|
|
13264
13270
|
isSetupLastError,
|
|
@@ -13315,6 +13321,7 @@ export {
|
|
|
13315
13321
|
listCursorModels,
|
|
13316
13322
|
listGitHubIssueCommentsSince,
|
|
13317
13323
|
listGitHubIssues,
|
|
13324
|
+
listGitHubProjects,
|
|
13318
13325
|
listGlobalThreads,
|
|
13319
13326
|
listIssues,
|
|
13320
13327
|
listLinearAssignedIssues,
|
|
@@ -13322,6 +13329,7 @@ export {
|
|
|
13322
13329
|
listLinearIssues,
|
|
13323
13330
|
listLinearIssuesDirect,
|
|
13324
13331
|
listLinearIssuesFiltered,
|
|
13332
|
+
listLinearProjects,
|
|
13325
13333
|
listLinearTeamCycles,
|
|
13326
13334
|
listLinearTeams,
|
|
13327
13335
|
listModelsForAgent,
|
|
@@ -13393,6 +13401,7 @@ export {
|
|
|
13393
13401
|
normalizeThinkingEffort,
|
|
13394
13402
|
normalizeThread,
|
|
13395
13403
|
normalizeTurnInput,
|
|
13404
|
+
normalizeWorkspaceTags,
|
|
13396
13405
|
normalizeWorktreePath,
|
|
13397
13406
|
notifyOrchestrator,
|
|
13398
13407
|
npmGlobalNodeModules,
|
|
@@ -13554,6 +13563,7 @@ export {
|
|
|
13554
13563
|
runSlackListen,
|
|
13555
13564
|
runSlackRelayClient,
|
|
13556
13565
|
runWorkspaceSetup,
|
|
13566
|
+
sameWorkspacePath,
|
|
13557
13567
|
sameWorktreePath,
|
|
13558
13568
|
sanitizeGitBranchPrefix,
|
|
13559
13569
|
sanitizeMcpServerName,
|
|
@@ -13703,6 +13713,8 @@ export {
|
|
|
13703
13713
|
withTurnScopedCost,
|
|
13704
13714
|
workspaceAgentChatSummaries,
|
|
13705
13715
|
workspaceSettingsSourceLabel,
|
|
13716
|
+
workspaceTagTargetRef,
|
|
13717
|
+
workspaceTagsFromGroup,
|
|
13706
13718
|
worktreeAgentBlock,
|
|
13707
13719
|
worktreeBoardStatus,
|
|
13708
13720
|
worktreeCleanupSettings,
|