@sideboard-ai/core 0.1.201 → 0.1.203
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{abletime-KGPD3UUJ.js → abletime-JFGZBDHH.js} +4 -2
- package/dist/{abletime-345SE3B4.js → abletime-SISJEPJ3.js} +4 -2
- package/dist/abletime-oauth-7TD5Z2QV.js +265 -0
- package/dist/abletime-oauth-OSQYFB3E.js +40 -0
- package/dist/abletime-rest-QPJTDLKX.js +25 -0
- package/dist/abletime-rest-ZJGWYALM.js +24 -0
- package/dist/{agents-KFAGRM46.js → agents-3755WYQM.js} +4 -4
- package/dist/{agents-HB6N3XJL.js → agents-ZMWL43SP.js} +4 -4
- package/dist/{app-settings-V6IHESEQ.js → app-settings-LGHMNHSP.js} +1 -1
- package/dist/{app-settings-AXCURTZJ.js → app-settings-YEPF46IJ.js} +1 -1
- package/dist/{chunk-AHPIU2VP.js → chunk-2DVCCLII.js} +97 -64
- package/dist/chunk-3PYH2RLA.js +388 -0
- package/dist/{chunk-RV3ZKE4Y.js → chunk-5IXJTUST.js} +5 -5
- package/dist/chunk-6VEIKW5C.js +390 -0
- package/dist/chunk-BFEBFGCS.js +250 -0
- package/dist/{chunk-STPZQR5B.js → chunk-CMGRV36E.js} +1 -1
- package/dist/{chunk-PGQGP4FD.js → chunk-FZ6IBUKY.js} +36 -218
- package/dist/{chunk-7AHAABKN.js → chunk-FZYYEMET.js} +36 -228
- package/dist/{chunk-54RSAMBH.js → chunk-LCU6GBZP.js} +2 -2
- package/dist/chunk-MHPINQKY.js +260 -0
- package/dist/chunk-PJHFNFXA.js +243 -0
- package/dist/{chunk-CXFW5LIH.js → chunk-QFBBUFD4.js} +38 -3
- package/dist/{chunk-RPX6VDW7.js → chunk-QSEF2YVQ.js} +1 -1
- package/dist/{chunk-LJHC62LZ.js → chunk-TNDUTGDV.js} +2 -2
- package/dist/{chunk-EXZMNINM.js → chunk-TZNJKZ2A.js} +101 -68
- package/dist/{chunk-3TOGAUQT.js → chunk-X2TFUFIS.js} +5 -5
- package/dist/{chunk-ISV5IFH3.js → chunk-X743YN5Y.js} +38 -3
- package/dist/{coordinator-prompt-TY5FHAXC.js → coordinator-prompt-2B2JA2IE.js} +3 -3
- package/dist/{coordinator-prompt-BH4EHFOO.js → coordinator-prompt-U4PEZDAW.js} +3 -3
- package/dist/{global-workspace-NLLAPQJI.js → global-workspace-JPJ3FKKY.js} +3 -3
- package/dist/{global-workspace-5AZEEQRU.js → global-workspace-PU7ZS3LS.js} +3 -3
- package/dist/index.cjs +932 -145
- package/dist/index.d.cts +61 -9
- package/dist/index.d.ts +61 -9
- package/dist/index.js +63 -18
- package/dist/mcp/run-stdio.cjs +863 -117
- package/dist/mcp/run-stdio.js +22 -20
- package/dist/{orchestrator-SWWQQFFD.js → orchestrator-3A6KUTNI.js} +5 -5
- package/dist/{orchestrator-47JZUHRC.js → orchestrator-Q33M24ZJ.js} +6 -6
- package/dist/{workspaces-RP76JH3J.js → workspaces-IMG7ZRUQ.js} +3 -3
- package/dist/{workspaces-PEWYWVHZ.js → workspaces-U6P6DB44.js} +3 -3
- package/dist/{worktree-ZQEUKSNE.js → worktree-FL3UIUKQ.js} +2 -2
- package/dist/{worktree-72TZWUSH.js → worktree-JSCDJPBU.js} +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -929,6 +929,9 @@ function normalizeVault(raw) {
|
|
|
929
929
|
if (typeof raw.abletimeAccessToken === "string" && raw.abletimeAccessToken.trim()) {
|
|
930
930
|
out.abletimeAccessToken = raw.abletimeAccessToken.trim();
|
|
931
931
|
}
|
|
932
|
+
if (typeof raw.abletimeRefreshToken === "string" && raw.abletimeRefreshToken.trim()) {
|
|
933
|
+
out.abletimeRefreshToken = raw.abletimeRefreshToken.trim();
|
|
934
|
+
}
|
|
932
935
|
if (typeof raw.vercelToken === "string" && raw.vercelToken.trim()) {
|
|
933
936
|
out.vercelToken = raw.vercelToken.trim();
|
|
934
937
|
}
|
|
@@ -1134,6 +1137,7 @@ function emptyPublicIntegrations() {
|
|
|
1134
1137
|
hasSlackAppToken: false,
|
|
1135
1138
|
hasGithubPat: false,
|
|
1136
1139
|
hasAbleTimeToken: false,
|
|
1140
|
+
hasAbleTimeOAuth: false,
|
|
1137
1141
|
hasVercelToken: false,
|
|
1138
1142
|
hasSupabaseToken: false,
|
|
1139
1143
|
hasPosthogToken: false,
|
|
@@ -1158,6 +1162,7 @@ function toPublicAppSettings(settings) {
|
|
|
1158
1162
|
const slackAppToken = integrations.slackAppToken;
|
|
1159
1163
|
const githubPat = integrations.githubPat;
|
|
1160
1164
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
1165
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
1161
1166
|
const vercelToken = integrations.vercelToken;
|
|
1162
1167
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
1163
1168
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -1170,6 +1175,7 @@ function toPublicAppSettings(settings) {
|
|
|
1170
1175
|
delete integrations.slackAppToken;
|
|
1171
1176
|
delete integrations.githubPat;
|
|
1172
1177
|
delete integrations.abletimeAccessToken;
|
|
1178
|
+
delete integrations.abletimeRefreshToken;
|
|
1173
1179
|
delete integrations.vercelToken;
|
|
1174
1180
|
delete integrations.supabaseAccessToken;
|
|
1175
1181
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -1184,7 +1190,8 @@ function toPublicAppSettings(settings) {
|
|
|
1184
1190
|
hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
|
|
1185
1191
|
hasSlackAppToken: Boolean(slackAppToken?.trim()),
|
|
1186
1192
|
hasGithubPat: Boolean(githubPat?.trim()),
|
|
1187
|
-
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim()),
|
|
1193
|
+
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim() || abletimeRefreshToken?.trim()),
|
|
1194
|
+
hasAbleTimeOAuth: Boolean(abletimeRefreshToken?.trim()),
|
|
1188
1195
|
hasVercelToken: Boolean(vercelToken?.trim()),
|
|
1189
1196
|
hasSupabaseToken: Boolean(supabaseAccessToken?.trim()),
|
|
1190
1197
|
hasPosthogToken: Boolean(posthogPersonalApiKey?.trim()),
|
|
@@ -1322,6 +1329,16 @@ function normalizeIntegrations(raw) {
|
|
|
1322
1329
|
const token = source.abletimeAccessToken.trim();
|
|
1323
1330
|
if (token) out.abletimeAccessToken = token;
|
|
1324
1331
|
}
|
|
1332
|
+
if (typeof source.abletimeRefreshToken === "string") {
|
|
1333
|
+
const token = source.abletimeRefreshToken.trim();
|
|
1334
|
+
if (token) out.abletimeRefreshToken = token;
|
|
1335
|
+
}
|
|
1336
|
+
if (typeof source.abletimeTokenExpiresAt === "number" && Number.isFinite(source.abletimeTokenExpiresAt)) {
|
|
1337
|
+
out.abletimeTokenExpiresAt = source.abletimeTokenExpiresAt;
|
|
1338
|
+
} else if (typeof source.abletimeTokenExpiresAt === "string") {
|
|
1339
|
+
const n = Number(source.abletimeTokenExpiresAt);
|
|
1340
|
+
if (Number.isFinite(n)) out.abletimeTokenExpiresAt = n;
|
|
1341
|
+
}
|
|
1325
1342
|
if (typeof source.abletimeHost === "string") {
|
|
1326
1343
|
const host = source.abletimeHost.trim();
|
|
1327
1344
|
if (host) out.abletimeHost = host;
|
|
@@ -1599,7 +1616,7 @@ function readSettingsFile() {
|
|
|
1599
1616
|
}
|
|
1600
1617
|
function diskHoldsSecrets(disk) {
|
|
1601
1618
|
return Boolean(
|
|
1602
|
-
disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || disk.integrations.abletimeAccessToken || disk.integrations.vercelToken || disk.integrations.supabaseAccessToken || disk.integrations.posthogPersonalApiKey || disk.integrations.sentryAuthToken || Object.keys(disk.environment).length > 0
|
|
1619
|
+
disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || disk.integrations.abletimeAccessToken || disk.integrations.abletimeRefreshToken || disk.integrations.vercelToken || disk.integrations.supabaseAccessToken || disk.integrations.posthogPersonalApiKey || disk.integrations.sentryAuthToken || Object.keys(disk.environment).length > 0
|
|
1603
1620
|
);
|
|
1604
1621
|
}
|
|
1605
1622
|
function mergeVault(disk) {
|
|
@@ -1630,6 +1647,9 @@ function mergeVault(disk) {
|
|
|
1630
1647
|
if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
|
|
1631
1648
|
integrations.abletimeAccessToken = vault.abletimeAccessToken;
|
|
1632
1649
|
}
|
|
1650
|
+
if (vault.abletimeRefreshToken && !integrations.abletimeRefreshToken) {
|
|
1651
|
+
integrations.abletimeRefreshToken = vault.abletimeRefreshToken;
|
|
1652
|
+
}
|
|
1633
1653
|
if (vault.vercelToken && !integrations.vercelToken) {
|
|
1634
1654
|
integrations.vercelToken = vault.vercelToken;
|
|
1635
1655
|
}
|
|
@@ -1654,6 +1674,7 @@ function persistSplit(settings) {
|
|
|
1654
1674
|
const slackAppToken = integrations.slackAppToken;
|
|
1655
1675
|
const githubPat = integrations.githubPat;
|
|
1656
1676
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
1677
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
1657
1678
|
const vercelToken = integrations.vercelToken;
|
|
1658
1679
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
1659
1680
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -1666,6 +1687,7 @@ function persistSplit(settings) {
|
|
|
1666
1687
|
delete integrations.slackAppToken;
|
|
1667
1688
|
delete integrations.githubPat;
|
|
1668
1689
|
delete integrations.abletimeAccessToken;
|
|
1690
|
+
delete integrations.abletimeRefreshToken;
|
|
1669
1691
|
delete integrations.vercelToken;
|
|
1670
1692
|
delete integrations.supabaseAccessToken;
|
|
1671
1693
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -1684,6 +1706,7 @@ function persistSplit(settings) {
|
|
|
1684
1706
|
slackAppToken,
|
|
1685
1707
|
githubPat,
|
|
1686
1708
|
abletimeAccessToken,
|
|
1709
|
+
abletimeRefreshToken,
|
|
1687
1710
|
vercelToken,
|
|
1688
1711
|
supabaseAccessToken,
|
|
1689
1712
|
posthogPersonalApiKey,
|
|
@@ -1870,6 +1893,8 @@ function updateIntegrationsSettings(patch) {
|
|
|
1870
1893
|
if ("abletimeAccessToken" in patch) {
|
|
1871
1894
|
if (patch.abletimeAccessToken == null || patch.abletimeAccessToken.trim() === "") {
|
|
1872
1895
|
delete integrations.abletimeAccessToken;
|
|
1896
|
+
delete integrations.abletimeRefreshToken;
|
|
1897
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
1873
1898
|
delete integrations.abletimeViewerName;
|
|
1874
1899
|
} else {
|
|
1875
1900
|
integrations.abletimeAccessToken = patch.abletimeAccessToken.trim();
|
|
@@ -2220,7 +2245,9 @@ function disconnectLinearConnection() {
|
|
|
2220
2245
|
return saveAppSettings({ ...current, integrations });
|
|
2221
2246
|
}
|
|
2222
2247
|
function isAbleTimeConnected(settings = loadAppSettings()) {
|
|
2223
|
-
return Boolean(
|
|
2248
|
+
return Boolean(
|
|
2249
|
+
settings.integrations.abletimeAccessToken?.trim() || settings.integrations.abletimeRefreshToken?.trim()
|
|
2250
|
+
);
|
|
2224
2251
|
}
|
|
2225
2252
|
function getAbleTimeAccessToken(settings = loadAppSettings()) {
|
|
2226
2253
|
const token = settings.integrations.abletimeAccessToken?.trim();
|
|
@@ -2233,6 +2260,12 @@ function saveAbleTimeConnection(input) {
|
|
|
2233
2260
|
const current = loadAppSettings();
|
|
2234
2261
|
const integrations = { ...current.integrations };
|
|
2235
2262
|
integrations.abletimeAccessToken = input.accessToken.trim();
|
|
2263
|
+
if (input.refreshToken?.trim()) {
|
|
2264
|
+
integrations.abletimeRefreshToken = input.refreshToken.trim();
|
|
2265
|
+
}
|
|
2266
|
+
if (typeof input.expiresIn === "number" && Number.isFinite(input.expiresIn)) {
|
|
2267
|
+
integrations.abletimeTokenExpiresAt = Date.now() + Math.max(0, input.expiresIn) * 1e3;
|
|
2268
|
+
}
|
|
2236
2269
|
if (input.host?.trim()) {
|
|
2237
2270
|
integrations.abletimeHost = input.host.trim();
|
|
2238
2271
|
}
|
|
@@ -2245,6 +2278,8 @@ function disconnectAbleTimeConnection() {
|
|
|
2245
2278
|
const current = loadAppSettings();
|
|
2246
2279
|
const integrations = { ...current.integrations };
|
|
2247
2280
|
delete integrations.abletimeAccessToken;
|
|
2281
|
+
delete integrations.abletimeRefreshToken;
|
|
2282
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
2248
2283
|
delete integrations.abletimeViewerName;
|
|
2249
2284
|
return saveAppSettings({ ...current, integrations });
|
|
2250
2285
|
}
|
|
@@ -3799,7 +3834,7 @@ function applyAgentEvent(parts, event) {
|
|
|
3799
3834
|
next.push(
|
|
3800
3835
|
withPartTimes({
|
|
3801
3836
|
type: "thinking",
|
|
3802
|
-
text: data,
|
|
3837
|
+
text: clipThinkingForStore(data),
|
|
3803
3838
|
...event.parentId ? { parentId: event.parentId } : {}
|
|
3804
3839
|
})
|
|
3805
3840
|
);
|
|
@@ -17004,6 +17039,9 @@ function childHaltNotice(child, status) {
|
|
|
17004
17039
|
"This is information \u2014 not a user command. Resume with send_to_thread or tell the user. Do not treat this as a successful turn."
|
|
17005
17040
|
].join("\n");
|
|
17006
17041
|
}
|
|
17042
|
+
function shouldNotifyParentAfterTurnError(opts) {
|
|
17043
|
+
return !opts.quotaFailoverHandled && !opts.crashContinued;
|
|
17044
|
+
}
|
|
17007
17045
|
function shouldNotifyParentOfChildHalt(opts) {
|
|
17008
17046
|
if (!isIncompleteChildStatus(opts.status)) return false;
|
|
17009
17047
|
if (!opts.child.parentThreadId) return false;
|
|
@@ -17456,6 +17494,645 @@ var init_detect = __esm({
|
|
|
17456
17494
|
}
|
|
17457
17495
|
});
|
|
17458
17496
|
|
|
17497
|
+
// src/integrations/abletime-oauth.ts
|
|
17498
|
+
var abletime_oauth_exports = {};
|
|
17499
|
+
__export(abletime_oauth_exports, {
|
|
17500
|
+
ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
|
|
17501
|
+
ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
|
|
17502
|
+
ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
|
|
17503
|
+
ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
|
|
17504
|
+
AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
|
|
17505
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
17506
|
+
ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
|
|
17507
|
+
ableTimeOAuthClientId: () => ableTimeOAuthClientId,
|
|
17508
|
+
ableTimeOAuthResource: () => ableTimeOAuthResource,
|
|
17509
|
+
createAbleTimePkce: () => createAbleTimePkce,
|
|
17510
|
+
ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
|
|
17511
|
+
hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
|
|
17512
|
+
isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
|
|
17513
|
+
startAbleTimeOAuth: () => startAbleTimeOAuth
|
|
17514
|
+
});
|
|
17515
|
+
function hasBakedAbleTimeOAuth() {
|
|
17516
|
+
return Boolean(BAKED_ABLETIME_OAUTH_CLIENT_ID.trim());
|
|
17517
|
+
}
|
|
17518
|
+
function ableTimeOAuthClientId() {
|
|
17519
|
+
return process.env.SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID?.trim() || BAKED_ABLETIME_OAUTH_CLIENT_ID.trim();
|
|
17520
|
+
}
|
|
17521
|
+
function ableTimeOAuthResource(host) {
|
|
17522
|
+
return `${normalizeAbleTimeHost(host)}/api/public/v2/mcp`;
|
|
17523
|
+
}
|
|
17524
|
+
function createAbleTimePkce() {
|
|
17525
|
+
const verifier = (0, import_node_crypto10.randomBytes)(32).toString("base64url");
|
|
17526
|
+
const challenge = (0, import_node_crypto10.createHash)("sha256").update(verifier).digest("base64url");
|
|
17527
|
+
return { verifier, challenge };
|
|
17528
|
+
}
|
|
17529
|
+
function ableTimeOAuthAuthorizeUrl(clientId, state, codeChallenge, host) {
|
|
17530
|
+
const base = normalizeAbleTimeHost(host);
|
|
17531
|
+
const params = new URLSearchParams({
|
|
17532
|
+
response_type: "code",
|
|
17533
|
+
client_id: clientId,
|
|
17534
|
+
redirect_uri: ABLETIME_OAUTH_REDIRECT,
|
|
17535
|
+
code_challenge: codeChallenge,
|
|
17536
|
+
code_challenge_method: "S256",
|
|
17537
|
+
state,
|
|
17538
|
+
resource: ableTimeOAuthResource(host)
|
|
17539
|
+
});
|
|
17540
|
+
return `${base}/oauth/authorize?${params.toString()}`;
|
|
17541
|
+
}
|
|
17542
|
+
function htmlPage(title, body) {
|
|
17543
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
|
|
17544
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
17545
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
17546
|
+
}
|
|
17547
|
+
async function exchangeAbleTimeToken(body, host) {
|
|
17548
|
+
const url = `${normalizeAbleTimeHost(host)}/api/oauth/token`;
|
|
17549
|
+
const res = await httpFetch(url, {
|
|
17550
|
+
method: "POST",
|
|
17551
|
+
headers: {
|
|
17552
|
+
Accept: "application/json",
|
|
17553
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
17554
|
+
},
|
|
17555
|
+
body
|
|
17556
|
+
});
|
|
17557
|
+
const data = await res.json().catch(() => ({}));
|
|
17558
|
+
if (!res.ok || data.error || !data.access_token?.trim()) {
|
|
17559
|
+
const detail = data.error_description || data.error || `HTTP ${res.status}`;
|
|
17560
|
+
throw new Error(`AbleTime OAuth token exchange failed: ${detail}`);
|
|
17561
|
+
}
|
|
17562
|
+
return data;
|
|
17563
|
+
}
|
|
17564
|
+
function persistAbleTimeTokens(data, input) {
|
|
17565
|
+
return saveAbleTimeConnection({
|
|
17566
|
+
accessToken: data.access_token.trim(),
|
|
17567
|
+
refreshToken: data.refresh_token?.trim(),
|
|
17568
|
+
expiresIn: data.expires_in,
|
|
17569
|
+
host: input?.host,
|
|
17570
|
+
viewerName: input?.viewerName
|
|
17571
|
+
});
|
|
17572
|
+
}
|
|
17573
|
+
async function refreshAbleTimeAccessToken(refreshToken) {
|
|
17574
|
+
const host = getAbleTimeHost();
|
|
17575
|
+
const body = new URLSearchParams({
|
|
17576
|
+
grant_type: "refresh_token",
|
|
17577
|
+
refresh_token: refreshToken,
|
|
17578
|
+
client_id: ableTimeOAuthClientId(),
|
|
17579
|
+
resource: ableTimeOAuthResource(host)
|
|
17580
|
+
});
|
|
17581
|
+
try {
|
|
17582
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
17583
|
+
persistAbleTimeTokens(data, { host });
|
|
17584
|
+
return data.access_token.trim();
|
|
17585
|
+
} catch (err) {
|
|
17586
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17587
|
+
if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
|
|
17588
|
+
disconnectAbleTimeConnection();
|
|
17589
|
+
throw new Error("AbleTime sign-in expired \u2014 connect again in Account settings");
|
|
17590
|
+
}
|
|
17591
|
+
throw err;
|
|
17592
|
+
}
|
|
17593
|
+
}
|
|
17594
|
+
async function ensureAbleTimeAccessToken() {
|
|
17595
|
+
const settings = loadAppSettings();
|
|
17596
|
+
const access = settings.integrations.abletimeAccessToken?.trim() || "";
|
|
17597
|
+
const refresh = settings.integrations.abletimeRefreshToken?.trim() || "";
|
|
17598
|
+
const expiresAt = settings.integrations.abletimeTokenExpiresAt ?? 0;
|
|
17599
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
|
|
17600
|
+
return access;
|
|
17601
|
+
}
|
|
17602
|
+
if (refresh) {
|
|
17603
|
+
if (!refreshInFlight) {
|
|
17604
|
+
refreshInFlight = refreshAbleTimeAccessToken(refresh).finally(() => {
|
|
17605
|
+
refreshInFlight = null;
|
|
17606
|
+
});
|
|
17607
|
+
}
|
|
17608
|
+
return refreshInFlight;
|
|
17609
|
+
}
|
|
17610
|
+
return access || null;
|
|
17611
|
+
}
|
|
17612
|
+
function isAbleTimeOAuthCancelled(err) {
|
|
17613
|
+
if (err instanceof AbleTimeOAuthCancelledError) return true;
|
|
17614
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17615
|
+
return message.includes(ABLETIME_OAUTH_CANCELLED);
|
|
17616
|
+
}
|
|
17617
|
+
async function startAbleTimeOAuth(opts) {
|
|
17618
|
+
if (opts?.signal?.aborted) {
|
|
17619
|
+
throw new AbleTimeOAuthCancelledError();
|
|
17620
|
+
}
|
|
17621
|
+
const clientId = ableTimeOAuthClientId();
|
|
17622
|
+
if (!clientId) {
|
|
17623
|
+
throw new Error(
|
|
17624
|
+
`AbleTime browser sign-in needs a Client ID Metadata Document URL. Host ${BAKED_ABLETIME_OAUTH_CLIENT_ID} (see site/oauth/abletime-client.json), or set SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID.`
|
|
17625
|
+
);
|
|
17626
|
+
}
|
|
17627
|
+
const host = opts?.host?.trim() || void 0;
|
|
17628
|
+
const state = (0, import_node_crypto10.randomBytes)(16).toString("hex");
|
|
17629
|
+
const pkce = createAbleTimePkce();
|
|
17630
|
+
const authorizeUrl = ableTimeOAuthAuthorizeUrl(clientId, state, pkce.challenge, host);
|
|
17631
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
17632
|
+
const code = await new Promise((resolve, reject) => {
|
|
17633
|
+
let settled = false;
|
|
17634
|
+
const finish = (err, value) => {
|
|
17635
|
+
if (settled) return;
|
|
17636
|
+
settled = true;
|
|
17637
|
+
cleanup();
|
|
17638
|
+
if (err) reject(err);
|
|
17639
|
+
else resolve(value);
|
|
17640
|
+
};
|
|
17641
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
17642
|
+
try {
|
|
17643
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${ABLETIME_OAUTH_PORT}`);
|
|
17644
|
+
if (url.pathname !== "/callback") {
|
|
17645
|
+
res.writeHead(404);
|
|
17646
|
+
res.end("Not found");
|
|
17647
|
+
return;
|
|
17648
|
+
}
|
|
17649
|
+
const err = url.searchParams.get("error");
|
|
17650
|
+
const gotState = url.searchParams.get("state");
|
|
17651
|
+
const gotCode = url.searchParams.get("code");
|
|
17652
|
+
if (err) {
|
|
17653
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
17654
|
+
res.end(htmlPage("AbleTime", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
17655
|
+
finish(new AbleTimeOAuthCancelledError(`AbleTime OAuth: ${err}`));
|
|
17656
|
+
return;
|
|
17657
|
+
}
|
|
17658
|
+
if (gotState !== state || !gotCode) {
|
|
17659
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
17660
|
+
res.end(htmlPage("AbleTime", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
17661
|
+
finish(new Error("AbleTime OAuth callback was invalid"));
|
|
17662
|
+
return;
|
|
17663
|
+
}
|
|
17664
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
17665
|
+
res.end(
|
|
17666
|
+
htmlPage(
|
|
17667
|
+
"AbleTime connected",
|
|
17668
|
+
"<h1>AbleTime connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
17669
|
+
)
|
|
17670
|
+
);
|
|
17671
|
+
finish(void 0, gotCode);
|
|
17672
|
+
} catch (e) {
|
|
17673
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
17674
|
+
}
|
|
17675
|
+
});
|
|
17676
|
+
const timer = setTimeout(() => {
|
|
17677
|
+
finish(new Error("AbleTime sign-in timed out \u2014 try again"));
|
|
17678
|
+
}, timeoutMs);
|
|
17679
|
+
const onAbort = () => finish(new AbleTimeOAuthCancelledError());
|
|
17680
|
+
const cleanup = () => {
|
|
17681
|
+
clearTimeout(timer);
|
|
17682
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
17683
|
+
server.close();
|
|
17684
|
+
};
|
|
17685
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
17686
|
+
if (opts?.signal?.aborted) {
|
|
17687
|
+
onAbort();
|
|
17688
|
+
return;
|
|
17689
|
+
}
|
|
17690
|
+
server.on("error", (e) => {
|
|
17691
|
+
finish(
|
|
17692
|
+
e instanceof Error && e.code === "EADDRINUSE" ? new Error(
|
|
17693
|
+
`Port ${ABLETIME_OAUTH_PORT} is in use. Close whatever is bound there, or paste an AbleTime token instead.`
|
|
17694
|
+
) : e instanceof Error ? e : new Error(String(e))
|
|
17695
|
+
);
|
|
17696
|
+
});
|
|
17697
|
+
server.listen(ABLETIME_OAUTH_PORT, "127.0.0.1", () => {
|
|
17698
|
+
void Promise.resolve(opts?.openUrl?.(authorizeUrl)).catch((e) => {
|
|
17699
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
17700
|
+
});
|
|
17701
|
+
});
|
|
17702
|
+
});
|
|
17703
|
+
const body = new URLSearchParams({
|
|
17704
|
+
grant_type: "authorization_code",
|
|
17705
|
+
code,
|
|
17706
|
+
redirect_uri: ABLETIME_OAUTH_REDIRECT,
|
|
17707
|
+
client_id: clientId,
|
|
17708
|
+
code_verifier: pkce.verifier,
|
|
17709
|
+
resource: ableTimeOAuthResource(host)
|
|
17710
|
+
});
|
|
17711
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
17712
|
+
persistAbleTimeTokens(data, { host });
|
|
17713
|
+
const token = data.access_token.trim();
|
|
17714
|
+
try {
|
|
17715
|
+
const { getAbleTimeOrientation: getAbleTimeOrientation2 } = await Promise.resolve().then(() => (init_abletime(), abletime_exports));
|
|
17716
|
+
const orientation = await getAbleTimeOrientation2({ token, host });
|
|
17717
|
+
return saveAbleTimeConnection({
|
|
17718
|
+
accessToken: token,
|
|
17719
|
+
refreshToken: data.refresh_token?.trim(),
|
|
17720
|
+
expiresIn: data.expires_in,
|
|
17721
|
+
host,
|
|
17722
|
+
viewerName: orientation.viewer.name
|
|
17723
|
+
});
|
|
17724
|
+
} catch {
|
|
17725
|
+
return loadAppSettings();
|
|
17726
|
+
}
|
|
17727
|
+
}
|
|
17728
|
+
var import_node_crypto10, import_node_http, ABLETIME_OAUTH_PORT, ABLETIME_OAUTH_REDIRECT, BAKED_ABLETIME_OAUTH_CLIENT_ID, ABLETIME_OAUTH_RESOURCE, REFRESH_SKEW_MS2, refreshInFlight, ABLETIME_OAUTH_CANCELLED, AbleTimeOAuthCancelledError;
|
|
17729
|
+
var init_abletime_oauth = __esm({
|
|
17730
|
+
"src/integrations/abletime-oauth.ts"() {
|
|
17731
|
+
"use strict";
|
|
17732
|
+
import_node_crypto10 = require("crypto");
|
|
17733
|
+
import_node_http = require("http");
|
|
17734
|
+
init_fetch();
|
|
17735
|
+
init_app_settings();
|
|
17736
|
+
init_abletime_mcp();
|
|
17737
|
+
ABLETIME_OAUTH_PORT = 19849;
|
|
17738
|
+
ABLETIME_OAUTH_REDIRECT = `http://127.0.0.1:${ABLETIME_OAUTH_PORT}/callback`;
|
|
17739
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID = "https://www.sideboard.cloud/oauth/abletime-client.json";
|
|
17740
|
+
ABLETIME_OAUTH_RESOURCE = `${DEFAULT_ABLETIME_HOST}/api/public/v2/mcp`;
|
|
17741
|
+
REFRESH_SKEW_MS2 = 5 * 6e4;
|
|
17742
|
+
refreshInFlight = null;
|
|
17743
|
+
ABLETIME_OAUTH_CANCELLED = "AbleTime sign-in cancelled";
|
|
17744
|
+
AbleTimeOAuthCancelledError = class extends Error {
|
|
17745
|
+
constructor(message = ABLETIME_OAUTH_CANCELLED) {
|
|
17746
|
+
super(message);
|
|
17747
|
+
this.name = "AbleTimeOAuthCancelledError";
|
|
17748
|
+
}
|
|
17749
|
+
};
|
|
17750
|
+
}
|
|
17751
|
+
});
|
|
17752
|
+
|
|
17753
|
+
// src/integrations/abletime-rest.ts
|
|
17754
|
+
var abletime_rest_exports = {};
|
|
17755
|
+
__export(abletime_rest_exports, {
|
|
17756
|
+
ABLETIME_REST_PREFIX: () => ABLETIME_REST_PREFIX,
|
|
17757
|
+
ableTimeDocFromText: () => ableTimeDocFromText,
|
|
17758
|
+
abletimeRestUrl: () => abletimeRestUrl,
|
|
17759
|
+
callAbleTimeRestTool: () => callAbleTimeRestTool,
|
|
17760
|
+
looksLikeAbleTimeUlid: () => looksLikeAbleTimeUlid,
|
|
17761
|
+
textFromAbleTimeDoc: () => textFromAbleTimeDoc
|
|
17762
|
+
});
|
|
17763
|
+
function asRecord5(value) {
|
|
17764
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17765
|
+
}
|
|
17766
|
+
function asString(value) {
|
|
17767
|
+
return typeof value === "string" ? value.trim() : "";
|
|
17768
|
+
}
|
|
17769
|
+
function asList(value) {
|
|
17770
|
+
if (Array.isArray(value)) return value;
|
|
17771
|
+
const record = asRecord5(value);
|
|
17772
|
+
if (!record) return [];
|
|
17773
|
+
for (const key of ["data", "tasks", "projects", "users", "items", "results"]) {
|
|
17774
|
+
if (Array.isArray(record[key])) return record[key];
|
|
17775
|
+
}
|
|
17776
|
+
return [];
|
|
17777
|
+
}
|
|
17778
|
+
function ableTimeDocFromText(text7) {
|
|
17779
|
+
const trimmed = text7.trim();
|
|
17780
|
+
if (!trimmed) return { type: "doc", content: [] };
|
|
17781
|
+
return {
|
|
17782
|
+
type: "doc",
|
|
17783
|
+
content: [{ type: "paragraph", content: [{ type: "text", text: trimmed }] }]
|
|
17784
|
+
};
|
|
17785
|
+
}
|
|
17786
|
+
function textFromAbleTimeDoc(value) {
|
|
17787
|
+
if (typeof value === "string") return value.trim();
|
|
17788
|
+
const record = asRecord5(value);
|
|
17789
|
+
if (!record) return "";
|
|
17790
|
+
if (typeof record.text === "string" && record.text.trim()) return record.text.trim();
|
|
17791
|
+
const chunks = [];
|
|
17792
|
+
const walk = (node) => {
|
|
17793
|
+
const rec = asRecord5(node);
|
|
17794
|
+
if (!rec) return;
|
|
17795
|
+
if (typeof rec.text === "string" && rec.text) chunks.push(rec.text);
|
|
17796
|
+
if (Array.isArray(rec.content)) rec.content.forEach(walk);
|
|
17797
|
+
};
|
|
17798
|
+
walk(record);
|
|
17799
|
+
return chunks.join("").trim();
|
|
17800
|
+
}
|
|
17801
|
+
function abletimeRestUrl(path2, host, query) {
|
|
17802
|
+
const base = `${normalizeAbleTimeHost(host)}${ABLETIME_REST_PREFIX}`;
|
|
17803
|
+
const suffix = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
17804
|
+
const url = new URL(`${base}${suffix}`);
|
|
17805
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
17806
|
+
if (value) url.searchParams.set(key, value);
|
|
17807
|
+
}
|
|
17808
|
+
return url.href;
|
|
17809
|
+
}
|
|
17810
|
+
function looksLikeAbleTimeUlid(id) {
|
|
17811
|
+
return ULID_RE.test(id.trim());
|
|
17812
|
+
}
|
|
17813
|
+
async function abletimeRestRequest(path2, opts = {}) {
|
|
17814
|
+
const raw = opts.token ?? getAbleTimeAccessToken();
|
|
17815
|
+
const token = normalizeAbleTimeCredential(raw ?? "");
|
|
17816
|
+
if (!token) {
|
|
17817
|
+
throw new Error("AbleTime is not connected \u2014 paste an API key or personal access token in Account settings");
|
|
17818
|
+
}
|
|
17819
|
+
const host = opts.host ?? (opts.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost());
|
|
17820
|
+
const url = abletimeRestUrl(path2, host, opts.query);
|
|
17821
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
17822
|
+
const res = await httpFetch(url, {
|
|
17823
|
+
method,
|
|
17824
|
+
headers: {
|
|
17825
|
+
Accept: "application/json",
|
|
17826
|
+
Authorization: `Bearer ${token}`,
|
|
17827
|
+
...opts.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
17828
|
+
},
|
|
17829
|
+
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
|
|
17830
|
+
});
|
|
17831
|
+
const text7 = await res.text().catch(() => "");
|
|
17832
|
+
if (!res.ok) {
|
|
17833
|
+
throw new Error(
|
|
17834
|
+
rewriteAbleTimeError(
|
|
17835
|
+
`AbleTime API error ${res.status}${text7 ? `: ${text7.slice(0, 240)}` : ""}`
|
|
17836
|
+
)
|
|
17837
|
+
);
|
|
17838
|
+
}
|
|
17839
|
+
if (!text7.trim()) return null;
|
|
17840
|
+
try {
|
|
17841
|
+
return JSON.parse(text7);
|
|
17842
|
+
} catch {
|
|
17843
|
+
throw new Error(`AbleTime API returned a non-JSON response: ${text7.slice(0, 200)}`);
|
|
17844
|
+
}
|
|
17845
|
+
}
|
|
17846
|
+
async function restPages(path2, opts, maxPages = 20) {
|
|
17847
|
+
const items = [];
|
|
17848
|
+
let cursor;
|
|
17849
|
+
for (let i = 0; i < maxPages; i += 1) {
|
|
17850
|
+
const raw = await abletimeRestRequest(path2, {
|
|
17851
|
+
...opts,
|
|
17852
|
+
query: { ...opts.query, ...cursor ? { cursor } : {} }
|
|
17853
|
+
});
|
|
17854
|
+
items.push(...asList(raw));
|
|
17855
|
+
const page = asRecord5(asRecord5(raw)?.page);
|
|
17856
|
+
const next = page ? asString(page.nextCursor) : "";
|
|
17857
|
+
if (!next) break;
|
|
17858
|
+
cursor = next;
|
|
17859
|
+
}
|
|
17860
|
+
return items;
|
|
17861
|
+
}
|
|
17862
|
+
function userDisplayName(raw) {
|
|
17863
|
+
const rec = asRecord5(raw);
|
|
17864
|
+
if (!rec) return null;
|
|
17865
|
+
const id = asString(rec.userId) || asString(rec.id);
|
|
17866
|
+
const name = [asString(rec.firstName), asString(rec.lastName)].filter(Boolean).join(" ") || asString(rec.username) || asString(rec.email) || asString(rec.name);
|
|
17867
|
+
if (!name) return null;
|
|
17868
|
+
return { id: id || void 0, name };
|
|
17869
|
+
}
|
|
17870
|
+
function restTaskToRaw(raw, users) {
|
|
17871
|
+
const rec = asRecord5(raw);
|
|
17872
|
+
if (!rec) return null;
|
|
17873
|
+
const id = asString(rec.timeflowTaskId) || asString(rec.taskId) || asString(rec.id);
|
|
17874
|
+
const identifier = asString(rec.taskRef) || asString(rec.reference) || id;
|
|
17875
|
+
const title = asString(rec.title);
|
|
17876
|
+
if (!id && !identifier && !title) return null;
|
|
17877
|
+
const assigned = asString(rec.assignedUserId);
|
|
17878
|
+
const assignee = assigned ? users.get(assigned) : void 0;
|
|
17879
|
+
const comments = asList(rec.comments).map((item) => {
|
|
17880
|
+
const comment = asRecord5(item) ?? {};
|
|
17881
|
+
const user = userDisplayName(comment);
|
|
17882
|
+
return {
|
|
17883
|
+
id: asString(comment.commentId) || asString(comment.id),
|
|
17884
|
+
body: textFromAbleTimeDoc(comment.content) || asString(comment.contentSanitized) || asString(comment.body),
|
|
17885
|
+
createdAt: asString(comment.createdAt) || asString(comment.lastUpdate),
|
|
17886
|
+
user: user?.name || asString(comment.username)
|
|
17887
|
+
};
|
|
17888
|
+
});
|
|
17889
|
+
return {
|
|
17890
|
+
id,
|
|
17891
|
+
taskId: id,
|
|
17892
|
+
timeflowTaskId: id,
|
|
17893
|
+
reference: identifier,
|
|
17894
|
+
taskRef: identifier,
|
|
17895
|
+
title: title || identifier,
|
|
17896
|
+
state: asString(rec.taskState) || asString(rec.state),
|
|
17897
|
+
description: textFromAbleTimeDoc(rec.description) || asString(rec.descriptionSanitized),
|
|
17898
|
+
projectId: asString(rec.projectId),
|
|
17899
|
+
categoryId: asString(rec.projectCategoryId) || asString(rec.categoryId),
|
|
17900
|
+
tags: rec.tags,
|
|
17901
|
+
comments,
|
|
17902
|
+
createdAt: asString(rec.dateCreated) || asString(rec.createdAt),
|
|
17903
|
+
updatedAt: asString(rec.lastUpdate) || asString(rec.updatedAt),
|
|
17904
|
+
...assignee ? { assignee } : {}
|
|
17905
|
+
};
|
|
17906
|
+
}
|
|
17907
|
+
function restProjectToRaw(raw) {
|
|
17908
|
+
const rec = asRecord5(raw);
|
|
17909
|
+
if (!rec) return null;
|
|
17910
|
+
const id = asString(rec.projectId) || asString(rec.id);
|
|
17911
|
+
const name = asString(rec.projectName) || asString(rec.name);
|
|
17912
|
+
if (!id && !name) return null;
|
|
17913
|
+
const categories = asList(rec.categories).map((item) => {
|
|
17914
|
+
const cat = asRecord5(item) ?? {};
|
|
17915
|
+
return {
|
|
17916
|
+
id: asString(cat.projectCategoryId) || asString(cat.categoryId) || asString(cat.id),
|
|
17917
|
+
name: asString(cat.categoryName) || asString(cat.name)
|
|
17918
|
+
};
|
|
17919
|
+
});
|
|
17920
|
+
return { id, name, projectId: id, projectName: name, categories };
|
|
17921
|
+
}
|
|
17922
|
+
async function listUsers(opts) {
|
|
17923
|
+
const users = /* @__PURE__ */ new Map();
|
|
17924
|
+
for (const item of await restPages("/users", opts).catch(() => [])) {
|
|
17925
|
+
const user = userDisplayName(item);
|
|
17926
|
+
const rec = asRecord5(item);
|
|
17927
|
+
const id = user?.id || (rec ? asString(rec.userId) : "");
|
|
17928
|
+
if (user && id) users.set(id, user);
|
|
17929
|
+
}
|
|
17930
|
+
return users;
|
|
17931
|
+
}
|
|
17932
|
+
async function listProjectsRaw(opts) {
|
|
17933
|
+
const items = await restPages("/projects", { ...opts, query: { expand: "categories" } });
|
|
17934
|
+
return items.map(restProjectToRaw).filter((item) => Boolean(item));
|
|
17935
|
+
}
|
|
17936
|
+
async function listTasksRaw(opts) {
|
|
17937
|
+
const [users, items] = await Promise.all([
|
|
17938
|
+
listUsers(opts),
|
|
17939
|
+
restPages("/tasks", {
|
|
17940
|
+
...opts,
|
|
17941
|
+
query: {
|
|
17942
|
+
...opts.projectId ? { projectId: opts.projectId } : {}
|
|
17943
|
+
}
|
|
17944
|
+
})
|
|
17945
|
+
]);
|
|
17946
|
+
let tasks = items.map((item) => restTaskToRaw(item, users)).filter((item) => Boolean(item));
|
|
17947
|
+
const needle = opts.query?.trim().toLowerCase();
|
|
17948
|
+
if (needle) {
|
|
17949
|
+
tasks = tasks.filter((task) => {
|
|
17950
|
+
const hay = [task.title, task.reference, task.taskRef, task.id].map((value) => String(value ?? "").toLowerCase()).join(" ");
|
|
17951
|
+
const tags = Array.isArray(task.tags) ? task.tags.join(" ").toLowerCase() : "";
|
|
17952
|
+
return hay.includes(needle) || tags.includes(needle);
|
|
17953
|
+
});
|
|
17954
|
+
}
|
|
17955
|
+
return tasks;
|
|
17956
|
+
}
|
|
17957
|
+
async function resolveTaskId(id, opts) {
|
|
17958
|
+
const wanted = id.trim();
|
|
17959
|
+
if (!wanted) throw new Error("AbleTime task id is required");
|
|
17960
|
+
if (looksLikeAbleTimeUlid(wanted)) return wanted;
|
|
17961
|
+
const tasks = await listTasksRaw(opts);
|
|
17962
|
+
const match = tasks.find((task) => {
|
|
17963
|
+
const ref = String(task.reference ?? task.taskRef ?? "").toLowerCase();
|
|
17964
|
+
const taskId = String(task.id ?? "").toLowerCase();
|
|
17965
|
+
return ref === wanted.toLowerCase() || taskId === wanted.toLowerCase();
|
|
17966
|
+
});
|
|
17967
|
+
if (!match?.id) throw new Error(`AbleTime task not found: ${id}`);
|
|
17968
|
+
return String(match.id);
|
|
17969
|
+
}
|
|
17970
|
+
async function getTaskRaw(id, opts) {
|
|
17971
|
+
const taskId = await resolveTaskId(id, opts);
|
|
17972
|
+
const [users, raw] = await Promise.all([
|
|
17973
|
+
listUsers(opts),
|
|
17974
|
+
abletimeRestRequest(`/tasks/${encodeURIComponent(taskId)}`, {
|
|
17975
|
+
...opts,
|
|
17976
|
+
query: { expand: "comments" }
|
|
17977
|
+
})
|
|
17978
|
+
]);
|
|
17979
|
+
const task = restTaskToRaw(raw, users);
|
|
17980
|
+
if (!task) throw new Error(`AbleTime task not found: ${id}`);
|
|
17981
|
+
return task;
|
|
17982
|
+
}
|
|
17983
|
+
function firstStringArg(args, keys) {
|
|
17984
|
+
for (const key of keys) {
|
|
17985
|
+
const value = asString(args[key]);
|
|
17986
|
+
if (value) return value;
|
|
17987
|
+
}
|
|
17988
|
+
return "";
|
|
17989
|
+
}
|
|
17990
|
+
async function callAbleTimeRestTool(name, args = {}, opts) {
|
|
17991
|
+
const token = normalizeAbleTimeCredential(opts?.token ?? getAbleTimeAccessToken() ?? "");
|
|
17992
|
+
if (ableTimeCredentialKind(token) !== "org") {
|
|
17993
|
+
throw new Error("AbleTime REST transport is for organization API keys (atk_\u2026)");
|
|
17994
|
+
}
|
|
17995
|
+
const requestOpts = { ...opts, token };
|
|
17996
|
+
switch (name) {
|
|
17997
|
+
case "orientation": {
|
|
17998
|
+
const [users, projects, tasks] = await Promise.all([
|
|
17999
|
+
listUsers(requestOpts),
|
|
18000
|
+
listProjectsRaw(requestOpts),
|
|
18001
|
+
listTasksRaw(requestOpts)
|
|
18002
|
+
]);
|
|
18003
|
+
const viewer = [...users.values()].find((user) => user.name) ?? [...users.values()][0] ?? { name: "AbleTime" };
|
|
18004
|
+
return { viewer, projects, tasks };
|
|
18005
|
+
}
|
|
18006
|
+
case "list_projects":
|
|
18007
|
+
return { data: await listProjectsRaw(requestOpts) };
|
|
18008
|
+
case "list_users":
|
|
18009
|
+
return { data: [...(await listUsers(requestOpts)).values()] };
|
|
18010
|
+
case "list_tasks":
|
|
18011
|
+
return {
|
|
18012
|
+
data: await listTasksRaw({
|
|
18013
|
+
...requestOpts,
|
|
18014
|
+
projectId: firstStringArg(args, ["project_id", "projectId", "project"])
|
|
18015
|
+
})
|
|
18016
|
+
};
|
|
18017
|
+
case "search_tasks":
|
|
18018
|
+
return {
|
|
18019
|
+
data: await listTasksRaw({
|
|
18020
|
+
...requestOpts,
|
|
18021
|
+
query: firstStringArg(args, ["query", "q"])
|
|
18022
|
+
})
|
|
18023
|
+
};
|
|
18024
|
+
case "get_task":
|
|
18025
|
+
return await getTaskRaw(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18026
|
+
case "create_task": {
|
|
18027
|
+
const title = firstStringArg(args, ["title"]);
|
|
18028
|
+
if (!title) throw new Error("AbleTime task title is required");
|
|
18029
|
+
const projectId = firstStringArg(args, ["project_id", "projectId", "project"]);
|
|
18030
|
+
if (!projectId) throw new Error("AbleTime project is required");
|
|
18031
|
+
const projects = await listProjectsRaw(requestOpts);
|
|
18032
|
+
const project = projects.find(
|
|
18033
|
+
(item) => String(item.id).toLowerCase() === projectId.toLowerCase() || String(item.name).toLowerCase() === projectId.toLowerCase()
|
|
18034
|
+
) ?? projects[0];
|
|
18035
|
+
if (!project) throw new Error("AbleTime has no projects to create a task in");
|
|
18036
|
+
const categories = asList(project.categories);
|
|
18037
|
+
const wantedCategory = firstStringArg(args, ["category_id", "categoryId", "category"]);
|
|
18038
|
+
const category = categories.map((item) => asRecord5(item)).find((item) => {
|
|
18039
|
+
if (!item || !wantedCategory) return false;
|
|
18040
|
+
return asString(item.id).toLowerCase() === wantedCategory.toLowerCase() || asString(item.name).toLowerCase() === wantedCategory.toLowerCase();
|
|
18041
|
+
}) ?? asRecord5(categories[0]);
|
|
18042
|
+
const created = await abletimeRestRequest("/tasks", {
|
|
18043
|
+
...requestOpts,
|
|
18044
|
+
method: "POST",
|
|
18045
|
+
body: {
|
|
18046
|
+
projectId: project.id,
|
|
18047
|
+
projectCategoryId: category ? asString(category.id) : void 0,
|
|
18048
|
+
title
|
|
18049
|
+
}
|
|
18050
|
+
});
|
|
18051
|
+
const state = firstStringArg(args, ["state"]);
|
|
18052
|
+
const createdId = asString(asRecord5(created)?.timeflowTaskId);
|
|
18053
|
+
const description = asString(args.description);
|
|
18054
|
+
if (createdId && description) {
|
|
18055
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}`, {
|
|
18056
|
+
...requestOpts,
|
|
18057
|
+
method: "PATCH",
|
|
18058
|
+
body: { description: ableTimeDocFromText(description) }
|
|
18059
|
+
}).catch(() => void 0);
|
|
18060
|
+
}
|
|
18061
|
+
if (createdId && state && state !== "todo") {
|
|
18062
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}/state`, {
|
|
18063
|
+
...requestOpts,
|
|
18064
|
+
method: "POST",
|
|
18065
|
+
body: { taskState: state }
|
|
18066
|
+
}).catch(() => void 0);
|
|
18067
|
+
}
|
|
18068
|
+
return createdId ? await getTaskRaw(createdId, requestOpts) : restTaskToRaw(created, await listUsers(requestOpts));
|
|
18069
|
+
}
|
|
18070
|
+
case "update_task": {
|
|
18071
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18072
|
+
const patch = {};
|
|
18073
|
+
const title = firstStringArg(args, ["title"]);
|
|
18074
|
+
if (title) patch.title = title;
|
|
18075
|
+
if (args.description !== void 0) {
|
|
18076
|
+
patch.description = args.description == null || args.description === "" ? null : ableTimeDocFromText(String(args.description));
|
|
18077
|
+
}
|
|
18078
|
+
if (args.tags !== void 0 || args.labels !== void 0) {
|
|
18079
|
+
patch.tags = args.tags ?? args.labels ?? args.tag_names;
|
|
18080
|
+
}
|
|
18081
|
+
if (Object.keys(patch).length > 0) {
|
|
18082
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}`, {
|
|
18083
|
+
...requestOpts,
|
|
18084
|
+
method: "PATCH",
|
|
18085
|
+
body: patch
|
|
18086
|
+
});
|
|
18087
|
+
}
|
|
18088
|
+
return await getTaskRaw(id, requestOpts);
|
|
18089
|
+
}
|
|
18090
|
+
case "set_task_state": {
|
|
18091
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18092
|
+
const state = firstStringArg(args, ["state", "taskState"]);
|
|
18093
|
+
if (!state) throw new Error("AbleTime task state is required");
|
|
18094
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/state`, {
|
|
18095
|
+
...requestOpts,
|
|
18096
|
+
method: "POST",
|
|
18097
|
+
body: { taskState: state }
|
|
18098
|
+
});
|
|
18099
|
+
return await getTaskRaw(id, requestOpts);
|
|
18100
|
+
}
|
|
18101
|
+
case "create_comment": {
|
|
18102
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18103
|
+
const body = firstStringArg(args, ["body", "comment", "text"]);
|
|
18104
|
+
if (!body) throw new Error("AbleTime comment body is required");
|
|
18105
|
+
const created = await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/comments`, {
|
|
18106
|
+
...requestOpts,
|
|
18107
|
+
method: "POST",
|
|
18108
|
+
body: { content: ableTimeDocFromText(body) }
|
|
18109
|
+
});
|
|
18110
|
+
const rec = asRecord5(created) ?? {};
|
|
18111
|
+
return {
|
|
18112
|
+
id: asString(rec.commentId) || void 0,
|
|
18113
|
+
body: textFromAbleTimeDoc(rec.content) || body
|
|
18114
|
+
};
|
|
18115
|
+
}
|
|
18116
|
+
case "set_task_dependency":
|
|
18117
|
+
throw new Error(
|
|
18118
|
+
"AbleTime organization API keys cannot set task dependencies on REST yet \u2014 use a personal access token (apt_\u2026) for MCP project-management tools."
|
|
18119
|
+
);
|
|
18120
|
+
default:
|
|
18121
|
+
throw new Error(`AbleTime organization API keys do not support ${name} on REST`);
|
|
18122
|
+
}
|
|
18123
|
+
}
|
|
18124
|
+
var ABLETIME_REST_PREFIX, ULID_RE;
|
|
18125
|
+
var init_abletime_rest = __esm({
|
|
18126
|
+
"src/integrations/abletime-rest.ts"() {
|
|
18127
|
+
"use strict";
|
|
18128
|
+
init_fetch();
|
|
18129
|
+
init_app_settings();
|
|
18130
|
+
init_abletime_mcp();
|
|
18131
|
+
ABLETIME_REST_PREFIX = "/api/public/v2";
|
|
18132
|
+
ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
18133
|
+
}
|
|
18134
|
+
});
|
|
18135
|
+
|
|
17459
18136
|
// src/integrations/abletime-mcp.ts
|
|
17460
18137
|
function abletimeToolUsesPm(name) {
|
|
17461
18138
|
return ABLETIME_PM_TOOL_NAMES.includes(name);
|
|
@@ -17469,22 +18146,49 @@ function abletimeMcpUrl(host, opts) {
|
|
|
17469
18146
|
const path2 = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
|
|
17470
18147
|
return `${normalizeAbleTimeHost(host)}${path2}`;
|
|
17471
18148
|
}
|
|
18149
|
+
function normalizeAbleTimeCredential(raw) {
|
|
18150
|
+
return raw.trim().replace(/^bearer\s+/i, "").trim();
|
|
18151
|
+
}
|
|
18152
|
+
function ableTimeCredentialKind(raw) {
|
|
18153
|
+
const token = normalizeAbleTimeCredential(raw).toLowerCase();
|
|
18154
|
+
if (token.startsWith(ABLETIME_PAT_PREFIX)) return "pat";
|
|
18155
|
+
if (token.startsWith(ABLETIME_ORG_KEY_PREFIX)) return "org";
|
|
18156
|
+
return "unknown";
|
|
18157
|
+
}
|
|
18158
|
+
function assertAbleTimeMcpCredential(raw) {
|
|
18159
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
18160
|
+
if (!token) {
|
|
18161
|
+
throw new Error("AbleTime personal access token is required");
|
|
18162
|
+
}
|
|
18163
|
+
if (ableTimeCredentialKind(token) === "org") {
|
|
18164
|
+
throw new Error(ORG_KEY_HINT);
|
|
18165
|
+
}
|
|
18166
|
+
return token;
|
|
18167
|
+
}
|
|
18168
|
+
function assertAbleTimeCredential(raw) {
|
|
18169
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
18170
|
+
if (!token) {
|
|
18171
|
+
throw new Error("AbleTime API key or personal access token is required");
|
|
18172
|
+
}
|
|
18173
|
+
return token;
|
|
18174
|
+
}
|
|
17472
18175
|
function rewriteAbleTimeError(message) {
|
|
17473
|
-
if (
|
|
18176
|
+
if (REWRITE_APPLIED.test(message)) return message;
|
|
18177
|
+
if (/INTEGRATION_AGENT_ACCESS_DISABLED/i.test(message)) {
|
|
17474
18178
|
return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
|
|
17475
18179
|
}
|
|
17476
|
-
if (/INTEGRATION_PAT_REQUIRED
|
|
17477
|
-
return `${message} \u2014
|
|
18180
|
+
if (/INTEGRATION_PAT_REQUIRED/i.test(message)) {
|
|
18181
|
+
return `${message} \u2014 ${ORG_KEY_HINT}`;
|
|
17478
18182
|
}
|
|
17479
18183
|
if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
|
|
17480
18184
|
return `${message} \u2014 AbleTime plan does not include API access.`;
|
|
17481
18185
|
}
|
|
17482
18186
|
if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
|
|
17483
|
-
return `${message} \u2014
|
|
18187
|
+
return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
|
|
17484
18188
|
}
|
|
17485
18189
|
return message;
|
|
17486
18190
|
}
|
|
17487
|
-
function
|
|
18191
|
+
function asRecord6(value) {
|
|
17488
18192
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17489
18193
|
}
|
|
17490
18194
|
function parseSseJson(body) {
|
|
@@ -17506,12 +18210,12 @@ function parseSseJson(body) {
|
|
|
17506
18210
|
}
|
|
17507
18211
|
}
|
|
17508
18212
|
function unwrapToolResult(result) {
|
|
17509
|
-
const record =
|
|
18213
|
+
const record = asRecord6(result);
|
|
17510
18214
|
if (!record) return result;
|
|
17511
18215
|
const content = record.content;
|
|
17512
18216
|
if (Array.isArray(content)) {
|
|
17513
18217
|
const texts = content.map((part) => {
|
|
17514
|
-
const item =
|
|
18218
|
+
const item = asRecord6(part);
|
|
17515
18219
|
if (!item) return "";
|
|
17516
18220
|
return typeof item.text === "string" ? item.text : "";
|
|
17517
18221
|
}).filter(Boolean);
|
|
@@ -17533,17 +18237,18 @@ function unwrapToolResult(result) {
|
|
|
17533
18237
|
return result;
|
|
17534
18238
|
}
|
|
17535
18239
|
function textsFromUnknown(value) {
|
|
17536
|
-
const record =
|
|
18240
|
+
const record = asRecord6(value);
|
|
17537
18241
|
if (!record) return "";
|
|
17538
18242
|
if (typeof record.message === "string") return record.message;
|
|
17539
18243
|
if (typeof record.error === "string") return record.error;
|
|
17540
18244
|
return "";
|
|
17541
18245
|
}
|
|
17542
18246
|
async function abletimeMcpRequest(method, params, opts) {
|
|
17543
|
-
const
|
|
17544
|
-
if (!
|
|
18247
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
18248
|
+
if (!rawToken?.trim()) {
|
|
17545
18249
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
17546
18250
|
}
|
|
18251
|
+
const token = assertAbleTimeMcpCredential(rawToken);
|
|
17547
18252
|
const url = abletimeMcpUrl(
|
|
17548
18253
|
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
|
|
17549
18254
|
{ pm: opts?.pm }
|
|
@@ -17579,7 +18284,7 @@ async function abletimeMcpRequest(method, params, opts) {
|
|
|
17579
18284
|
return parseSseJson(body);
|
|
17580
18285
|
}
|
|
17581
18286
|
})();
|
|
17582
|
-
const rpc =
|
|
18287
|
+
const rpc = asRecord6(parsed);
|
|
17583
18288
|
if (rpc?.error) {
|
|
17584
18289
|
const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
|
|
17585
18290
|
throw new Error(rewriteAbleTimeError(detail));
|
|
@@ -17610,6 +18315,11 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
17610
18315
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
17611
18316
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
17612
18317
|
}
|
|
18318
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
18319
|
+
if (rawToken && ableTimeCredentialKind(rawToken) === "org") {
|
|
18320
|
+
const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
|
|
18321
|
+
return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
|
|
18322
|
+
}
|
|
17613
18323
|
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
17614
18324
|
const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
|
|
17615
18325
|
return unwrapToolResult(result);
|
|
@@ -17625,7 +18335,7 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
17625
18335
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
17626
18336
|
}
|
|
17627
18337
|
}
|
|
17628
|
-
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
|
|
18338
|
+
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES, ABLETIME_PAT_PREFIX, ABLETIME_ORG_KEY_PREFIX, ORG_KEY_HINT, REJECTED_CREDENTIAL_HINT, REWRITE_APPLIED;
|
|
17629
18339
|
var init_abletime_mcp = __esm({
|
|
17630
18340
|
"src/integrations/abletime-mcp.ts"() {
|
|
17631
18341
|
"use strict";
|
|
@@ -17647,6 +18357,11 @@ var init_abletime_mcp = __esm({
|
|
|
17647
18357
|
"archive_task",
|
|
17648
18358
|
"delete_task"
|
|
17649
18359
|
];
|
|
18360
|
+
ABLETIME_PAT_PREFIX = "apt_";
|
|
18361
|
+
ABLETIME_ORG_KEY_PREFIX = "atk_";
|
|
18362
|
+
ORG_KEY_HINT = "AbleTime MCP needs a personal access token (apt_\u2026) from Edit Profile \u2192 API Access, not an organization API key (atk_\u2026) from Settings \u2192 Integrations \u2192 API Keys.";
|
|
18363
|
+
REJECTED_CREDENTIAL_HINT = "AbleTime rejected this credential (unknown, revoked, or expired). Use a personal access token (apt_\u2026) from Edit Profile \u2192 API Access \u2014 organization API keys (atk_\u2026) cannot call MCP. Tokens are shown once; rotating replaces the previous one.";
|
|
18364
|
+
REWRITE_APPLIED = /— Enable Agent access \(MCP\)|— AbleTime MCP needs a personal access token|— AbleTime plan does not include API access|— AbleTime rejected this credential/;
|
|
17650
18365
|
}
|
|
17651
18366
|
});
|
|
17652
18367
|
|
|
@@ -17670,16 +18385,16 @@ __export(abletime_exports, {
|
|
|
17670
18385
|
updateAbleTimeTask: () => updateAbleTimeTask,
|
|
17671
18386
|
verifyAbleTimeConnection: () => verifyAbleTimeConnection
|
|
17672
18387
|
});
|
|
17673
|
-
function
|
|
18388
|
+
function asRecord7(value) {
|
|
17674
18389
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17675
18390
|
}
|
|
17676
|
-
function
|
|
18391
|
+
function asString2(value) {
|
|
17677
18392
|
return typeof value === "string" ? value.trim() : "";
|
|
17678
18393
|
}
|
|
17679
18394
|
function firstString(record, keys) {
|
|
17680
18395
|
if (!record) return "";
|
|
17681
18396
|
for (const key of keys) {
|
|
17682
|
-
const value =
|
|
18397
|
+
const value = asString2(record[key]);
|
|
17683
18398
|
if (value) return value;
|
|
17684
18399
|
}
|
|
17685
18400
|
return "";
|
|
@@ -17687,14 +18402,14 @@ function firstString(record, keys) {
|
|
|
17687
18402
|
function firstRecord(record, keys) {
|
|
17688
18403
|
if (!record) return null;
|
|
17689
18404
|
for (const key of keys) {
|
|
17690
|
-
const value =
|
|
18405
|
+
const value = asRecord7(record[key]);
|
|
17691
18406
|
if (value) return value;
|
|
17692
18407
|
}
|
|
17693
18408
|
return null;
|
|
17694
18409
|
}
|
|
17695
|
-
function
|
|
18410
|
+
function asList2(value) {
|
|
17696
18411
|
if (Array.isArray(value)) return value;
|
|
17697
|
-
const record =
|
|
18412
|
+
const record = asRecord7(value);
|
|
17698
18413
|
if (!record) return [];
|
|
17699
18414
|
for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
|
|
17700
18415
|
if (Array.isArray(record[key])) return record[key];
|
|
@@ -17706,21 +18421,21 @@ function labelsOf(record) {
|
|
|
17706
18421
|
if (!Array.isArray(raw)) return [];
|
|
17707
18422
|
return raw.map((item) => {
|
|
17708
18423
|
if (typeof item === "string") return item.trim();
|
|
17709
|
-
const rec =
|
|
18424
|
+
const rec = asRecord7(item);
|
|
17710
18425
|
return rec ? firstString(rec, ["name", "title", "label"]) : "";
|
|
17711
18426
|
}).filter(Boolean);
|
|
17712
18427
|
}
|
|
17713
18428
|
function commentsOf(record) {
|
|
17714
18429
|
const raw = record.comments ?? record.notes ?? record.discussion;
|
|
17715
18430
|
const out = [];
|
|
17716
|
-
for (const item of
|
|
17717
|
-
const rec =
|
|
18431
|
+
for (const item of asList2(raw)) {
|
|
18432
|
+
const rec = asRecord7(item);
|
|
17718
18433
|
if (!rec) continue;
|
|
17719
|
-
const body = firstString(rec, ["body", "text", "comment", "
|
|
18434
|
+
const body = firstString(rec, ["body", "text", "comment", "message", "contentSanitized"]) || textFromAbleTimeDoc(rec.content);
|
|
17720
18435
|
if (!body) continue;
|
|
17721
|
-
const user = firstString(
|
|
18436
|
+
const user = firstString(asRecord7(rec.user) ?? asRecord7(rec.author), ["name", "display_name", "username"]) || firstString(rec, ["user_name", "author", "username"]);
|
|
17722
18437
|
const comment = { body };
|
|
17723
|
-
const id = firstString(rec, ["id", "comment_id"]);
|
|
18438
|
+
const id = firstString(rec, ["id", "comment_id", "commentId"]);
|
|
17724
18439
|
if (id) comment.id = id;
|
|
17725
18440
|
const url = firstString(rec, ["url", "permalink"]);
|
|
17726
18441
|
if (url) comment.url = url;
|
|
@@ -17744,11 +18459,11 @@ function taskUrl(task, host) {
|
|
|
17744
18459
|
return `${base}/tasks/${slug}`;
|
|
17745
18460
|
}
|
|
17746
18461
|
function mapAbleTimeTask(raw, host) {
|
|
17747
|
-
const record =
|
|
18462
|
+
const record = asRecord7(raw);
|
|
17748
18463
|
if (!record) return null;
|
|
17749
18464
|
const nested = firstRecord(record, ["task", "data"]) ?? record;
|
|
17750
|
-
const id = firstString(nested, ["id", "task_id", "taskId"]);
|
|
17751
|
-
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number"]) || id;
|
|
18465
|
+
const id = firstString(nested, ["id", "task_id", "taskId", "timeflowTaskId"]);
|
|
18466
|
+
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number", "taskRef"]) || id;
|
|
17752
18467
|
const title = firstString(nested, ["title", "name"]);
|
|
17753
18468
|
if (!id && !identifier && !title) return null;
|
|
17754
18469
|
const resolvedId = id || identifier;
|
|
@@ -17759,21 +18474,22 @@ function mapAbleTimeTask(raw, host) {
|
|
|
17759
18474
|
identifier: resolvedIdentifier,
|
|
17760
18475
|
title: title || resolvedIdentifier,
|
|
17761
18476
|
url,
|
|
17762
|
-
description: firstString(nested, ["description", "body", "details"]) || void 0,
|
|
17763
|
-
state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
|
|
17764
|
-
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(
|
|
17765
|
-
categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(
|
|
18477
|
+
description: firstString(nested, ["description", "body", "details", "descriptionSanitized"]) || textFromAbleTimeDoc(nested.description) || void 0,
|
|
18478
|
+
state: firstString(nested, ["state", "board_state", "boardState", "status", "taskState"]) || void 0,
|
|
18479
|
+
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord7(nested.project), ["id"]) || void 0,
|
|
18480
|
+
categoryId: firstString(nested, ["category_id", "categoryId", "projectCategoryId"]) || firstString(asRecord7(nested.category), ["id"]) || void 0,
|
|
17766
18481
|
assignee: assigneeOf(nested),
|
|
17767
18482
|
labels: labelsOf(nested),
|
|
17768
18483
|
comments: commentsOf(nested),
|
|
17769
|
-
createdAt: firstString(nested, ["created_at", "createdAt", "created"]) || void 0,
|
|
18484
|
+
createdAt: firstString(nested, ["created_at", "createdAt", "created", "dateCreated"]) || void 0,
|
|
17770
18485
|
updatedAt: firstString(nested, [
|
|
17771
18486
|
"updated_at",
|
|
17772
18487
|
"updatedAt",
|
|
17773
18488
|
"modified_at",
|
|
17774
18489
|
"modifiedAt",
|
|
17775
18490
|
"last_activity_at",
|
|
17776
|
-
"lastActivityAt"
|
|
18491
|
+
"lastActivityAt",
|
|
18492
|
+
"lastUpdate"
|
|
17777
18493
|
]) || void 0
|
|
17778
18494
|
};
|
|
17779
18495
|
}
|
|
@@ -17804,16 +18520,16 @@ function issueAttachmentForAbleTimeTask(task) {
|
|
|
17804
18520
|
};
|
|
17805
18521
|
}
|
|
17806
18522
|
function mapProject(raw) {
|
|
17807
|
-
const record =
|
|
18523
|
+
const record = asRecord7(raw);
|
|
17808
18524
|
if (!record) return null;
|
|
17809
|
-
const id = firstString(record, ["id", "project_id"]);
|
|
17810
|
-
const name = firstString(record, ["name", "title"]);
|
|
18525
|
+
const id = firstString(record, ["id", "project_id", "projectId"]);
|
|
18526
|
+
const name = firstString(record, ["name", "title", "projectName"]);
|
|
17811
18527
|
if (!id && !name) return null;
|
|
17812
|
-
const categories =
|
|
17813
|
-
const rec =
|
|
18528
|
+
const categories = asList2(record.categories ?? record.category).map((item) => {
|
|
18529
|
+
const rec = asRecord7(item);
|
|
17814
18530
|
if (!rec) return null;
|
|
17815
|
-
const categoryId = firstString(rec, ["id", "category_id"]);
|
|
17816
|
-
const categoryName = firstString(rec, ["name", "title"]);
|
|
18531
|
+
const categoryId = firstString(rec, ["id", "category_id", "projectCategoryId", "categoryId"]);
|
|
18532
|
+
const categoryName = firstString(rec, ["name", "title", "categoryName"]);
|
|
17817
18533
|
if (!categoryId && !categoryName) return null;
|
|
17818
18534
|
return { id: categoryId || categoryName, name: categoryName || categoryId };
|
|
17819
18535
|
}).filter((item) => Boolean(item));
|
|
@@ -17825,21 +18541,21 @@ function isOpenTask(task) {
|
|
|
17825
18541
|
}
|
|
17826
18542
|
async function getAbleTimeOrientation(opts) {
|
|
17827
18543
|
const raw = await callAbleTimeTool("orientation", {}, opts);
|
|
17828
|
-
const record =
|
|
18544
|
+
const record = asRecord7(raw);
|
|
17829
18545
|
const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
|
|
17830
18546
|
const viewer = {
|
|
17831
18547
|
id: firstString(viewerRec, ["id", "user_id"]) || void 0,
|
|
17832
18548
|
name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
|
|
17833
18549
|
};
|
|
17834
|
-
const projects =
|
|
18550
|
+
const projects = asList2(
|
|
17835
18551
|
record?.projects ?? record?.current_projects ?? record?.currentProjects
|
|
17836
18552
|
).map(mapProject).filter((item) => Boolean(item));
|
|
17837
|
-
const tasks =
|
|
18553
|
+
const tasks = asList2(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
17838
18554
|
return { viewer, projects, tasks, raw };
|
|
17839
18555
|
}
|
|
17840
18556
|
async function listAbleTimeProjects(opts) {
|
|
17841
18557
|
const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
|
|
17842
|
-
return
|
|
18558
|
+
return asList2(raw).map(mapProject).filter((item) => Boolean(item));
|
|
17843
18559
|
}
|
|
17844
18560
|
async function listAbleTimeTasks(opts) {
|
|
17845
18561
|
const args = {};
|
|
@@ -17849,14 +18565,14 @@ async function listAbleTimeTasks(opts) {
|
|
|
17849
18565
|
}
|
|
17850
18566
|
if (opts?.query) args.query = opts.query;
|
|
17851
18567
|
const raw = await callAbleTimeTool("list_tasks", args, opts);
|
|
17852
|
-
const tasks =
|
|
18568
|
+
const tasks = asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
17853
18569
|
return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
|
|
17854
18570
|
}
|
|
17855
18571
|
async function searchAbleTimeTasks(query, opts) {
|
|
17856
18572
|
const q = query.trim();
|
|
17857
18573
|
if (!q) return [];
|
|
17858
18574
|
const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
|
|
17859
|
-
return
|
|
18575
|
+
return asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
|
|
17860
18576
|
}
|
|
17861
18577
|
async function getAbleTimeTask(id, opts) {
|
|
17862
18578
|
const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
|
|
@@ -17904,7 +18620,7 @@ async function commentAbleTimeTask(input, opts) {
|
|
|
17904
18620
|
{ id, task_id: id, task: id, body, comment: body, text: body },
|
|
17905
18621
|
opts
|
|
17906
18622
|
);
|
|
17907
|
-
const rec =
|
|
18623
|
+
const rec = asRecord7(raw);
|
|
17908
18624
|
return {
|
|
17909
18625
|
id: rec ? firstString(rec, ["id", "comment_id"]) || void 0 : void 0,
|
|
17910
18626
|
body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
|
|
@@ -18094,8 +18810,7 @@ async function listAbleTimeAssignedIssues(opts) {
|
|
|
18094
18810
|
};
|
|
18095
18811
|
}
|
|
18096
18812
|
async function verifyAbleTimeConnection(input) {
|
|
18097
|
-
const token = input.token
|
|
18098
|
-
if (!token) throw new Error("AbleTime personal access token is required");
|
|
18813
|
+
const token = assertAbleTimeCredential(input.token);
|
|
18099
18814
|
const host = input.host?.trim() || void 0;
|
|
18100
18815
|
const orientation = await getAbleTimeOrientation({ token, host });
|
|
18101
18816
|
saveAbleTimeConnection({
|
|
@@ -18111,6 +18826,7 @@ var init_abletime = __esm({
|
|
|
18111
18826
|
"use strict";
|
|
18112
18827
|
init_app_settings();
|
|
18113
18828
|
init_abletime_mcp();
|
|
18829
|
+
init_abletime_rest();
|
|
18114
18830
|
CLOSED_STATES = /* @__PURE__ */ new Set([
|
|
18115
18831
|
"done",
|
|
18116
18832
|
"completed",
|
|
@@ -18694,12 +19410,19 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
18694
19410
|
...instructions
|
|
18695
19411
|
].join("\n");
|
|
18696
19412
|
return {
|
|
18697
|
-
id: (0,
|
|
19413
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
18698
19414
|
name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
|
|
18699
19415
|
kind: "transcript",
|
|
18700
19416
|
content: body
|
|
18701
19417
|
};
|
|
18702
19418
|
}
|
|
19419
|
+
async function isolateQuotaFailover(run2) {
|
|
19420
|
+
try {
|
|
19421
|
+
return await run2();
|
|
19422
|
+
} catch {
|
|
19423
|
+
return true;
|
|
19424
|
+
}
|
|
19425
|
+
}
|
|
18703
19426
|
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
18704
19427
|
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
18705
19428
|
const tab = createChatTab({
|
|
@@ -18718,11 +19441,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
18718
19441
|
}
|
|
18719
19442
|
return updateThread(tab.id, patch);
|
|
18720
19443
|
}
|
|
18721
|
-
var
|
|
19444
|
+
var import_node_crypto11, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
|
|
18722
19445
|
var init_quota_failover = __esm({
|
|
18723
19446
|
"src/orchestrator/quota-failover.ts"() {
|
|
18724
19447
|
"use strict";
|
|
18725
|
-
|
|
19448
|
+
import_node_crypto11 = require("crypto");
|
|
18726
19449
|
init_session_quota();
|
|
18727
19450
|
init_app_settings();
|
|
18728
19451
|
init_global_workspace();
|
|
@@ -20975,16 +21698,16 @@ function normalizeServiceOrigin(raw, fallback) {
|
|
|
20975
21698
|
return fallback;
|
|
20976
21699
|
}
|
|
20977
21700
|
}
|
|
20978
|
-
function
|
|
21701
|
+
function asRecord8(value) {
|
|
20979
21702
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20980
21703
|
}
|
|
20981
|
-
function
|
|
21704
|
+
function asString3(value) {
|
|
20982
21705
|
return typeof value === "string" ? value.trim() : "";
|
|
20983
21706
|
}
|
|
20984
21707
|
function firstString2(record, keys) {
|
|
20985
21708
|
if (!record) return "";
|
|
20986
21709
|
for (const key of keys) {
|
|
20987
|
-
const value =
|
|
21710
|
+
const value = asString3(record[key]);
|
|
20988
21711
|
if (value) return value;
|
|
20989
21712
|
}
|
|
20990
21713
|
return "";
|
|
@@ -21008,7 +21731,7 @@ async function authorizedGet(url, token) {
|
|
|
21008
21731
|
});
|
|
21009
21732
|
const body = await readJson(res);
|
|
21010
21733
|
if (!res.ok) {
|
|
21011
|
-
const record =
|
|
21734
|
+
const record = asRecord8(body);
|
|
21012
21735
|
const message = firstString2(record, ["message", "error", "error_description", "detail"]) || (typeof body === "string" ? body.trim().slice(0, 160) : "");
|
|
21013
21736
|
throw new Error(
|
|
21014
21737
|
message ? `${res.status}: ${message}` : `Request failed (${res.status})`
|
|
@@ -21022,28 +21745,28 @@ async function verifyOptionalService(input) {
|
|
|
21022
21745
|
const spec = optionalServiceSpec(input.id);
|
|
21023
21746
|
try {
|
|
21024
21747
|
if (input.id === "vercel") {
|
|
21025
|
-
const body2 =
|
|
21026
|
-
const user =
|
|
21748
|
+
const body2 = asRecord8(await authorizedGet("https://api.vercel.com/v2/user", token));
|
|
21749
|
+
const user = asRecord8(body2?.user) ?? body2;
|
|
21027
21750
|
const name2 = firstString2(user, ["username", "name", "email"]) || "Vercel user";
|
|
21028
21751
|
return { viewerName: name2 };
|
|
21029
21752
|
}
|
|
21030
21753
|
if (input.id === "supabase") {
|
|
21031
21754
|
const body2 = await authorizedGet("https://api.supabase.com/v1/organizations", token);
|
|
21032
21755
|
const list2 = Array.isArray(body2) ? body2 : [];
|
|
21033
|
-
const first2 =
|
|
21756
|
+
const first2 = asRecord8(list2[0]);
|
|
21034
21757
|
const name2 = firstString2(first2, ["name", "slug"]) || "Supabase";
|
|
21035
21758
|
return { viewerName: list2.length > 1 ? `${name2} +${list2.length - 1}` : name2 };
|
|
21036
21759
|
}
|
|
21037
21760
|
if (input.id === "posthog") {
|
|
21038
21761
|
const host2 = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://us.posthog.com");
|
|
21039
|
-
const body2 =
|
|
21762
|
+
const body2 = asRecord8(await authorizedGet(`${host2}/api/users/@me/`, token));
|
|
21040
21763
|
const name2 = firstString2(body2, ["email", "distinct_id"]) || [firstString2(body2, ["first_name"]), firstString2(body2, ["last_name"])].filter(Boolean).join(" ") || "PostHog user";
|
|
21041
21764
|
return { viewerName: name2, host: host2 };
|
|
21042
21765
|
}
|
|
21043
21766
|
const host = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://sentry.io");
|
|
21044
21767
|
const body = await authorizedGet(`${host}/api/0/organizations/`, token);
|
|
21045
21768
|
const list = Array.isArray(body) ? body : [];
|
|
21046
|
-
const first =
|
|
21769
|
+
const first = asRecord8(list[0]);
|
|
21047
21770
|
const name = firstString2(first, ["slug", "name"]) || "Sentry";
|
|
21048
21771
|
return { viewerName: list.length > 1 ? `${name} +${list.length - 1}` : name, host };
|
|
21049
21772
|
} catch (err) {
|
|
@@ -21230,7 +21953,7 @@ var init_optional_services = __esm({
|
|
|
21230
21953
|
});
|
|
21231
21954
|
|
|
21232
21955
|
// src/plan/plan-present.ts
|
|
21233
|
-
function
|
|
21956
|
+
function asRecord9(v) {
|
|
21234
21957
|
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
21235
21958
|
}
|
|
21236
21959
|
function isPresentPlanToolName(name) {
|
|
@@ -21242,7 +21965,7 @@ function extractPresentedPlan(parts) {
|
|
|
21242
21965
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
21243
21966
|
const p = parts[i];
|
|
21244
21967
|
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
21245
|
-
const input =
|
|
21968
|
+
const input = asRecord9(p.input) ?? {};
|
|
21246
21969
|
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
21247
21970
|
if (!content.trim()) continue;
|
|
21248
21971
|
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
@@ -21919,62 +22642,75 @@ var init_orchestrator = __esm({
|
|
|
21919
22642
|
*/
|
|
21920
22643
|
async maybeHandleOrchestrationQuotaFailover(threadId, limitText) {
|
|
21921
22644
|
const thread = findThreadByRef(threadId);
|
|
21922
|
-
if (!thread) return;
|
|
22645
|
+
if (!thread) return false;
|
|
21923
22646
|
const plan = planOrchestrationQuotaFailover(thread, limitText);
|
|
21924
|
-
if (!plan || plan.action === "none") return;
|
|
22647
|
+
if (!plan || plan.action === "none") return false;
|
|
21925
22648
|
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
21926
22649
|
this.haltDrain.add(threadId);
|
|
21927
22650
|
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
22651
|
+
try {
|
|
22652
|
+
setStatus(threadId, "idle", null);
|
|
22653
|
+
appendMessage(threadId, {
|
|
22654
|
+
role: "agent",
|
|
22655
|
+
text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
22656
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
22657
|
+
});
|
|
22658
|
+
this.emit({
|
|
22659
|
+
type: "quota_failover",
|
|
22660
|
+
threadId,
|
|
22661
|
+
action: "wait_reset",
|
|
22662
|
+
message: plan.reason,
|
|
22663
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
22664
|
+
});
|
|
22665
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
22666
|
+
} catch {
|
|
22667
|
+
}
|
|
22668
|
+
return true;
|
|
21943
22669
|
}
|
|
21944
22670
|
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
21945
22671
|
this.haltDrain.add(threadId);
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
22672
|
+
let next;
|
|
22673
|
+
try {
|
|
22674
|
+
next = createQuotaFailoverChat(
|
|
22675
|
+
thread,
|
|
22676
|
+
plan.fallbackAgent,
|
|
22677
|
+
plan.limitText
|
|
22678
|
+
);
|
|
22679
|
+
} catch {
|
|
22680
|
+
return false;
|
|
22681
|
+
}
|
|
21951
22682
|
this.clearQuotaResumeTimer(threadId);
|
|
21952
22683
|
try {
|
|
21953
22684
|
updateThread(threadId, { quotaResumeAt: null });
|
|
21954
22685
|
} catch {
|
|
21955
22686
|
}
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
22687
|
+
try {
|
|
22688
|
+
appendMessage(threadId, {
|
|
22689
|
+
role: "agent",
|
|
22690
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
22691
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
22692
|
+
});
|
|
22693
|
+
this.emit({
|
|
22694
|
+
type: "quota_failover",
|
|
22695
|
+
threadId,
|
|
22696
|
+
action: "switch_agent",
|
|
22697
|
+
toThreadId: next.id,
|
|
22698
|
+
message: plan.reason
|
|
22699
|
+
});
|
|
22700
|
+
this.emit({
|
|
22701
|
+
type: "status_changed",
|
|
22702
|
+
threadId: next.id,
|
|
22703
|
+
status: next.status
|
|
22704
|
+
});
|
|
22705
|
+
} catch {
|
|
22706
|
+
}
|
|
22707
|
+
void this.send(
|
|
21974
22708
|
next.id,
|
|
21975
22709
|
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
21976
22710
|
);
|
|
22711
|
+
return true;
|
|
21977
22712
|
}
|
|
22713
|
+
return false;
|
|
21978
22714
|
}
|
|
21979
22715
|
/**
|
|
21980
22716
|
* Cursor-style recovery: after a runner crash, feed the error back as the
|
|
@@ -22698,13 +23434,18 @@ var init_orchestrator = __esm({
|
|
|
22698
23434
|
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
22699
23435
|
} else {
|
|
22700
23436
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
22701
|
-
|
|
23437
|
+
const failedOver = await isolateQuotaFailover(
|
|
23438
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, blob)
|
|
23439
|
+
);
|
|
22702
23440
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
22703
23441
|
detail: detail || failDetail,
|
|
22704
23442
|
assistantText: chatText,
|
|
22705
23443
|
partsCount: parts.length
|
|
22706
23444
|
});
|
|
22707
|
-
if (
|
|
23445
|
+
if (shouldNotifyParentAfterTurnError({
|
|
23446
|
+
quotaFailoverHandled: failedOver,
|
|
23447
|
+
crashContinued: this.crashContinued.has(threadId)
|
|
23448
|
+
})) {
|
|
22708
23449
|
const failed = readThread(threadId);
|
|
22709
23450
|
if (failed) {
|
|
22710
23451
|
notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
|
|
@@ -22728,13 +23469,18 @@ var init_orchestrator = __esm({
|
|
|
22728
23469
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
22729
23470
|
}
|
|
22730
23471
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
22731
|
-
|
|
23472
|
+
const failedOver = await isolateQuotaFailover(
|
|
23473
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, message)
|
|
23474
|
+
);
|
|
22732
23475
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
22733
23476
|
detail: message,
|
|
22734
23477
|
assistantText: "",
|
|
22735
23478
|
partsCount: 0
|
|
22736
23479
|
});
|
|
22737
|
-
if (
|
|
23480
|
+
if (shouldNotifyParentAfterTurnError({
|
|
23481
|
+
quotaFailoverHandled: failedOver,
|
|
23482
|
+
crashContinued: this.crashContinued.has(threadId)
|
|
23483
|
+
})) {
|
|
22738
23484
|
const failed = readThread(threadId);
|
|
22739
23485
|
if (failed) {
|
|
22740
23486
|
notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
|
|
@@ -23936,10 +24682,18 @@ var index_exports = {};
|
|
|
23936
24682
|
__export(index_exports, {
|
|
23937
24683
|
ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
|
|
23938
24684
|
ABLETIME_MCP_PM_PATH: () => ABLETIME_MCP_PM_PATH,
|
|
24685
|
+
ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
|
|
24686
|
+
ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
|
|
24687
|
+
ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
|
|
24688
|
+
ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
|
|
24689
|
+
ABLETIME_ORG_KEY_PREFIX: () => ABLETIME_ORG_KEY_PREFIX,
|
|
24690
|
+
ABLETIME_PAT_PREFIX: () => ABLETIME_PAT_PREFIX,
|
|
23939
24691
|
ABLETIME_PM_TOOL_NAMES: () => ABLETIME_PM_TOOL_NAMES,
|
|
23940
24692
|
AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
|
|
23941
24693
|
AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
23942
24694
|
ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
|
|
24695
|
+
AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
|
|
24696
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
23943
24697
|
BAKED_SLACK_RELAY_URL: () => BAKED_SLACK_RELAY_URL,
|
|
23944
24698
|
BRIGHTSY_MCP_ALLOWED_TOOLS: () => BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
23945
24699
|
BRIGHTSY_SUMMARIZE_CONTEXT_TOOL: () => BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
|
|
@@ -24028,7 +24782,11 @@ __export(index_exports, {
|
|
|
24028
24782
|
WORKTREE_GITHUB_MCP_TOOLS: () => WORKTREE_GITHUB_MCP_TOOLS,
|
|
24029
24783
|
WORKTREE_LINEAR_MCP_TOOLS: () => WORKTREE_LINEAR_MCP_TOOLS,
|
|
24030
24784
|
WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
|
|
24785
|
+
ableTimeCredentialKind: () => ableTimeCredentialKind,
|
|
24031
24786
|
ableTimeDependencyTarget: () => ableTimeDependencyTarget,
|
|
24787
|
+
ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
|
|
24788
|
+
ableTimeOAuthClientId: () => ableTimeOAuthClientId,
|
|
24789
|
+
ableTimeOAuthResource: () => ableTimeOAuthResource,
|
|
24032
24790
|
abletimeMcpRequest: () => abletimeMcpRequest,
|
|
24033
24791
|
abletimeMcpUrl: () => abletimeMcpUrl,
|
|
24034
24792
|
abletimeToolUsesPm: () => abletimeToolUsesPm,
|
|
@@ -24059,6 +24817,8 @@ __export(index_exports, {
|
|
|
24059
24817
|
applyThreadIntoMain: () => applyThreadIntoMain,
|
|
24060
24818
|
applyTurnUsage: () => applyTurnUsage,
|
|
24061
24819
|
armSchedules: () => armSchedules,
|
|
24820
|
+
assertAbleTimeCredential: () => assertAbleTimeCredential,
|
|
24821
|
+
assertAbleTimeMcpCredential: () => assertAbleTimeMcpCredential,
|
|
24062
24822
|
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
24063
24823
|
attachmentFromAbsolutePath: () => attachmentFromAbsolutePath,
|
|
24064
24824
|
attachmentsFromBuffers: () => attachmentsFromBuffers,
|
|
@@ -24138,6 +24898,7 @@ __export(index_exports, {
|
|
|
24138
24898
|
countCacheControlBlocks: () => countCacheControlBlocks,
|
|
24139
24899
|
countUnpushedVsOrigin: () => countUnpushedVsOrigin,
|
|
24140
24900
|
cowboyModeEnabled: () => cowboyModeEnabled,
|
|
24901
|
+
createAbleTimePkce: () => createAbleTimePkce,
|
|
24141
24902
|
createAbleTimeTask: () => createAbleTimeTask,
|
|
24142
24903
|
createChatTab: () => createChatTab,
|
|
24143
24904
|
createEmptyThread: () => createEmptyThread,
|
|
@@ -24177,6 +24938,7 @@ __export(index_exports, {
|
|
|
24177
24938
|
encodeBrightsyTarget: () => encodeBrightsyTarget,
|
|
24178
24939
|
enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
|
|
24179
24940
|
enrichWorkspacesWithGithub: () => enrichWorkspacesWithGithub,
|
|
24941
|
+
ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
|
|
24180
24942
|
ensureAbleTimeTask: () => ensureAbleTimeTask,
|
|
24181
24943
|
ensureAgentPath: () => ensureAgentPath,
|
|
24182
24944
|
ensureBrightsyLocalConfigFresh: () => ensureBrightsyLocalConfigFresh,
|
|
@@ -24323,6 +25085,7 @@ __export(index_exports, {
|
|
|
24323
25085
|
groupHomeBoardWorktrees: () => groupHomeBoardWorktrees,
|
|
24324
25086
|
handleSlackInbound: () => handleSlackInbound,
|
|
24325
25087
|
harnessEnvKey: () => harnessEnvKey,
|
|
25088
|
+
hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
|
|
24326
25089
|
hasBakedLinearOAuth: () => hasBakedLinearOAuth,
|
|
24327
25090
|
hasBakedSlackOAuth: () => hasBakedSlackOAuth,
|
|
24328
25091
|
hasConductorHook: () => hasConductorHook,
|
|
@@ -24347,6 +25110,7 @@ __export(index_exports, {
|
|
|
24347
25110
|
invalidateThreadListCache: () => invalidateThreadListCache,
|
|
24348
25111
|
invalidateThreadRecord: () => invalidateThreadRecord,
|
|
24349
25112
|
isAbleTimeConnected: () => isAbleTimeConnected,
|
|
25113
|
+
isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
|
|
24350
25114
|
isAskUserToolName: () => isAskUserToolName,
|
|
24351
25115
|
isBrightsyConnected: () => isBrightsyConnected,
|
|
24352
25116
|
isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
|
|
@@ -24476,6 +25240,7 @@ __export(index_exports, {
|
|
|
24476
25240
|
nextPastedTextName: () => nextPastedTextName,
|
|
24477
25241
|
nextThinkingEffort: () => nextThinkingEffort,
|
|
24478
25242
|
nonInteractiveGitProcessEnv: () => nonInteractiveGitProcessEnv,
|
|
25243
|
+
normalizeAbleTimeCredential: () => normalizeAbleTimeCredential,
|
|
24479
25244
|
normalizeAbleTimeHost: () => normalizeAbleTimeHost,
|
|
24480
25245
|
normalizeCodeSelection: () => normalizeCodeSelection,
|
|
24481
25246
|
normalizeLinearRelationType: () => normalizeLinearRelationType,
|
|
@@ -24657,6 +25422,7 @@ __export(index_exports, {
|
|
|
24657
25422
|
stackMergeReadiness: () => stackMergeReadiness,
|
|
24658
25423
|
stageAbsolutePathsAsAttachments: () => stageAbsolutePathsAsAttachments,
|
|
24659
25424
|
stageBuffersAsAttachments: () => stageBuffersAsAttachments,
|
|
25425
|
+
startAbleTimeOAuth: () => startAbleTimeOAuth,
|
|
24660
25426
|
startDevServer: () => startDevServer,
|
|
24661
25427
|
startLinearOAuth: () => startLinearOAuth,
|
|
24662
25428
|
startMcpServer: () => startMcpServer,
|
|
@@ -24959,8 +25725,8 @@ function issueActivityKind(issue, sinceIso) {
|
|
|
24959
25725
|
}
|
|
24960
25726
|
|
|
24961
25727
|
// src/integrations/linear-oauth.ts
|
|
24962
|
-
var
|
|
24963
|
-
var
|
|
25728
|
+
var import_node_crypto12 = require("crypto");
|
|
25729
|
+
var import_node_http2 = require("http");
|
|
24964
25730
|
init_app_settings();
|
|
24965
25731
|
init_fetch();
|
|
24966
25732
|
|
|
@@ -24978,7 +25744,7 @@ var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
|
|
|
24978
25744
|
var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
|
|
24979
25745
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
24980
25746
|
var LINEAR_OAUTH_SCOPES = "read,write";
|
|
24981
|
-
var
|
|
25747
|
+
var REFRESH_SKEW_MS3 = 5 * 6e4;
|
|
24982
25748
|
function linearOAuthCredentials() {
|
|
24983
25749
|
const settings = loadAppSettings();
|
|
24984
25750
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
@@ -24991,8 +25757,8 @@ function linearOAuthCredentials() {
|
|
|
24991
25757
|
return { clientId, clientSecret };
|
|
24992
25758
|
}
|
|
24993
25759
|
function createLinearPkce() {
|
|
24994
|
-
const verifier = (0,
|
|
24995
|
-
const challenge = (0,
|
|
25760
|
+
const verifier = (0, import_node_crypto12.randomBytes)(32).toString("base64url");
|
|
25761
|
+
const challenge = (0, import_node_crypto12.createHash)("sha256").update(verifier).digest("base64url");
|
|
24996
25762
|
return { verifier, challenge };
|
|
24997
25763
|
}
|
|
24998
25764
|
function linearOAuthAuthorizeUrl(clientId, state, codeChallenge) {
|
|
@@ -25016,7 +25782,7 @@ function linearAuthorizationHeader(token) {
|
|
|
25016
25782
|
if (t.startsWith("lin_api_")) return t;
|
|
25017
25783
|
return `Bearer ${t}`;
|
|
25018
25784
|
}
|
|
25019
|
-
function
|
|
25785
|
+
function htmlPage2(title, body) {
|
|
25020
25786
|
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
|
|
25021
25787
|
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
25022
25788
|
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
@@ -25062,7 +25828,7 @@ async function fetchLinearViewer(accessToken) {
|
|
|
25062
25828
|
organizationName: viewer?.organization?.name?.trim() || void 0
|
|
25063
25829
|
};
|
|
25064
25830
|
}
|
|
25065
|
-
var
|
|
25831
|
+
var refreshInFlight2 = null;
|
|
25066
25832
|
async function refreshLinearAccessToken(refreshToken) {
|
|
25067
25833
|
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
25068
25834
|
const body = new URLSearchParams({
|
|
@@ -25089,16 +25855,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
25089
25855
|
const refresh = settings.integrations.linearRefreshToken?.trim() || "";
|
|
25090
25856
|
const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
|
|
25091
25857
|
const apiKey = settings.integrations.linearApiKey?.trim() || "";
|
|
25092
|
-
if (access && (!refresh || Date.now() < expiresAt -
|
|
25858
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS3)) {
|
|
25093
25859
|
return access;
|
|
25094
25860
|
}
|
|
25095
25861
|
if (refresh) {
|
|
25096
|
-
if (!
|
|
25097
|
-
|
|
25098
|
-
|
|
25862
|
+
if (!refreshInFlight2) {
|
|
25863
|
+
refreshInFlight2 = refreshLinearAccessToken(refresh).finally(() => {
|
|
25864
|
+
refreshInFlight2 = null;
|
|
25099
25865
|
});
|
|
25100
25866
|
}
|
|
25101
|
-
return
|
|
25867
|
+
return refreshInFlight2.catch((err) => {
|
|
25102
25868
|
if (apiKey) return apiKey;
|
|
25103
25869
|
throw err;
|
|
25104
25870
|
});
|
|
@@ -25123,7 +25889,7 @@ async function startLinearOAuth(opts) {
|
|
|
25123
25889
|
throw new LinearOAuthCancelledError();
|
|
25124
25890
|
}
|
|
25125
25891
|
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
25126
|
-
const state = (0,
|
|
25892
|
+
const state = (0, import_node_crypto12.randomBytes)(16).toString("hex");
|
|
25127
25893
|
const pkce = createLinearPkce();
|
|
25128
25894
|
const authorizeUrl = linearOAuthAuthorizeUrl(clientId, state, pkce.challenge);
|
|
25129
25895
|
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
@@ -25136,7 +25902,7 @@ async function startLinearOAuth(opts) {
|
|
|
25136
25902
|
if (err) reject(err);
|
|
25137
25903
|
else resolve(value);
|
|
25138
25904
|
};
|
|
25139
|
-
const server = (0,
|
|
25905
|
+
const server = (0, import_node_http2.createServer)((req, res) => {
|
|
25140
25906
|
try {
|
|
25141
25907
|
const url = new URL(req.url || "/", `http://127.0.0.1:${LINEAR_OAUTH_PORT}`);
|
|
25142
25908
|
if (url.pathname !== "/callback") {
|
|
@@ -25149,19 +25915,19 @@ async function startLinearOAuth(opts) {
|
|
|
25149
25915
|
const gotCode = url.searchParams.get("code");
|
|
25150
25916
|
if (err) {
|
|
25151
25917
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
25152
|
-
res.end(
|
|
25918
|
+
res.end(htmlPage2("Linear", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
25153
25919
|
finish(new LinearOAuthCancelledError(`Linear OAuth: ${err}`));
|
|
25154
25920
|
return;
|
|
25155
25921
|
}
|
|
25156
25922
|
if (gotState !== state || !gotCode) {
|
|
25157
25923
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
25158
|
-
res.end(
|
|
25924
|
+
res.end(htmlPage2("Linear", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
25159
25925
|
finish(new Error("Linear OAuth callback was invalid"));
|
|
25160
25926
|
return;
|
|
25161
25927
|
}
|
|
25162
25928
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
25163
25929
|
res.end(
|
|
25164
|
-
|
|
25930
|
+
htmlPage2(
|
|
25165
25931
|
"Linear connected",
|
|
25166
25932
|
"<h1>Linear connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
25167
25933
|
)
|
|
@@ -26574,6 +27340,7 @@ async function createGitHubIssue(input, opts) {
|
|
|
26574
27340
|
// src/index.ts
|
|
26575
27341
|
init_abletime();
|
|
26576
27342
|
init_abletime_mcp();
|
|
27343
|
+
init_abletime_oauth();
|
|
26577
27344
|
|
|
26578
27345
|
// src/integrations/issues.ts
|
|
26579
27346
|
init_worktree();
|
|
@@ -26976,7 +27743,7 @@ function buildPathRefAttachment(input) {
|
|
|
26976
27743
|
init_stage_files();
|
|
26977
27744
|
|
|
26978
27745
|
// src/composer/pasted-text.ts
|
|
26979
|
-
var
|
|
27746
|
+
var import_node_crypto13 = require("crypto");
|
|
26980
27747
|
var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
26981
27748
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
26982
27749
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
@@ -27003,7 +27770,7 @@ function nextPastedTextName(existing) {
|
|
|
27003
27770
|
}
|
|
27004
27771
|
function buildPastedTextAttachment(text7, opts) {
|
|
27005
27772
|
return {
|
|
27006
|
-
id: opts?.id ?? (0,
|
|
27773
|
+
id: opts?.id ?? (0, import_node_crypto13.randomUUID)(),
|
|
27007
27774
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
27008
27775
|
kind: "file",
|
|
27009
27776
|
path: opts?.path,
|
|
@@ -27034,7 +27801,7 @@ function planQuestionsSignature(questions) {
|
|
|
27034
27801
|
return `${q.question}${opts}`;
|
|
27035
27802
|
}).join("");
|
|
27036
27803
|
}
|
|
27037
|
-
function
|
|
27804
|
+
function asRecord10(v) {
|
|
27038
27805
|
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
27039
27806
|
}
|
|
27040
27807
|
function parseOptions(raw) {
|
|
@@ -27045,7 +27812,7 @@ function parseOptions(raw) {
|
|
|
27045
27812
|
out.push({ label: item.trim() });
|
|
27046
27813
|
continue;
|
|
27047
27814
|
}
|
|
27048
|
-
const o =
|
|
27815
|
+
const o = asRecord10(item);
|
|
27049
27816
|
if (!o) continue;
|
|
27050
27817
|
const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
|
|
27051
27818
|
if (!label) continue;
|
|
@@ -27055,7 +27822,7 @@ function parseOptions(raw) {
|
|
|
27055
27822
|
return out;
|
|
27056
27823
|
}
|
|
27057
27824
|
function parseOneQuestion(raw) {
|
|
27058
|
-
const o =
|
|
27825
|
+
const o = asRecord10(raw);
|
|
27059
27826
|
if (!o) return null;
|
|
27060
27827
|
const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
|
|
27061
27828
|
if (!question) return null;
|
|
@@ -27066,7 +27833,7 @@ function parseOneQuestion(raw) {
|
|
|
27066
27833
|
return { question, header, multiSelect, options };
|
|
27067
27834
|
}
|
|
27068
27835
|
function parsePlanQuestionsInput(input) {
|
|
27069
|
-
const root =
|
|
27836
|
+
const root = asRecord10(input);
|
|
27070
27837
|
if (!root) return [];
|
|
27071
27838
|
const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
|
|
27072
27839
|
const out = [];
|
|
@@ -27157,7 +27924,7 @@ init_orphan_cleanup();
|
|
|
27157
27924
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
27158
27925
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
27159
27926
|
var import_zod9 = require("zod");
|
|
27160
|
-
var
|
|
27927
|
+
var import_node_crypto15 = require("crypto");
|
|
27161
27928
|
var import_node_path53 = require("path");
|
|
27162
27929
|
init_orchestrator();
|
|
27163
27930
|
init_worktree();
|
|
@@ -28884,7 +29651,7 @@ init_worktree();
|
|
|
28884
29651
|
init_paths();
|
|
28885
29652
|
|
|
28886
29653
|
// src/board/board-pins.ts
|
|
28887
|
-
var
|
|
29654
|
+
var import_node_crypto14 = require("crypto");
|
|
28888
29655
|
var import_node_fs55 = require("fs");
|
|
28889
29656
|
var import_node_path51 = require("path");
|
|
28890
29657
|
init_paths();
|
|
@@ -28916,7 +29683,7 @@ function addBoardPin(input) {
|
|
|
28916
29683
|
const ref = input.ref.trim();
|
|
28917
29684
|
if (!ref) throw new Error("Board item needs a ref");
|
|
28918
29685
|
const pin = {
|
|
28919
|
-
id: (0,
|
|
29686
|
+
id: (0, import_node_crypto14.randomUUID)(),
|
|
28920
29687
|
kind: input.kind,
|
|
28921
29688
|
ref,
|
|
28922
29689
|
repoPath: input.repoPath.trim(),
|
|
@@ -29750,7 +30517,7 @@ async function startMcpServer() {
|
|
|
29750
30517
|
const ident = issue?.identifier ?? ref.trim();
|
|
29751
30518
|
const attachments = issue ? [
|
|
29752
30519
|
{
|
|
29753
|
-
id: (0,
|
|
30520
|
+
id: (0, import_node_crypto15.randomUUID)(),
|
|
29754
30521
|
name: issue.identifier,
|
|
29755
30522
|
kind: "issue",
|
|
29756
30523
|
content: [
|
|
@@ -30705,7 +31472,7 @@ init_orch_mcp_isolation();
|
|
|
30705
31472
|
init_workspaces2();
|
|
30706
31473
|
|
|
30707
31474
|
// src/slack/oauth.ts
|
|
30708
|
-
var
|
|
31475
|
+
var import_node_crypto16 = require("crypto");
|
|
30709
31476
|
init_app_settings();
|
|
30710
31477
|
init_workspaces2();
|
|
30711
31478
|
|
|
@@ -30929,7 +31696,7 @@ async function startSlackOAuth(opts) {
|
|
|
30929
31696
|
throw new SlackOAuthCancelledError();
|
|
30930
31697
|
}
|
|
30931
31698
|
const { clientId } = slackOAuthCredentials();
|
|
30932
|
-
const state = (0,
|
|
31699
|
+
const state = (0, import_node_crypto16.randomBytes)(16).toString("hex");
|
|
30933
31700
|
const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
|
|
30934
31701
|
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
30935
31702
|
const pollIntervalMs = opts?.pollIntervalMs ?? 400;
|
|
@@ -31211,7 +31978,7 @@ init_workspaces2();
|
|
|
31211
31978
|
init_reply_target();
|
|
31212
31979
|
|
|
31213
31980
|
// src/slack/relay-protocol.ts
|
|
31214
|
-
var
|
|
31981
|
+
var import_node_crypto17 = require("crypto");
|
|
31215
31982
|
var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
|
|
31216
31983
|
var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
|
|
31217
31984
|
function slackRelaySessionKey(teamId, userId, deviceId) {
|
|
@@ -31221,7 +31988,7 @@ function slackRelayUserKey(teamId, userId) {
|
|
|
31221
31988
|
return `${teamId.trim()}:${userId.trim()}`;
|
|
31222
31989
|
}
|
|
31223
31990
|
function newSlackRelayEventId() {
|
|
31224
|
-
return (0,
|
|
31991
|
+
return (0, import_node_crypto17.randomBytes)(12).toString("hex");
|
|
31225
31992
|
}
|
|
31226
31993
|
function parseSlackRelayClientMessage(raw) {
|
|
31227
31994
|
try {
|
|
@@ -31294,7 +32061,7 @@ function parseSlackRelayServerMessage(raw) {
|
|
|
31294
32061
|
|
|
31295
32062
|
// src/slack/public-dns.ts
|
|
31296
32063
|
var import_node_dns = require("dns");
|
|
31297
|
-
var
|
|
32064
|
+
var import_node_http3 = require("http");
|
|
31298
32065
|
var import_node_https = require("https");
|
|
31299
32066
|
var PUBLIC_DNS = ["1.1.1.1", "8.8.8.8"];
|
|
31300
32067
|
function finishLookup(options, callback, address, family) {
|
|
@@ -32338,7 +33105,7 @@ var SlackRelayHub = class {
|
|
|
32338
33105
|
};
|
|
32339
33106
|
|
|
32340
33107
|
// src/slack/relay-server.ts
|
|
32341
|
-
var
|
|
33108
|
+
var import_node_http4 = require("http");
|
|
32342
33109
|
var import_ws3 = require("ws");
|
|
32343
33110
|
|
|
32344
33111
|
// src/slack/relay-static.ts
|
|
@@ -32510,7 +33277,7 @@ async function startSlackRelayServer(opts) {
|
|
|
32510
33277
|
sendJson(res, 200, { ok: true, ...value.payload });
|
|
32511
33278
|
return true;
|
|
32512
33279
|
};
|
|
32513
|
-
const httpServer = (0,
|
|
33280
|
+
const httpServer = (0, import_node_http4.createServer)((req, res) => {
|
|
32514
33281
|
void (async () => {
|
|
32515
33282
|
const reqUrl = req.url || "/";
|
|
32516
33283
|
if (await handleCallback(reqUrl, res)) return;
|
|
@@ -32647,10 +33414,18 @@ init_outbound_watch();
|
|
|
32647
33414
|
0 && (module.exports = {
|
|
32648
33415
|
ABLETIME_MCP_PATH,
|
|
32649
33416
|
ABLETIME_MCP_PM_PATH,
|
|
33417
|
+
ABLETIME_OAUTH_CANCELLED,
|
|
33418
|
+
ABLETIME_OAUTH_PORT,
|
|
33419
|
+
ABLETIME_OAUTH_REDIRECT,
|
|
33420
|
+
ABLETIME_OAUTH_RESOURCE,
|
|
33421
|
+
ABLETIME_ORG_KEY_PREFIX,
|
|
33422
|
+
ABLETIME_PAT_PREFIX,
|
|
32650
33423
|
ABLETIME_PM_TOOL_NAMES,
|
|
32651
33424
|
AGENT_GIT_ACTIONS,
|
|
32652
33425
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
32653
33426
|
ATTACHMENTS_DIR,
|
|
33427
|
+
AbleTimeOAuthCancelledError,
|
|
33428
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
32654
33429
|
BAKED_SLACK_RELAY_URL,
|
|
32655
33430
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
32656
33431
|
BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
|
|
@@ -32739,7 +33514,11 @@ init_outbound_watch();
|
|
|
32739
33514
|
WORKTREE_GITHUB_MCP_TOOLS,
|
|
32740
33515
|
WORKTREE_LINEAR_MCP_TOOLS,
|
|
32741
33516
|
WORKTREE_MCP_TOOLS,
|
|
33517
|
+
ableTimeCredentialKind,
|
|
32742
33518
|
ableTimeDependencyTarget,
|
|
33519
|
+
ableTimeOAuthAuthorizeUrl,
|
|
33520
|
+
ableTimeOAuthClientId,
|
|
33521
|
+
ableTimeOAuthResource,
|
|
32743
33522
|
abletimeMcpRequest,
|
|
32744
33523
|
abletimeMcpUrl,
|
|
32745
33524
|
abletimeToolUsesPm,
|
|
@@ -32770,6 +33549,8 @@ init_outbound_watch();
|
|
|
32770
33549
|
applyThreadIntoMain,
|
|
32771
33550
|
applyTurnUsage,
|
|
32772
33551
|
armSchedules,
|
|
33552
|
+
assertAbleTimeCredential,
|
|
33553
|
+
assertAbleTimeMcpCredential,
|
|
32773
33554
|
assertOrchestratorCapableAgent,
|
|
32774
33555
|
attachmentFromAbsolutePath,
|
|
32775
33556
|
attachmentsFromBuffers,
|
|
@@ -32849,6 +33630,7 @@ init_outbound_watch();
|
|
|
32849
33630
|
countCacheControlBlocks,
|
|
32850
33631
|
countUnpushedVsOrigin,
|
|
32851
33632
|
cowboyModeEnabled,
|
|
33633
|
+
createAbleTimePkce,
|
|
32852
33634
|
createAbleTimeTask,
|
|
32853
33635
|
createChatTab,
|
|
32854
33636
|
createEmptyThread,
|
|
@@ -32888,6 +33670,7 @@ init_outbound_watch();
|
|
|
32888
33670
|
encodeBrightsyTarget,
|
|
32889
33671
|
enrichPathWithNpmGlobalBin,
|
|
32890
33672
|
enrichWorkspacesWithGithub,
|
|
33673
|
+
ensureAbleTimeAccessToken,
|
|
32891
33674
|
ensureAbleTimeTask,
|
|
32892
33675
|
ensureAgentPath,
|
|
32893
33676
|
ensureBrightsyLocalConfigFresh,
|
|
@@ -33034,6 +33817,7 @@ init_outbound_watch();
|
|
|
33034
33817
|
groupHomeBoardWorktrees,
|
|
33035
33818
|
handleSlackInbound,
|
|
33036
33819
|
harnessEnvKey,
|
|
33820
|
+
hasBakedAbleTimeOAuth,
|
|
33037
33821
|
hasBakedLinearOAuth,
|
|
33038
33822
|
hasBakedSlackOAuth,
|
|
33039
33823
|
hasConductorHook,
|
|
@@ -33058,6 +33842,7 @@ init_outbound_watch();
|
|
|
33058
33842
|
invalidateThreadListCache,
|
|
33059
33843
|
invalidateThreadRecord,
|
|
33060
33844
|
isAbleTimeConnected,
|
|
33845
|
+
isAbleTimeOAuthCancelled,
|
|
33061
33846
|
isAskUserToolName,
|
|
33062
33847
|
isBrightsyConnected,
|
|
33063
33848
|
isBrightsyNdjsonLine,
|
|
@@ -33187,6 +33972,7 @@ init_outbound_watch();
|
|
|
33187
33972
|
nextPastedTextName,
|
|
33188
33973
|
nextThinkingEffort,
|
|
33189
33974
|
nonInteractiveGitProcessEnv,
|
|
33975
|
+
normalizeAbleTimeCredential,
|
|
33190
33976
|
normalizeAbleTimeHost,
|
|
33191
33977
|
normalizeCodeSelection,
|
|
33192
33978
|
normalizeLinearRelationType,
|
|
@@ -33368,6 +34154,7 @@ init_outbound_watch();
|
|
|
33368
34154
|
stackMergeReadiness,
|
|
33369
34155
|
stageAbsolutePathsAsAttachments,
|
|
33370
34156
|
stageBuffersAsAttachments,
|
|
34157
|
+
startAbleTimeOAuth,
|
|
33371
34158
|
startDevServer,
|
|
33372
34159
|
startLinearOAuth,
|
|
33373
34160
|
startMcpServer,
|