@sideboard-ai/core 0.1.74 → 0.1.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agents-H7M5CQY2.js → agents-EWPHOM6Y.js} +6 -6
- package/dist/{agents-2S2JVLZ6.js → agents-GRAPUXWW.js} +5 -5
- package/dist/{app-settings-3TLA2EJ7.js → app-settings-6IOQYGBK.js} +7 -1
- package/dist/{app-settings-P42Z3VOB.js → app-settings-NILNWNNQ.js} +7 -1
- package/dist/{caffeinate-hold-KLC6SABD.js → caffeinate-hold-EAZNWJBE.js} +5 -1
- package/dist/caffeinate-hold-OHAUWOA6.js +20 -0
- package/dist/{chunk-77W62MTX.js → chunk-5LXWTU3J.js} +6 -6
- package/dist/{chunk-GJ2LZQJI.js → chunk-5VTWBI3I.js} +2 -2
- package/dist/{chunk-2NKSHIRI.js → chunk-6T2SKGCS.js} +3 -3
- package/dist/{chunk-OE7YBB5X.js → chunk-FUUVPN4A.js} +108 -3
- package/dist/{chunk-HCWAIEBU.js → chunk-HCGEFU3J.js} +8 -7
- package/dist/{chunk-XA2FQJTN.js → chunk-HSLQXL6M.js} +6 -5
- package/dist/{chunk-W7YOTDVO.js → chunk-IFPN6TER.js} +47 -2
- package/dist/chunk-MFF2RE3I.js +168 -0
- package/dist/{chunk-KBKPVKYZ.js → chunk-P33ZQ7ZC.js} +3 -3
- package/dist/{chunk-DG3S2UXP.js → chunk-PW5YHHP3.js} +2 -2
- package/dist/chunk-RV6DBEDQ.js +170 -0
- package/dist/{chunk-5KJMNNCB.js → chunk-SHCR6RPU.js} +3 -3
- package/dist/{chunk-PTJJIQK7.js → chunk-XKNBSYA7.js} +47 -2
- package/dist/{chunk-MF2YMEGD.js → chunk-YVVXWWCC.js} +110 -3
- package/dist/{coordinator-prompt-3XXSG7M2.js → coordinator-prompt-2OVON2LQ.js} +4 -4
- package/dist/{coordinator-prompt-RTUCCPCI.js → coordinator-prompt-K2R34A5T.js} +3 -3
- package/dist/{global-workspace-PJU6HISJ.js → global-workspace-RI367AM6.js} +4 -4
- package/dist/{global-workspace-CSIS62Z4.js → global-workspace-VG44RZUH.js} +5 -5
- package/dist/index.cjs +1548 -614
- package/dist/index.d.cts +226 -28
- package/dist/index.d.ts +226 -28
- package/dist/index.js +1277 -575
- package/dist/mcp/run-stdio.cjs +3153 -2509
- package/dist/mcp/run-stdio.js +690 -262
- package/dist/{workspaces-L4TXMUNM.js → workspaces-BQWQEZWE.js} +5 -5
- package/dist/{workspaces-W72KZL4B.js → workspaces-TYPJNUSM.js} +6 -6
- package/dist/{worktree-VOCH3WS3.js → worktree-5VLLFI5W.js} +5 -1
- package/dist/{worktree-KWXHMKRN.js → worktree-UNSOCYZU.js} +5 -1
- package/package.json +1 -1
- package/dist/caffeinate-hold-BEJIYPJ7.js +0 -107
- package/dist/chunk-JAWEDVVA.js +0 -106
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
listWorkspaces,
|
|
5
5
|
removeWorkspace,
|
|
6
6
|
syncWorkspacesFromThreads
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PW5YHHP3.js";
|
|
8
8
|
import {
|
|
9
9
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
10
10
|
CLAUDE_MODEL_CATALOG,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
sideboardMcpProfile,
|
|
59
59
|
threadRequestsBrightsyMcp,
|
|
60
60
|
writeInjectedMcpConfig
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-SHCR6RPU.js";
|
|
62
62
|
import {
|
|
63
63
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
64
64
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -85,7 +85,7 @@ import {
|
|
|
85
85
|
parseForceStopMessage,
|
|
86
86
|
slackCoordinatorSourceRef,
|
|
87
87
|
takenTeamSlugsForOrchestration
|
|
88
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-5LXWTU3J.js";
|
|
89
89
|
import {
|
|
90
90
|
COORDINATOR_TOOL_PLAYBOOK,
|
|
91
91
|
SLACK_REPLY_FORMATTING,
|
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
enrichWorkspacesWithGithub,
|
|
95
95
|
ensureGlobalCoordinatorCwd,
|
|
96
96
|
formatWorkspaceInventory
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-HCGEFU3J.js";
|
|
98
98
|
import {
|
|
99
99
|
brightsyConfigPath,
|
|
100
100
|
brightsyMcpServerName,
|
|
@@ -129,79 +129,20 @@ import {
|
|
|
129
129
|
pushTurnStderr,
|
|
130
130
|
summarizeTurnStderr
|
|
131
131
|
} from "./chunk-SEOICVGB.js";
|
|
132
|
-
import {
|
|
133
|
-
HARNESS_ENV_KEYS,
|
|
134
|
-
appSettingsPath,
|
|
135
|
-
applyAppEnvironment,
|
|
136
|
-
autoArchiveOnMergeEnabled,
|
|
137
|
-
autoCleanupOrphansEnabled,
|
|
138
|
-
autoRenameBranchEnabled,
|
|
139
|
-
autoRunAfterSetupEnabled,
|
|
140
|
-
brightsyCloudConnectAgent,
|
|
141
|
-
brightsyCloudConnectEnabled,
|
|
142
|
-
brightsyInjectWorktreeMcpEnabled,
|
|
143
|
-
caffeinateWhileCloudConnectEnabled,
|
|
144
|
-
caffeinateWhileRunningEnabled,
|
|
145
|
-
caffeinateWhileSlackListenEnabled,
|
|
146
|
-
childEnvWithAppSettings,
|
|
147
|
-
claudeChromeEnabled,
|
|
148
|
-
claudeUserSettingsPath,
|
|
149
|
-
deleteBranchOnPurgeEnabled,
|
|
150
|
-
disconnectLinearConnection,
|
|
151
|
-
ensureSlackDeviceIdentity,
|
|
152
|
-
getDefaultAgent,
|
|
153
|
-
getDefaultEffort,
|
|
154
|
-
getDefaultFast,
|
|
155
|
-
getDefaultModel,
|
|
156
|
-
getIssueSource,
|
|
157
|
-
getLinearApiKey,
|
|
158
|
-
harnessEnvKey,
|
|
159
|
-
isLinearConnected,
|
|
160
|
-
isSecureFileEncrypted,
|
|
161
|
-
loadAppSettings,
|
|
162
|
-
maxConcurrentAgents,
|
|
163
|
-
orchestrationQuotaFallbackAgent,
|
|
164
|
-
orchestrationQuotaOnLimit,
|
|
165
|
-
persistVaultKeyInKeychain,
|
|
166
|
-
readKeychainVaultKey,
|
|
167
|
-
readSecureJson,
|
|
168
|
-
resolveAgentExecutable,
|
|
169
|
-
resolveClaudeExecutable,
|
|
170
|
-
resolveEffectiveIssueSource,
|
|
171
|
-
resolveNewThreadOptions,
|
|
172
|
-
resolveThreadDefaults,
|
|
173
|
-
resolveVaultKey,
|
|
174
|
-
saveAppSettings,
|
|
175
|
-
saveLinearOAuth,
|
|
176
|
-
secureFileUnlocksWith,
|
|
177
|
-
setVaultMasterKey,
|
|
178
|
-
slackAppLevelToken,
|
|
179
|
-
slackListenEnabled,
|
|
180
|
-
toPublicAppSettings,
|
|
181
|
-
updateAdvancedSettings,
|
|
182
|
-
updateAgentExecutable,
|
|
183
|
-
updateAppEnvironment,
|
|
184
|
-
updateBrightsySettings,
|
|
185
|
-
updateClaudeSettings,
|
|
186
|
-
updateCodexSettings,
|
|
187
|
-
updateDefaultsSettings,
|
|
188
|
-
updateIntegrationsSettings,
|
|
189
|
-
updateOpencodeSettings,
|
|
190
|
-
writeSecureJson
|
|
191
|
-
} from "./chunk-W7YOTDVO.js";
|
|
192
132
|
import {
|
|
193
133
|
caffeinateHoldPath,
|
|
194
134
|
getCaffeinateHold,
|
|
135
|
+
isThreadCaffeinated,
|
|
136
|
+
releaseCaffeinateHoldForThread,
|
|
195
137
|
setCaffeinateHold
|
|
196
|
-
} from "./chunk-
|
|
197
|
-
import {
|
|
198
|
-
writePrivateFile
|
|
199
|
-
} from "./chunk-JT7R45JB.js";
|
|
138
|
+
} from "./chunk-MFF2RE3I.js";
|
|
200
139
|
import {
|
|
201
140
|
FAMOUS_SOCCER_TEAMS,
|
|
202
141
|
addPrStackLayer,
|
|
203
142
|
allocateTeamName,
|
|
204
143
|
allocateTeamSlug,
|
|
144
|
+
appendIndexedGitConfig,
|
|
145
|
+
applyGithubGitAuthEnv,
|
|
205
146
|
branchDisplayLabel,
|
|
206
147
|
checkoutPrStackLayer,
|
|
207
148
|
collectTakenTeamSlugs,
|
|
@@ -216,6 +157,7 @@ import {
|
|
|
216
157
|
extractGhErrorDetail,
|
|
217
158
|
fetchPrHead,
|
|
218
159
|
formatGhLandError,
|
|
160
|
+
formatGitAuthModeDirective,
|
|
219
161
|
formatIpcInvokeError,
|
|
220
162
|
formatRateLimitResetHint,
|
|
221
163
|
getPr,
|
|
@@ -225,6 +167,7 @@ import {
|
|
|
225
167
|
getPrStack,
|
|
226
168
|
ghHeadRef,
|
|
227
169
|
ghRepoSelectArgs,
|
|
170
|
+
githubAgentGitEnv,
|
|
228
171
|
initPrStack,
|
|
229
172
|
isDirty,
|
|
230
173
|
isGhRateLimitError,
|
|
@@ -259,13 +202,79 @@ import {
|
|
|
259
202
|
worktreeDisplayLabel,
|
|
260
203
|
worktreeDisplayLabelForGroup,
|
|
261
204
|
worktreeNameFromPath
|
|
262
|
-
} from "./chunk-
|
|
205
|
+
} from "./chunk-YVVXWWCC.js";
|
|
263
206
|
import {
|
|
264
207
|
ATTACHMENTS_DIR,
|
|
265
208
|
LEGACY_ATTACHMENTS_DIR,
|
|
266
209
|
attachmentsGitignoreBody,
|
|
267
210
|
isWorkspaceScratchPath
|
|
268
211
|
} from "./chunk-FKOIHGKV.js";
|
|
212
|
+
import {
|
|
213
|
+
GITHUB_GIT_AUTH_MODES,
|
|
214
|
+
HARNESS_ENV_KEYS,
|
|
215
|
+
appSettingsPath,
|
|
216
|
+
applyAppEnvironment,
|
|
217
|
+
autoArchiveOnMergeEnabled,
|
|
218
|
+
autoCleanupOrphansEnabled,
|
|
219
|
+
autoRenameBranchEnabled,
|
|
220
|
+
autoRunAfterSetupEnabled,
|
|
221
|
+
brightsyCloudConnectAgent,
|
|
222
|
+
brightsyCloudConnectEnabled,
|
|
223
|
+
brightsyInjectWorktreeMcpEnabled,
|
|
224
|
+
caffeinateWhileCloudConnectEnabled,
|
|
225
|
+
caffeinateWhileRunningEnabled,
|
|
226
|
+
caffeinateWhileSlackListenEnabled,
|
|
227
|
+
childEnvWithAppSettings,
|
|
228
|
+
claudeChromeEnabled,
|
|
229
|
+
claudeUserSettingsPath,
|
|
230
|
+
deleteBranchOnPurgeEnabled,
|
|
231
|
+
disconnectLinearConnection,
|
|
232
|
+
ensureSlackDeviceIdentity,
|
|
233
|
+
getDefaultAgent,
|
|
234
|
+
getDefaultEffort,
|
|
235
|
+
getDefaultFast,
|
|
236
|
+
getDefaultModel,
|
|
237
|
+
getGithubGitAuthMode,
|
|
238
|
+
getGithubPat,
|
|
239
|
+
getIssueSource,
|
|
240
|
+
getLinearApiKey,
|
|
241
|
+
harnessEnvKey,
|
|
242
|
+
isLinearConnected,
|
|
243
|
+
isSecureFileEncrypted,
|
|
244
|
+
loadAppSettings,
|
|
245
|
+
maxConcurrentAgents,
|
|
246
|
+
orchestrationQuotaFallbackAgent,
|
|
247
|
+
orchestrationQuotaOnLimit,
|
|
248
|
+
persistVaultKeyInKeychain,
|
|
249
|
+
readKeychainVaultKey,
|
|
250
|
+
readSecureJson,
|
|
251
|
+
resolveAgentExecutable,
|
|
252
|
+
resolveClaudeExecutable,
|
|
253
|
+
resolveEffectiveIssueSource,
|
|
254
|
+
resolveNewThreadOptions,
|
|
255
|
+
resolveThreadDefaults,
|
|
256
|
+
resolveVaultKey,
|
|
257
|
+
saveAppSettings,
|
|
258
|
+
saveLinearOAuth,
|
|
259
|
+
secureFileUnlocksWith,
|
|
260
|
+
setVaultMasterKey,
|
|
261
|
+
slackAppLevelToken,
|
|
262
|
+
slackListenEnabled,
|
|
263
|
+
toPublicAppSettings,
|
|
264
|
+
updateAdvancedSettings,
|
|
265
|
+
updateAgentExecutable,
|
|
266
|
+
updateAppEnvironment,
|
|
267
|
+
updateBrightsySettings,
|
|
268
|
+
updateClaudeSettings,
|
|
269
|
+
updateCodexSettings,
|
|
270
|
+
updateDefaultsSettings,
|
|
271
|
+
updateIntegrationsSettings,
|
|
272
|
+
updateOpencodeSettings,
|
|
273
|
+
writeSecureJson
|
|
274
|
+
} from "./chunk-IFPN6TER.js";
|
|
275
|
+
import {
|
|
276
|
+
writePrivateFile
|
|
277
|
+
} from "./chunk-JT7R45JB.js";
|
|
269
278
|
import {
|
|
270
279
|
appendMessage,
|
|
271
280
|
createEmptyThread,
|
|
@@ -374,9 +383,9 @@ async function getGitHubStatus() {
|
|
|
374
383
|
};
|
|
375
384
|
}
|
|
376
385
|
const status = await run("gh", ["auth", "status"], { reject: false });
|
|
377
|
-
const
|
|
386
|
+
const text3 = `${status.stdout}
|
|
378
387
|
${status.stderr}`;
|
|
379
|
-
const loginMatch =
|
|
388
|
+
const loginMatch = text3.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text3.match(/Logged in to ([^\s]+) as (\S+)/i);
|
|
380
389
|
if (loginMatch) {
|
|
381
390
|
return {
|
|
382
391
|
connected: true,
|
|
@@ -396,13 +405,39 @@ async function refreshGitHubAuth() {
|
|
|
396
405
|
return getGitHubStatus();
|
|
397
406
|
}
|
|
398
407
|
|
|
408
|
+
// src/http/fetch.ts
|
|
409
|
+
var injected = null;
|
|
410
|
+
function setHttpFetchImpl(fetchImpl) {
|
|
411
|
+
injected = fetchImpl;
|
|
412
|
+
}
|
|
413
|
+
function formatFetchError(err, url) {
|
|
414
|
+
if (!(err instanceof Error)) return `${String(err)} (${url})`;
|
|
415
|
+
const cause = err.cause;
|
|
416
|
+
let detail = "";
|
|
417
|
+
if (cause instanceof Error) {
|
|
418
|
+
const code = typeof cause.code === "string" ? cause.code : void 0;
|
|
419
|
+
detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
|
|
420
|
+
} else if (cause != null) {
|
|
421
|
+
detail = ` [${String(cause)}]`;
|
|
422
|
+
}
|
|
423
|
+
return `${err.message}${detail} (${url})`;
|
|
424
|
+
}
|
|
425
|
+
async function httpFetch(input, init) {
|
|
426
|
+
const url = typeof input === "string" ? input : input.href;
|
|
427
|
+
const fn = injected ?? globalThis.fetch.bind(globalThis);
|
|
428
|
+
try {
|
|
429
|
+
return await fn(url, init);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
throw new Error(formatFetchError(err, url));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
399
435
|
// src/integrations/linear-oauth.ts
|
|
400
436
|
import { createHash, randomBytes } from "crypto";
|
|
401
437
|
import { createServer } from "http";
|
|
402
438
|
|
|
403
439
|
// src/integrations/linear-app.ts
|
|
404
440
|
var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
|
|
405
|
-
var BAKED_LINEAR_CLIENT_SECRET = "";
|
|
406
441
|
function hasBakedLinearOAuth() {
|
|
407
442
|
return Boolean(BAKED_LINEAR_CLIENT_ID.trim());
|
|
408
443
|
}
|
|
@@ -414,12 +449,12 @@ var LINEAR_AUTHORIZE = "https://linear.app/oauth/authorize";
|
|
|
414
449
|
var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
|
|
415
450
|
var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
|
|
416
451
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
417
|
-
var LINEAR_OAUTH_SCOPES = "read";
|
|
452
|
+
var LINEAR_OAUTH_SCOPES = "read,write";
|
|
418
453
|
var REFRESH_SKEW_MS = 5 * 6e4;
|
|
419
454
|
function linearOAuthCredentials() {
|
|
420
455
|
const settings = loadAppSettings();
|
|
421
456
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
422
|
-
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() ||
|
|
457
|
+
const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || "";
|
|
423
458
|
if (!clientId) {
|
|
424
459
|
throw new Error(
|
|
425
460
|
`Linear browser sign-in needs a Linear OAuth app Client ID. Create one at linear.app/settings/api/applications/new with callback ${LINEAR_OAUTH_REDIRECT}, then set SIDEBOARD_LINEAR_CLIENT_ID (PKCE does not require a secret). You can still paste a personal API key.`
|
|
@@ -459,7 +494,7 @@ function htmlPage(title, body) {
|
|
|
459
494
|
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
460
495
|
}
|
|
461
496
|
async function exchangeLinearToken(body) {
|
|
462
|
-
const res = await
|
|
497
|
+
const res = await httpFetch(LINEAR_TOKEN, {
|
|
463
498
|
method: "POST",
|
|
464
499
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
465
500
|
body
|
|
@@ -481,7 +516,7 @@ async function persistLinearTokens(data, viewer) {
|
|
|
481
516
|
});
|
|
482
517
|
}
|
|
483
518
|
async function fetchLinearViewer(accessToken) {
|
|
484
|
-
const res = await
|
|
519
|
+
const res = await httpFetch(LINEAR_GRAPHQL, {
|
|
485
520
|
method: "POST",
|
|
486
521
|
headers: {
|
|
487
522
|
"Content-Type": "application/json",
|
|
@@ -652,7 +687,7 @@ async function revokeLinearToken(token, tokenTypeHint) {
|
|
|
652
687
|
token,
|
|
653
688
|
token_type_hint: tokenTypeHint
|
|
654
689
|
});
|
|
655
|
-
await
|
|
690
|
+
await httpFetch(LINEAR_REVOKE, {
|
|
656
691
|
method: "POST",
|
|
657
692
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
658
693
|
body
|
|
@@ -669,6 +704,18 @@ async function disconnectLinear() {
|
|
|
669
704
|
|
|
670
705
|
// src/integrations/linear.ts
|
|
671
706
|
var LINEAR_GRAPHQL2 = "https://api.linear.app/graphql";
|
|
707
|
+
var ISSUE_FIELDS = `
|
|
708
|
+
id
|
|
709
|
+
identifier
|
|
710
|
+
title
|
|
711
|
+
description
|
|
712
|
+
url
|
|
713
|
+
priority
|
|
714
|
+
state { id name type }
|
|
715
|
+
assignee { id name }
|
|
716
|
+
team { id key name states { nodes { id name type } } }
|
|
717
|
+
labels { nodes { name } }
|
|
718
|
+
`;
|
|
672
719
|
var ASSIGNED_ISSUES_QUERY = `
|
|
673
720
|
query SideboardAssignedIssues($first: Int!) {
|
|
674
721
|
viewer {
|
|
@@ -677,53 +724,280 @@ query SideboardAssignedIssues($first: Int!) {
|
|
|
677
724
|
orderBy: updatedAt
|
|
678
725
|
filter: { state: { type: { nin: ["completed", "canceled"] } } }
|
|
679
726
|
) {
|
|
680
|
-
nodes {
|
|
681
|
-
id
|
|
682
|
-
identifier
|
|
683
|
-
title
|
|
684
|
-
url
|
|
685
|
-
labels { nodes { name } }
|
|
686
|
-
}
|
|
727
|
+
nodes { ${ISSUE_FIELDS} }
|
|
687
728
|
}
|
|
688
729
|
}
|
|
689
730
|
}
|
|
690
731
|
`;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
732
|
+
var TEAMS_QUERY = `
|
|
733
|
+
query SideboardTeams {
|
|
734
|
+
viewer { id name }
|
|
735
|
+
teams(first: 50) {
|
|
736
|
+
nodes {
|
|
737
|
+
id
|
|
738
|
+
key
|
|
739
|
+
name
|
|
740
|
+
states { nodes { id name type } }
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
`;
|
|
745
|
+
var ISSUE_QUERY = `
|
|
746
|
+
query SideboardIssue($id: String!) {
|
|
747
|
+
issue(id: $id) { ${ISSUE_FIELDS} }
|
|
748
|
+
}
|
|
749
|
+
`;
|
|
750
|
+
var ISSUE_CREATE = `
|
|
751
|
+
mutation SideboardIssueCreate($input: IssueCreateInput!) {
|
|
752
|
+
issueCreate(input: $input) {
|
|
753
|
+
success
|
|
754
|
+
issue { ${ISSUE_FIELDS} }
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
`;
|
|
758
|
+
var ISSUE_UPDATE = `
|
|
759
|
+
mutation SideboardIssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
760
|
+
issueUpdate(id: $id, input: $input) {
|
|
761
|
+
success
|
|
762
|
+
issue { ${ISSUE_FIELDS} }
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
`;
|
|
766
|
+
var COMMENT_CREATE = `
|
|
767
|
+
mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
768
|
+
commentCreate(input: $input) {
|
|
769
|
+
success
|
|
770
|
+
comment { id body url }
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
`;
|
|
774
|
+
function rewriteLinearError(message) {
|
|
775
|
+
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
776
|
+
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
777
|
+
}
|
|
778
|
+
return message;
|
|
779
|
+
}
|
|
780
|
+
async function requireLinearToken(apiKey) {
|
|
781
|
+
const token = (apiKey ?? await getLinearAuthToken())?.trim();
|
|
782
|
+
if (!token) {
|
|
694
783
|
throw new Error("Linear is not connected \u2014 sign in from Account settings");
|
|
695
784
|
}
|
|
696
|
-
|
|
697
|
-
|
|
785
|
+
return token;
|
|
786
|
+
}
|
|
787
|
+
async function linearGraphql(query, variables, opts) {
|
|
788
|
+
const apiKey = await requireLinearToken(opts?.apiKey);
|
|
789
|
+
const res = await httpFetch(LINEAR_GRAPHQL2, {
|
|
698
790
|
method: "POST",
|
|
699
791
|
headers: {
|
|
700
792
|
"Content-Type": "application/json",
|
|
701
793
|
Authorization: linearAuthorizationHeader(apiKey)
|
|
702
794
|
},
|
|
703
|
-
body: JSON.stringify({
|
|
704
|
-
query: ASSIGNED_ISSUES_QUERY,
|
|
705
|
-
variables: { first }
|
|
706
|
-
})
|
|
795
|
+
body: JSON.stringify({ query, variables })
|
|
707
796
|
});
|
|
708
797
|
if (!res.ok) {
|
|
709
798
|
const body = await res.text().catch(() => "");
|
|
710
799
|
throw new Error(
|
|
711
|
-
|
|
800
|
+
rewriteLinearError(
|
|
801
|
+
`Linear API error ${res.status}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
802
|
+
)
|
|
712
803
|
);
|
|
713
804
|
}
|
|
714
805
|
const json = await res.json();
|
|
715
806
|
if (json.errors?.length) {
|
|
716
|
-
throw new Error(
|
|
807
|
+
throw new Error(
|
|
808
|
+
rewriteLinearError(json.errors.map((e) => e.message ?? "Linear error").join("; "))
|
|
809
|
+
);
|
|
717
810
|
}
|
|
718
|
-
|
|
719
|
-
|
|
811
|
+
if (json.data == null) {
|
|
812
|
+
throw new Error("Linear API returned no data");
|
|
813
|
+
}
|
|
814
|
+
return json.data;
|
|
815
|
+
}
|
|
816
|
+
function mapState(node) {
|
|
817
|
+
if (!node?.id) return void 0;
|
|
818
|
+
return {
|
|
819
|
+
id: String(node.id),
|
|
820
|
+
name: String(node.name ?? ""),
|
|
821
|
+
type: String(node.type ?? "")
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function mapIssue(node) {
|
|
825
|
+
const team = node.team;
|
|
826
|
+
return {
|
|
720
827
|
id: String(node.id ?? node.identifier ?? ""),
|
|
721
828
|
identifier: String(node.identifier ?? node.id ?? ""),
|
|
722
829
|
title: String(node.title ?? ""),
|
|
723
830
|
url: String(node.url ?? ""),
|
|
724
|
-
|
|
831
|
+
description: node.description?.trim() || void 0,
|
|
832
|
+
priority: typeof node.priority === "number" ? node.priority : void 0,
|
|
833
|
+
state: mapState(node.state),
|
|
834
|
+
assignee: node.assignee?.id ? { id: String(node.assignee.id), name: String(node.assignee.name ?? "") } : void 0,
|
|
835
|
+
team: team?.id ? {
|
|
836
|
+
id: String(team.id),
|
|
837
|
+
key: String(team.key ?? ""),
|
|
838
|
+
name: String(team.name ?? ""),
|
|
839
|
+
states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
840
|
+
} : void 0,
|
|
841
|
+
labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function toIssueInfo(issue) {
|
|
845
|
+
return {
|
|
846
|
+
id: issue.id,
|
|
847
|
+
identifier: issue.identifier,
|
|
848
|
+
title: issue.title,
|
|
849
|
+
url: issue.url,
|
|
850
|
+
labels: issue.labels,
|
|
725
851
|
provider: "linear"
|
|
726
|
-
}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function mapTeam(node) {
|
|
855
|
+
return {
|
|
856
|
+
id: String(node.id ?? ""),
|
|
857
|
+
key: String(node.key ?? ""),
|
|
858
|
+
name: String(node.name ?? ""),
|
|
859
|
+
states: (node.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function resolveLinearTeam(teams, team) {
|
|
863
|
+
const t = team.trim();
|
|
864
|
+
if (!t) throw new Error("Linear team is required (id, key, or name from linear_list_teams)");
|
|
865
|
+
const lower = t.toLowerCase();
|
|
866
|
+
const found = teams.find(
|
|
867
|
+
(x) => x.id === t || x.key.toLowerCase() === lower || x.name.toLowerCase() === lower
|
|
868
|
+
);
|
|
869
|
+
if (!found) {
|
|
870
|
+
const keys = teams.map((x) => x.key).filter(Boolean).join(", ") || "(none)";
|
|
871
|
+
throw new Error(`Linear team not found: ${team}. Known keys: ${keys}`);
|
|
872
|
+
}
|
|
873
|
+
return found;
|
|
874
|
+
}
|
|
875
|
+
function resolveLinearState(team, state) {
|
|
876
|
+
const s = state.trim();
|
|
877
|
+
if (!s) throw new Error("Linear state is empty");
|
|
878
|
+
const lower = s.toLowerCase();
|
|
879
|
+
const found = team.states.find((x) => x.id === s) || team.states.find((x) => x.name.toLowerCase() === lower) || team.states.find((x) => x.type.toLowerCase() === lower);
|
|
880
|
+
if (!found) {
|
|
881
|
+
const available = team.states.map((x) => `${x.name} (${x.type})`).join(", ") || "(none)";
|
|
882
|
+
throw new Error(`Linear state not found on ${team.key}: ${state}. Available: ${available}`);
|
|
883
|
+
}
|
|
884
|
+
return found;
|
|
885
|
+
}
|
|
886
|
+
function normalizePriority(priority) {
|
|
887
|
+
if (priority == null) return void 0;
|
|
888
|
+
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
889
|
+
throw new Error("Linear priority must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low)");
|
|
890
|
+
}
|
|
891
|
+
return priority;
|
|
892
|
+
}
|
|
893
|
+
async function listLinearIssuesDirect(opts) {
|
|
894
|
+
const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
|
|
895
|
+
const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
|
|
896
|
+
return (json.viewer?.assignedIssues?.nodes ?? []).map((node) => toIssueInfo(mapIssue(node)));
|
|
897
|
+
}
|
|
898
|
+
async function listLinearTeams(opts) {
|
|
899
|
+
const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
|
|
900
|
+
return {
|
|
901
|
+
viewer: {
|
|
902
|
+
id: String(json.viewer?.id ?? ""),
|
|
903
|
+
name: String(json.viewer?.name ?? "")
|
|
904
|
+
},
|
|
905
|
+
teams: (json.teams?.nodes ?? []).map(mapTeam)
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
async function getLinearIssue(id, opts) {
|
|
909
|
+
const issueId = id.trim();
|
|
910
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
911
|
+
const json = await linearGraphql(
|
|
912
|
+
ISSUE_QUERY,
|
|
913
|
+
{ id: issueId },
|
|
914
|
+
opts
|
|
915
|
+
);
|
|
916
|
+
if (!json.issue) {
|
|
917
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
918
|
+
}
|
|
919
|
+
return mapIssue(json.issue);
|
|
920
|
+
}
|
|
921
|
+
async function createLinearIssue(input, opts) {
|
|
922
|
+
const title = input.title.trim();
|
|
923
|
+
if (!title) throw new Error("Linear issue title is required");
|
|
924
|
+
const { viewer, teams } = await listLinearTeams(opts);
|
|
925
|
+
const team = resolveLinearTeam(teams, input.team);
|
|
926
|
+
const mutationInput = {
|
|
927
|
+
teamId: team.id,
|
|
928
|
+
title
|
|
929
|
+
};
|
|
930
|
+
const description = input.description?.trim();
|
|
931
|
+
if (description) mutationInput.description = description;
|
|
932
|
+
if (input.state?.trim()) {
|
|
933
|
+
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
934
|
+
}
|
|
935
|
+
const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
|
|
936
|
+
if (assignee === "me") mutationInput.assigneeId = viewer.id;
|
|
937
|
+
else if (assignee) mutationInput.assigneeId = assignee;
|
|
938
|
+
const priority = normalizePriority(input.priority);
|
|
939
|
+
if (priority != null) mutationInput.priority = priority;
|
|
940
|
+
const json = await linearGraphql(ISSUE_CREATE, { input: mutationInput }, opts);
|
|
941
|
+
if (!json.issueCreate?.success || !json.issueCreate.issue) {
|
|
942
|
+
throw new Error("Linear issueCreate failed");
|
|
943
|
+
}
|
|
944
|
+
return mapIssue(json.issueCreate.issue);
|
|
945
|
+
}
|
|
946
|
+
async function updateLinearIssue(input, opts) {
|
|
947
|
+
const issueId = input.id.trim();
|
|
948
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
949
|
+
const mutationInput = {};
|
|
950
|
+
if (input.title !== void 0) {
|
|
951
|
+
const title = input.title.trim();
|
|
952
|
+
if (!title) throw new Error("Linear issue title cannot be empty");
|
|
953
|
+
mutationInput.title = title;
|
|
954
|
+
}
|
|
955
|
+
if (input.description !== void 0) {
|
|
956
|
+
mutationInput.description = input.description;
|
|
957
|
+
}
|
|
958
|
+
if (input.state?.trim()) {
|
|
959
|
+
const existing = await getLinearIssue(issueId, opts);
|
|
960
|
+
const team = existing.team;
|
|
961
|
+
if (!team?.states.length) {
|
|
962
|
+
throw new Error(`Linear issue ${existing.identifier} has no workflow states to resolve "${input.state}"`);
|
|
963
|
+
}
|
|
964
|
+
mutationInput.stateId = resolveLinearState(team, input.state).id;
|
|
965
|
+
}
|
|
966
|
+
if (input.assignee !== void 0) {
|
|
967
|
+
const assignee = input.assignee?.trim() || null;
|
|
968
|
+
if (assignee === "me") {
|
|
969
|
+
const { viewer } = await listLinearTeams(opts);
|
|
970
|
+
mutationInput.assigneeId = viewer.id;
|
|
971
|
+
} else {
|
|
972
|
+
mutationInput.assigneeId = assignee;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (input.priority !== void 0) {
|
|
976
|
+
mutationInput.priority = normalizePriority(input.priority);
|
|
977
|
+
}
|
|
978
|
+
if (Object.keys(mutationInput).length === 0) {
|
|
979
|
+
throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority");
|
|
980
|
+
}
|
|
981
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
982
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
983
|
+
throw new Error("Linear issueUpdate failed");
|
|
984
|
+
}
|
|
985
|
+
return mapIssue(json.issueUpdate.issue);
|
|
986
|
+
}
|
|
987
|
+
async function commentLinearIssue(input, opts) {
|
|
988
|
+
const issueId = input.id.trim();
|
|
989
|
+
const body = input.body.trim();
|
|
990
|
+
if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
|
|
991
|
+
if (!body) throw new Error("Linear comment body is required");
|
|
992
|
+
const json = await linearGraphql(COMMENT_CREATE, { input: { issueId, body } }, opts);
|
|
993
|
+
if (!json.commentCreate?.success || !json.commentCreate.comment?.id) {
|
|
994
|
+
throw new Error("Linear commentCreate failed");
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
id: String(json.commentCreate.comment.id),
|
|
998
|
+
body: String(json.commentCreate.comment.body ?? body),
|
|
999
|
+
url: json.commentCreate.comment.url?.trim() || void 0
|
|
1000
|
+
};
|
|
727
1001
|
}
|
|
728
1002
|
async function validateLinearApiKey(apiKey) {
|
|
729
1003
|
const key = apiKey.trim();
|
|
@@ -808,8 +1082,8 @@ function toolDetail(name, input) {
|
|
|
808
1082
|
if (!input) return void 0;
|
|
809
1083
|
const command = str(input.command) ?? str(input.cmd);
|
|
810
1084
|
if (command) return command;
|
|
811
|
-
const
|
|
812
|
-
if (
|
|
1085
|
+
const path2 = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
|
|
1086
|
+
if (path2) return path2;
|
|
813
1087
|
const pattern = str(input.pattern) ?? str(input.glob) ?? str(input.glob_pattern);
|
|
814
1088
|
if (pattern) return pattern;
|
|
815
1089
|
const query = str(input.query) ?? str(input.prompt);
|
|
@@ -853,12 +1127,12 @@ function toolDescription(name, input) {
|
|
|
853
1127
|
return "Run shell command";
|
|
854
1128
|
}
|
|
855
1129
|
if (/edit|write|apply/i.test(name)) {
|
|
856
|
-
const
|
|
857
|
-
return
|
|
1130
|
+
const path2 = str(input.file_path) ?? str(input.path);
|
|
1131
|
+
return path2 ? `Edit ${path2.split("/").pop()}` : "Edit file";
|
|
858
1132
|
}
|
|
859
1133
|
if (/read/i.test(name)) {
|
|
860
|
-
const
|
|
861
|
-
return
|
|
1134
|
+
const path2 = str(input.file_path) ?? str(input.path);
|
|
1135
|
+
return path2 ? `Read ${path2.split("/").pop()}` : "Read file";
|
|
862
1136
|
}
|
|
863
1137
|
if (/grep/i.test(name)) return "Search files";
|
|
864
1138
|
if (/glob/i.test(name)) return "Find files";
|
|
@@ -868,9 +1142,9 @@ function toolFilePath(input) {
|
|
|
868
1142
|
if (!input) return void 0;
|
|
869
1143
|
return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
|
|
870
1144
|
}
|
|
871
|
-
function countLines(
|
|
872
|
-
if (!
|
|
873
|
-
return
|
|
1145
|
+
function countLines(text3) {
|
|
1146
|
+
if (!text3) return 0;
|
|
1147
|
+
return text3.split("\n").length;
|
|
874
1148
|
}
|
|
875
1149
|
function diffFromInput(input) {
|
|
876
1150
|
if (!input) return {};
|
|
@@ -997,9 +1271,9 @@ function applyAgentEvent(parts, event) {
|
|
|
997
1271
|
function partsToAssistantText(parts) {
|
|
998
1272
|
return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
|
|
999
1273
|
}
|
|
1000
|
-
function stripBrightsyNdjsonNoise(
|
|
1001
|
-
if (!
|
|
1002
|
-
let out =
|
|
1274
|
+
function stripBrightsyNdjsonNoise(text3) {
|
|
1275
|
+
if (!text3 || !text3.includes('"type"')) return text3;
|
|
1276
|
+
let out = text3;
|
|
1003
1277
|
out = out.replace(
|
|
1004
1278
|
/\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
|
|
1005
1279
|
""
|
|
@@ -1065,9 +1339,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1065
1339
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
1066
1340
|
);
|
|
1067
1341
|
}
|
|
1068
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
1342
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-VG44RZUH.js");
|
|
1069
1343
|
if (isGlobalThread2(thread)) {
|
|
1070
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
1344
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-2OVON2LQ.js");
|
|
1071
1345
|
ensureGlobalCoordinatorCwd2(
|
|
1072
1346
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
1073
1347
|
);
|
|
@@ -1084,8 +1358,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1084
1358
|
}
|
|
1085
1359
|
const env = childEnvWithAppSettings(cmd.env);
|
|
1086
1360
|
if (!isOrchestratorThread(thread)) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1361
|
+
try {
|
|
1362
|
+
const originEnv = await originGhRepoEnv(thread.worktreePath, { env });
|
|
1363
|
+
Object.assign(env, originEnv);
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1366
|
+
console.warn(`[sideboard] originGhRepoEnv failed (${thread.worktreePath}): ${detail}`);
|
|
1367
|
+
}
|
|
1089
1368
|
}
|
|
1090
1369
|
const child = execa(cmd.file, cmd.args, {
|
|
1091
1370
|
cwd: cmd.cwd,
|
|
@@ -1154,8 +1433,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1154
1433
|
onEvent({ type: "exit", data: exitCode });
|
|
1155
1434
|
const finalized = finalizeParts(parts);
|
|
1156
1435
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
1157
|
-
const
|
|
1158
|
-
return { exitCode, sessionId, assistantText:
|
|
1436
|
+
const text3 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
1437
|
+
return { exitCode, sessionId, assistantText: text3, parts: finalized, usage };
|
|
1159
1438
|
});
|
|
1160
1439
|
return {
|
|
1161
1440
|
pid: child.pid,
|
|
@@ -1220,6 +1499,8 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
1220
1499
|
lines.push(
|
|
1221
1500
|
"- Push with `git push -u origin HEAD` (or the current branch name). Do not `git push upstream`."
|
|
1222
1501
|
);
|
|
1502
|
+
lines.push("");
|
|
1503
|
+
lines.push(formatGitAuthModeDirective(opts?.gitAuthMode ?? "auto"));
|
|
1223
1504
|
lines.push(
|
|
1224
1505
|
"- Derive the PR title and body from what the changes actually do and why \u2014 inspect the diff/commits and the user request. Do not use the soccer-team worktree nickname or placeholder branch as the PR title."
|
|
1225
1506
|
);
|
|
@@ -1405,9 +1686,9 @@ function matchSimpleGlob(pattern, name) {
|
|
|
1405
1686
|
return new RegExp(`^${escaped}$`).test(name);
|
|
1406
1687
|
}
|
|
1407
1688
|
function readWorktreeInclude(repoPath) {
|
|
1408
|
-
const
|
|
1409
|
-
if (!existsSync2(
|
|
1410
|
-
return readFileSync2(
|
|
1689
|
+
const path2 = join2(repoPath, ".worktreeinclude");
|
|
1690
|
+
if (!existsSync2(path2)) return [];
|
|
1691
|
+
return readFileSync2(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
1411
1692
|
}
|
|
1412
1693
|
function resolveFilesToCopy(repoPath) {
|
|
1413
1694
|
const fromInclude = readWorktreeInclude(repoPath);
|
|
@@ -1482,8 +1763,20 @@ async function captureLoginEnv() {
|
|
|
1482
1763
|
cachedLoginEnv = { ...process.env };
|
|
1483
1764
|
return { ...cachedLoginEnv };
|
|
1484
1765
|
}
|
|
1766
|
+
var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
|
|
1767
|
+
function stripNestedElectronEnv(env) {
|
|
1768
|
+
const out = { ...env };
|
|
1769
|
+
for (const key of Object.keys(out)) {
|
|
1770
|
+
if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
1771
|
+
delete out[key];
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return out;
|
|
1775
|
+
}
|
|
1485
1776
|
function buildWorkspaceScriptEnv(opts, baseEnv) {
|
|
1486
|
-
const env = {
|
|
1777
|
+
const env = stripNestedElectronEnv({
|
|
1778
|
+
...baseEnv ?? process.env
|
|
1779
|
+
});
|
|
1487
1780
|
const name = opts.workspaceName ?? basename(opts.worktreePath);
|
|
1488
1781
|
const ports = opts.ports ?? [];
|
|
1489
1782
|
const primary = ports[0];
|
|
@@ -1708,10 +2001,10 @@ import { join as join3 } from "path";
|
|
|
1708
2001
|
import { execa as execa3 } from "execa";
|
|
1709
2002
|
import { createInterface as createInterface3 } from "readline";
|
|
1710
2003
|
function loadCursorWorktreesJson(rootPath) {
|
|
1711
|
-
const
|
|
1712
|
-
if (!existsSync3(
|
|
2004
|
+
const path2 = join3(rootPath, ".cursor", "worktrees.json");
|
|
2005
|
+
if (!existsSync3(path2)) return null;
|
|
1713
2006
|
try {
|
|
1714
|
-
return JSON.parse(readFileSync3(
|
|
2007
|
+
return JSON.parse(readFileSync3(path2, "utf8"));
|
|
1715
2008
|
} catch {
|
|
1716
2009
|
return null;
|
|
1717
2010
|
}
|
|
@@ -1812,9 +2105,9 @@ function parseNameStatus(stdout) {
|
|
|
1812
2105
|
const out = [];
|
|
1813
2106
|
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
1814
2107
|
const [status, ...pathParts] = line.split(" ");
|
|
1815
|
-
const
|
|
1816
|
-
if (!status || !
|
|
1817
|
-
out.push({ status: status[0] ?? status, path });
|
|
2108
|
+
const path2 = pathParts[pathParts.length - 1];
|
|
2109
|
+
if (!status || !path2) continue;
|
|
2110
|
+
out.push({ status: status[0] ?? status, path: path2 });
|
|
1818
2111
|
}
|
|
1819
2112
|
return out;
|
|
1820
2113
|
}
|
|
@@ -1822,12 +2115,12 @@ function parseNumstat(stdout) {
|
|
|
1822
2115
|
const map = /* @__PURE__ */ new Map();
|
|
1823
2116
|
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
1824
2117
|
const [addRaw, delRaw, ...pathParts] = line.split(" ");
|
|
1825
|
-
const
|
|
1826
|
-
if (!
|
|
2118
|
+
const path2 = pathParts[pathParts.length - 1];
|
|
2119
|
+
if (!path2) continue;
|
|
1827
2120
|
const additions = addRaw === "-" ? 0 : Number(addRaw);
|
|
1828
2121
|
const deletions = delRaw === "-" ? 0 : Number(delRaw);
|
|
1829
2122
|
if (!Number.isFinite(additions) && !Number.isFinite(deletions)) continue;
|
|
1830
|
-
map.set(
|
|
2123
|
+
map.set(path2, {
|
|
1831
2124
|
additions: Number.isFinite(additions) ? additions : 0,
|
|
1832
2125
|
deletions: Number.isFinite(deletions) ? deletions : 0
|
|
1833
2126
|
});
|
|
@@ -1904,70 +2197,70 @@ function splitCombinedDiff(stdout) {
|
|
|
1904
2197
|
}
|
|
1905
2198
|
return map;
|
|
1906
2199
|
}
|
|
1907
|
-
function syntheticAddPatch(
|
|
2200
|
+
function syntheticAddPatch(path2, content, maxHunk) {
|
|
1908
2201
|
const lines = content.split("\n");
|
|
1909
2202
|
const body = lines.map((l) => `+${l}`).join("\n");
|
|
1910
|
-
const header = `diff --git a/${
|
|
2203
|
+
const header = `diff --git a/${path2} b/${path2}
|
|
1911
2204
|
new file mode 100644
|
|
1912
2205
|
--- /dev/null
|
|
1913
|
-
+++ b/${
|
|
2206
|
+
+++ b/${path2}
|
|
1914
2207
|
@@ -0,0 +1,${lines.length} @@
|
|
1915
2208
|
`;
|
|
1916
2209
|
return {
|
|
1917
|
-
path,
|
|
2210
|
+
path: path2,
|
|
1918
2211
|
status: "A",
|
|
1919
2212
|
patch: capPatch(`${header}${body}`, maxHunk),
|
|
1920
2213
|
additions: lines.length,
|
|
1921
2214
|
deletions: 0
|
|
1922
2215
|
};
|
|
1923
2216
|
}
|
|
1924
|
-
async function untrackedPatch(worktreePath,
|
|
1925
|
-
const abs = join4(worktreePath,
|
|
2217
|
+
async function untrackedPatch(worktreePath, path2, maxHunk) {
|
|
2218
|
+
const abs = join4(worktreePath, path2);
|
|
1926
2219
|
try {
|
|
1927
2220
|
const st = statSync2(abs);
|
|
1928
2221
|
if (st.isFile() && st.size > maxHunk) {
|
|
1929
2222
|
const buf = readFileSync4(abs).subarray(0, maxHunk);
|
|
1930
|
-
return syntheticAddPatch(
|
|
2223
|
+
return syntheticAddPatch(path2, buf.toString("utf8"), maxHunk);
|
|
1931
2224
|
}
|
|
1932
2225
|
} catch {
|
|
1933
2226
|
}
|
|
1934
2227
|
const { stdout: patch } = await git(
|
|
1935
|
-
["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null",
|
|
2228
|
+
["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path2],
|
|
1936
2229
|
worktreePath,
|
|
1937
2230
|
{ reject: false, timeoutMs: 4e3 }
|
|
1938
2231
|
);
|
|
1939
|
-
const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${
|
|
2232
|
+
const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path2} b/${path2}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path2}`);
|
|
1940
2233
|
const addLines = normalized.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
|
|
1941
2234
|
return {
|
|
1942
|
-
path,
|
|
2235
|
+
path: path2,
|
|
1943
2236
|
status: "A",
|
|
1944
2237
|
patch: capPatch(normalized, maxHunk),
|
|
1945
2238
|
additions: addLines,
|
|
1946
2239
|
deletions: 0
|
|
1947
2240
|
};
|
|
1948
2241
|
}
|
|
1949
|
-
async function listUntrackedPaths(worktreePath,
|
|
2242
|
+
async function listUntrackedPaths(worktreePath, path2) {
|
|
1950
2243
|
const args = ["ls-files", "-z", "--others", "--exclude-standard"];
|
|
1951
|
-
if (
|
|
2244
|
+
if (path2) args.push("--", path2);
|
|
1952
2245
|
const { stdout } = await git(args, worktreePath, { reject: false });
|
|
1953
2246
|
return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
|
|
1954
2247
|
}
|
|
1955
2248
|
async function listUntrackedFiles(worktreePath, maxHunk, paths) {
|
|
1956
2249
|
const list = paths ?? await listUntrackedPaths(worktreePath);
|
|
1957
2250
|
if (list.length === 0) return [];
|
|
1958
|
-
return Promise.all(list.map((
|
|
2251
|
+
return Promise.all(list.map((path2) => untrackedPatch(worktreePath, path2, maxHunk)));
|
|
1959
2252
|
}
|
|
1960
2253
|
function untrackedStubs(paths) {
|
|
1961
|
-
return paths.map((
|
|
1962
|
-
path,
|
|
2254
|
+
return paths.map((path2) => ({
|
|
2255
|
+
path: path2,
|
|
1963
2256
|
status: "A",
|
|
1964
2257
|
patch: "",
|
|
1965
2258
|
additions: 0,
|
|
1966
2259
|
deletions: 0
|
|
1967
2260
|
}));
|
|
1968
2261
|
}
|
|
1969
|
-
function pathspec(
|
|
1970
|
-
return
|
|
2262
|
+
function pathspec(path2) {
|
|
2263
|
+
return path2 ? ["--", path2] : [];
|
|
1971
2264
|
}
|
|
1972
2265
|
function gitDiff(args, cwd, opts) {
|
|
1973
2266
|
return git(["diff", "--no-ext-diff", "--no-color", ...args], cwd, {
|
|
@@ -1975,7 +2268,7 @@ function gitDiff(args, cwd, opts) {
|
|
|
1975
2268
|
timeoutMs: opts?.timeoutMs
|
|
1976
2269
|
});
|
|
1977
2270
|
}
|
|
1978
|
-
function fileFromPatch(
|
|
2271
|
+
function fileFromPatch(path2, patch, maxHunk) {
|
|
1979
2272
|
let status = "M";
|
|
1980
2273
|
if (/^new file mode /m.test(patch) || /^--- \/dev\/null/m.test(patch)) {
|
|
1981
2274
|
status = "A";
|
|
@@ -1987,15 +2280,15 @@ function fileFromPatch(path, patch, maxHunk) {
|
|
|
1987
2280
|
const additions = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
|
|
1988
2281
|
const deletions = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
|
|
1989
2282
|
return {
|
|
1990
|
-
path,
|
|
2283
|
+
path: path2,
|
|
1991
2284
|
status,
|
|
1992
2285
|
patch: capPatch(patch, maxHunk),
|
|
1993
2286
|
additions,
|
|
1994
2287
|
deletions
|
|
1995
2288
|
};
|
|
1996
2289
|
}
|
|
1997
|
-
async function gitDiffParts(worktreePath, diffArgs, includePatches,
|
|
1998
|
-
const extra = pathspec(
|
|
2290
|
+
async function gitDiffParts(worktreePath, diffArgs, includePatches, path2) {
|
|
2291
|
+
const extra = pathspec(path2);
|
|
1999
2292
|
if (includePatches) {
|
|
2000
2293
|
const diff = await gitDiff([...diffArgs, ...extra], worktreePath, {
|
|
2001
2294
|
timeoutMs: 4e3
|
|
@@ -2020,30 +2313,30 @@ function formatStat(files) {
|
|
|
2020
2313
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
2021
2314
|
}
|
|
2022
2315
|
function emptyScopeStats() {
|
|
2023
|
-
const
|
|
2316
|
+
const z4 = emptyStat();
|
|
2024
2317
|
return {
|
|
2025
|
-
commits:
|
|
2026
|
-
uncommitted:
|
|
2027
|
-
staged:
|
|
2028
|
-
unstaged:
|
|
2029
|
-
last_turn:
|
|
2318
|
+
commits: z4,
|
|
2319
|
+
uncommitted: z4,
|
|
2320
|
+
staged: z4,
|
|
2321
|
+
unstaged: z4,
|
|
2322
|
+
last_turn: z4
|
|
2030
2323
|
};
|
|
2031
2324
|
}
|
|
2032
2325
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
2033
2326
|
const patches = splitCombinedDiff(combinedDiff);
|
|
2034
2327
|
if (!nameStatus.trim() && patches.size > 0) {
|
|
2035
2328
|
return [...patches.entries()].map(
|
|
2036
|
-
([
|
|
2329
|
+
([path2, patch]) => fileFromPatch(path2, patch, maxHunk)
|
|
2037
2330
|
);
|
|
2038
2331
|
}
|
|
2039
2332
|
const counts = parseNumstat(numstat);
|
|
2040
2333
|
const files = [];
|
|
2041
|
-
for (const { status, path } of parseNameStatus(nameStatus)) {
|
|
2042
|
-
const n = counts.get(
|
|
2334
|
+
for (const { status, path: path2 } of parseNameStatus(nameStatus)) {
|
|
2335
|
+
const n = counts.get(path2);
|
|
2043
2336
|
files.push({
|
|
2044
|
-
path,
|
|
2337
|
+
path: path2,
|
|
2045
2338
|
status,
|
|
2046
|
-
patch: capPatch(patches.get(
|
|
2339
|
+
patch: capPatch(patches.get(path2) ?? "", maxHunk),
|
|
2047
2340
|
additions: n?.additions,
|
|
2048
2341
|
deletions: n?.deletions
|
|
2049
2342
|
});
|
|
@@ -2127,7 +2420,7 @@ function emptyPathDiff(scope, base, hasLastTurnBase, commitSha = null) {
|
|
|
2127
2420
|
};
|
|
2128
2421
|
}
|
|
2129
2422
|
async function getPathDiff(worktreePath, repoPath, opts) {
|
|
2130
|
-
const { path, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
|
|
2423
|
+
const { path: path2, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
|
|
2131
2424
|
if (scope === "last_turn" && !lastTurnBase) {
|
|
2132
2425
|
return emptyPathDiff(scope, "last turn", false);
|
|
2133
2426
|
}
|
|
@@ -2166,7 +2459,7 @@ async function getPathDiff(worktreePath, repoPath, opts) {
|
|
|
2166
2459
|
labelBase = "HEAD";
|
|
2167
2460
|
}
|
|
2168
2461
|
const diff = await gitDiff(
|
|
2169
|
-
includePatches ? [...diffArgs, "--",
|
|
2462
|
+
includePatches ? [...diffArgs, "--", path2] : ["--name-status", ...diffArgs, "--", path2],
|
|
2170
2463
|
worktreePath,
|
|
2171
2464
|
{ timeoutMs: 4e3 }
|
|
2172
2465
|
);
|
|
@@ -2174,8 +2467,8 @@ async function getPathDiff(worktreePath, repoPath, opts) {
|
|
|
2174
2467
|
if (includePatches) {
|
|
2175
2468
|
const parsed = filesFromDiff("", "", diff.stdout, maxHunk);
|
|
2176
2469
|
for (const file of parsed) filesMap.set(file.path, file);
|
|
2177
|
-
if (diff.stdout.trim() && !filesMap.has(
|
|
2178
|
-
filesMap.set(
|
|
2470
|
+
if (diff.stdout.trim() && !filesMap.has(path2)) {
|
|
2471
|
+
filesMap.set(path2, fileFromPatch(path2, diff.stdout, maxHunk));
|
|
2179
2472
|
}
|
|
2180
2473
|
} else {
|
|
2181
2474
|
for (const file of filesFromDiff(diff.stdout, "", "", maxHunk)) {
|
|
@@ -2183,7 +2476,7 @@ async function getPathDiff(worktreePath, repoPath, opts) {
|
|
|
2183
2476
|
}
|
|
2184
2477
|
}
|
|
2185
2478
|
if (filesMap.size === 0) {
|
|
2186
|
-
const maybe = await listUntrackedPaths(worktreePath,
|
|
2479
|
+
const maybe = await listUntrackedPaths(worktreePath, path2);
|
|
2187
2480
|
if (maybe.length > 0) {
|
|
2188
2481
|
const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
|
|
2189
2482
|
for (const file of extra) filesMap.set(file.path, file);
|
|
@@ -3016,16 +3309,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
|
3016
3309
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
3017
3310
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
3018
3311
|
var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
|
|
3019
|
-
function pastedTextStats(
|
|
3020
|
-
const chars =
|
|
3312
|
+
function pastedTextStats(text3) {
|
|
3313
|
+
const chars = text3.length;
|
|
3021
3314
|
if (chars === 0) return { chars: 0, lines: 0 };
|
|
3022
|
-
const lines =
|
|
3315
|
+
const lines = text3.split(/\r\n|\r|\n/).length;
|
|
3023
3316
|
return { chars, lines };
|
|
3024
3317
|
}
|
|
3025
|
-
function shouldAttachPastedText(
|
|
3026
|
-
const trimmed =
|
|
3318
|
+
function shouldAttachPastedText(text3) {
|
|
3319
|
+
const trimmed = text3.trim();
|
|
3027
3320
|
if (!trimmed) return false;
|
|
3028
|
-
const { chars, lines } = pastedTextStats(
|
|
3321
|
+
const { chars, lines } = pastedTextStats(text3);
|
|
3029
3322
|
return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
|
|
3030
3323
|
}
|
|
3031
3324
|
function nextPastedTextName(existing) {
|
|
@@ -3036,13 +3329,13 @@ function nextPastedTextName(existing) {
|
|
|
3036
3329
|
}
|
|
3037
3330
|
return `Pasted text #${max + 1}.txt`;
|
|
3038
3331
|
}
|
|
3039
|
-
function buildPastedTextAttachment(
|
|
3332
|
+
function buildPastedTextAttachment(text3, opts) {
|
|
3040
3333
|
return {
|
|
3041
3334
|
id: opts?.id ?? randomUUID2(),
|
|
3042
3335
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
3043
3336
|
kind: "file",
|
|
3044
3337
|
path: opts?.path,
|
|
3045
|
-
content:
|
|
3338
|
+
content: text3
|
|
3046
3339
|
};
|
|
3047
3340
|
}
|
|
3048
3341
|
|
|
@@ -3090,9 +3383,9 @@ async function tryClaudeSummary(transcript, opts) {
|
|
|
3090
3383
|
{ cwd: opts?.cwd, reject: false }
|
|
3091
3384
|
);
|
|
3092
3385
|
if (exitCode !== 0) return null;
|
|
3093
|
-
const
|
|
3094
|
-
if (
|
|
3095
|
-
return
|
|
3386
|
+
const text3 = stdout.trim();
|
|
3387
|
+
if (text3.length < 40) return null;
|
|
3388
|
+
return text3;
|
|
3096
3389
|
} catch {
|
|
3097
3390
|
return null;
|
|
3098
3391
|
}
|
|
@@ -3141,9 +3434,9 @@ function extractiveSummary(transcript) {
|
|
|
3141
3434
|
);
|
|
3142
3435
|
return parts.join("\n");
|
|
3143
3436
|
}
|
|
3144
|
-
function clipSummary(
|
|
3145
|
-
if (
|
|
3146
|
-
return `${
|
|
3437
|
+
function clipSummary(text3) {
|
|
3438
|
+
if (text3.length <= MAX_SUMMARY_CHARS) return text3;
|
|
3439
|
+
return `${text3.slice(0, MAX_SUMMARY_CHARS)}
|
|
3147
3440
|
|
|
3148
3441
|
[\u2026summary truncated\u2026]`;
|
|
3149
3442
|
}
|
|
@@ -3206,8 +3499,8 @@ function splitForCompaction(messages, thresholds = {}) {
|
|
|
3206
3499
|
function formatToolPart(t, detail) {
|
|
3207
3500
|
if (detail === "summary") {
|
|
3208
3501
|
const label = t.description || t.detail || t.name;
|
|
3209
|
-
const
|
|
3210
|
-
return `- ${t.name}: ${label}${
|
|
3502
|
+
const path2 = t.filePath ? ` (${t.filePath})` : "";
|
|
3503
|
+
return `- ${t.name}: ${label}${path2}`;
|
|
3211
3504
|
}
|
|
3212
3505
|
const lines = [`#### Tool: ${t.name}`, `Status: ${t.status}`];
|
|
3213
3506
|
if (t.description) lines.push(`Description: ${t.description}`);
|
|
@@ -3545,7 +3838,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
3545
3838
|
return readThread(thread.id) ?? thread;
|
|
3546
3839
|
}
|
|
3547
3840
|
async function listLinearIssues(agent, repoPath) {
|
|
3548
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
3841
|
+
const { getAdapter: getAdapter2 } = await import("./agents-EWPHOM6Y.js");
|
|
3549
3842
|
await requireAgent(agent, { requireLinear: true });
|
|
3550
3843
|
const adapter = getAdapter2(agent);
|
|
3551
3844
|
if (!adapter.listLinearIssues) {
|
|
@@ -4043,13 +4336,13 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
4043
4336
|
for (const hash of hashes) {
|
|
4044
4337
|
const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
4045
4338
|
if (!existsSync9(agentsFile)) continue;
|
|
4046
|
-
let
|
|
4339
|
+
let text3;
|
|
4047
4340
|
try {
|
|
4048
|
-
|
|
4341
|
+
text3 = readFileSync7(agentsFile, "utf8");
|
|
4049
4342
|
} catch {
|
|
4050
4343
|
continue;
|
|
4051
4344
|
}
|
|
4052
|
-
for (const line of
|
|
4345
|
+
for (const line of text3.split("\n")) {
|
|
4053
4346
|
const trimmed = line.trim();
|
|
4054
4347
|
if (!trimmed) continue;
|
|
4055
4348
|
try {
|
|
@@ -4088,7 +4381,7 @@ async function adoptThread(input) {
|
|
|
4088
4381
|
messages: input.messages ?? []
|
|
4089
4382
|
});
|
|
4090
4383
|
writeThread(thread);
|
|
4091
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
4384
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TYPJNUSM.js");
|
|
4092
4385
|
await ensureWorkspace2(repoPath);
|
|
4093
4386
|
return thread;
|
|
4094
4387
|
}
|
|
@@ -4339,10 +4632,10 @@ function storePath() {
|
|
|
4339
4632
|
return join8(appDataDir(), "slack-reply-to.json");
|
|
4340
4633
|
}
|
|
4341
4634
|
function readStore() {
|
|
4342
|
-
const
|
|
4343
|
-
if (!existsSync10(
|
|
4635
|
+
const path2 = storePath();
|
|
4636
|
+
if (!existsSync10(path2)) return {};
|
|
4344
4637
|
try {
|
|
4345
|
-
const parsed = isSecureFileEncrypted(
|
|
4638
|
+
const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync8(path2, "utf8"));
|
|
4346
4639
|
return parsed?.targets && typeof parsed.targets === "object" ? parsed.targets : {};
|
|
4347
4640
|
} catch {
|
|
4348
4641
|
return {};
|
|
@@ -4365,12 +4658,12 @@ function storePath2() {
|
|
|
4365
4658
|
}
|
|
4366
4659
|
function readStore2() {
|
|
4367
4660
|
try {
|
|
4368
|
-
const
|
|
4369
|
-
const wasEncrypted = isSecureFileEncrypted(
|
|
4370
|
-
const parsed = readSecureJson(
|
|
4661
|
+
const path2 = storePath2();
|
|
4662
|
+
const wasEncrypted = isSecureFileEncrypted(path2);
|
|
4663
|
+
const parsed = readSecureJson(path2);
|
|
4371
4664
|
const workspaces = Array.isArray(parsed?.workspaces) ? parsed.workspaces : [];
|
|
4372
4665
|
if (workspaces.length > 0 && !wasEncrypted && resolveVaultKey()) {
|
|
4373
|
-
writeSecureJson(
|
|
4666
|
+
writeSecureJson(path2, { workspaces });
|
|
4374
4667
|
}
|
|
4375
4668
|
return workspaces;
|
|
4376
4669
|
} catch {
|
|
@@ -4506,10 +4799,10 @@ function tsNewer(a, b) {
|
|
|
4506
4799
|
return Number(a) > Number(b);
|
|
4507
4800
|
}
|
|
4508
4801
|
function readStore3() {
|
|
4509
|
-
const
|
|
4510
|
-
if (!existsSync11(
|
|
4802
|
+
const path2 = storePath3();
|
|
4803
|
+
if (!existsSync11(path2)) return [];
|
|
4511
4804
|
try {
|
|
4512
|
-
const parsed = isSecureFileEncrypted(
|
|
4805
|
+
const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync9(path2, "utf8"));
|
|
4513
4806
|
return Array.isArray(parsed?.watches) ? parsed.watches : [];
|
|
4514
4807
|
} catch {
|
|
4515
4808
|
return [];
|
|
@@ -4539,8 +4832,8 @@ ${input.permalink}` : "";
|
|
|
4539
4832
|
|
|
4540
4833
|
${body}${link}`;
|
|
4541
4834
|
}
|
|
4542
|
-
function isSlackExternalReplyPrompt(
|
|
4543
|
-
return
|
|
4835
|
+
function isSlackExternalReplyPrompt(text3) {
|
|
4836
|
+
return text3.startsWith("Slack reply from ") && text3.includes("not a command");
|
|
4544
4837
|
}
|
|
4545
4838
|
function pendingSlackExternalReplies(messages) {
|
|
4546
4839
|
let i = messages.length - 1;
|
|
@@ -4564,9 +4857,9 @@ function formatSlackRepliesForTurn(replies) {
|
|
|
4564
4857
|
function listSlackOutboundWatches() {
|
|
4565
4858
|
return pruneWatches(readStore3());
|
|
4566
4859
|
}
|
|
4567
|
-
function formatOwnerSlackFyi(userName,
|
|
4860
|
+
function formatOwnerSlackFyi(userName, text3) {
|
|
4568
4861
|
const who = userName.trim() || "Someone";
|
|
4569
|
-
const body =
|
|
4862
|
+
const body = text3.trim() || "(no text)";
|
|
4570
4863
|
return `${who} replied in Slack:
|
|
4571
4864
|
${body}`;
|
|
4572
4865
|
}
|
|
@@ -4581,7 +4874,7 @@ async function relayExternalReply(opts) {
|
|
|
4581
4874
|
const thread = readThread(threadId);
|
|
4582
4875
|
if (!thread || thread.status === "archived") return true;
|
|
4583
4876
|
try {
|
|
4584
|
-
const
|
|
4877
|
+
const text3 = formatSlackExternalReplyPrompt({
|
|
4585
4878
|
userName: opts.reply.userName,
|
|
4586
4879
|
kind: opts.watch.kind,
|
|
4587
4880
|
toLabel: opts.watch.toLabel,
|
|
@@ -4590,7 +4883,7 @@ async function relayExternalReply(opts) {
|
|
|
4590
4883
|
});
|
|
4591
4884
|
appendMessage(threadId, {
|
|
4592
4885
|
role: "agent",
|
|
4593
|
-
text:
|
|
4886
|
+
text: text3,
|
|
4594
4887
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
4595
4888
|
});
|
|
4596
4889
|
} catch {
|
|
@@ -4787,7 +5080,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
4787
5080
|
const messages = await fetchMessages(token, watch, opts?.fetchImpl);
|
|
4788
5081
|
const replies = messages.filter((m) => isHumanReply(m, watch)).sort((a, b) => Number(a.ts) - Number(b.ts));
|
|
4789
5082
|
if (replies.length === 0) continue;
|
|
4790
|
-
const
|
|
5083
|
+
const injected2 = new Set(watch.injectedReplyTs ?? []);
|
|
4791
5084
|
const collected = [...watch.replies ?? []];
|
|
4792
5085
|
let lastSeenTs = watch.lastSeenTs;
|
|
4793
5086
|
let latestUser;
|
|
@@ -4821,7 +5114,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
4821
5114
|
latestName = replyUserName;
|
|
4822
5115
|
latestText = reply.text;
|
|
4823
5116
|
latestPermalink = permalink;
|
|
4824
|
-
if (
|
|
5117
|
+
if (injected2.has(ts)) {
|
|
4825
5118
|
lastSeenTs = ts;
|
|
4826
5119
|
continue;
|
|
4827
5120
|
}
|
|
@@ -4832,7 +5125,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
4832
5125
|
fetchImpl: opts?.fetchImpl
|
|
4833
5126
|
});
|
|
4834
5127
|
if (!delivered) break;
|
|
4835
|
-
|
|
5128
|
+
injected2.add(ts);
|
|
4836
5129
|
lastSeenTs = ts;
|
|
4837
5130
|
}
|
|
4838
5131
|
watches[i] = {
|
|
@@ -4844,7 +5137,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
4844
5137
|
replyTs: lastSeenTs,
|
|
4845
5138
|
replyPreview: latestText.slice(0, 140),
|
|
4846
5139
|
permalink: latestPermalink,
|
|
4847
|
-
injectedReplyTs: [...
|
|
5140
|
+
injectedReplyTs: [...injected2],
|
|
4848
5141
|
replies: collected.slice(-MAX_REPLIES_PER_WATCH)
|
|
4849
5142
|
};
|
|
4850
5143
|
changed = true;
|
|
@@ -4873,8 +5166,8 @@ function shouldAutoArchiveOnPrMerge(opts) {
|
|
|
4873
5166
|
// src/git/orphan-cleanup.ts
|
|
4874
5167
|
import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
4875
5168
|
import { join as join11 } from "path";
|
|
4876
|
-
function isSideboardWorktreePath(
|
|
4877
|
-
return
|
|
5169
|
+
function isSideboardWorktreePath(path2) {
|
|
5170
|
+
return path2.includes("/.sideboard/worktrees/") || path2.includes("/sideboard/workspaces/");
|
|
4878
5171
|
}
|
|
4879
5172
|
async function findOrphanWorktrees(repoPaths) {
|
|
4880
5173
|
const threads = listThreads({ includeArchived: true });
|
|
@@ -4899,18 +5192,18 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
4899
5192
|
try {
|
|
4900
5193
|
const wts = await listWorktrees(repoPath);
|
|
4901
5194
|
for (const wt of wts) {
|
|
4902
|
-
const
|
|
4903
|
-
if (!isSideboardWorktreePath(
|
|
4904
|
-
if (known.has(
|
|
4905
|
-
if (seen.has(
|
|
4906
|
-
seen.add(
|
|
5195
|
+
const path2 = wt.path.replace(/\/$/, "");
|
|
5196
|
+
if (!isSideboardWorktreePath(path2)) continue;
|
|
5197
|
+
if (known.has(path2)) continue;
|
|
5198
|
+
if (seen.has(path2)) continue;
|
|
5199
|
+
seen.add(path2);
|
|
4907
5200
|
let mtimeMs = 0;
|
|
4908
5201
|
try {
|
|
4909
|
-
mtimeMs = statSync5(
|
|
5202
|
+
mtimeMs = statSync5(path2).mtimeMs;
|
|
4910
5203
|
} catch {
|
|
4911
5204
|
mtimeMs = 0;
|
|
4912
5205
|
}
|
|
4913
|
-
orphans.push({ path, repoPath, mtimeMs });
|
|
5206
|
+
orphans.push({ path: path2, repoPath, mtimeMs });
|
|
4914
5207
|
}
|
|
4915
5208
|
} catch {
|
|
4916
5209
|
}
|
|
@@ -4919,17 +5212,17 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
4919
5212
|
if (existsSync12(root)) {
|
|
4920
5213
|
for (const entry of readdirSync4(root, { withFileTypes: true })) {
|
|
4921
5214
|
if (!entry.isDirectory()) continue;
|
|
4922
|
-
const
|
|
4923
|
-
if (known.has(
|
|
4924
|
-
if (!existsSync12(join11(
|
|
4925
|
-
seen.add(
|
|
5215
|
+
const path2 = join11(root, entry.name).replace(/\/$/, "");
|
|
5216
|
+
if (known.has(path2) || seen.has(path2)) continue;
|
|
5217
|
+
if (!existsSync12(join11(path2, ".git"))) continue;
|
|
5218
|
+
seen.add(path2);
|
|
4926
5219
|
let mtimeMs = 0;
|
|
4927
5220
|
try {
|
|
4928
|
-
mtimeMs = statSync5(
|
|
5221
|
+
mtimeMs = statSync5(path2).mtimeMs;
|
|
4929
5222
|
} catch {
|
|
4930
5223
|
mtimeMs = Date.now();
|
|
4931
5224
|
}
|
|
4932
|
-
orphans.push({ path, repoPath, mtimeMs });
|
|
5225
|
+
orphans.push({ path: path2, repoPath, mtimeMs });
|
|
4933
5226
|
}
|
|
4934
5227
|
}
|
|
4935
5228
|
} catch {
|
|
@@ -5312,9 +5605,9 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
5312
5605
|
const localAbs = join13(worktreePath, REVIEW_REQUEST_PATH);
|
|
5313
5606
|
const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
|
|
5314
5607
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
5315
|
-
const
|
|
5608
|
+
const path2 = existsSync14(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
|
|
5316
5609
|
return {
|
|
5317
|
-
path,
|
|
5610
|
+
path: path2,
|
|
5318
5611
|
name: REVIEW_REQUEST_NAME,
|
|
5319
5612
|
content: localContent,
|
|
5320
5613
|
source: "local"
|
|
@@ -5330,13 +5623,13 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
5330
5623
|
};
|
|
5331
5624
|
}
|
|
5332
5625
|
function buildReviewRequestAttachment(content, opts) {
|
|
5333
|
-
const
|
|
5334
|
-
const name = opts?.name ?? (
|
|
5626
|
+
const path2 = opts?.path ?? REVIEW_REQUEST_PATH;
|
|
5627
|
+
const name = opts?.name ?? (path2 === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
|
|
5335
5628
|
return {
|
|
5336
5629
|
id: randomUUID4(),
|
|
5337
5630
|
name,
|
|
5338
5631
|
kind: "file",
|
|
5339
|
-
path,
|
|
5632
|
+
path: path2,
|
|
5340
5633
|
content
|
|
5341
5634
|
};
|
|
5342
5635
|
}
|
|
@@ -5421,8 +5714,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
5421
5714
|
);
|
|
5422
5715
|
const recent = from.messages.slice(-8).map((m) => {
|
|
5423
5716
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
5424
|
-
const
|
|
5425
|
-
return
|
|
5717
|
+
const text3 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
5718
|
+
return text3 ? `- ${role}: ${text3}` : null;
|
|
5426
5719
|
}).filter(Boolean);
|
|
5427
5720
|
const body = [
|
|
5428
5721
|
`# Orchestration handoff`,
|
|
@@ -5605,7 +5898,7 @@ var Orchestrator = class {
|
|
|
5605
5898
|
orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
|
|
5606
5899
|
});
|
|
5607
5900
|
}
|
|
5608
|
-
const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-
|
|
5901
|
+
const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-6IOQYGBK.js");
|
|
5609
5902
|
if (autoCleanupOrphansEnabled2() && shouldRunWorktreeCleanup() && orphans.length > 0) {
|
|
5610
5903
|
await cleanupOrphanWorktrees({ repoPaths });
|
|
5611
5904
|
}
|
|
@@ -5762,7 +6055,7 @@ var Orchestrator = class {
|
|
|
5762
6055
|
}
|
|
5763
6056
|
async finishCreateThread(threadId, prompt) {
|
|
5764
6057
|
await this.runSetupAfterCreate(threadId);
|
|
5765
|
-
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-
|
|
6058
|
+
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-6IOQYGBK.js");
|
|
5766
6059
|
if (autoRunAfterSetupEnabled2()) {
|
|
5767
6060
|
try {
|
|
5768
6061
|
await this.startDev(threadId);
|
|
@@ -5842,11 +6135,11 @@ var Orchestrator = class {
|
|
|
5842
6135
|
return results;
|
|
5843
6136
|
}
|
|
5844
6137
|
/** Edit the text of a not-yet-started queued message. */
|
|
5845
|
-
async editQueuedMessage(threadRef, index,
|
|
6138
|
+
async editQueuedMessage(threadRef, index, text3) {
|
|
5846
6139
|
const thread = this.requireThread(threadRef);
|
|
5847
6140
|
return withThreadLock(thread.id, async () => {
|
|
5848
6141
|
const current = this.requireThread(thread.id);
|
|
5849
|
-
const trimmed =
|
|
6142
|
+
const trimmed = text3.trim();
|
|
5850
6143
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
5851
6144
|
return current;
|
|
5852
6145
|
}
|
|
@@ -6040,14 +6333,16 @@ var Orchestrator = class {
|
|
|
6040
6333
|
}
|
|
6041
6334
|
const isBrightsy = fresh.agent === "brightsy";
|
|
6042
6335
|
const isOrchestration = isOrchestratorThread(fresh);
|
|
6336
|
+
const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await import("./app-settings-6IOQYGBK.js");
|
|
6337
|
+
const gitAuthMode = getGithubGitAuthMode2();
|
|
6043
6338
|
const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
|
|
6044
6339
|
githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
|
|
6045
6340
|
() => null
|
|
6046
|
-
)
|
|
6341
|
+
),
|
|
6342
|
+
gitAuthMode
|
|
6047
6343
|
});
|
|
6048
6344
|
const artifactDirective = isBrightsy ? null : formatArtifactDirective();
|
|
6049
6345
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
6050
|
-
const { autoRenameBranchEnabled: autoRenameBranchEnabled2 } = await import("./app-settings-3TLA2EJ7.js");
|
|
6051
6346
|
const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
|
|
6052
6347
|
customPrompt: settings?.prompts?.renameBranch
|
|
6053
6348
|
}) : null;
|
|
@@ -6606,6 +6901,14 @@ var Orchestrator = class {
|
|
|
6606
6901
|
throw new Error(`${action} is not available on the global coordinator`);
|
|
6607
6902
|
}
|
|
6608
6903
|
}
|
|
6904
|
+
/** MCP set_caffeinate is a detached hold — closing the chat must not leave the Mac awake. */
|
|
6905
|
+
releaseOrchestratorCaffeinate(thread) {
|
|
6906
|
+
if (!isOrchestratorThread(thread)) return;
|
|
6907
|
+
try {
|
|
6908
|
+
releaseCaffeinateHoldForThread(thread.id);
|
|
6909
|
+
} catch {
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6609
6912
|
async diff(threadRef, opts) {
|
|
6610
6913
|
const thread = this.requireThread(threadRef);
|
|
6611
6914
|
this.assertNotGlobal(thread, "Diff");
|
|
@@ -6745,7 +7048,7 @@ var Orchestrator = class {
|
|
|
6745
7048
|
updateThread(thread.id, { title: meta.title });
|
|
6746
7049
|
}
|
|
6747
7050
|
}
|
|
6748
|
-
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-
|
|
7051
|
+
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-6IOQYGBK.js");
|
|
6749
7052
|
const latest = this.requireThread(thread.id);
|
|
6750
7053
|
if (!shouldAutoArchiveOnPrMerge({
|
|
6751
7054
|
previousPrState: prevState || null,
|
|
@@ -6876,8 +7179,9 @@ var Orchestrator = class {
|
|
|
6876
7179
|
return tab;
|
|
6877
7180
|
}
|
|
6878
7181
|
/**
|
|
6879
|
-
*
|
|
6880
|
-
*
|
|
7182
|
+
* Desktop git buttons + MCP `ask_git`.
|
|
7183
|
+
* When the worktree is clean, push / open the PR here (HTTPS via `gh` if SSH
|
|
7184
|
+
* is missing). When dirty, queue the worktree agent to commit first.
|
|
6881
7185
|
*/
|
|
6882
7186
|
async askGit(threadRef, action) {
|
|
6883
7187
|
if (!AGENT_GIT_ACTIONS.includes(action)) {
|
|
@@ -6895,6 +7199,10 @@ var Orchestrator = class {
|
|
|
6895
7199
|
"No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
|
|
6896
7200
|
);
|
|
6897
7201
|
}
|
|
7202
|
+
if ((action === "commit-push" || action === "create-draft" || action === "create-web") && thread.worktreePath?.trim() && !await isDirty(thread.worktreePath)) {
|
|
7203
|
+
await this.pushAndMaybeOpenPr(thread, action);
|
|
7204
|
+
return this.requireThread(threadRef);
|
|
7205
|
+
}
|
|
6898
7206
|
let prBase;
|
|
6899
7207
|
if (action === "resolve-conflicts") {
|
|
6900
7208
|
try {
|
|
@@ -6905,6 +7213,34 @@ var Orchestrator = class {
|
|
|
6905
7213
|
}
|
|
6906
7214
|
return this.send(threadRef, agentGitPrompt(action, { prBase }));
|
|
6907
7215
|
}
|
|
7216
|
+
/** Push origin (gh HTTPS fallback) and create/update the PR when requested. */
|
|
7217
|
+
async pushAndMaybeOpenPr(thread, action) {
|
|
7218
|
+
const cwd = thread.worktreePath;
|
|
7219
|
+
const branch = await currentBranch(cwd);
|
|
7220
|
+
await pushBranch(cwd, branch);
|
|
7221
|
+
if (action === "commit-push") return;
|
|
7222
|
+
const base = await resolveDefaultBranch(thread.repoPath);
|
|
7223
|
+
const meta = await suggestPrMetadata(cwd, {
|
|
7224
|
+
base,
|
|
7225
|
+
fallbackTitle: thread.prTitle ?? thread.title
|
|
7226
|
+
});
|
|
7227
|
+
const url = await createOrUpdatePr(cwd, {
|
|
7228
|
+
title: meta.title,
|
|
7229
|
+
body: meta.body,
|
|
7230
|
+
base,
|
|
7231
|
+
head: branch,
|
|
7232
|
+
draft: action === "create-draft",
|
|
7233
|
+
web: action === "create-web"
|
|
7234
|
+
});
|
|
7235
|
+
if (!url) return;
|
|
7236
|
+
const patch = { prUrl: url, prTitle: meta.title };
|
|
7237
|
+
try {
|
|
7238
|
+
const fetched = await getPrMeta(cwd, url);
|
|
7239
|
+
if (fetched?.title) patch.prTitle = fetched.title;
|
|
7240
|
+
} catch {
|
|
7241
|
+
}
|
|
7242
|
+
updateThread(thread.id, patch);
|
|
7243
|
+
}
|
|
6908
7244
|
setThreadOptions(threadRef, patch) {
|
|
6909
7245
|
const thread = this.requireThread(threadRef);
|
|
6910
7246
|
const next = {};
|
|
@@ -6975,6 +7311,7 @@ var Orchestrator = class {
|
|
|
6975
7311
|
async archive(threadRef) {
|
|
6976
7312
|
const thread = this.requireThread(threadRef);
|
|
6977
7313
|
this.stop(thread.id);
|
|
7314
|
+
this.releaseOrchestratorCaffeinate(thread);
|
|
6978
7315
|
if (isGlobalThread(thread)) {
|
|
6979
7316
|
const archived2 = setStatus(thread.id, "archived");
|
|
6980
7317
|
this.emit({ type: "status_changed", threadId: archived2.id, status: "archived" });
|
|
@@ -6999,7 +7336,7 @@ var Orchestrator = class {
|
|
|
6999
7336
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
7000
7337
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
7001
7338
|
try {
|
|
7002
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
7339
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TYPJNUSM.js");
|
|
7003
7340
|
await ensureWorkspace2(thread.repoPath);
|
|
7004
7341
|
} catch {
|
|
7005
7342
|
}
|
|
@@ -7009,6 +7346,7 @@ var Orchestrator = class {
|
|
|
7009
7346
|
async purge(threadRef, opts) {
|
|
7010
7347
|
const thread = this.requireThread(threadRef);
|
|
7011
7348
|
this.stop(thread.id);
|
|
7349
|
+
this.releaseOrchestratorCaffeinate(thread);
|
|
7012
7350
|
if (isGlobalThread(thread)) {
|
|
7013
7351
|
deleteThreadRecord(thread.id);
|
|
7014
7352
|
return;
|
|
@@ -7020,7 +7358,7 @@ var Orchestrator = class {
|
|
|
7020
7358
|
await runArchiveScript(thread.repoPath, thread.worktreePath);
|
|
7021
7359
|
} catch {
|
|
7022
7360
|
}
|
|
7023
|
-
const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-
|
|
7361
|
+
const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-6IOQYGBK.js");
|
|
7024
7362
|
const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled2();
|
|
7025
7363
|
await removeWorktree(thread.repoPath, thread.worktreePath, {
|
|
7026
7364
|
deleteBranch: deleteBranch ? thread.branchName : void 0
|
|
@@ -7041,7 +7379,7 @@ var Orchestrator = class {
|
|
|
7041
7379
|
return restored2;
|
|
7042
7380
|
}
|
|
7043
7381
|
if (!existsSync15(thread.worktreePath)) {
|
|
7044
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
7382
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-5VLLFI5W.js");
|
|
7045
7383
|
const { execa: execa6 } = await import("execa");
|
|
7046
7384
|
const slug = thread.worktreePath.split("/").pop();
|
|
7047
7385
|
const dest = thread.worktreePath;
|
|
@@ -7150,7 +7488,7 @@ async function startOrchestration(opts) {
|
|
|
7150
7488
|
sourceRef: "default",
|
|
7151
7489
|
...createOpts
|
|
7152
7490
|
}).catch(async () => {
|
|
7153
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
7491
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-5VLLFI5W.js");
|
|
7154
7492
|
const repo = await resolveRepoRoot2(repoPath);
|
|
7155
7493
|
const def = await resolveDefaultBranch2(repo);
|
|
7156
7494
|
return createThread({
|
|
@@ -7272,7 +7610,7 @@ function formatPlanQuestionsForChat(questions) {
|
|
|
7272
7610
|
// src/mcp/server.ts
|
|
7273
7611
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7274
7612
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7275
|
-
import { z as
|
|
7613
|
+
import { z as z3 } from "zod";
|
|
7276
7614
|
import { basename as basename4 } from "path";
|
|
7277
7615
|
|
|
7278
7616
|
// src/mcp/archive-guard.ts
|
|
@@ -7468,9 +7806,9 @@ function labelGithubUrl(url) {
|
|
|
7468
7806
|
return null;
|
|
7469
7807
|
}
|
|
7470
7808
|
if (!/(^|\.)github\.com$/i.test(parsed.hostname)) return null;
|
|
7471
|
-
const
|
|
7809
|
+
const path2 = parsed.pathname;
|
|
7472
7810
|
const hash = parsed.hash || "";
|
|
7473
|
-
const pr =
|
|
7811
|
+
const pr = path2.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/i);
|
|
7474
7812
|
if (pr) {
|
|
7475
7813
|
const n = pr[3];
|
|
7476
7814
|
if (/#discussion_r\d+/i.test(hash) || /#pullrequestreview-\d+/i.test(hash)) {
|
|
@@ -7481,11 +7819,11 @@ function labelGithubUrl(url) {
|
|
|
7481
7819
|
}
|
|
7482
7820
|
return { kind: "pr", label: `PR #${n}`, url: raw };
|
|
7483
7821
|
}
|
|
7484
|
-
const issueComment =
|
|
7822
|
+
const issueComment = path2.match(/\/([^/]+)\/([^/]+)\/issues\/(\d+)/i);
|
|
7485
7823
|
if (issueComment && /#issuecomment-\d+/i.test(hash)) {
|
|
7486
7824
|
return { kind: "comment", label: `Issue #${issueComment[3]} comment`, url: raw };
|
|
7487
7825
|
}
|
|
7488
|
-
const blob =
|
|
7826
|
+
const blob = path2.match(/\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/i);
|
|
7489
7827
|
if (blob) {
|
|
7490
7828
|
const filePath = decodeURIComponent(blob[3]);
|
|
7491
7829
|
const fileName = filePath.split("/").pop() || filePath;
|
|
@@ -7500,8 +7838,8 @@ function labelGithubUrl(url) {
|
|
|
7500
7838
|
}
|
|
7501
7839
|
return { kind: "other", label: "GitHub", url: raw };
|
|
7502
7840
|
}
|
|
7503
|
-
function appendGithubLink(
|
|
7504
|
-
const body =
|
|
7841
|
+
function appendGithubLink(text3, githubUrl) {
|
|
7842
|
+
const body = text3.trimEnd();
|
|
7505
7843
|
if (!githubUrl?.trim()) return body;
|
|
7506
7844
|
const labeled = labelGithubUrl(githubUrl);
|
|
7507
7845
|
if (!labeled) {
|
|
@@ -7784,30 +8122,125 @@ function registerSlackTools(server) {
|
|
|
7784
8122
|
);
|
|
7785
8123
|
}
|
|
7786
8124
|
|
|
7787
|
-
// src/mcp/
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
8125
|
+
// src/mcp/linear-tools.ts
|
|
8126
|
+
import { z as z2 } from "zod";
|
|
8127
|
+
function text2(payload, isError = false) {
|
|
8128
|
+
return {
|
|
8129
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
8130
|
+
...isError ? { isError: true } : {}
|
|
8131
|
+
};
|
|
8132
|
+
}
|
|
8133
|
+
function fail2(err) {
|
|
8134
|
+
return text2(
|
|
8135
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
8136
|
+
true
|
|
8137
|
+
);
|
|
8138
|
+
}
|
|
8139
|
+
var prioritySchema = z2.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
|
|
8140
|
+
function registerLinearTools(server) {
|
|
8141
|
+
server.tool(
|
|
8142
|
+
"linear_list_teams",
|
|
8143
|
+
"List Linear teams (id, key, name) and workflow states for the connected Account. Use team key (e.g. ENG) on linear_create_issue; use state name or type (started, completed) on create/update. Viewer id is for assignee=me.",
|
|
8144
|
+
{},
|
|
8145
|
+
async () => {
|
|
8146
|
+
try {
|
|
8147
|
+
return text2(await listLinearTeams());
|
|
8148
|
+
} catch (err) {
|
|
8149
|
+
return fail2(err);
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
);
|
|
8153
|
+
server.tool(
|
|
8154
|
+
"linear_get_issue",
|
|
8155
|
+
"Get a Linear issue by uuid or identifier (ENG-123).",
|
|
8156
|
+
{ id: z2.string() },
|
|
8157
|
+
async ({ id }) => {
|
|
8158
|
+
try {
|
|
8159
|
+
return text2(await getLinearIssue(id));
|
|
8160
|
+
} catch (err) {
|
|
8161
|
+
return fail2(err);
|
|
8162
|
+
}
|
|
8163
|
+
}
|
|
8164
|
+
);
|
|
8165
|
+
server.tool(
|
|
8166
|
+
"linear_create_issue",
|
|
8167
|
+
'Create a Linear issue. Call linear_list_teams first. team is id, key, or name. state is name, type (unstarted/started/completed/canceled/backlog), or id. assignee is "me" or a user id.',
|
|
8168
|
+
{
|
|
8169
|
+
team: z2.string(),
|
|
8170
|
+
title: z2.string(),
|
|
8171
|
+
description: z2.string().optional(),
|
|
8172
|
+
state: z2.string().optional(),
|
|
8173
|
+
assignee: z2.string().nullable().optional(),
|
|
8174
|
+
priority: prioritySchema
|
|
8175
|
+
},
|
|
8176
|
+
async (args) => {
|
|
8177
|
+
try {
|
|
8178
|
+
return text2(await createLinearIssue(args));
|
|
8179
|
+
} catch (err) {
|
|
8180
|
+
return fail2(err);
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
);
|
|
8184
|
+
server.tool(
|
|
8185
|
+
"linear_update_issue",
|
|
8186
|
+
'Update a Linear issue (uuid or ENG-123). Pass at least one of title, description, state, assignee, priority. state is name, type, or id. assignee is "me", a user id, or null to unassign.',
|
|
8187
|
+
{
|
|
8188
|
+
id: z2.string(),
|
|
8189
|
+
title: z2.string().optional(),
|
|
8190
|
+
description: z2.string().optional(),
|
|
8191
|
+
state: z2.string().optional(),
|
|
8192
|
+
assignee: z2.string().nullable().optional(),
|
|
8193
|
+
priority: prioritySchema
|
|
8194
|
+
},
|
|
8195
|
+
async (args) => {
|
|
8196
|
+
try {
|
|
8197
|
+
return text2(await updateLinearIssue(args));
|
|
8198
|
+
} catch (err) {
|
|
8199
|
+
return fail2(err);
|
|
8200
|
+
}
|
|
8201
|
+
}
|
|
8202
|
+
);
|
|
8203
|
+
server.tool(
|
|
8204
|
+
"linear_comment",
|
|
8205
|
+
"Add a markdown comment on a Linear issue (uuid or ENG-123).",
|
|
8206
|
+
{
|
|
8207
|
+
id: z2.string(),
|
|
8208
|
+
body: z2.string()
|
|
8209
|
+
},
|
|
8210
|
+
async (args) => {
|
|
8211
|
+
try {
|
|
8212
|
+
return text2(await commentLinearIssue(args));
|
|
8213
|
+
} catch (err) {
|
|
8214
|
+
return fail2(err);
|
|
8215
|
+
}
|
|
8216
|
+
}
|
|
8217
|
+
);
|
|
8218
|
+
}
|
|
8219
|
+
|
|
8220
|
+
// src/mcp/server.ts
|
|
8221
|
+
var MAX_ORCH_THREADS = 5;
|
|
8222
|
+
var CREATE_THREAD_TIMEOUT_MS = 9e4;
|
|
8223
|
+
function withTimeout(promise, ms, label) {
|
|
8224
|
+
return new Promise((resolve, reject) => {
|
|
8225
|
+
const timer = setTimeout(() => {
|
|
8226
|
+
reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
|
|
8227
|
+
}, ms);
|
|
8228
|
+
promise.then(
|
|
8229
|
+
(value) => {
|
|
8230
|
+
clearTimeout(timer);
|
|
8231
|
+
resolve(value);
|
|
8232
|
+
},
|
|
8233
|
+
(err) => {
|
|
8234
|
+
clearTimeout(timer);
|
|
8235
|
+
reject(err);
|
|
8236
|
+
}
|
|
7804
8237
|
);
|
|
7805
8238
|
});
|
|
7806
8239
|
}
|
|
7807
8240
|
async function startMcpServer() {
|
|
7808
8241
|
const orch = getOrchestrator();
|
|
7809
8242
|
try {
|
|
7810
|
-
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-
|
|
8243
|
+
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-6IOQYGBK.js");
|
|
7811
8244
|
orch.setMaxConcurrent(maxConcurrentAgents2());
|
|
7812
8245
|
} catch {
|
|
7813
8246
|
}
|
|
@@ -7860,7 +8293,7 @@ async function startMcpServer() {
|
|
|
7860
8293
|
server.tool(
|
|
7861
8294
|
"get_thread",
|
|
7862
8295
|
"Get a compact thread summary by id/ref",
|
|
7863
|
-
{ ref:
|
|
8296
|
+
{ ref: z3.string() },
|
|
7864
8297
|
async ({ ref }) => {
|
|
7865
8298
|
const t = orch.getThread(ref);
|
|
7866
8299
|
if (!t) {
|
|
@@ -7889,14 +8322,14 @@ async function startMcpServer() {
|
|
|
7889
8322
|
"present_artifact",
|
|
7890
8323
|
"Show an HTML, SVG, markdown, or React document in Sideboard\u2019s Claude-style side column (desktop). Use instead of claude.ai\u2019s artifact tool \u2014 pass the full document content. Prefer type=html for interactive pages. For type=react, pass a single component module that `export default`s a component (JSX/TSX ok) \u2014 Sideboard bootstraps React/ReactDOM/Babel and renders it; only `react`/`react-dom` imports are available, no other npm packages.",
|
|
7891
8324
|
{
|
|
7892
|
-
title:
|
|
7893
|
-
type:
|
|
8325
|
+
title: z3.string().describe("Short title shown in the artifact pane header"),
|
|
8326
|
+
type: z3.enum(["html", "svg", "markdown", "react"]).describe(
|
|
7894
8327
|
"Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
|
|
7895
8328
|
),
|
|
7896
|
-
content:
|
|
8329
|
+
content: z3.string().describe(
|
|
7897
8330
|
"Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
|
|
7898
8331
|
),
|
|
7899
|
-
artifact_id:
|
|
8332
|
+
artifact_id: z3.string().optional().describe("Stable id when updating the same artifact across turns")
|
|
7900
8333
|
},
|
|
7901
8334
|
async ({ title, type, artifact_id }) => {
|
|
7902
8335
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7914,15 +8347,15 @@ async function startMcpServer() {
|
|
|
7914
8347
|
"ask_user",
|
|
7915
8348
|
"Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
|
|
7916
8349
|
{
|
|
7917
|
-
questions:
|
|
7918
|
-
|
|
7919
|
-
question:
|
|
7920
|
-
header:
|
|
7921
|
-
multiSelect:
|
|
7922
|
-
options:
|
|
7923
|
-
|
|
7924
|
-
label:
|
|
7925
|
-
description:
|
|
8350
|
+
questions: z3.array(
|
|
8351
|
+
z3.object({
|
|
8352
|
+
question: z3.string().describe("Full question text ending with ?"),
|
|
8353
|
+
header: z3.string().max(24).optional().describe("Short label shown above the question"),
|
|
8354
|
+
multiSelect: z3.boolean().optional().describe("Allow selecting multiple options"),
|
|
8355
|
+
options: z3.array(
|
|
8356
|
+
z3.object({
|
|
8357
|
+
label: z3.string(),
|
|
8358
|
+
description: z3.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
7926
8359
|
})
|
|
7927
8360
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
7928
8361
|
})
|
|
@@ -7941,9 +8374,9 @@ async function startMcpServer() {
|
|
|
7941
8374
|
"present_plan",
|
|
7942
8375
|
"Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
|
|
7943
8376
|
{
|
|
7944
|
-
title:
|
|
7945
|
-
content:
|
|
7946
|
-
thread_id:
|
|
8377
|
+
title: z3.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
8378
|
+
content: z3.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
8379
|
+
thread_id: z3.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
7947
8380
|
},
|
|
7948
8381
|
async ({ title, content, thread_id }) => {
|
|
7949
8382
|
const { writePlanFile: writePlanFile2 } = await import("./plan-file-6O7G4VPQ.js");
|
|
@@ -7952,10 +8385,10 @@ async function startMcpServer() {
|
|
|
7952
8385
|
const t = orch.getThread(thread_id.trim());
|
|
7953
8386
|
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
7954
8387
|
}
|
|
7955
|
-
const
|
|
8388
|
+
const path2 = writePlanFile2(root, content);
|
|
7956
8389
|
const payload = {
|
|
7957
8390
|
ok: true,
|
|
7958
|
-
path,
|
|
8391
|
+
path: path2,
|
|
7959
8392
|
title: title?.trim() || "Plan",
|
|
7960
8393
|
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
7961
8394
|
};
|
|
@@ -7966,15 +8399,15 @@ async function startMcpServer() {
|
|
|
7966
8399
|
"present_schema",
|
|
7967
8400
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form). 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.",
|
|
7968
8401
|
{
|
|
7969
|
-
title:
|
|
7970
|
-
mode:
|
|
7971
|
-
datasource:
|
|
7972
|
-
resource_id:
|
|
7973
|
-
record_id:
|
|
7974
|
-
resource:
|
|
7975
|
-
record:
|
|
7976
|
-
records:
|
|
7977
|
-
pane_id:
|
|
8402
|
+
title: z3.string().describe("Pane title"),
|
|
8403
|
+
mode: z3.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
8404
|
+
datasource: z3.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
8405
|
+
resource_id: z3.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
8406
|
+
record_id: z3.string().optional().describe("Record id when opening form mode"),
|
|
8407
|
+
resource: z3.record(z3.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
8408
|
+
record: z3.record(z3.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
8409
|
+
records: z3.array(z3.record(z3.unknown())).optional().describe("Inline records for table mode"),
|
|
8410
|
+
pane_id: z3.string().optional().describe("Stable pane id across updates")
|
|
7978
8411
|
},
|
|
7979
8412
|
async (args) => {
|
|
7980
8413
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -7995,10 +8428,10 @@ async function startMcpServer() {
|
|
|
7995
8428
|
"present_files",
|
|
7996
8429
|
"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.",
|
|
7997
8430
|
{
|
|
7998
|
-
title:
|
|
7999
|
-
datasource:
|
|
8000
|
-
path:
|
|
8001
|
-
pane_id:
|
|
8431
|
+
title: z3.string().optional().describe("Pane title (default: Files)"),
|
|
8432
|
+
datasource: z3.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
8433
|
+
path: z3.string().optional().describe("Initial folder path (e.g. public)"),
|
|
8434
|
+
pane_id: z3.string().optional().describe("Stable pane id across updates")
|
|
8002
8435
|
},
|
|
8003
8436
|
async (args) => {
|
|
8004
8437
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -8014,19 +8447,21 @@ async function startMcpServer() {
|
|
|
8014
8447
|
}
|
|
8015
8448
|
);
|
|
8016
8449
|
registerSlackTools(server);
|
|
8450
|
+
registerLinearTools(server);
|
|
8017
8451
|
if (sideboardMcpProfile() !== "worktree") {
|
|
8018
|
-
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-
|
|
8019
|
-
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-
|
|
8452
|
+
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-EAZNWJBE.js");
|
|
8453
|
+
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-6IOQYGBK.js");
|
|
8020
8454
|
const accountDefaults = resolveThreadDefaults2();
|
|
8021
8455
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
8022
8456
|
server.tool(
|
|
8023
8457
|
"set_caffeinate",
|
|
8024
|
-
"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. macOS only.",
|
|
8458
|
+
"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.",
|
|
8025
8459
|
{
|
|
8026
|
-
enabled:
|
|
8460
|
+
enabled: z3.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
8027
8461
|
},
|
|
8028
8462
|
async ({ enabled }) => {
|
|
8029
|
-
const
|
|
8463
|
+
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
8464
|
+
const state = setCaffeinateHold2(enabled, { threadId });
|
|
8030
8465
|
if (enabled && !state.held) {
|
|
8031
8466
|
return {
|
|
8032
8467
|
content: [
|
|
@@ -8049,7 +8484,7 @@ async function startMcpServer() {
|
|
|
8049
8484
|
text: JSON.stringify({
|
|
8050
8485
|
...getCaffeinateHold2(),
|
|
8051
8486
|
ok: true,
|
|
8052
|
-
message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false
|
|
8487
|
+
message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false, the user says they are done, or this orchestration chat is closed." : "Caffeinate hold released. The Mac can sleep (unless Settings caffeinate toggles are on)."
|
|
8053
8488
|
})
|
|
8054
8489
|
}
|
|
8055
8490
|
]
|
|
@@ -8060,15 +8495,15 @@ async function startMcpServer() {
|
|
|
8060
8495
|
"create_thread",
|
|
8061
8496
|
`Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Then use send_to_thread to chat.`,
|
|
8062
8497
|
{
|
|
8063
|
-
sourceType:
|
|
8064
|
-
sourceRef:
|
|
8065
|
-
agent:
|
|
8066
|
-
model:
|
|
8498
|
+
sourceType: z3.enum(["branch", "pr", "ticket"]),
|
|
8499
|
+
sourceRef: z3.string(),
|
|
8500
|
+
agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
8501
|
+
model: z3.string().nullable().optional().describe(
|
|
8067
8502
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
8068
8503
|
),
|
|
8069
|
-
repoPath:
|
|
8070
|
-
title:
|
|
8071
|
-
parentThreadId:
|
|
8504
|
+
repoPath: z3.string(),
|
|
8505
|
+
title: z3.string().optional(),
|
|
8506
|
+
parentThreadId: z3.string().optional().describe(
|
|
8072
8507
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
8073
8508
|
)
|
|
8074
8509
|
},
|
|
@@ -8175,9 +8610,9 @@ async function startMcpServer() {
|
|
|
8175
8610
|
"send_to_thread",
|
|
8176
8611
|
"Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
|
|
8177
8612
|
{
|
|
8178
|
-
ref:
|
|
8179
|
-
prompt:
|
|
8180
|
-
force_stop:
|
|
8613
|
+
ref: z3.string(),
|
|
8614
|
+
prompt: z3.string(),
|
|
8615
|
+
force_stop: z3.boolean().optional()
|
|
8181
8616
|
},
|
|
8182
8617
|
async ({ ref, prompt, force_stop }) => {
|
|
8183
8618
|
if (force_stop) {
|
|
@@ -8206,8 +8641,8 @@ async function startMcpServer() {
|
|
|
8206
8641
|
"wait_for_turn",
|
|
8207
8642
|
"Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
|
|
8208
8643
|
{
|
|
8209
|
-
ref:
|
|
8210
|
-
timeoutMs:
|
|
8644
|
+
ref: z3.string(),
|
|
8645
|
+
timeoutMs: z3.number().optional()
|
|
8211
8646
|
},
|
|
8212
8647
|
async ({ ref, timeoutMs }) => {
|
|
8213
8648
|
const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
|
|
@@ -8229,7 +8664,7 @@ async function startMcpServer() {
|
|
|
8229
8664
|
server.tool(
|
|
8230
8665
|
"get_turn_result",
|
|
8231
8666
|
"Final assistant message only (not full transcript)",
|
|
8232
|
-
{ ref:
|
|
8667
|
+
{ ref: z3.string() },
|
|
8233
8668
|
async ({ ref }) => {
|
|
8234
8669
|
const result = orch.getTurnResult(ref);
|
|
8235
8670
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
@@ -8239,8 +8674,8 @@ async function startMcpServer() {
|
|
|
8239
8674
|
"stop_thread",
|
|
8240
8675
|
"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.",
|
|
8241
8676
|
{
|
|
8242
|
-
ref:
|
|
8243
|
-
force:
|
|
8677
|
+
ref: z3.string(),
|
|
8678
|
+
force: z3.boolean().optional()
|
|
8244
8679
|
},
|
|
8245
8680
|
async ({ ref, force }) => {
|
|
8246
8681
|
const t = orch.getThread(ref);
|
|
@@ -8270,7 +8705,7 @@ async function startMcpServer() {
|
|
|
8270
8705
|
server.tool(
|
|
8271
8706
|
"archive_thread",
|
|
8272
8707
|
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
|
|
8273
|
-
{ ref:
|
|
8708
|
+
{ ref: z3.string() },
|
|
8274
8709
|
async ({ ref }) => {
|
|
8275
8710
|
const t = orch.getThread(ref);
|
|
8276
8711
|
if (!t) {
|
|
@@ -8303,7 +8738,7 @@ async function startMcpServer() {
|
|
|
8303
8738
|
server.tool(
|
|
8304
8739
|
"restore_thread",
|
|
8305
8740
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
8306
|
-
{ ref:
|
|
8741
|
+
{ ref: z3.string() },
|
|
8307
8742
|
async ({ ref }) => {
|
|
8308
8743
|
try {
|
|
8309
8744
|
const restored = await orch.restore(ref);
|
|
@@ -8329,8 +8764,8 @@ async function startMcpServer() {
|
|
|
8329
8764
|
"get_diff",
|
|
8330
8765
|
"Compact diff summary (capped hunks, paginated)",
|
|
8331
8766
|
{
|
|
8332
|
-
ref:
|
|
8333
|
-
maxFiles:
|
|
8767
|
+
ref: z3.string(),
|
|
8768
|
+
maxFiles: z3.number().optional()
|
|
8334
8769
|
},
|
|
8335
8770
|
async ({ ref, maxFiles }) => {
|
|
8336
8771
|
const summary = await orch.diffSummary(ref);
|
|
@@ -8350,7 +8785,7 @@ async function startMcpServer() {
|
|
|
8350
8785
|
server.tool(
|
|
8351
8786
|
"request_review",
|
|
8352
8787
|
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
|
|
8353
|
-
{ ref:
|
|
8788
|
+
{ ref: z3.string().describe("Worktree thread id/ref to review") },
|
|
8354
8789
|
async ({ ref }) => {
|
|
8355
8790
|
try {
|
|
8356
8791
|
const tab = await orch.requestReview(ref);
|
|
@@ -8377,10 +8812,10 @@ async function startMcpServer() {
|
|
|
8377
8812
|
);
|
|
8378
8813
|
server.tool(
|
|
8379
8814
|
"ask_git",
|
|
8380
|
-
"
|
|
8815
|
+
"Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). Do not run git or gh from the orchestration cwd.",
|
|
8381
8816
|
{
|
|
8382
|
-
ref:
|
|
8383
|
-
action:
|
|
8817
|
+
ref: z3.string().describe("Worktree thread id/ref"),
|
|
8818
|
+
action: z3.enum(AGENT_GIT_ACTIONS).describe(
|
|
8384
8819
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
8385
8820
|
)
|
|
8386
8821
|
},
|
|
@@ -8407,7 +8842,7 @@ async function startMcpServer() {
|
|
|
8407
8842
|
}
|
|
8408
8843
|
}
|
|
8409
8844
|
);
|
|
8410
|
-
const agentEnum =
|
|
8845
|
+
const agentEnum = z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
8411
8846
|
server.tool(
|
|
8412
8847
|
"list_models",
|
|
8413
8848
|
"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.",
|
|
@@ -8430,11 +8865,11 @@ async function startMcpServer() {
|
|
|
8430
8865
|
"fork_worktree",
|
|
8431
8866
|
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
|
|
8432
8867
|
{
|
|
8433
|
-
ref:
|
|
8434
|
-
through_index:
|
|
8868
|
+
ref: z3.string().describe("Worktree thread id/ref to fork"),
|
|
8869
|
+
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
8435
8870
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
8436
|
-
model:
|
|
8437
|
-
title:
|
|
8871
|
+
model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
8872
|
+
title: z3.string().optional()
|
|
8438
8873
|
},
|
|
8439
8874
|
async ({ ref, through_index, agent, model, title }) => {
|
|
8440
8875
|
try {
|
|
@@ -8475,11 +8910,11 @@ async function startMcpServer() {
|
|
|
8475
8910
|
"fork_chat",
|
|
8476
8911
|
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
8477
8912
|
{
|
|
8478
|
-
ref:
|
|
8479
|
-
through_index:
|
|
8913
|
+
ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
8914
|
+
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
8480
8915
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
8481
|
-
model:
|
|
8482
|
-
title:
|
|
8916
|
+
model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
8917
|
+
title: z3.string().optional()
|
|
8483
8918
|
},
|
|
8484
8919
|
async ({ ref, through_index, agent, model, title }) => {
|
|
8485
8920
|
try {
|
|
@@ -8525,8 +8960,8 @@ async function startMcpServer() {
|
|
|
8525
8960
|
"run_dev_script",
|
|
8526
8961
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
8527
8962
|
{
|
|
8528
|
-
ref:
|
|
8529
|
-
name:
|
|
8963
|
+
ref: z3.string(),
|
|
8964
|
+
name: z3.string().optional()
|
|
8530
8965
|
},
|
|
8531
8966
|
async ({ ref, name }) => {
|
|
8532
8967
|
const result = await orch.startDev(ref, name);
|
|
@@ -8548,7 +8983,7 @@ async function startMcpServer() {
|
|
|
8548
8983
|
server.tool(
|
|
8549
8984
|
"list_run_scripts",
|
|
8550
8985
|
"List named run scripts available for a thread",
|
|
8551
|
-
{ ref:
|
|
8986
|
+
{ ref: z3.string() },
|
|
8552
8987
|
async ({ ref }) => {
|
|
8553
8988
|
const scripts = orch.listThreadRunScripts(ref);
|
|
8554
8989
|
const active = orch.getActiveRuns(ref);
|
|
@@ -8566,8 +9001,8 @@ async function startMcpServer() {
|
|
|
8566
9001
|
"stop_dev_script",
|
|
8567
9002
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
8568
9003
|
{
|
|
8569
|
-
ref:
|
|
8570
|
-
name:
|
|
9004
|
+
ref: z3.string(),
|
|
9005
|
+
name: z3.string().optional()
|
|
8571
9006
|
},
|
|
8572
9007
|
async ({ ref, name }) => {
|
|
8573
9008
|
orch.stopDev(ref, name);
|
|
@@ -8577,7 +9012,7 @@ async function startMcpServer() {
|
|
|
8577
9012
|
server.tool(
|
|
8578
9013
|
"run_setup",
|
|
8579
9014
|
"Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
|
|
8580
|
-
{ ref:
|
|
9015
|
+
{ ref: z3.string() },
|
|
8581
9016
|
async ({ ref }) => {
|
|
8582
9017
|
const result = await orch.runSetup(ref);
|
|
8583
9018
|
return {
|
|
@@ -8588,7 +9023,7 @@ async function startMcpServer() {
|
|
|
8588
9023
|
server.tool(
|
|
8589
9024
|
"add_workspace",
|
|
8590
9025
|
"Register a git repo as a Sideboard workspace",
|
|
8591
|
-
{ repoPath:
|
|
9026
|
+
{ repoPath: z3.string() },
|
|
8592
9027
|
async ({ repoPath }) => {
|
|
8593
9028
|
const ws = await orch.addWorkspace(repoPath);
|
|
8594
9029
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -8597,7 +9032,7 @@ async function startMcpServer() {
|
|
|
8597
9032
|
server.tool(
|
|
8598
9033
|
"remove_workspace",
|
|
8599
9034
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
8600
|
-
{ repoPath:
|
|
9035
|
+
{ repoPath: z3.string() },
|
|
8601
9036
|
async ({ repoPath }) => {
|
|
8602
9037
|
orch.removeWorkspace(repoPath);
|
|
8603
9038
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -8607,12 +9042,12 @@ async function startMcpServer() {
|
|
|
8607
9042
|
"fanout",
|
|
8608
9043
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
8609
9044
|
{
|
|
8610
|
-
prompt:
|
|
8611
|
-
agents:
|
|
8612
|
-
repoPath:
|
|
8613
|
-
sourceType:
|
|
8614
|
-
sourceRef:
|
|
8615
|
-
title:
|
|
9045
|
+
prompt: z3.string(),
|
|
9046
|
+
agents: z3.array(z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
9047
|
+
repoPath: z3.string(),
|
|
9048
|
+
sourceType: z3.enum(["branch", "pr", "ticket"]).optional(),
|
|
9049
|
+
sourceRef: z3.string().optional(),
|
|
9050
|
+
title: z3.string().optional()
|
|
8616
9051
|
},
|
|
8617
9052
|
async (args) => {
|
|
8618
9053
|
const threads = await orch.bestOfN(args);
|
|
@@ -8639,8 +9074,8 @@ async function startMcpServer() {
|
|
|
8639
9074
|
"list_branches",
|
|
8640
9075
|
"List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
|
|
8641
9076
|
{
|
|
8642
|
-
repoPath:
|
|
8643
|
-
unmergedOnly:
|
|
9077
|
+
repoPath: z3.string(),
|
|
9078
|
+
unmergedOnly: z3.boolean().optional()
|
|
8644
9079
|
},
|
|
8645
9080
|
async ({ repoPath, unmergedOnly }) => {
|
|
8646
9081
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -8660,7 +9095,7 @@ async function startMcpServer() {
|
|
|
8660
9095
|
server.tool(
|
|
8661
9096
|
"list_prs",
|
|
8662
9097
|
"List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
|
|
8663
|
-
{ repoPath:
|
|
9098
|
+
{ repoPath: z3.string() },
|
|
8664
9099
|
async ({ repoPath }) => {
|
|
8665
9100
|
const root = await resolveRepoRoot(repoPath);
|
|
8666
9101
|
const prs = await listPrs(root);
|
|
@@ -8679,7 +9114,7 @@ async function startMcpServer() {
|
|
|
8679
9114
|
server.tool(
|
|
8680
9115
|
"get_pr_stack",
|
|
8681
9116
|
"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.",
|
|
8682
|
-
{ ref:
|
|
9117
|
+
{ ref: z3.string() },
|
|
8683
9118
|
async ({ ref }) => {
|
|
8684
9119
|
const stack = await orch.getPrStack(ref);
|
|
8685
9120
|
return {
|
|
@@ -8691,8 +9126,8 @@ async function startMcpServer() {
|
|
|
8691
9126
|
"open_pr_stack_layers",
|
|
8692
9127
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
8693
9128
|
{
|
|
8694
|
-
ref:
|
|
8695
|
-
layer:
|
|
9129
|
+
ref: z3.string(),
|
|
9130
|
+
layer: z3.number().int().positive().optional()
|
|
8696
9131
|
},
|
|
8697
9132
|
async ({ ref, layer }) => {
|
|
8698
9133
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -8726,9 +9161,9 @@ async function startMcpServer() {
|
|
|
8726
9161
|
"add_stack_layer",
|
|
8727
9162
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
8728
9163
|
{
|
|
8729
|
-
ref:
|
|
8730
|
-
branchName:
|
|
8731
|
-
title:
|
|
9164
|
+
ref: z3.string(),
|
|
9165
|
+
branchName: z3.string(),
|
|
9166
|
+
title: z3.string().optional()
|
|
8732
9167
|
},
|
|
8733
9168
|
async ({ ref, branchName, title }) => {
|
|
8734
9169
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -8757,11 +9192,11 @@ async function startMcpServer() {
|
|
|
8757
9192
|
"create_pr_stack",
|
|
8758
9193
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
8759
9194
|
{
|
|
8760
|
-
repoPath:
|
|
8761
|
-
branches:
|
|
8762
|
-
agent:
|
|
8763
|
-
base:
|
|
8764
|
-
title:
|
|
9195
|
+
repoPath: z3.string(),
|
|
9196
|
+
branches: z3.array(z3.string()).min(1),
|
|
9197
|
+
agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
9198
|
+
base: z3.string().optional(),
|
|
9199
|
+
title: z3.string().optional()
|
|
8765
9200
|
},
|
|
8766
9201
|
async (args) => {
|
|
8767
9202
|
const result = await orch.createPrStack({
|
|
@@ -8799,7 +9234,7 @@ async function startMcpServer() {
|
|
|
8799
9234
|
server.tool(
|
|
8800
9235
|
"list_issues",
|
|
8801
9236
|
"List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
|
|
8802
|
-
{ repoPath:
|
|
9237
|
+
{ repoPath: z3.string() },
|
|
8803
9238
|
async ({ repoPath }) => {
|
|
8804
9239
|
const root = await resolveRepoRoot(repoPath);
|
|
8805
9240
|
const result = await listIssues(root);
|
|
@@ -8810,8 +9245,8 @@ async function startMcpServer() {
|
|
|
8810
9245
|
"list_linear_issues",
|
|
8811
9246
|
"Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
|
|
8812
9247
|
{
|
|
8813
|
-
agent:
|
|
8814
|
-
repoPath:
|
|
9248
|
+
agent: z3.enum(["claude", "codex", "opencode"]),
|
|
9249
|
+
repoPath: z3.string()
|
|
8815
9250
|
},
|
|
8816
9251
|
async ({ agent, repoPath }) => {
|
|
8817
9252
|
const issues = await listLinearIssues(agent, repoPath);
|
|
@@ -8824,18 +9259,7 @@ async function startMcpServer() {
|
|
|
8824
9259
|
}
|
|
8825
9260
|
|
|
8826
9261
|
// src/brightsy/api.ts
|
|
8827
|
-
|
|
8828
|
-
if (!(err instanceof Error)) return `${String(err)} (${url})`;
|
|
8829
|
-
const cause = err.cause;
|
|
8830
|
-
let detail = "";
|
|
8831
|
-
if (cause instanceof Error) {
|
|
8832
|
-
const code = typeof cause.code === "string" ? cause.code : void 0;
|
|
8833
|
-
detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
|
|
8834
|
-
} else if (cause != null) {
|
|
8835
|
-
detail = ` [${String(cause)}]`;
|
|
8836
|
-
}
|
|
8837
|
-
return `${err.message}${detail} (${url})`;
|
|
8838
|
-
}
|
|
9262
|
+
var formatBrightsyFetchError = formatFetchError;
|
|
8839
9263
|
var BrightsySideboardApi = class {
|
|
8840
9264
|
cfg;
|
|
8841
9265
|
fetchImpl;
|
|
@@ -8856,9 +9280,9 @@ var BrightsySideboardApi = class {
|
|
|
8856
9280
|
get endpoint() {
|
|
8857
9281
|
return (this.cfg.endpoint || "https://brightsy.ai").replace(/\/$/, "");
|
|
8858
9282
|
}
|
|
8859
|
-
async request(
|
|
9283
|
+
async request(path2, init = {}) {
|
|
8860
9284
|
await this.refreshIfNeeded();
|
|
8861
|
-
const url = `${this.endpoint}${
|
|
9285
|
+
const url = `${this.endpoint}${path2}`;
|
|
8862
9286
|
const headers = {
|
|
8863
9287
|
Authorization: `Bearer ${this.cfg.access_token}`,
|
|
8864
9288
|
Accept: "application/json"
|
|
@@ -8955,8 +9379,8 @@ var BrightsySideboardApi = class {
|
|
|
8955
9379
|
};
|
|
8956
9380
|
function taskMessageText(task) {
|
|
8957
9381
|
const parts = task.message?.parts ?? [];
|
|
8958
|
-
const
|
|
8959
|
-
return
|
|
9382
|
+
const text3 = parts.find((p) => p.kind === "text")?.text ?? "";
|
|
9383
|
+
return text3.trim();
|
|
8960
9384
|
}
|
|
8961
9385
|
|
|
8962
9386
|
// src/brightsy/cloud-connect.ts
|
|
@@ -9152,63 +9576,128 @@ async function runCloudConnect(opts) {
|
|
|
9152
9576
|
|
|
9153
9577
|
// src/slack/oauth.ts
|
|
9154
9578
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
9155
|
-
import { createServer as createServer3 } from "http";
|
|
9156
9579
|
|
|
9157
9580
|
// src/slack/baked-app.ts
|
|
9158
9581
|
var BAKED_SLACK_CLIENT_ID = "7592788819232.11813536272562";
|
|
9159
|
-
var
|
|
9160
|
-
var BAKED_SLACK_RELAY_URL = "wss://slack-relay.sideboard.cloud/desktop";
|
|
9582
|
+
var BAKED_SLACK_RELAY_URL = "wss://relay.sideboard.cloud/slack/desktop";
|
|
9161
9583
|
function hasBakedSlackOAuth() {
|
|
9162
|
-
return Boolean(BAKED_SLACK_CLIENT_ID.trim()
|
|
9584
|
+
return Boolean(BAKED_SLACK_CLIENT_ID.trim());
|
|
9163
9585
|
}
|
|
9164
9586
|
function slackRelayUrl() {
|
|
9165
9587
|
return process.env.SIDEBOARD_SLACK_RELAY_URL?.trim() || BAKED_SLACK_RELAY_URL.trim();
|
|
9166
9588
|
}
|
|
9167
9589
|
|
|
9168
9590
|
// src/slack/oauth-redirect.ts
|
|
9169
|
-
var
|
|
9170
|
-
var
|
|
9171
|
-
var
|
|
9172
|
-
var
|
|
9173
|
-
var
|
|
9591
|
+
var SLACK_OAUTH_CALLBACK_PATH = "/slack/callback";
|
|
9592
|
+
var SLACK_OAUTH_RESULT_PATH = "/slack/oauth/result";
|
|
9593
|
+
var SLACK_RELAY_DESKTOP_PATH = "/slack/desktop";
|
|
9594
|
+
var SLACK_OAUTH_LOCAL_PORT = 19847;
|
|
9595
|
+
var SLACK_OAUTH_LOCAL_CALLBACK = `http://127.0.0.1:${SLACK_OAUTH_LOCAL_PORT}${SLACK_OAUTH_CALLBACK_PATH}`;
|
|
9596
|
+
var SLACK_OAUTH_REDIRECT = `https://relay.sideboard.cloud${SLACK_OAUTH_CALLBACK_PATH}`;
|
|
9174
9597
|
function slackOAuthRedirectUri() {
|
|
9175
9598
|
return process.env.SIDEBOARD_SLACK_OAUTH_REDIRECT?.trim() || SLACK_OAUTH_REDIRECT;
|
|
9176
9599
|
}
|
|
9177
|
-
function slackOAuthLocalBounceTarget(params) {
|
|
9178
|
-
const dest = new URL(SLACK_OAUTH_LOCAL_CALLBACK);
|
|
9179
|
-
for (const key of BOUNCE_PARAMS) {
|
|
9180
|
-
const value = params.get(key);
|
|
9181
|
-
if (value) dest.searchParams.set(key, value);
|
|
9182
|
-
}
|
|
9183
|
-
return dest.toString();
|
|
9184
|
-
}
|
|
9185
9600
|
function escapeHtml(value) {
|
|
9186
9601
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
9187
9602
|
}
|
|
9188
|
-
function
|
|
9189
|
-
|
|
9190
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${safe}"><title>Connecting Slack</title>
|
|
9603
|
+
function slackOAuthHtmlPage(title, body) {
|
|
9604
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(title)}</title>
|
|
9191
9605
|
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
9192
|
-
h1{font-size:1.25rem}p{line-height:1.5;color:#444}
|
|
9193
|
-
<script>location.replace(${JSON.stringify(target)})</script>
|
|
9194
|
-
</head><body><h1>Connecting Slack</h1><p>Returning to Sideboard\u2026 <a href="${safe}">Continue</a></p></body></html>`;
|
|
9606
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
9195
9607
|
}
|
|
9196
|
-
function
|
|
9608
|
+
function parseSlackOAuthCallbackUrl(reqUrl) {
|
|
9197
9609
|
let url;
|
|
9198
9610
|
try {
|
|
9199
9611
|
url = new URL(reqUrl, SLACK_OAUTH_REDIRECT);
|
|
9200
9612
|
} catch {
|
|
9201
9613
|
return null;
|
|
9202
9614
|
}
|
|
9203
|
-
if (url.pathname !==
|
|
9204
|
-
|
|
9615
|
+
if (url.pathname !== SLACK_OAUTH_CALLBACK_PATH) return null;
|
|
9616
|
+
return url;
|
|
9617
|
+
}
|
|
9618
|
+
function parseSlackOAuthResultUrl(reqUrl) {
|
|
9619
|
+
let url;
|
|
9620
|
+
try {
|
|
9621
|
+
url = new URL(reqUrl, SLACK_OAUTH_REDIRECT);
|
|
9622
|
+
} catch {
|
|
9623
|
+
return null;
|
|
9624
|
+
}
|
|
9625
|
+
if (url.pathname !== SLACK_OAUTH_RESULT_PATH) return null;
|
|
9626
|
+
return url;
|
|
9627
|
+
}
|
|
9628
|
+
|
|
9629
|
+
// src/slack/oauth-exchange.ts
|
|
9630
|
+
var PENDING_TTL_MS = 5 * 6e4;
|
|
9631
|
+
var SlackOAuthPendingStore = class {
|
|
9632
|
+
items = /* @__PURE__ */ new Map();
|
|
9633
|
+
put(state, value) {
|
|
9634
|
+
const id = state.trim();
|
|
9635
|
+
if (!id) return;
|
|
9636
|
+
this.sweep();
|
|
9637
|
+
const existing = this.items.get(id);
|
|
9638
|
+
if (existing && existing.value.ok && !value.ok) return;
|
|
9639
|
+
this.items.set(id, { expiresAt: Date.now() + PENDING_TTL_MS, value });
|
|
9640
|
+
}
|
|
9641
|
+
/** Consume a pending result. Missing/expired → null (desktop should keep polling). */
|
|
9642
|
+
take(state) {
|
|
9643
|
+
const id = state.trim();
|
|
9644
|
+
if (!id) return null;
|
|
9645
|
+
this.sweep();
|
|
9646
|
+
const row = this.items.get(id);
|
|
9647
|
+
if (!row) return null;
|
|
9648
|
+
this.items.delete(id);
|
|
9649
|
+
return row.value;
|
|
9650
|
+
}
|
|
9651
|
+
sweep() {
|
|
9652
|
+
const now = Date.now();
|
|
9653
|
+
for (const [key, row] of this.items) {
|
|
9654
|
+
if (row.expiresAt <= now) this.items.delete(key);
|
|
9655
|
+
}
|
|
9656
|
+
}
|
|
9657
|
+
};
|
|
9658
|
+
function slackRelayHttpOrigin(relayWsUrl) {
|
|
9659
|
+
const raw = (relayWsUrl ?? slackRelayUrl()).trim();
|
|
9660
|
+
const http = raw.replace(/^ws/i, "http");
|
|
9661
|
+
const u = new URL(http);
|
|
9662
|
+
return `${u.protocol}//${u.host}`;
|
|
9663
|
+
}
|
|
9664
|
+
function slackOAuthResultUrl(state, relayWsUrl) {
|
|
9665
|
+
const u = new URL(SLACK_OAUTH_RESULT_PATH, slackRelayHttpOrigin(relayWsUrl));
|
|
9666
|
+
u.searchParams.set("state", state);
|
|
9667
|
+
return u.toString();
|
|
9668
|
+
}
|
|
9669
|
+
function slackOAuthRelayClientId() {
|
|
9670
|
+
return process.env.SIDEBOARD_SLACK_CLIENT_ID?.trim() || BAKED_SLACK_CLIENT_ID.trim();
|
|
9671
|
+
}
|
|
9672
|
+
function slackOAuthRelayRedirectUri() {
|
|
9673
|
+
return slackOAuthRedirectUri() || SLACK_OAUTH_REDIRECT;
|
|
9674
|
+
}
|
|
9675
|
+
async function exchangeSlackOAuthCode(opts) {
|
|
9676
|
+
const body = new URLSearchParams({
|
|
9677
|
+
client_id: opts.clientId,
|
|
9678
|
+
client_secret: opts.clientSecret,
|
|
9679
|
+
code: opts.code,
|
|
9680
|
+
redirect_uri: opts.redirectUri
|
|
9681
|
+
});
|
|
9682
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
9683
|
+
const res = await fetchImpl("https://slack.com/api/oauth.v2.access", {
|
|
9684
|
+
method: "POST",
|
|
9685
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
9686
|
+
body
|
|
9687
|
+
});
|
|
9688
|
+
const data = await res.json();
|
|
9689
|
+
if (!data.ok) {
|
|
9690
|
+
throw new Error(`Slack OAuth exchange failed: ${data.error || res.status}`);
|
|
9691
|
+
}
|
|
9692
|
+
const teamId = data.team?.id?.trim();
|
|
9693
|
+
if (!teamId) throw new Error("Slack OAuth did not return a team id");
|
|
9205
9694
|
return {
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9695
|
+
team_id: teamId,
|
|
9696
|
+
team_name: data.team?.name?.trim() || teamId,
|
|
9697
|
+
user_id: data.authed_user?.id?.trim() || void 0,
|
|
9698
|
+
bot_token: data.access_token?.trim() || void 0,
|
|
9699
|
+
user_token: data.authed_user?.access_token?.trim() || void 0,
|
|
9700
|
+
scopes: [data.scope, data.authed_user?.scope].filter(Boolean).join(",")
|
|
9212
9701
|
};
|
|
9213
9702
|
}
|
|
9214
9703
|
|
|
@@ -9244,13 +9733,13 @@ var SLACK_USER_SCOPES = [
|
|
|
9244
9733
|
function slackOAuthCredentials() {
|
|
9245
9734
|
const settings = loadAppSettings();
|
|
9246
9735
|
const clientId = process.env.SIDEBOARD_SLACK_CLIENT_ID?.trim() || settings.integrations.slackClientId?.trim() || BAKED_SLACK_CLIENT_ID.trim();
|
|
9247
|
-
const clientSecret = process.env.SIDEBOARD_SLACK_CLIENT_SECRET?.trim() || settings.integrations.slackClientSecret?.trim() ||
|
|
9248
|
-
if (!clientId
|
|
9736
|
+
const clientSecret = process.env.SIDEBOARD_SLACK_CLIENT_SECRET?.trim() || settings.integrations.slackClientSecret?.trim() || "";
|
|
9737
|
+
if (!clientId) {
|
|
9249
9738
|
throw new Error(
|
|
9250
|
-
"Slack browser sign-in needs a Slack app Client ID
|
|
9739
|
+
"Slack browser sign-in needs a Slack app Client ID (Account \u2192 Slack, or SIDEBOARD_SLACK_CLIENT_ID). You can still paste an xoxb- or xoxp- token."
|
|
9251
9740
|
);
|
|
9252
9741
|
}
|
|
9253
|
-
return { clientId, clientSecret };
|
|
9742
|
+
return { clientId, clientSecret: clientSecret || null };
|
|
9254
9743
|
}
|
|
9255
9744
|
var SLACK_OAUTH_CANCELLED = "Slack sign-in cancelled";
|
|
9256
9745
|
var SlackOAuthCancelledError = class extends Error {
|
|
@@ -9275,127 +9764,100 @@ function slackOAuthAuthorizeUrl(clientId, state) {
|
|
|
9275
9764
|
});
|
|
9276
9765
|
return `https://slack.com/oauth/v2/authorize?${params.toString()}`;
|
|
9277
9766
|
}
|
|
9278
|
-
function
|
|
9279
|
-
return
|
|
9280
|
-
|
|
9281
|
-
|
|
9767
|
+
function sleep(ms, signal) {
|
|
9768
|
+
return new Promise((resolve, reject) => {
|
|
9769
|
+
if (signal?.aborted) {
|
|
9770
|
+
reject(new SlackOAuthCancelledError());
|
|
9771
|
+
return;
|
|
9772
|
+
}
|
|
9773
|
+
const timer = setTimeout(() => {
|
|
9774
|
+
signal?.removeEventListener("abort", onAbort);
|
|
9775
|
+
resolve();
|
|
9776
|
+
}, ms);
|
|
9777
|
+
const onAbort = () => {
|
|
9778
|
+
clearTimeout(timer);
|
|
9779
|
+
reject(new SlackOAuthCancelledError());
|
|
9780
|
+
};
|
|
9781
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
9782
|
+
});
|
|
9783
|
+
}
|
|
9784
|
+
function upsertFromPayload(payload) {
|
|
9785
|
+
return upsertSlackWorkspace({
|
|
9786
|
+
team_id: payload.team_id,
|
|
9787
|
+
team_name: payload.team_name,
|
|
9788
|
+
user_id: payload.user_id,
|
|
9789
|
+
bot_token: payload.bot_token,
|
|
9790
|
+
user_token: payload.user_token,
|
|
9791
|
+
scopes: payload.scopes,
|
|
9792
|
+
connected_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9793
|
+
});
|
|
9282
9794
|
}
|
|
9283
9795
|
async function startSlackOAuth(opts) {
|
|
9284
9796
|
if (opts?.signal?.aborted) {
|
|
9285
9797
|
throw new SlackOAuthCancelledError();
|
|
9286
9798
|
}
|
|
9287
|
-
const { clientId
|
|
9799
|
+
const { clientId } = slackOAuthCredentials();
|
|
9288
9800
|
const state = randomBytes2(16).toString("hex");
|
|
9289
9801
|
const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
|
|
9290
9802
|
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
9291
|
-
const
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
const server = createServer3((req, res2) => {
|
|
9301
|
-
try {
|
|
9302
|
-
const url = new URL(req.url || "/", `http://127.0.0.1:${SLACK_OAUTH_PORT}`);
|
|
9303
|
-
if (url.pathname !== "/callback") {
|
|
9304
|
-
res2.writeHead(404);
|
|
9305
|
-
res2.end("Not found");
|
|
9306
|
-
return;
|
|
9307
|
-
}
|
|
9308
|
-
const err = url.searchParams.get("error");
|
|
9309
|
-
const gotState = url.searchParams.get("state");
|
|
9310
|
-
const gotCode = url.searchParams.get("code");
|
|
9311
|
-
if (err) {
|
|
9312
|
-
res2.writeHead(400, { "Content-Type": "text/html" });
|
|
9313
|
-
res2.end(htmlPage2("Slack", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
9314
|
-
finish(new SlackOAuthCancelledError(`Slack OAuth: ${err}`));
|
|
9315
|
-
return;
|
|
9316
|
-
}
|
|
9317
|
-
if (gotState !== state || !gotCode) {
|
|
9318
|
-
res2.writeHead(400, { "Content-Type": "text/html" });
|
|
9319
|
-
res2.end(htmlPage2("Slack", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
9320
|
-
finish(new Error("Slack OAuth callback was invalid"));
|
|
9321
|
-
return;
|
|
9322
|
-
}
|
|
9323
|
-
res2.writeHead(200, { "Content-Type": "text/html" });
|
|
9324
|
-
res2.end(
|
|
9325
|
-
htmlPage2(
|
|
9326
|
-
"Slack connected",
|
|
9327
|
-
"<h1>Slack workspace connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
9328
|
-
)
|
|
9329
|
-
);
|
|
9330
|
-
finish(void 0, gotCode);
|
|
9331
|
-
} catch (e) {
|
|
9332
|
-
finish(e instanceof Error ? e : new Error(String(e)));
|
|
9333
|
-
}
|
|
9334
|
-
});
|
|
9335
|
-
const timer = setTimeout(() => {
|
|
9336
|
-
finish(new Error("Slack sign-in timed out \u2014 try again"));
|
|
9337
|
-
}, timeoutMs);
|
|
9338
|
-
const onAbort = () => finish(new SlackOAuthCancelledError());
|
|
9339
|
-
const cleanup = () => {
|
|
9340
|
-
clearTimeout(timer);
|
|
9341
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
9342
|
-
server.close();
|
|
9343
|
-
};
|
|
9344
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
9803
|
+
const pollIntervalMs = opts?.pollIntervalMs ?? 400;
|
|
9804
|
+
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
9805
|
+
const resultUrl = opts?.resultUrlForState?.(state) ?? slackOAuthResultUrl(state);
|
|
9806
|
+
await Promise.resolve(opts?.openUrl?.(authorizeUrl));
|
|
9807
|
+
if (opts?.signal?.aborted) {
|
|
9808
|
+
throw new SlackOAuthCancelledError();
|
|
9809
|
+
}
|
|
9810
|
+
const deadline = Date.now() + timeoutMs;
|
|
9811
|
+
while (Date.now() < deadline) {
|
|
9345
9812
|
if (opts?.signal?.aborted) {
|
|
9346
|
-
|
|
9347
|
-
return;
|
|
9813
|
+
throw new SlackOAuthCancelledError();
|
|
9348
9814
|
}
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
);
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9815
|
+
let res;
|
|
9816
|
+
try {
|
|
9817
|
+
res = await fetchImpl(resultUrl);
|
|
9818
|
+
} catch {
|
|
9819
|
+
if (opts?.signal?.aborted) throw new SlackOAuthCancelledError();
|
|
9820
|
+
await sleep(pollIntervalMs, opts?.signal);
|
|
9821
|
+
continue;
|
|
9822
|
+
}
|
|
9823
|
+
let data;
|
|
9824
|
+
try {
|
|
9825
|
+
data = await res.json();
|
|
9826
|
+
} catch {
|
|
9827
|
+
await sleep(pollIntervalMs, opts?.signal);
|
|
9828
|
+
continue;
|
|
9829
|
+
}
|
|
9830
|
+
if (data.ok && data.team_id?.trim()) {
|
|
9831
|
+
return upsertFromPayload({
|
|
9832
|
+
team_id: data.team_id.trim(),
|
|
9833
|
+
team_name: data.team_name?.trim() || data.team_id.trim(),
|
|
9834
|
+
user_id: data.user_id?.trim() || void 0,
|
|
9835
|
+
bot_token: data.bot_token?.trim() || void 0,
|
|
9836
|
+
user_token: data.user_token?.trim() || void 0,
|
|
9837
|
+
scopes: data.scopes?.trim() || ""
|
|
9359
9838
|
});
|
|
9360
|
-
}
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
method: "POST",
|
|
9370
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
9371
|
-
body
|
|
9372
|
-
});
|
|
9373
|
-
const data = await res.json();
|
|
9374
|
-
if (!data.ok) {
|
|
9375
|
-
throw new Error(`Slack OAuth exchange failed: ${data.error || res.status}`);
|
|
9839
|
+
}
|
|
9840
|
+
const err = data.error?.trim();
|
|
9841
|
+
if (err && err !== "pending") {
|
|
9842
|
+
if (/cancel/i.test(err) || /access_denied/i.test(err)) {
|
|
9843
|
+
throw new SlackOAuthCancelledError(`Slack OAuth: ${err}`);
|
|
9844
|
+
}
|
|
9845
|
+
throw new Error(err.startsWith("Slack") ? err : `Slack OAuth: ${err}`);
|
|
9846
|
+
}
|
|
9847
|
+
await sleep(pollIntervalMs, opts?.signal);
|
|
9376
9848
|
}
|
|
9377
|
-
|
|
9378
|
-
if (!teamId) throw new Error("Slack OAuth did not return a team id");
|
|
9379
|
-
return upsertSlackWorkspace({
|
|
9380
|
-
team_id: teamId,
|
|
9381
|
-
team_name: data.team?.name?.trim() || teamId,
|
|
9382
|
-
user_id: data.authed_user?.id,
|
|
9383
|
-
bot_token: data.access_token,
|
|
9384
|
-
user_token: data.authed_user?.access_token,
|
|
9385
|
-
scopes: [data.scope, data.authed_user?.scope].filter(Boolean).join(","),
|
|
9386
|
-
connected_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9387
|
-
});
|
|
9849
|
+
throw new Error("Slack sign-in timed out \u2014 try again");
|
|
9388
9850
|
}
|
|
9389
9851
|
|
|
9390
9852
|
// src/slack/socket-mode.ts
|
|
9391
9853
|
import { WebSocket as WsWebSocket } from "ws";
|
|
9392
9854
|
var BACKOFF_START_MS = 1e3;
|
|
9393
9855
|
var BACKOFF_MAX_MS = 3e4;
|
|
9394
|
-
function stripSlackMentions(
|
|
9395
|
-
return
|
|
9856
|
+
function stripSlackMentions(text3) {
|
|
9857
|
+
return text3.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
9396
9858
|
}
|
|
9397
|
-
function isSlackStopCommand(
|
|
9398
|
-
const t = stripSlackMentions(
|
|
9859
|
+
function isSlackStopCommand(text3) {
|
|
9860
|
+
const t = stripSlackMentions(text3).toLowerCase();
|
|
9399
9861
|
return t === "stop" || t === "sideboard_force_stop";
|
|
9400
9862
|
}
|
|
9401
9863
|
function parseSlackSocketFrame(raw) {
|
|
@@ -9417,8 +9879,8 @@ function inboundFromSocketFrame(frame) {
|
|
|
9417
9879
|
const ts = event.ts?.trim();
|
|
9418
9880
|
if (!channelId || !ts) return null;
|
|
9419
9881
|
const rawText = event.text?.trim() ?? "";
|
|
9420
|
-
const
|
|
9421
|
-
if (!
|
|
9882
|
+
const text3 = stripSlackMentions(rawText);
|
|
9883
|
+
if (!text3) return null;
|
|
9422
9884
|
const teamId = (frame.payload?.team_id || event.team || "").trim();
|
|
9423
9885
|
if (!teamId) return null;
|
|
9424
9886
|
const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
|
|
@@ -9430,7 +9892,7 @@ function inboundFromSocketFrame(frame) {
|
|
|
9430
9892
|
ts,
|
|
9431
9893
|
threadTs: event.thread_ts?.trim() || void 0,
|
|
9432
9894
|
userId: event.user?.trim(),
|
|
9433
|
-
text:
|
|
9895
|
+
text: text3,
|
|
9434
9896
|
kind: isMention ? "mention" : "dm"
|
|
9435
9897
|
};
|
|
9436
9898
|
}
|
|
@@ -9681,7 +10143,40 @@ function parseSlackRelayServerMessage(raw) {
|
|
|
9681
10143
|
}
|
|
9682
10144
|
}
|
|
9683
10145
|
|
|
10146
|
+
// src/slack/public-dns.ts
|
|
10147
|
+
import { lookup as systemLookup, Resolver } from "dns";
|
|
10148
|
+
import { request as httpRequest } from "http";
|
|
10149
|
+
import { request as httpsRequest } from "https";
|
|
10150
|
+
var PUBLIC_DNS = ["1.1.1.1", "8.8.8.8"];
|
|
10151
|
+
function finishLookup(options, callback, address, family) {
|
|
10152
|
+
if (options.all) {
|
|
10153
|
+
callback(null, [{ address, family }]);
|
|
10154
|
+
return;
|
|
10155
|
+
}
|
|
10156
|
+
callback(null, address, family);
|
|
10157
|
+
}
|
|
10158
|
+
var lookupPreferPublicDns = (hostname, options, callback) => {
|
|
10159
|
+
if (options.family === 6) {
|
|
10160
|
+
systemLookup(hostname, options, (err, address, family) => {
|
|
10161
|
+
callback(err, address, family);
|
|
10162
|
+
});
|
|
10163
|
+
return;
|
|
10164
|
+
}
|
|
10165
|
+
const resolver = new Resolver();
|
|
10166
|
+
resolver.setServers(PUBLIC_DNS);
|
|
10167
|
+
resolver.resolve4(hostname, (err, addresses) => {
|
|
10168
|
+
if (!err && addresses[0]) {
|
|
10169
|
+
finishLookup(options, callback, addresses[0], 4);
|
|
10170
|
+
return;
|
|
10171
|
+
}
|
|
10172
|
+
systemLookup(hostname, options, (sysErr, address, family) => {
|
|
10173
|
+
callback(sysErr, address, family);
|
|
10174
|
+
});
|
|
10175
|
+
});
|
|
10176
|
+
};
|
|
10177
|
+
|
|
9684
10178
|
// src/slack/relay-client.ts
|
|
10179
|
+
import { WebSocket as WsWebSocket2 } from "ws";
|
|
9685
10180
|
var BACKOFF_START_MS2 = 1e3;
|
|
9686
10181
|
var BACKOFF_MAX_MS2 = 3e4;
|
|
9687
10182
|
function wait2(ms, signal) {
|
|
@@ -9706,7 +10201,11 @@ function send(ws, msg) {
|
|
|
9706
10201
|
}
|
|
9707
10202
|
async function runSlackRelayClient(opts) {
|
|
9708
10203
|
const log = opts.onLog ?? (() => void 0);
|
|
9709
|
-
const Ws = resolveWebSocket(opts.WebSocketImpl)
|
|
10204
|
+
const Ws = opts.WebSocketImpl ? resolveWebSocket(opts.WebSocketImpl) : class {
|
|
10205
|
+
constructor(url2) {
|
|
10206
|
+
return new WsWebSocket2(url2, { lookup: lookupPreferPublicDns });
|
|
10207
|
+
}
|
|
10208
|
+
};
|
|
9710
10209
|
const url = opts.url.trim();
|
|
9711
10210
|
const deviceId = opts.deviceId.trim();
|
|
9712
10211
|
if (!url) throw new Error("Slack relay URL is empty");
|
|
@@ -9869,12 +10368,12 @@ function formatSlackInboundPrompt(msg) {
|
|
|
9869
10368
|
|
|
9870
10369
|
${msg.text}`;
|
|
9871
10370
|
}
|
|
9872
|
-
function isSlackInboundUserPrompt(
|
|
9873
|
-
return
|
|
10371
|
+
function isSlackInboundUserPrompt(text3) {
|
|
10372
|
+
return text3.startsWith("Slack DM\n") || text3.startsWith("Slack @mention\n");
|
|
9874
10373
|
}
|
|
9875
|
-
function formatSlackSignedReply(deviceLabel,
|
|
10374
|
+
function formatSlackSignedReply(deviceLabel, text3) {
|
|
9876
10375
|
const label = deviceLabel.trim();
|
|
9877
|
-
const body =
|
|
10376
|
+
const body = text3.trim();
|
|
9878
10377
|
if (!body) return body;
|
|
9879
10378
|
if (!label) return body;
|
|
9880
10379
|
const head = `${label}:`;
|
|
@@ -9883,8 +10382,8 @@ function formatSlackSignedReply(deviceLabel, text2) {
|
|
|
9883
10382
|
}
|
|
9884
10383
|
return `${label}: ${body}`;
|
|
9885
10384
|
}
|
|
9886
|
-
function signForThisMac(
|
|
9887
|
-
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel,
|
|
10385
|
+
function signForThisMac(text3) {
|
|
10386
|
+
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text3);
|
|
9888
10387
|
}
|
|
9889
10388
|
function slackReplyThreadTs(msg) {
|
|
9890
10389
|
if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
|
|
@@ -9927,7 +10426,7 @@ async function ackSlackInboundSeen(msg, opts) {
|
|
|
9927
10426
|
log(`react error: ${errMsg}${hint}`);
|
|
9928
10427
|
}
|
|
9929
10428
|
}
|
|
9930
|
-
async function postSlackText(target,
|
|
10429
|
+
async function postSlackText(target, text3, opts) {
|
|
9931
10430
|
const stub = {
|
|
9932
10431
|
teamId: target.teamId,
|
|
9933
10432
|
channelId: target.channelId,
|
|
@@ -9937,7 +10436,7 @@ async function postSlackText(target, text2, opts) {
|
|
|
9937
10436
|
kind: "dm"
|
|
9938
10437
|
};
|
|
9939
10438
|
if (opts.postReply) {
|
|
9940
|
-
await opts.postReply(stub,
|
|
10439
|
+
await opts.postReply(stub, text3);
|
|
9941
10440
|
return;
|
|
9942
10441
|
}
|
|
9943
10442
|
const token = writeTokenForTeam(target.teamId);
|
|
@@ -9946,29 +10445,29 @@ async function postSlackText(target, text2, opts) {
|
|
|
9946
10445
|
"chat.postMessage",
|
|
9947
10446
|
{
|
|
9948
10447
|
channel: target.channelId,
|
|
9949
|
-
text:
|
|
10448
|
+
text: text3,
|
|
9950
10449
|
thread_ts: target.threadTs
|
|
9951
10450
|
},
|
|
9952
10451
|
opts.fetchImpl
|
|
9953
10452
|
);
|
|
9954
10453
|
}
|
|
9955
|
-
async function postSlackReply(msg,
|
|
10454
|
+
async function postSlackReply(msg, text3, opts) {
|
|
9956
10455
|
await postSlackText(
|
|
9957
10456
|
{
|
|
9958
10457
|
teamId: msg.teamId,
|
|
9959
10458
|
channelId: msg.channelId,
|
|
9960
10459
|
threadTs: slackReplyThreadTs(msg)
|
|
9961
10460
|
},
|
|
9962
|
-
signForThisMac(
|
|
10461
|
+
signForThisMac(text3),
|
|
9963
10462
|
opts
|
|
9964
10463
|
);
|
|
9965
10464
|
}
|
|
9966
10465
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
9967
|
-
function markRelayed(threadId,
|
|
9968
|
-
lastRelayed.set(threadId, `${threadId}:${
|
|
10466
|
+
function markRelayed(threadId, text3) {
|
|
10467
|
+
lastRelayed.set(threadId, `${threadId}:${text3}`);
|
|
9969
10468
|
}
|
|
9970
|
-
function alreadyRelayed(threadId,
|
|
9971
|
-
return lastRelayed.get(threadId) === `${threadId}:${
|
|
10469
|
+
function alreadyRelayed(threadId, text3) {
|
|
10470
|
+
return lastRelayed.get(threadId) === `${threadId}:${text3}`;
|
|
9972
10471
|
}
|
|
9973
10472
|
async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
9974
10473
|
const target = getSlackReplyTarget(threadId);
|
|
@@ -9977,14 +10476,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
|
9977
10476
|
const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
|
|
9978
10477
|
if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
|
|
9979
10478
|
const result = getOrchestrator().getTurnResult(threadId);
|
|
9980
|
-
const
|
|
9981
|
-
if (!
|
|
9982
|
-
if (alreadyRelayed(threadId,
|
|
9983
|
-
markRelayed(threadId,
|
|
10479
|
+
const text3 = result.text.trim();
|
|
10480
|
+
if (!text3) return;
|
|
10481
|
+
if (alreadyRelayed(threadId, text3)) return;
|
|
10482
|
+
markRelayed(threadId, text3);
|
|
9984
10483
|
const log = opts.onLog ?? (() => void 0);
|
|
9985
10484
|
try {
|
|
9986
|
-
await postSlackText(target, signForThisMac(
|
|
9987
|
-
log(`replied ${target.threadTs ?? target.channelId} (${
|
|
10485
|
+
await postSlackText(target, signForThisMac(text3), opts);
|
|
10486
|
+
log(`replied ${target.threadTs ?? target.channelId} (${text3.length} chars)`);
|
|
9988
10487
|
} catch (err) {
|
|
9989
10488
|
lastRelayed.delete(threadId);
|
|
9990
10489
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -10148,10 +10647,10 @@ function resolveSlackListenMode(opts) {
|
|
|
10148
10647
|
}
|
|
10149
10648
|
|
|
10150
10649
|
// src/slack/relay-hub.ts
|
|
10151
|
-
function parseSlackDeviceDestination(
|
|
10152
|
-
const m =
|
|
10153
|
-
if (!m) return { label: null, rest:
|
|
10154
|
-
return { label: m[1], rest:
|
|
10650
|
+
function parseSlackDeviceDestination(text3) {
|
|
10651
|
+
const m = text3.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
|
|
10652
|
+
if (!m) return { label: null, rest: text3 };
|
|
10653
|
+
return { label: m[1], rest: text3.slice(m[0].length) };
|
|
10155
10654
|
}
|
|
10156
10655
|
var SlackRelayHub = class {
|
|
10157
10656
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -10369,40 +10868,222 @@ var SlackRelayHub = class {
|
|
|
10369
10868
|
};
|
|
10370
10869
|
|
|
10371
10870
|
// src/slack/relay-server.ts
|
|
10372
|
-
import { createServer as
|
|
10871
|
+
import { createServer as createServer3 } from "http";
|
|
10373
10872
|
import { WebSocketServer } from "ws";
|
|
10873
|
+
|
|
10874
|
+
// src/slack/relay-static.ts
|
|
10875
|
+
import { createReadStream } from "fs";
|
|
10876
|
+
import { stat } from "fs/promises";
|
|
10877
|
+
import path from "path";
|
|
10878
|
+
var TYPES = {
|
|
10879
|
+
".css": "text/css; charset=utf-8",
|
|
10880
|
+
".html": "text/html; charset=utf-8",
|
|
10881
|
+
".ico": "image/x-icon",
|
|
10882
|
+
".jpeg": "image/jpeg",
|
|
10883
|
+
".jpg": "image/jpeg",
|
|
10884
|
+
".js": "text/javascript; charset=utf-8",
|
|
10885
|
+
".json": "application/json; charset=utf-8",
|
|
10886
|
+
".png": "image/png",
|
|
10887
|
+
".svg": "image/svg+xml",
|
|
10888
|
+
".txt": "text/plain; charset=utf-8",
|
|
10889
|
+
".webp": "image/webp",
|
|
10890
|
+
".woff2": "font/woff2"
|
|
10891
|
+
};
|
|
10892
|
+
function requestHostname(req) {
|
|
10893
|
+
const raw = req.headers.host ?? "";
|
|
10894
|
+
return raw.split(":")[0]?.toLowerCase() ?? "";
|
|
10895
|
+
}
|
|
10896
|
+
function hostnameAllowed(host, allowed) {
|
|
10897
|
+
if (!allowed || allowed.length === 0) return true;
|
|
10898
|
+
return allowed.includes(host);
|
|
10899
|
+
}
|
|
10900
|
+
function resolveStaticPath(root, requestUrl) {
|
|
10901
|
+
let pathname;
|
|
10902
|
+
try {
|
|
10903
|
+
pathname = decodeURIComponent(new URL(requestUrl, "http://relay.local").pathname);
|
|
10904
|
+
} catch {
|
|
10905
|
+
return null;
|
|
10906
|
+
}
|
|
10907
|
+
if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
|
|
10908
|
+
const rootResolved = path.resolve(root);
|
|
10909
|
+
const candidate = path.resolve(rootResolved, `.${pathname}`);
|
|
10910
|
+
if (candidate !== rootResolved && !candidate.startsWith(rootResolved + path.sep)) {
|
|
10911
|
+
return null;
|
|
10912
|
+
}
|
|
10913
|
+
return candidate;
|
|
10914
|
+
}
|
|
10915
|
+
async function fileSize(file) {
|
|
10916
|
+
try {
|
|
10917
|
+
const st = await stat(file);
|
|
10918
|
+
return st.isFile() ? st.size : null;
|
|
10919
|
+
} catch {
|
|
10920
|
+
return null;
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
10923
|
+
function sendFile(req, res, file, size) {
|
|
10924
|
+
const ext = path.extname(file).toLowerCase();
|
|
10925
|
+
res.writeHead(200, {
|
|
10926
|
+
"Content-Type": TYPES[ext] ?? "application/octet-stream",
|
|
10927
|
+
"Content-Length": size,
|
|
10928
|
+
"Cache-Control": ext === ".html" ? "no-cache" : "public, max-age=3600"
|
|
10929
|
+
});
|
|
10930
|
+
if (req.method === "HEAD") {
|
|
10931
|
+
res.end();
|
|
10932
|
+
return true;
|
|
10933
|
+
}
|
|
10934
|
+
createReadStream(file).pipe(res);
|
|
10935
|
+
return true;
|
|
10936
|
+
}
|
|
10937
|
+
async function tryServeStatic(req, res, root) {
|
|
10938
|
+
if (req.method !== "GET" && req.method !== "HEAD") return false;
|
|
10939
|
+
const candidate = resolveStaticPath(root, req.url || "/");
|
|
10940
|
+
if (!candidate) return false;
|
|
10941
|
+
const direct = await fileSize(candidate);
|
|
10942
|
+
if (direct != null) return sendFile(req, res, candidate, direct);
|
|
10943
|
+
const asIndex = path.join(candidate, "index.html");
|
|
10944
|
+
const indexSize = await fileSize(asIndex);
|
|
10945
|
+
if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
|
|
10946
|
+
return false;
|
|
10947
|
+
}
|
|
10948
|
+
|
|
10949
|
+
// src/slack/relay-server.ts
|
|
10950
|
+
function sendHtml(res, status, title, body) {
|
|
10951
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
10952
|
+
res.end(slackOAuthHtmlPage(title, body));
|
|
10953
|
+
}
|
|
10954
|
+
function sendJson(res, status, body) {
|
|
10955
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
10956
|
+
res.end(JSON.stringify(body));
|
|
10957
|
+
}
|
|
10374
10958
|
async function startSlackRelayServer(opts) {
|
|
10375
10959
|
const log = opts.onLog ?? console.log;
|
|
10376
10960
|
const appToken = opts.appToken.trim();
|
|
10377
10961
|
if (!appToken.startsWith("xapp-")) {
|
|
10378
10962
|
throw new Error("SIDEBOARD_SLACK_APP_TOKEN must be an xapp-\u2026 app-level token");
|
|
10379
10963
|
}
|
|
10964
|
+
const clientId = (opts.clientId ?? slackOAuthRelayClientId()).trim();
|
|
10965
|
+
const clientSecret = (opts.clientSecret ?? process.env.SIDEBOARD_SLACK_CLIENT_SECRET ?? "").trim();
|
|
10966
|
+
const redirectUri = (opts.oauthRedirectUri ?? slackOAuthRelayRedirectUri()).trim();
|
|
10967
|
+
const pending = new SlackOAuthPendingStore();
|
|
10380
10968
|
const hub = opts.hub ?? new SlackRelayHub({
|
|
10381
10969
|
fetchImpl: opts.fetchImpl,
|
|
10382
10970
|
onLog: log
|
|
10383
10971
|
});
|
|
10384
|
-
const
|
|
10385
|
-
const
|
|
10386
|
-
if (
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10972
|
+
const handleCallback = async (reqUrl, res) => {
|
|
10973
|
+
const url2 = parseSlackOAuthCallbackUrl(reqUrl);
|
|
10974
|
+
if (!url2) return false;
|
|
10975
|
+
const error = url2.searchParams.get("error")?.trim();
|
|
10976
|
+
const state = url2.searchParams.get("state")?.trim() ?? "";
|
|
10977
|
+
const code = url2.searchParams.get("code")?.trim() ?? "";
|
|
10978
|
+
if (error) {
|
|
10979
|
+
if (state) pending.put(state, { ok: false, error: `Slack OAuth: ${error}` });
|
|
10980
|
+
sendHtml(
|
|
10981
|
+
res,
|
|
10982
|
+
400,
|
|
10983
|
+
"Slack",
|
|
10984
|
+
`<h1>Authorization cancelled</h1><p>${escapeHtml(error)}</p>`
|
|
10985
|
+
);
|
|
10986
|
+
return true;
|
|
10390
10987
|
}
|
|
10391
|
-
if (
|
|
10392
|
-
res
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10988
|
+
if (!state || !code) {
|
|
10989
|
+
sendHtml(res, 400, "Slack", "<h1>Invalid callback</h1><p>State or code missing.</p>");
|
|
10990
|
+
return true;
|
|
10991
|
+
}
|
|
10992
|
+
if (!clientSecret) {
|
|
10993
|
+
pending.put(state, {
|
|
10994
|
+
ok: false,
|
|
10995
|
+
error: "Relay is missing SIDEBOARD_SLACK_CLIENT_SECRET"
|
|
10996
|
+
});
|
|
10997
|
+
sendHtml(
|
|
10998
|
+
res,
|
|
10999
|
+
500,
|
|
11000
|
+
"Slack",
|
|
11001
|
+
"<h1>Relay misconfigured</h1><p>OAuth client secret is not set on the relay.</p>"
|
|
10399
11002
|
);
|
|
10400
|
-
return;
|
|
11003
|
+
return true;
|
|
11004
|
+
}
|
|
11005
|
+
try {
|
|
11006
|
+
const payload = await exchangeSlackOAuthCode({
|
|
11007
|
+
clientId,
|
|
11008
|
+
clientSecret,
|
|
11009
|
+
code,
|
|
11010
|
+
redirectUri,
|
|
11011
|
+
fetchImpl: opts.fetchImpl
|
|
11012
|
+
});
|
|
11013
|
+
pending.put(state, { ok: true, payload });
|
|
11014
|
+
sendHtml(
|
|
11015
|
+
res,
|
|
11016
|
+
200,
|
|
11017
|
+
"Slack connected",
|
|
11018
|
+
"<h1>Slack workspace connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
11019
|
+
);
|
|
11020
|
+
} catch (err) {
|
|
11021
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11022
|
+
pending.put(state, { ok: false, error: message });
|
|
11023
|
+
sendHtml(res, 400, "Slack", `<h1>Could not connect Slack</h1><p>${escapeHtml(message)}</p>`);
|
|
11024
|
+
}
|
|
11025
|
+
return true;
|
|
11026
|
+
};
|
|
11027
|
+
const handleResult = (reqUrl, res) => {
|
|
11028
|
+
const url2 = parseSlackOAuthResultUrl(reqUrl);
|
|
11029
|
+
if (!url2) return false;
|
|
11030
|
+
const state = url2.searchParams.get("state")?.trim() ?? "";
|
|
11031
|
+
const value = pending.take(state);
|
|
11032
|
+
if (!value) {
|
|
11033
|
+
sendJson(res, 404, { ok: false, error: "pending" });
|
|
11034
|
+
return true;
|
|
11035
|
+
}
|
|
11036
|
+
if (!value.ok) {
|
|
11037
|
+
sendJson(res, 400, { ok: false, error: value.error });
|
|
11038
|
+
return true;
|
|
10401
11039
|
}
|
|
10402
|
-
res.
|
|
10403
|
-
|
|
11040
|
+
sendJson(res, 200, { ok: true, ...value.payload });
|
|
11041
|
+
return true;
|
|
11042
|
+
};
|
|
11043
|
+
const httpServer = createServer3((req, res) => {
|
|
11044
|
+
void (async () => {
|
|
11045
|
+
const reqUrl = req.url || "/";
|
|
11046
|
+
if (await handleCallback(reqUrl, res)) return;
|
|
11047
|
+
if (handleResult(reqUrl, res)) return;
|
|
11048
|
+
if (req.url === "/health") {
|
|
11049
|
+
sendJson(res, 200, {
|
|
11050
|
+
ok: true,
|
|
11051
|
+
service: "sideboard-slack-relay",
|
|
11052
|
+
sessions: hub.listSessions().length,
|
|
11053
|
+
oauth: Boolean(clientSecret)
|
|
11054
|
+
});
|
|
11055
|
+
return;
|
|
11056
|
+
}
|
|
11057
|
+
const hostname = requestHostname(req);
|
|
11058
|
+
const canonical = (opts.canonicalSiteHost ?? "www.sideboard.cloud").toLowerCase();
|
|
11059
|
+
if (opts.redirectHosts?.includes(hostname) && hostname !== canonical) {
|
|
11060
|
+
res.writeHead(301, { Location: `https://${canonical}${reqUrl}` });
|
|
11061
|
+
res.end();
|
|
11062
|
+
return;
|
|
11063
|
+
}
|
|
11064
|
+
if (opts.staticRoot && hostnameAllowed(hostname, opts.staticHosts) && await tryServeStatic(req, res, opts.staticRoot)) {
|
|
11065
|
+
return;
|
|
11066
|
+
}
|
|
11067
|
+
if (req.url === "/") {
|
|
11068
|
+
sendJson(res, 200, {
|
|
11069
|
+
ok: true,
|
|
11070
|
+
service: "sideboard-slack-relay",
|
|
11071
|
+
sessions: hub.listSessions().length,
|
|
11072
|
+
oauth: Boolean(clientSecret)
|
|
11073
|
+
});
|
|
11074
|
+
return;
|
|
11075
|
+
}
|
|
11076
|
+
res.writeHead(404);
|
|
11077
|
+
res.end("Not found");
|
|
11078
|
+
})().catch((err) => {
|
|
11079
|
+
log(`http error: ${err instanceof Error ? err.message : String(err)}`);
|
|
11080
|
+
if (!res.headersSent) {
|
|
11081
|
+
res.writeHead(500);
|
|
11082
|
+
res.end("Internal error");
|
|
11083
|
+
}
|
|
11084
|
+
});
|
|
10404
11085
|
});
|
|
10405
|
-
const wss = new WebSocketServer({ server: httpServer, path:
|
|
11086
|
+
const wss = new WebSocketServer({ server: httpServer, path: SLACK_RELAY_DESKTOP_PATH });
|
|
10406
11087
|
wss.on("connection", (ws, _req) => {
|
|
10407
11088
|
const socket = {
|
|
10408
11089
|
send: (data) => {
|
|
@@ -10440,12 +11121,12 @@ async function startSlackRelayServer(opts) {
|
|
|
10440
11121
|
});
|
|
10441
11122
|
const address = httpServer.address();
|
|
10442
11123
|
const boundPort = typeof address === "object" && address ? address.port : typeof port === "number" ? port : 0;
|
|
10443
|
-
const url = `ws://${host === "0.0.0.0" ? "127.0.0.1" : host}:${boundPort}
|
|
11124
|
+
const url = `ws://${host === "0.0.0.0" ? "127.0.0.1" : host}:${boundPort}${SLACK_RELAY_DESKTOP_PATH}`;
|
|
10444
11125
|
log(`relay listening on ${url}`);
|
|
10445
11126
|
const ac = new AbortController();
|
|
10446
11127
|
const onAbort = () => ac.abort();
|
|
10447
11128
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10448
|
-
const socketModeDone = runSlackSocketMode({
|
|
11129
|
+
const socketModeDone = opts.skipSocketMode ? Promise.resolve() : runSlackSocketMode({
|
|
10449
11130
|
appToken,
|
|
10450
11131
|
signal: ac.signal,
|
|
10451
11132
|
fetchImpl: opts.fetchImpl,
|
|
@@ -10492,6 +11173,7 @@ export {
|
|
|
10492
11173
|
CONTEXT_MIN_MESSAGES,
|
|
10493
11174
|
COORDINATOR_TOOL_PLAYBOOK,
|
|
10494
11175
|
FAMOUS_SOCCER_TEAMS,
|
|
11176
|
+
GITHUB_GIT_AUTH_MODES,
|
|
10495
11177
|
GLOBAL_WORKSPACE_ID,
|
|
10496
11178
|
HARNESS_ENV_KEYS,
|
|
10497
11179
|
LEGACY_ATTACHMENTS_DIR,
|
|
@@ -10522,8 +11204,6 @@ export {
|
|
|
10522
11204
|
SLACK_LISTEN_STOPPED_REPLY,
|
|
10523
11205
|
SLACK_LISTEN_TIMEOUT_REPLY,
|
|
10524
11206
|
SLACK_OAUTH_CANCELLED,
|
|
10525
|
-
SLACK_OAUTH_LOCAL_CALLBACK,
|
|
10526
|
-
SLACK_OAUTH_PORT,
|
|
10527
11207
|
SLACK_OAUTH_REDIRECT,
|
|
10528
11208
|
SLACK_REPLY_FORMATTING,
|
|
10529
11209
|
SLACK_SEEN_REACTION,
|
|
@@ -10543,10 +11223,12 @@ export {
|
|
|
10543
11223
|
allocateTeamSlug,
|
|
10544
11224
|
appDataDir,
|
|
10545
11225
|
appSettingsPath,
|
|
11226
|
+
appendIndexedGitConfig,
|
|
10546
11227
|
appendMessage,
|
|
10547
11228
|
applyAgentEvent,
|
|
10548
11229
|
applyAppEnvironment,
|
|
10549
11230
|
applyCompaction,
|
|
11231
|
+
applyGithubGitAuthEnv,
|
|
10550
11232
|
applyThreadIntoMain,
|
|
10551
11233
|
applyTurnUsage,
|
|
10552
11234
|
assertOrchestratorCapableAgent,
|
|
@@ -10590,6 +11272,7 @@ export {
|
|
|
10590
11272
|
codexAdapter,
|
|
10591
11273
|
coerceOrchestratorAgent,
|
|
10592
11274
|
collectTakenTeamSlugs,
|
|
11275
|
+
commentLinearIssue,
|
|
10593
11276
|
commitAll,
|
|
10594
11277
|
conductorBundledBinDir,
|
|
10595
11278
|
conductorDbPath,
|
|
@@ -10605,6 +11288,7 @@ export {
|
|
|
10605
11288
|
createEmptyThread,
|
|
10606
11289
|
createExistingBranchWorktree,
|
|
10607
11290
|
createGlobalChat,
|
|
11291
|
+
createLinearIssue,
|
|
10608
11292
|
createLinearPkce,
|
|
10609
11293
|
createOrUpdatePr,
|
|
10610
11294
|
createPrStack,
|
|
@@ -10657,7 +11341,9 @@ export {
|
|
|
10657
11341
|
formatAgentInstructions,
|
|
10658
11342
|
formatArtifactDirective,
|
|
10659
11343
|
formatBrightsyFetchError,
|
|
11344
|
+
formatFetchError,
|
|
10660
11345
|
formatGhLandError,
|
|
11346
|
+
formatGitAuthModeDirective,
|
|
10661
11347
|
formatIpcInvokeError,
|
|
10662
11348
|
formatMessagesAsTranscript,
|
|
10663
11349
|
formatPlanQuestionAnswers,
|
|
@@ -10684,9 +11370,12 @@ export {
|
|
|
10684
11370
|
getDiff,
|
|
10685
11371
|
getDiffSummary,
|
|
10686
11372
|
getGitHubStatus,
|
|
11373
|
+
getGithubGitAuthMode,
|
|
11374
|
+
getGithubPat,
|
|
10687
11375
|
getIssueSource,
|
|
10688
11376
|
getLinearApiKey,
|
|
10689
11377
|
getLinearAuthToken,
|
|
11378
|
+
getLinearIssue,
|
|
10690
11379
|
getOrchestrator,
|
|
10691
11380
|
getPr,
|
|
10692
11381
|
getPrChecks,
|
|
@@ -10701,6 +11390,7 @@ export {
|
|
|
10701
11390
|
ghHeadRef,
|
|
10702
11391
|
ghRepoSelectArgs,
|
|
10703
11392
|
git,
|
|
11393
|
+
githubAgentGitEnv,
|
|
10704
11394
|
globalAgentCwd,
|
|
10705
11395
|
handleSlackInbound,
|
|
10706
11396
|
harnessEnvKey,
|
|
@@ -10711,6 +11401,7 @@ export {
|
|
|
10711
11401
|
hasRepoHook,
|
|
10712
11402
|
hasWorkspaceHook,
|
|
10713
11403
|
healOrchestrationSoccerTitles,
|
|
11404
|
+
httpFetch,
|
|
10714
11405
|
importConductorWorkspace,
|
|
10715
11406
|
importConductorWorkspaceAsync,
|
|
10716
11407
|
initPrStack,
|
|
@@ -10744,8 +11435,10 @@ export {
|
|
|
10744
11435
|
isSlackExternalReplyPrompt,
|
|
10745
11436
|
isSlackOAuthCancelled,
|
|
10746
11437
|
isThinkingEffort,
|
|
11438
|
+
isThreadCaffeinated,
|
|
10747
11439
|
isWorkspaceScratchPath,
|
|
10748
11440
|
linearAuthorizationHeader,
|
|
11441
|
+
linearGraphql,
|
|
10749
11442
|
linearOAuthAuthorizeUrl,
|
|
10750
11443
|
linearOAuthCredentials,
|
|
10751
11444
|
listAgentSetupInfo,
|
|
@@ -10762,6 +11455,7 @@ export {
|
|
|
10762
11455
|
listIssues,
|
|
10763
11456
|
listLinearIssues,
|
|
10764
11457
|
listLinearIssuesDirect,
|
|
11458
|
+
listLinearTeams,
|
|
10765
11459
|
listModelsForAgent,
|
|
10766
11460
|
listOpencodeModels,
|
|
10767
11461
|
listPrs,
|
|
@@ -10837,6 +11531,7 @@ export {
|
|
|
10837
11531
|
recordSlackOutboundWatch,
|
|
10838
11532
|
refreshGitHubAuth,
|
|
10839
11533
|
refreshSlackReplyBadges,
|
|
11534
|
+
releaseCaffeinateHoldForThread,
|
|
10840
11535
|
removeWorkspace,
|
|
10841
11536
|
removeWorktree,
|
|
10842
11537
|
repoSlug,
|
|
@@ -10855,6 +11550,8 @@ export {
|
|
|
10855
11550
|
resolveFilesToCopy,
|
|
10856
11551
|
resolveGhAuthToken,
|
|
10857
11552
|
resolveGithubRepoSlug,
|
|
11553
|
+
resolveLinearState,
|
|
11554
|
+
resolveLinearTeam,
|
|
10858
11555
|
resolveLoginCommand,
|
|
10859
11556
|
resolveNewThreadOptions,
|
|
10860
11557
|
resolvePlanMarkdown,
|
|
@@ -10867,6 +11564,7 @@ export {
|
|
|
10867
11564
|
resolveThreadEffort,
|
|
10868
11565
|
resolveVaultKey,
|
|
10869
11566
|
resolveWorktreeStartPoint,
|
|
11567
|
+
rewriteLinearError,
|
|
10870
11568
|
run,
|
|
10871
11569
|
runArchiveScript,
|
|
10872
11570
|
runCloudConnect,
|
|
@@ -10880,6 +11578,7 @@ export {
|
|
|
10880
11578
|
saveLinearOAuth,
|
|
10881
11579
|
secureFileUnlocksWith,
|
|
10882
11580
|
setCaffeinateHold,
|
|
11581
|
+
setHttpFetchImpl,
|
|
10883
11582
|
setStatus,
|
|
10884
11583
|
setVaultMasterKey,
|
|
10885
11584
|
settingsSourceLabel,
|
|
@@ -10897,6 +11596,7 @@ export {
|
|
|
10897
11596
|
slackCoordinatorSourceRef,
|
|
10898
11597
|
slackListenEnabled,
|
|
10899
11598
|
slackOAuthCredentials,
|
|
11599
|
+
slackOAuthResultUrl,
|
|
10900
11600
|
slackRelayUrl,
|
|
10901
11601
|
slugify,
|
|
10902
11602
|
spawnAgentTurn,
|
|
@@ -10913,6 +11613,7 @@ export {
|
|
|
10913
11613
|
startSlackOAuth,
|
|
10914
11614
|
startSlackRelayServer,
|
|
10915
11615
|
stripBrightsyNdjsonNoise,
|
|
11616
|
+
stripNestedElectronEnv,
|
|
10916
11617
|
submitPrStack,
|
|
10917
11618
|
suggestSlug,
|
|
10918
11619
|
summarizeConversation,
|
|
@@ -10942,6 +11643,7 @@ export {
|
|
|
10942
11643
|
updateCodexSettings,
|
|
10943
11644
|
updateDefaultsSettings,
|
|
10944
11645
|
updateIntegrationsSettings,
|
|
11646
|
+
updateLinearIssue,
|
|
10945
11647
|
updateOpencodeSettings,
|
|
10946
11648
|
updateThread,
|
|
10947
11649
|
validateLinearApiKey,
|