@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/mcp/run-stdio.cjs
CHANGED
|
@@ -3424,6 +3424,9 @@ function normalizeVault(raw) {
|
|
|
3424
3424
|
if (typeof raw.abletimeAccessToken === "string" && raw.abletimeAccessToken.trim()) {
|
|
3425
3425
|
out.abletimeAccessToken = raw.abletimeAccessToken.trim();
|
|
3426
3426
|
}
|
|
3427
|
+
if (typeof raw.abletimeRefreshToken === "string" && raw.abletimeRefreshToken.trim()) {
|
|
3428
|
+
out.abletimeRefreshToken = raw.abletimeRefreshToken.trim();
|
|
3429
|
+
}
|
|
3427
3430
|
if (typeof raw.vercelToken === "string" && raw.vercelToken.trim()) {
|
|
3428
3431
|
out.vercelToken = raw.vercelToken.trim();
|
|
3429
3432
|
}
|
|
@@ -3629,6 +3632,7 @@ function emptyPublicIntegrations() {
|
|
|
3629
3632
|
hasSlackAppToken: false,
|
|
3630
3633
|
hasGithubPat: false,
|
|
3631
3634
|
hasAbleTimeToken: false,
|
|
3635
|
+
hasAbleTimeOAuth: false,
|
|
3632
3636
|
hasVercelToken: false,
|
|
3633
3637
|
hasSupabaseToken: false,
|
|
3634
3638
|
hasPosthogToken: false,
|
|
@@ -3653,6 +3657,7 @@ function toPublicAppSettings(settings) {
|
|
|
3653
3657
|
const slackAppToken = integrations.slackAppToken;
|
|
3654
3658
|
const githubPat = integrations.githubPat;
|
|
3655
3659
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
3660
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
3656
3661
|
const vercelToken = integrations.vercelToken;
|
|
3657
3662
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
3658
3663
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -3665,6 +3670,7 @@ function toPublicAppSettings(settings) {
|
|
|
3665
3670
|
delete integrations.slackAppToken;
|
|
3666
3671
|
delete integrations.githubPat;
|
|
3667
3672
|
delete integrations.abletimeAccessToken;
|
|
3673
|
+
delete integrations.abletimeRefreshToken;
|
|
3668
3674
|
delete integrations.vercelToken;
|
|
3669
3675
|
delete integrations.supabaseAccessToken;
|
|
3670
3676
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -3679,7 +3685,8 @@ function toPublicAppSettings(settings) {
|
|
|
3679
3685
|
hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
|
|
3680
3686
|
hasSlackAppToken: Boolean(slackAppToken?.trim()),
|
|
3681
3687
|
hasGithubPat: Boolean(githubPat?.trim()),
|
|
3682
|
-
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim()),
|
|
3688
|
+
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim() || abletimeRefreshToken?.trim()),
|
|
3689
|
+
hasAbleTimeOAuth: Boolean(abletimeRefreshToken?.trim()),
|
|
3683
3690
|
hasVercelToken: Boolean(vercelToken?.trim()),
|
|
3684
3691
|
hasSupabaseToken: Boolean(supabaseAccessToken?.trim()),
|
|
3685
3692
|
hasPosthogToken: Boolean(posthogPersonalApiKey?.trim()),
|
|
@@ -3817,6 +3824,16 @@ function normalizeIntegrations(raw) {
|
|
|
3817
3824
|
const token = source.abletimeAccessToken.trim();
|
|
3818
3825
|
if (token) out.abletimeAccessToken = token;
|
|
3819
3826
|
}
|
|
3827
|
+
if (typeof source.abletimeRefreshToken === "string") {
|
|
3828
|
+
const token = source.abletimeRefreshToken.trim();
|
|
3829
|
+
if (token) out.abletimeRefreshToken = token;
|
|
3830
|
+
}
|
|
3831
|
+
if (typeof source.abletimeTokenExpiresAt === "number" && Number.isFinite(source.abletimeTokenExpiresAt)) {
|
|
3832
|
+
out.abletimeTokenExpiresAt = source.abletimeTokenExpiresAt;
|
|
3833
|
+
} else if (typeof source.abletimeTokenExpiresAt === "string") {
|
|
3834
|
+
const n = Number(source.abletimeTokenExpiresAt);
|
|
3835
|
+
if (Number.isFinite(n)) out.abletimeTokenExpiresAt = n;
|
|
3836
|
+
}
|
|
3820
3837
|
if (typeof source.abletimeHost === "string") {
|
|
3821
3838
|
const host = source.abletimeHost.trim();
|
|
3822
3839
|
if (host) out.abletimeHost = host;
|
|
@@ -4094,7 +4111,7 @@ function readSettingsFile() {
|
|
|
4094
4111
|
}
|
|
4095
4112
|
function diskHoldsSecrets(disk) {
|
|
4096
4113
|
return Boolean(
|
|
4097
|
-
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
|
|
4114
|
+
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
|
|
4098
4115
|
);
|
|
4099
4116
|
}
|
|
4100
4117
|
function mergeVault(disk) {
|
|
@@ -4125,6 +4142,9 @@ function mergeVault(disk) {
|
|
|
4125
4142
|
if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
|
|
4126
4143
|
integrations.abletimeAccessToken = vault.abletimeAccessToken;
|
|
4127
4144
|
}
|
|
4145
|
+
if (vault.abletimeRefreshToken && !integrations.abletimeRefreshToken) {
|
|
4146
|
+
integrations.abletimeRefreshToken = vault.abletimeRefreshToken;
|
|
4147
|
+
}
|
|
4128
4148
|
if (vault.vercelToken && !integrations.vercelToken) {
|
|
4129
4149
|
integrations.vercelToken = vault.vercelToken;
|
|
4130
4150
|
}
|
|
@@ -4149,6 +4169,7 @@ function persistSplit(settings) {
|
|
|
4149
4169
|
const slackAppToken = integrations.slackAppToken;
|
|
4150
4170
|
const githubPat = integrations.githubPat;
|
|
4151
4171
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
4172
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
4152
4173
|
const vercelToken = integrations.vercelToken;
|
|
4153
4174
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
4154
4175
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -4161,6 +4182,7 @@ function persistSplit(settings) {
|
|
|
4161
4182
|
delete integrations.slackAppToken;
|
|
4162
4183
|
delete integrations.githubPat;
|
|
4163
4184
|
delete integrations.abletimeAccessToken;
|
|
4185
|
+
delete integrations.abletimeRefreshToken;
|
|
4164
4186
|
delete integrations.vercelToken;
|
|
4165
4187
|
delete integrations.supabaseAccessToken;
|
|
4166
4188
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -4179,6 +4201,7 @@ function persistSplit(settings) {
|
|
|
4179
4201
|
slackAppToken,
|
|
4180
4202
|
githubPat,
|
|
4181
4203
|
abletimeAccessToken,
|
|
4204
|
+
abletimeRefreshToken,
|
|
4182
4205
|
vercelToken,
|
|
4183
4206
|
supabaseAccessToken,
|
|
4184
4207
|
posthogPersonalApiKey,
|
|
@@ -4365,6 +4388,8 @@ function updateIntegrationsSettings(patch) {
|
|
|
4365
4388
|
if ("abletimeAccessToken" in patch) {
|
|
4366
4389
|
if (patch.abletimeAccessToken == null || patch.abletimeAccessToken.trim() === "") {
|
|
4367
4390
|
delete integrations.abletimeAccessToken;
|
|
4391
|
+
delete integrations.abletimeRefreshToken;
|
|
4392
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
4368
4393
|
delete integrations.abletimeViewerName;
|
|
4369
4394
|
} else {
|
|
4370
4395
|
integrations.abletimeAccessToken = patch.abletimeAccessToken.trim();
|
|
@@ -4715,7 +4740,9 @@ function disconnectLinearConnection() {
|
|
|
4715
4740
|
return saveAppSettings({ ...current, integrations });
|
|
4716
4741
|
}
|
|
4717
4742
|
function isAbleTimeConnected(settings = loadAppSettings()) {
|
|
4718
|
-
return Boolean(
|
|
4743
|
+
return Boolean(
|
|
4744
|
+
settings.integrations.abletimeAccessToken?.trim() || settings.integrations.abletimeRefreshToken?.trim()
|
|
4745
|
+
);
|
|
4719
4746
|
}
|
|
4720
4747
|
function getAbleTimeAccessToken(settings = loadAppSettings()) {
|
|
4721
4748
|
const token = settings.integrations.abletimeAccessToken?.trim();
|
|
@@ -4728,6 +4755,12 @@ function saveAbleTimeConnection(input) {
|
|
|
4728
4755
|
const current = loadAppSettings();
|
|
4729
4756
|
const integrations = { ...current.integrations };
|
|
4730
4757
|
integrations.abletimeAccessToken = input.accessToken.trim();
|
|
4758
|
+
if (input.refreshToken?.trim()) {
|
|
4759
|
+
integrations.abletimeRefreshToken = input.refreshToken.trim();
|
|
4760
|
+
}
|
|
4761
|
+
if (typeof input.expiresIn === "number" && Number.isFinite(input.expiresIn)) {
|
|
4762
|
+
integrations.abletimeTokenExpiresAt = Date.now() + Math.max(0, input.expiresIn) * 1e3;
|
|
4763
|
+
}
|
|
4731
4764
|
if (input.host?.trim()) {
|
|
4732
4765
|
integrations.abletimeHost = input.host.trim();
|
|
4733
4766
|
}
|
|
@@ -4740,6 +4773,8 @@ function disconnectAbleTimeConnection() {
|
|
|
4740
4773
|
const current = loadAppSettings();
|
|
4741
4774
|
const integrations = { ...current.integrations };
|
|
4742
4775
|
delete integrations.abletimeAccessToken;
|
|
4776
|
+
delete integrations.abletimeRefreshToken;
|
|
4777
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
4743
4778
|
delete integrations.abletimeViewerName;
|
|
4744
4779
|
return saveAppSettings({ ...current, integrations });
|
|
4745
4780
|
}
|
|
@@ -9503,7 +9538,7 @@ function applyAgentEvent(parts, event) {
|
|
|
9503
9538
|
next.push(
|
|
9504
9539
|
withPartTimes({
|
|
9505
9540
|
type: "thinking",
|
|
9506
|
-
text: data,
|
|
9541
|
+
text: clipThinkingForStore(data),
|
|
9507
9542
|
...event.parentId ? { parentId: event.parentId } : {}
|
|
9508
9543
|
})
|
|
9509
9544
|
);
|
|
@@ -14428,6 +14463,9 @@ function childHaltNotice(child, status) {
|
|
|
14428
14463
|
"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."
|
|
14429
14464
|
].join("\n");
|
|
14430
14465
|
}
|
|
14466
|
+
function shouldNotifyParentAfterTurnError(opts) {
|
|
14467
|
+
return !opts.quotaFailoverHandled && !opts.crashContinued;
|
|
14468
|
+
}
|
|
14431
14469
|
function shouldNotifyParentOfChildHalt(opts) {
|
|
14432
14470
|
if (!isIncompleteChildStatus(opts.status)) return false;
|
|
14433
14471
|
if (!opts.child.parentThreadId) return false;
|
|
@@ -15370,6 +15408,645 @@ var init_home_board = __esm({
|
|
|
15370
15408
|
}
|
|
15371
15409
|
});
|
|
15372
15410
|
|
|
15411
|
+
// src/integrations/abletime-oauth.ts
|
|
15412
|
+
var abletime_oauth_exports = {};
|
|
15413
|
+
__export(abletime_oauth_exports, {
|
|
15414
|
+
ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
|
|
15415
|
+
ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
|
|
15416
|
+
ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
|
|
15417
|
+
ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
|
|
15418
|
+
AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
|
|
15419
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
15420
|
+
ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
|
|
15421
|
+
ableTimeOAuthClientId: () => ableTimeOAuthClientId,
|
|
15422
|
+
ableTimeOAuthResource: () => ableTimeOAuthResource,
|
|
15423
|
+
createAbleTimePkce: () => createAbleTimePkce,
|
|
15424
|
+
ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
|
|
15425
|
+
hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
|
|
15426
|
+
isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
|
|
15427
|
+
startAbleTimeOAuth: () => startAbleTimeOAuth
|
|
15428
|
+
});
|
|
15429
|
+
function hasBakedAbleTimeOAuth() {
|
|
15430
|
+
return Boolean(BAKED_ABLETIME_OAUTH_CLIENT_ID.trim());
|
|
15431
|
+
}
|
|
15432
|
+
function ableTimeOAuthClientId() {
|
|
15433
|
+
return process.env.SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID?.trim() || BAKED_ABLETIME_OAUTH_CLIENT_ID.trim();
|
|
15434
|
+
}
|
|
15435
|
+
function ableTimeOAuthResource(host) {
|
|
15436
|
+
return `${normalizeAbleTimeHost(host)}/api/public/v2/mcp`;
|
|
15437
|
+
}
|
|
15438
|
+
function createAbleTimePkce() {
|
|
15439
|
+
const verifier = (0, import_node_crypto9.randomBytes)(32).toString("base64url");
|
|
15440
|
+
const challenge = (0, import_node_crypto9.createHash)("sha256").update(verifier).digest("base64url");
|
|
15441
|
+
return { verifier, challenge };
|
|
15442
|
+
}
|
|
15443
|
+
function ableTimeOAuthAuthorizeUrl(clientId, state, codeChallenge, host) {
|
|
15444
|
+
const base = normalizeAbleTimeHost(host);
|
|
15445
|
+
const params = new URLSearchParams({
|
|
15446
|
+
response_type: "code",
|
|
15447
|
+
client_id: clientId,
|
|
15448
|
+
redirect_uri: ABLETIME_OAUTH_REDIRECT,
|
|
15449
|
+
code_challenge: codeChallenge,
|
|
15450
|
+
code_challenge_method: "S256",
|
|
15451
|
+
state,
|
|
15452
|
+
resource: ableTimeOAuthResource(host)
|
|
15453
|
+
});
|
|
15454
|
+
return `${base}/oauth/authorize?${params.toString()}`;
|
|
15455
|
+
}
|
|
15456
|
+
function htmlPage(title, body) {
|
|
15457
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
|
|
15458
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
15459
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
15460
|
+
}
|
|
15461
|
+
async function exchangeAbleTimeToken(body, host) {
|
|
15462
|
+
const url = `${normalizeAbleTimeHost(host)}/api/oauth/token`;
|
|
15463
|
+
const res = await httpFetch(url, {
|
|
15464
|
+
method: "POST",
|
|
15465
|
+
headers: {
|
|
15466
|
+
Accept: "application/json",
|
|
15467
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
15468
|
+
},
|
|
15469
|
+
body
|
|
15470
|
+
});
|
|
15471
|
+
const data = await res.json().catch(() => ({}));
|
|
15472
|
+
if (!res.ok || data.error || !data.access_token?.trim()) {
|
|
15473
|
+
const detail = data.error_description || data.error || `HTTP ${res.status}`;
|
|
15474
|
+
throw new Error(`AbleTime OAuth token exchange failed: ${detail}`);
|
|
15475
|
+
}
|
|
15476
|
+
return data;
|
|
15477
|
+
}
|
|
15478
|
+
function persistAbleTimeTokens(data, input) {
|
|
15479
|
+
return saveAbleTimeConnection({
|
|
15480
|
+
accessToken: data.access_token.trim(),
|
|
15481
|
+
refreshToken: data.refresh_token?.trim(),
|
|
15482
|
+
expiresIn: data.expires_in,
|
|
15483
|
+
host: input?.host,
|
|
15484
|
+
viewerName: input?.viewerName
|
|
15485
|
+
});
|
|
15486
|
+
}
|
|
15487
|
+
async function refreshAbleTimeAccessToken(refreshToken) {
|
|
15488
|
+
const host = getAbleTimeHost();
|
|
15489
|
+
const body = new URLSearchParams({
|
|
15490
|
+
grant_type: "refresh_token",
|
|
15491
|
+
refresh_token: refreshToken,
|
|
15492
|
+
client_id: ableTimeOAuthClientId(),
|
|
15493
|
+
resource: ableTimeOAuthResource(host)
|
|
15494
|
+
});
|
|
15495
|
+
try {
|
|
15496
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
15497
|
+
persistAbleTimeTokens(data, { host });
|
|
15498
|
+
return data.access_token.trim();
|
|
15499
|
+
} catch (err) {
|
|
15500
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15501
|
+
if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
|
|
15502
|
+
disconnectAbleTimeConnection();
|
|
15503
|
+
throw new Error("AbleTime sign-in expired \u2014 connect again in Account settings");
|
|
15504
|
+
}
|
|
15505
|
+
throw err;
|
|
15506
|
+
}
|
|
15507
|
+
}
|
|
15508
|
+
async function ensureAbleTimeAccessToken() {
|
|
15509
|
+
const settings = loadAppSettings();
|
|
15510
|
+
const access = settings.integrations.abletimeAccessToken?.trim() || "";
|
|
15511
|
+
const refresh = settings.integrations.abletimeRefreshToken?.trim() || "";
|
|
15512
|
+
const expiresAt = settings.integrations.abletimeTokenExpiresAt ?? 0;
|
|
15513
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
|
|
15514
|
+
return access;
|
|
15515
|
+
}
|
|
15516
|
+
if (refresh) {
|
|
15517
|
+
if (!refreshInFlight) {
|
|
15518
|
+
refreshInFlight = refreshAbleTimeAccessToken(refresh).finally(() => {
|
|
15519
|
+
refreshInFlight = null;
|
|
15520
|
+
});
|
|
15521
|
+
}
|
|
15522
|
+
return refreshInFlight;
|
|
15523
|
+
}
|
|
15524
|
+
return access || null;
|
|
15525
|
+
}
|
|
15526
|
+
function isAbleTimeOAuthCancelled(err) {
|
|
15527
|
+
if (err instanceof AbleTimeOAuthCancelledError) return true;
|
|
15528
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15529
|
+
return message.includes(ABLETIME_OAUTH_CANCELLED);
|
|
15530
|
+
}
|
|
15531
|
+
async function startAbleTimeOAuth(opts) {
|
|
15532
|
+
if (opts?.signal?.aborted) {
|
|
15533
|
+
throw new AbleTimeOAuthCancelledError();
|
|
15534
|
+
}
|
|
15535
|
+
const clientId = ableTimeOAuthClientId();
|
|
15536
|
+
if (!clientId) {
|
|
15537
|
+
throw new Error(
|
|
15538
|
+
`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.`
|
|
15539
|
+
);
|
|
15540
|
+
}
|
|
15541
|
+
const host = opts?.host?.trim() || void 0;
|
|
15542
|
+
const state = (0, import_node_crypto9.randomBytes)(16).toString("hex");
|
|
15543
|
+
const pkce = createAbleTimePkce();
|
|
15544
|
+
const authorizeUrl = ableTimeOAuthAuthorizeUrl(clientId, state, pkce.challenge, host);
|
|
15545
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
15546
|
+
const code = await new Promise((resolve, reject) => {
|
|
15547
|
+
let settled = false;
|
|
15548
|
+
const finish = (err, value) => {
|
|
15549
|
+
if (settled) return;
|
|
15550
|
+
settled = true;
|
|
15551
|
+
cleanup();
|
|
15552
|
+
if (err) reject(err);
|
|
15553
|
+
else resolve(value);
|
|
15554
|
+
};
|
|
15555
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
15556
|
+
try {
|
|
15557
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${ABLETIME_OAUTH_PORT}`);
|
|
15558
|
+
if (url.pathname !== "/callback") {
|
|
15559
|
+
res.writeHead(404);
|
|
15560
|
+
res.end("Not found");
|
|
15561
|
+
return;
|
|
15562
|
+
}
|
|
15563
|
+
const err = url.searchParams.get("error");
|
|
15564
|
+
const gotState = url.searchParams.get("state");
|
|
15565
|
+
const gotCode = url.searchParams.get("code");
|
|
15566
|
+
if (err) {
|
|
15567
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
15568
|
+
res.end(htmlPage("AbleTime", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
15569
|
+
finish(new AbleTimeOAuthCancelledError(`AbleTime OAuth: ${err}`));
|
|
15570
|
+
return;
|
|
15571
|
+
}
|
|
15572
|
+
if (gotState !== state || !gotCode) {
|
|
15573
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
15574
|
+
res.end(htmlPage("AbleTime", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
15575
|
+
finish(new Error("AbleTime OAuth callback was invalid"));
|
|
15576
|
+
return;
|
|
15577
|
+
}
|
|
15578
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
15579
|
+
res.end(
|
|
15580
|
+
htmlPage(
|
|
15581
|
+
"AbleTime connected",
|
|
15582
|
+
"<h1>AbleTime connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
15583
|
+
)
|
|
15584
|
+
);
|
|
15585
|
+
finish(void 0, gotCode);
|
|
15586
|
+
} catch (e) {
|
|
15587
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
15588
|
+
}
|
|
15589
|
+
});
|
|
15590
|
+
const timer = setTimeout(() => {
|
|
15591
|
+
finish(new Error("AbleTime sign-in timed out \u2014 try again"));
|
|
15592
|
+
}, timeoutMs);
|
|
15593
|
+
const onAbort = () => finish(new AbleTimeOAuthCancelledError());
|
|
15594
|
+
const cleanup = () => {
|
|
15595
|
+
clearTimeout(timer);
|
|
15596
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
15597
|
+
server.close();
|
|
15598
|
+
};
|
|
15599
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
15600
|
+
if (opts?.signal?.aborted) {
|
|
15601
|
+
onAbort();
|
|
15602
|
+
return;
|
|
15603
|
+
}
|
|
15604
|
+
server.on("error", (e) => {
|
|
15605
|
+
finish(
|
|
15606
|
+
e instanceof Error && e.code === "EADDRINUSE" ? new Error(
|
|
15607
|
+
`Port ${ABLETIME_OAUTH_PORT} is in use. Close whatever is bound there, or paste an AbleTime token instead.`
|
|
15608
|
+
) : e instanceof Error ? e : new Error(String(e))
|
|
15609
|
+
);
|
|
15610
|
+
});
|
|
15611
|
+
server.listen(ABLETIME_OAUTH_PORT, "127.0.0.1", () => {
|
|
15612
|
+
void Promise.resolve(opts?.openUrl?.(authorizeUrl)).catch((e) => {
|
|
15613
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
15614
|
+
});
|
|
15615
|
+
});
|
|
15616
|
+
});
|
|
15617
|
+
const body = new URLSearchParams({
|
|
15618
|
+
grant_type: "authorization_code",
|
|
15619
|
+
code,
|
|
15620
|
+
redirect_uri: ABLETIME_OAUTH_REDIRECT,
|
|
15621
|
+
client_id: clientId,
|
|
15622
|
+
code_verifier: pkce.verifier,
|
|
15623
|
+
resource: ableTimeOAuthResource(host)
|
|
15624
|
+
});
|
|
15625
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
15626
|
+
persistAbleTimeTokens(data, { host });
|
|
15627
|
+
const token = data.access_token.trim();
|
|
15628
|
+
try {
|
|
15629
|
+
const { getAbleTimeOrientation: getAbleTimeOrientation2 } = await Promise.resolve().then(() => (init_abletime(), abletime_exports));
|
|
15630
|
+
const orientation = await getAbleTimeOrientation2({ token, host });
|
|
15631
|
+
return saveAbleTimeConnection({
|
|
15632
|
+
accessToken: token,
|
|
15633
|
+
refreshToken: data.refresh_token?.trim(),
|
|
15634
|
+
expiresIn: data.expires_in,
|
|
15635
|
+
host,
|
|
15636
|
+
viewerName: orientation.viewer.name
|
|
15637
|
+
});
|
|
15638
|
+
} catch {
|
|
15639
|
+
return loadAppSettings();
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15642
|
+
var import_node_crypto9, 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;
|
|
15643
|
+
var init_abletime_oauth = __esm({
|
|
15644
|
+
"src/integrations/abletime-oauth.ts"() {
|
|
15645
|
+
"use strict";
|
|
15646
|
+
import_node_crypto9 = require("crypto");
|
|
15647
|
+
import_node_http = require("http");
|
|
15648
|
+
init_fetch();
|
|
15649
|
+
init_app_settings();
|
|
15650
|
+
init_abletime_mcp();
|
|
15651
|
+
ABLETIME_OAUTH_PORT = 19849;
|
|
15652
|
+
ABLETIME_OAUTH_REDIRECT = `http://127.0.0.1:${ABLETIME_OAUTH_PORT}/callback`;
|
|
15653
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID = "https://www.sideboard.cloud/oauth/abletime-client.json";
|
|
15654
|
+
ABLETIME_OAUTH_RESOURCE = `${DEFAULT_ABLETIME_HOST}/api/public/v2/mcp`;
|
|
15655
|
+
REFRESH_SKEW_MS2 = 5 * 6e4;
|
|
15656
|
+
refreshInFlight = null;
|
|
15657
|
+
ABLETIME_OAUTH_CANCELLED = "AbleTime sign-in cancelled";
|
|
15658
|
+
AbleTimeOAuthCancelledError = class extends Error {
|
|
15659
|
+
constructor(message = ABLETIME_OAUTH_CANCELLED) {
|
|
15660
|
+
super(message);
|
|
15661
|
+
this.name = "AbleTimeOAuthCancelledError";
|
|
15662
|
+
}
|
|
15663
|
+
};
|
|
15664
|
+
}
|
|
15665
|
+
});
|
|
15666
|
+
|
|
15667
|
+
// src/integrations/abletime-rest.ts
|
|
15668
|
+
var abletime_rest_exports = {};
|
|
15669
|
+
__export(abletime_rest_exports, {
|
|
15670
|
+
ABLETIME_REST_PREFIX: () => ABLETIME_REST_PREFIX,
|
|
15671
|
+
ableTimeDocFromText: () => ableTimeDocFromText,
|
|
15672
|
+
abletimeRestUrl: () => abletimeRestUrl,
|
|
15673
|
+
callAbleTimeRestTool: () => callAbleTimeRestTool,
|
|
15674
|
+
looksLikeAbleTimeUlid: () => looksLikeAbleTimeUlid,
|
|
15675
|
+
textFromAbleTimeDoc: () => textFromAbleTimeDoc
|
|
15676
|
+
});
|
|
15677
|
+
function asRecord4(value) {
|
|
15678
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15679
|
+
}
|
|
15680
|
+
function asString(value) {
|
|
15681
|
+
return typeof value === "string" ? value.trim() : "";
|
|
15682
|
+
}
|
|
15683
|
+
function asList(value) {
|
|
15684
|
+
if (Array.isArray(value)) return value;
|
|
15685
|
+
const record = asRecord4(value);
|
|
15686
|
+
if (!record) return [];
|
|
15687
|
+
for (const key of ["data", "tasks", "projects", "users", "items", "results"]) {
|
|
15688
|
+
if (Array.isArray(record[key])) return record[key];
|
|
15689
|
+
}
|
|
15690
|
+
return [];
|
|
15691
|
+
}
|
|
15692
|
+
function ableTimeDocFromText(text7) {
|
|
15693
|
+
const trimmed = text7.trim();
|
|
15694
|
+
if (!trimmed) return { type: "doc", content: [] };
|
|
15695
|
+
return {
|
|
15696
|
+
type: "doc",
|
|
15697
|
+
content: [{ type: "paragraph", content: [{ type: "text", text: trimmed }] }]
|
|
15698
|
+
};
|
|
15699
|
+
}
|
|
15700
|
+
function textFromAbleTimeDoc(value) {
|
|
15701
|
+
if (typeof value === "string") return value.trim();
|
|
15702
|
+
const record = asRecord4(value);
|
|
15703
|
+
if (!record) return "";
|
|
15704
|
+
if (typeof record.text === "string" && record.text.trim()) return record.text.trim();
|
|
15705
|
+
const chunks = [];
|
|
15706
|
+
const walk = (node) => {
|
|
15707
|
+
const rec = asRecord4(node);
|
|
15708
|
+
if (!rec) return;
|
|
15709
|
+
if (typeof rec.text === "string" && rec.text) chunks.push(rec.text);
|
|
15710
|
+
if (Array.isArray(rec.content)) rec.content.forEach(walk);
|
|
15711
|
+
};
|
|
15712
|
+
walk(record);
|
|
15713
|
+
return chunks.join("").trim();
|
|
15714
|
+
}
|
|
15715
|
+
function abletimeRestUrl(path, host, query) {
|
|
15716
|
+
const base = `${normalizeAbleTimeHost(host)}${ABLETIME_REST_PREFIX}`;
|
|
15717
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
15718
|
+
const url = new URL(`${base}${suffix}`);
|
|
15719
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
15720
|
+
if (value) url.searchParams.set(key, value);
|
|
15721
|
+
}
|
|
15722
|
+
return url.href;
|
|
15723
|
+
}
|
|
15724
|
+
function looksLikeAbleTimeUlid(id) {
|
|
15725
|
+
return ULID_RE.test(id.trim());
|
|
15726
|
+
}
|
|
15727
|
+
async function abletimeRestRequest(path, opts = {}) {
|
|
15728
|
+
const raw = opts.token ?? getAbleTimeAccessToken();
|
|
15729
|
+
const token = normalizeAbleTimeCredential(raw ?? "");
|
|
15730
|
+
if (!token) {
|
|
15731
|
+
throw new Error("AbleTime is not connected \u2014 paste an API key or personal access token in Account settings");
|
|
15732
|
+
}
|
|
15733
|
+
const host = opts.host ?? (opts.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost());
|
|
15734
|
+
const url = abletimeRestUrl(path, host, opts.query);
|
|
15735
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
15736
|
+
const res = await httpFetch(url, {
|
|
15737
|
+
method,
|
|
15738
|
+
headers: {
|
|
15739
|
+
Accept: "application/json",
|
|
15740
|
+
Authorization: `Bearer ${token}`,
|
|
15741
|
+
...opts.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
15742
|
+
},
|
|
15743
|
+
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
|
|
15744
|
+
});
|
|
15745
|
+
const text7 = await res.text().catch(() => "");
|
|
15746
|
+
if (!res.ok) {
|
|
15747
|
+
throw new Error(
|
|
15748
|
+
rewriteAbleTimeError(
|
|
15749
|
+
`AbleTime API error ${res.status}${text7 ? `: ${text7.slice(0, 240)}` : ""}`
|
|
15750
|
+
)
|
|
15751
|
+
);
|
|
15752
|
+
}
|
|
15753
|
+
if (!text7.trim()) return null;
|
|
15754
|
+
try {
|
|
15755
|
+
return JSON.parse(text7);
|
|
15756
|
+
} catch {
|
|
15757
|
+
throw new Error(`AbleTime API returned a non-JSON response: ${text7.slice(0, 200)}`);
|
|
15758
|
+
}
|
|
15759
|
+
}
|
|
15760
|
+
async function restPages(path, opts, maxPages = 20) {
|
|
15761
|
+
const items = [];
|
|
15762
|
+
let cursor;
|
|
15763
|
+
for (let i = 0; i < maxPages; i += 1) {
|
|
15764
|
+
const raw = await abletimeRestRequest(path, {
|
|
15765
|
+
...opts,
|
|
15766
|
+
query: { ...opts.query, ...cursor ? { cursor } : {} }
|
|
15767
|
+
});
|
|
15768
|
+
items.push(...asList(raw));
|
|
15769
|
+
const page = asRecord4(asRecord4(raw)?.page);
|
|
15770
|
+
const next = page ? asString(page.nextCursor) : "";
|
|
15771
|
+
if (!next) break;
|
|
15772
|
+
cursor = next;
|
|
15773
|
+
}
|
|
15774
|
+
return items;
|
|
15775
|
+
}
|
|
15776
|
+
function userDisplayName(raw) {
|
|
15777
|
+
const rec = asRecord4(raw);
|
|
15778
|
+
if (!rec) return null;
|
|
15779
|
+
const id = asString(rec.userId) || asString(rec.id);
|
|
15780
|
+
const name = [asString(rec.firstName), asString(rec.lastName)].filter(Boolean).join(" ") || asString(rec.username) || asString(rec.email) || asString(rec.name);
|
|
15781
|
+
if (!name) return null;
|
|
15782
|
+
return { id: id || void 0, name };
|
|
15783
|
+
}
|
|
15784
|
+
function restTaskToRaw(raw, users) {
|
|
15785
|
+
const rec = asRecord4(raw);
|
|
15786
|
+
if (!rec) return null;
|
|
15787
|
+
const id = asString(rec.timeflowTaskId) || asString(rec.taskId) || asString(rec.id);
|
|
15788
|
+
const identifier = asString(rec.taskRef) || asString(rec.reference) || id;
|
|
15789
|
+
const title = asString(rec.title);
|
|
15790
|
+
if (!id && !identifier && !title) return null;
|
|
15791
|
+
const assigned = asString(rec.assignedUserId);
|
|
15792
|
+
const assignee = assigned ? users.get(assigned) : void 0;
|
|
15793
|
+
const comments = asList(rec.comments).map((item) => {
|
|
15794
|
+
const comment = asRecord4(item) ?? {};
|
|
15795
|
+
const user = userDisplayName(comment);
|
|
15796
|
+
return {
|
|
15797
|
+
id: asString(comment.commentId) || asString(comment.id),
|
|
15798
|
+
body: textFromAbleTimeDoc(comment.content) || asString(comment.contentSanitized) || asString(comment.body),
|
|
15799
|
+
createdAt: asString(comment.createdAt) || asString(comment.lastUpdate),
|
|
15800
|
+
user: user?.name || asString(comment.username)
|
|
15801
|
+
};
|
|
15802
|
+
});
|
|
15803
|
+
return {
|
|
15804
|
+
id,
|
|
15805
|
+
taskId: id,
|
|
15806
|
+
timeflowTaskId: id,
|
|
15807
|
+
reference: identifier,
|
|
15808
|
+
taskRef: identifier,
|
|
15809
|
+
title: title || identifier,
|
|
15810
|
+
state: asString(rec.taskState) || asString(rec.state),
|
|
15811
|
+
description: textFromAbleTimeDoc(rec.description) || asString(rec.descriptionSanitized),
|
|
15812
|
+
projectId: asString(rec.projectId),
|
|
15813
|
+
categoryId: asString(rec.projectCategoryId) || asString(rec.categoryId),
|
|
15814
|
+
tags: rec.tags,
|
|
15815
|
+
comments,
|
|
15816
|
+
createdAt: asString(rec.dateCreated) || asString(rec.createdAt),
|
|
15817
|
+
updatedAt: asString(rec.lastUpdate) || asString(rec.updatedAt),
|
|
15818
|
+
...assignee ? { assignee } : {}
|
|
15819
|
+
};
|
|
15820
|
+
}
|
|
15821
|
+
function restProjectToRaw(raw) {
|
|
15822
|
+
const rec = asRecord4(raw);
|
|
15823
|
+
if (!rec) return null;
|
|
15824
|
+
const id = asString(rec.projectId) || asString(rec.id);
|
|
15825
|
+
const name = asString(rec.projectName) || asString(rec.name);
|
|
15826
|
+
if (!id && !name) return null;
|
|
15827
|
+
const categories = asList(rec.categories).map((item) => {
|
|
15828
|
+
const cat = asRecord4(item) ?? {};
|
|
15829
|
+
return {
|
|
15830
|
+
id: asString(cat.projectCategoryId) || asString(cat.categoryId) || asString(cat.id),
|
|
15831
|
+
name: asString(cat.categoryName) || asString(cat.name)
|
|
15832
|
+
};
|
|
15833
|
+
});
|
|
15834
|
+
return { id, name, projectId: id, projectName: name, categories };
|
|
15835
|
+
}
|
|
15836
|
+
async function listUsers(opts) {
|
|
15837
|
+
const users = /* @__PURE__ */ new Map();
|
|
15838
|
+
for (const item of await restPages("/users", opts).catch(() => [])) {
|
|
15839
|
+
const user = userDisplayName(item);
|
|
15840
|
+
const rec = asRecord4(item);
|
|
15841
|
+
const id = user?.id || (rec ? asString(rec.userId) : "");
|
|
15842
|
+
if (user && id) users.set(id, user);
|
|
15843
|
+
}
|
|
15844
|
+
return users;
|
|
15845
|
+
}
|
|
15846
|
+
async function listProjectsRaw(opts) {
|
|
15847
|
+
const items = await restPages("/projects", { ...opts, query: { expand: "categories" } });
|
|
15848
|
+
return items.map(restProjectToRaw).filter((item) => Boolean(item));
|
|
15849
|
+
}
|
|
15850
|
+
async function listTasksRaw(opts) {
|
|
15851
|
+
const [users, items] = await Promise.all([
|
|
15852
|
+
listUsers(opts),
|
|
15853
|
+
restPages("/tasks", {
|
|
15854
|
+
...opts,
|
|
15855
|
+
query: {
|
|
15856
|
+
...opts.projectId ? { projectId: opts.projectId } : {}
|
|
15857
|
+
}
|
|
15858
|
+
})
|
|
15859
|
+
]);
|
|
15860
|
+
let tasks = items.map((item) => restTaskToRaw(item, users)).filter((item) => Boolean(item));
|
|
15861
|
+
const needle = opts.query?.trim().toLowerCase();
|
|
15862
|
+
if (needle) {
|
|
15863
|
+
tasks = tasks.filter((task) => {
|
|
15864
|
+
const hay = [task.title, task.reference, task.taskRef, task.id].map((value) => String(value ?? "").toLowerCase()).join(" ");
|
|
15865
|
+
const tags = Array.isArray(task.tags) ? task.tags.join(" ").toLowerCase() : "";
|
|
15866
|
+
return hay.includes(needle) || tags.includes(needle);
|
|
15867
|
+
});
|
|
15868
|
+
}
|
|
15869
|
+
return tasks;
|
|
15870
|
+
}
|
|
15871
|
+
async function resolveTaskId(id, opts) {
|
|
15872
|
+
const wanted = id.trim();
|
|
15873
|
+
if (!wanted) throw new Error("AbleTime task id is required");
|
|
15874
|
+
if (looksLikeAbleTimeUlid(wanted)) return wanted;
|
|
15875
|
+
const tasks = await listTasksRaw(opts);
|
|
15876
|
+
const match = tasks.find((task) => {
|
|
15877
|
+
const ref = String(task.reference ?? task.taskRef ?? "").toLowerCase();
|
|
15878
|
+
const taskId = String(task.id ?? "").toLowerCase();
|
|
15879
|
+
return ref === wanted.toLowerCase() || taskId === wanted.toLowerCase();
|
|
15880
|
+
});
|
|
15881
|
+
if (!match?.id) throw new Error(`AbleTime task not found: ${id}`);
|
|
15882
|
+
return String(match.id);
|
|
15883
|
+
}
|
|
15884
|
+
async function getTaskRaw(id, opts) {
|
|
15885
|
+
const taskId = await resolveTaskId(id, opts);
|
|
15886
|
+
const [users, raw] = await Promise.all([
|
|
15887
|
+
listUsers(opts),
|
|
15888
|
+
abletimeRestRequest(`/tasks/${encodeURIComponent(taskId)}`, {
|
|
15889
|
+
...opts,
|
|
15890
|
+
query: { expand: "comments" }
|
|
15891
|
+
})
|
|
15892
|
+
]);
|
|
15893
|
+
const task = restTaskToRaw(raw, users);
|
|
15894
|
+
if (!task) throw new Error(`AbleTime task not found: ${id}`);
|
|
15895
|
+
return task;
|
|
15896
|
+
}
|
|
15897
|
+
function firstStringArg(args, keys) {
|
|
15898
|
+
for (const key of keys) {
|
|
15899
|
+
const value = asString(args[key]);
|
|
15900
|
+
if (value) return value;
|
|
15901
|
+
}
|
|
15902
|
+
return "";
|
|
15903
|
+
}
|
|
15904
|
+
async function callAbleTimeRestTool(name, args = {}, opts) {
|
|
15905
|
+
const token = normalizeAbleTimeCredential(opts?.token ?? getAbleTimeAccessToken() ?? "");
|
|
15906
|
+
if (ableTimeCredentialKind(token) !== "org") {
|
|
15907
|
+
throw new Error("AbleTime REST transport is for organization API keys (atk_\u2026)");
|
|
15908
|
+
}
|
|
15909
|
+
const requestOpts = { ...opts, token };
|
|
15910
|
+
switch (name) {
|
|
15911
|
+
case "orientation": {
|
|
15912
|
+
const [users, projects, tasks] = await Promise.all([
|
|
15913
|
+
listUsers(requestOpts),
|
|
15914
|
+
listProjectsRaw(requestOpts),
|
|
15915
|
+
listTasksRaw(requestOpts)
|
|
15916
|
+
]);
|
|
15917
|
+
const viewer = [...users.values()].find((user) => user.name) ?? [...users.values()][0] ?? { name: "AbleTime" };
|
|
15918
|
+
return { viewer, projects, tasks };
|
|
15919
|
+
}
|
|
15920
|
+
case "list_projects":
|
|
15921
|
+
return { data: await listProjectsRaw(requestOpts) };
|
|
15922
|
+
case "list_users":
|
|
15923
|
+
return { data: [...(await listUsers(requestOpts)).values()] };
|
|
15924
|
+
case "list_tasks":
|
|
15925
|
+
return {
|
|
15926
|
+
data: await listTasksRaw({
|
|
15927
|
+
...requestOpts,
|
|
15928
|
+
projectId: firstStringArg(args, ["project_id", "projectId", "project"])
|
|
15929
|
+
})
|
|
15930
|
+
};
|
|
15931
|
+
case "search_tasks":
|
|
15932
|
+
return {
|
|
15933
|
+
data: await listTasksRaw({
|
|
15934
|
+
...requestOpts,
|
|
15935
|
+
query: firstStringArg(args, ["query", "q"])
|
|
15936
|
+
})
|
|
15937
|
+
};
|
|
15938
|
+
case "get_task":
|
|
15939
|
+
return await getTaskRaw(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
15940
|
+
case "create_task": {
|
|
15941
|
+
const title = firstStringArg(args, ["title"]);
|
|
15942
|
+
if (!title) throw new Error("AbleTime task title is required");
|
|
15943
|
+
const projectId = firstStringArg(args, ["project_id", "projectId", "project"]);
|
|
15944
|
+
if (!projectId) throw new Error("AbleTime project is required");
|
|
15945
|
+
const projects = await listProjectsRaw(requestOpts);
|
|
15946
|
+
const project = projects.find(
|
|
15947
|
+
(item) => String(item.id).toLowerCase() === projectId.toLowerCase() || String(item.name).toLowerCase() === projectId.toLowerCase()
|
|
15948
|
+
) ?? projects[0];
|
|
15949
|
+
if (!project) throw new Error("AbleTime has no projects to create a task in");
|
|
15950
|
+
const categories = asList(project.categories);
|
|
15951
|
+
const wantedCategory = firstStringArg(args, ["category_id", "categoryId", "category"]);
|
|
15952
|
+
const category = categories.map((item) => asRecord4(item)).find((item) => {
|
|
15953
|
+
if (!item || !wantedCategory) return false;
|
|
15954
|
+
return asString(item.id).toLowerCase() === wantedCategory.toLowerCase() || asString(item.name).toLowerCase() === wantedCategory.toLowerCase();
|
|
15955
|
+
}) ?? asRecord4(categories[0]);
|
|
15956
|
+
const created = await abletimeRestRequest("/tasks", {
|
|
15957
|
+
...requestOpts,
|
|
15958
|
+
method: "POST",
|
|
15959
|
+
body: {
|
|
15960
|
+
projectId: project.id,
|
|
15961
|
+
projectCategoryId: category ? asString(category.id) : void 0,
|
|
15962
|
+
title
|
|
15963
|
+
}
|
|
15964
|
+
});
|
|
15965
|
+
const state = firstStringArg(args, ["state"]);
|
|
15966
|
+
const createdId = asString(asRecord4(created)?.timeflowTaskId);
|
|
15967
|
+
const description = asString(args.description);
|
|
15968
|
+
if (createdId && description) {
|
|
15969
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}`, {
|
|
15970
|
+
...requestOpts,
|
|
15971
|
+
method: "PATCH",
|
|
15972
|
+
body: { description: ableTimeDocFromText(description) }
|
|
15973
|
+
}).catch(() => void 0);
|
|
15974
|
+
}
|
|
15975
|
+
if (createdId && state && state !== "todo") {
|
|
15976
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}/state`, {
|
|
15977
|
+
...requestOpts,
|
|
15978
|
+
method: "POST",
|
|
15979
|
+
body: { taskState: state }
|
|
15980
|
+
}).catch(() => void 0);
|
|
15981
|
+
}
|
|
15982
|
+
return createdId ? await getTaskRaw(createdId, requestOpts) : restTaskToRaw(created, await listUsers(requestOpts));
|
|
15983
|
+
}
|
|
15984
|
+
case "update_task": {
|
|
15985
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
15986
|
+
const patch = {};
|
|
15987
|
+
const title = firstStringArg(args, ["title"]);
|
|
15988
|
+
if (title) patch.title = title;
|
|
15989
|
+
if (args.description !== void 0) {
|
|
15990
|
+
patch.description = args.description == null || args.description === "" ? null : ableTimeDocFromText(String(args.description));
|
|
15991
|
+
}
|
|
15992
|
+
if (args.tags !== void 0 || args.labels !== void 0) {
|
|
15993
|
+
patch.tags = args.tags ?? args.labels ?? args.tag_names;
|
|
15994
|
+
}
|
|
15995
|
+
if (Object.keys(patch).length > 0) {
|
|
15996
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}`, {
|
|
15997
|
+
...requestOpts,
|
|
15998
|
+
method: "PATCH",
|
|
15999
|
+
body: patch
|
|
16000
|
+
});
|
|
16001
|
+
}
|
|
16002
|
+
return await getTaskRaw(id, requestOpts);
|
|
16003
|
+
}
|
|
16004
|
+
case "set_task_state": {
|
|
16005
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
16006
|
+
const state = firstStringArg(args, ["state", "taskState"]);
|
|
16007
|
+
if (!state) throw new Error("AbleTime task state is required");
|
|
16008
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/state`, {
|
|
16009
|
+
...requestOpts,
|
|
16010
|
+
method: "POST",
|
|
16011
|
+
body: { taskState: state }
|
|
16012
|
+
});
|
|
16013
|
+
return await getTaskRaw(id, requestOpts);
|
|
16014
|
+
}
|
|
16015
|
+
case "create_comment": {
|
|
16016
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
16017
|
+
const body = firstStringArg(args, ["body", "comment", "text"]);
|
|
16018
|
+
if (!body) throw new Error("AbleTime comment body is required");
|
|
16019
|
+
const created = await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/comments`, {
|
|
16020
|
+
...requestOpts,
|
|
16021
|
+
method: "POST",
|
|
16022
|
+
body: { content: ableTimeDocFromText(body) }
|
|
16023
|
+
});
|
|
16024
|
+
const rec = asRecord4(created) ?? {};
|
|
16025
|
+
return {
|
|
16026
|
+
id: asString(rec.commentId) || void 0,
|
|
16027
|
+
body: textFromAbleTimeDoc(rec.content) || body
|
|
16028
|
+
};
|
|
16029
|
+
}
|
|
16030
|
+
case "set_task_dependency":
|
|
16031
|
+
throw new Error(
|
|
16032
|
+
"AbleTime organization API keys cannot set task dependencies on REST yet \u2014 use a personal access token (apt_\u2026) for MCP project-management tools."
|
|
16033
|
+
);
|
|
16034
|
+
default:
|
|
16035
|
+
throw new Error(`AbleTime organization API keys do not support ${name} on REST`);
|
|
16036
|
+
}
|
|
16037
|
+
}
|
|
16038
|
+
var ABLETIME_REST_PREFIX, ULID_RE;
|
|
16039
|
+
var init_abletime_rest = __esm({
|
|
16040
|
+
"src/integrations/abletime-rest.ts"() {
|
|
16041
|
+
"use strict";
|
|
16042
|
+
init_fetch();
|
|
16043
|
+
init_app_settings();
|
|
16044
|
+
init_abletime_mcp();
|
|
16045
|
+
ABLETIME_REST_PREFIX = "/api/public/v2";
|
|
16046
|
+
ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
16047
|
+
}
|
|
16048
|
+
});
|
|
16049
|
+
|
|
15373
16050
|
// src/integrations/abletime-mcp.ts
|
|
15374
16051
|
function abletimeToolUsesPm(name) {
|
|
15375
16052
|
return ABLETIME_PM_TOOL_NAMES.includes(name);
|
|
@@ -15383,22 +16060,49 @@ function abletimeMcpUrl(host, opts) {
|
|
|
15383
16060
|
const path = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
|
|
15384
16061
|
return `${normalizeAbleTimeHost(host)}${path}`;
|
|
15385
16062
|
}
|
|
16063
|
+
function normalizeAbleTimeCredential(raw) {
|
|
16064
|
+
return raw.trim().replace(/^bearer\s+/i, "").trim();
|
|
16065
|
+
}
|
|
16066
|
+
function ableTimeCredentialKind(raw) {
|
|
16067
|
+
const token = normalizeAbleTimeCredential(raw).toLowerCase();
|
|
16068
|
+
if (token.startsWith(ABLETIME_PAT_PREFIX)) return "pat";
|
|
16069
|
+
if (token.startsWith(ABLETIME_ORG_KEY_PREFIX)) return "org";
|
|
16070
|
+
return "unknown";
|
|
16071
|
+
}
|
|
16072
|
+
function assertAbleTimeMcpCredential(raw) {
|
|
16073
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
16074
|
+
if (!token) {
|
|
16075
|
+
throw new Error("AbleTime personal access token is required");
|
|
16076
|
+
}
|
|
16077
|
+
if (ableTimeCredentialKind(token) === "org") {
|
|
16078
|
+
throw new Error(ORG_KEY_HINT);
|
|
16079
|
+
}
|
|
16080
|
+
return token;
|
|
16081
|
+
}
|
|
16082
|
+
function assertAbleTimeCredential(raw) {
|
|
16083
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
16084
|
+
if (!token) {
|
|
16085
|
+
throw new Error("AbleTime API key or personal access token is required");
|
|
16086
|
+
}
|
|
16087
|
+
return token;
|
|
16088
|
+
}
|
|
15386
16089
|
function rewriteAbleTimeError(message) {
|
|
15387
|
-
if (
|
|
16090
|
+
if (REWRITE_APPLIED.test(message)) return message;
|
|
16091
|
+
if (/INTEGRATION_AGENT_ACCESS_DISABLED/i.test(message)) {
|
|
15388
16092
|
return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
|
|
15389
16093
|
}
|
|
15390
|
-
if (/INTEGRATION_PAT_REQUIRED
|
|
15391
|
-
return `${message} \u2014
|
|
16094
|
+
if (/INTEGRATION_PAT_REQUIRED/i.test(message)) {
|
|
16095
|
+
return `${message} \u2014 ${ORG_KEY_HINT}`;
|
|
15392
16096
|
}
|
|
15393
16097
|
if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
|
|
15394
16098
|
return `${message} \u2014 AbleTime plan does not include API access.`;
|
|
15395
16099
|
}
|
|
15396
16100
|
if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
|
|
15397
|
-
return `${message} \u2014
|
|
16101
|
+
return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
|
|
15398
16102
|
}
|
|
15399
16103
|
return message;
|
|
15400
16104
|
}
|
|
15401
|
-
function
|
|
16105
|
+
function asRecord5(value) {
|
|
15402
16106
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15403
16107
|
}
|
|
15404
16108
|
function parseSseJson(body) {
|
|
@@ -15420,12 +16124,12 @@ function parseSseJson(body) {
|
|
|
15420
16124
|
}
|
|
15421
16125
|
}
|
|
15422
16126
|
function unwrapToolResult(result) {
|
|
15423
|
-
const record =
|
|
16127
|
+
const record = asRecord5(result);
|
|
15424
16128
|
if (!record) return result;
|
|
15425
16129
|
const content = record.content;
|
|
15426
16130
|
if (Array.isArray(content)) {
|
|
15427
16131
|
const texts = content.map((part) => {
|
|
15428
|
-
const item =
|
|
16132
|
+
const item = asRecord5(part);
|
|
15429
16133
|
if (!item) return "";
|
|
15430
16134
|
return typeof item.text === "string" ? item.text : "";
|
|
15431
16135
|
}).filter(Boolean);
|
|
@@ -15447,17 +16151,18 @@ function unwrapToolResult(result) {
|
|
|
15447
16151
|
return result;
|
|
15448
16152
|
}
|
|
15449
16153
|
function textsFromUnknown(value) {
|
|
15450
|
-
const record =
|
|
16154
|
+
const record = asRecord5(value);
|
|
15451
16155
|
if (!record) return "";
|
|
15452
16156
|
if (typeof record.message === "string") return record.message;
|
|
15453
16157
|
if (typeof record.error === "string") return record.error;
|
|
15454
16158
|
return "";
|
|
15455
16159
|
}
|
|
15456
16160
|
async function abletimeMcpRequest(method, params, opts) {
|
|
15457
|
-
const
|
|
15458
|
-
if (!
|
|
16161
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
16162
|
+
if (!rawToken?.trim()) {
|
|
15459
16163
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
15460
16164
|
}
|
|
16165
|
+
const token = assertAbleTimeMcpCredential(rawToken);
|
|
15461
16166
|
const url = abletimeMcpUrl(
|
|
15462
16167
|
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
|
|
15463
16168
|
{ pm: opts?.pm }
|
|
@@ -15493,7 +16198,7 @@ async function abletimeMcpRequest(method, params, opts) {
|
|
|
15493
16198
|
return parseSseJson(body);
|
|
15494
16199
|
}
|
|
15495
16200
|
})();
|
|
15496
|
-
const rpc =
|
|
16201
|
+
const rpc = asRecord5(parsed);
|
|
15497
16202
|
if (rpc?.error) {
|
|
15498
16203
|
const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
|
|
15499
16204
|
throw new Error(rewriteAbleTimeError(detail));
|
|
@@ -15524,6 +16229,11 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
15524
16229
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
15525
16230
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
15526
16231
|
}
|
|
16232
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
16233
|
+
if (rawToken && ableTimeCredentialKind(rawToken) === "org") {
|
|
16234
|
+
const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
|
|
16235
|
+
return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
|
|
16236
|
+
}
|
|
15527
16237
|
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
15528
16238
|
const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
|
|
15529
16239
|
return unwrapToolResult(result);
|
|
@@ -15539,7 +16249,7 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
15539
16249
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
15540
16250
|
}
|
|
15541
16251
|
}
|
|
15542
|
-
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
|
|
16252
|
+
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;
|
|
15543
16253
|
var init_abletime_mcp = __esm({
|
|
15544
16254
|
"src/integrations/abletime-mcp.ts"() {
|
|
15545
16255
|
"use strict";
|
|
@@ -15561,6 +16271,11 @@ var init_abletime_mcp = __esm({
|
|
|
15561
16271
|
"archive_task",
|
|
15562
16272
|
"delete_task"
|
|
15563
16273
|
];
|
|
16274
|
+
ABLETIME_PAT_PREFIX = "apt_";
|
|
16275
|
+
ABLETIME_ORG_KEY_PREFIX = "atk_";
|
|
16276
|
+
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.";
|
|
16277
|
+
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.";
|
|
16278
|
+
REWRITE_APPLIED = /— Enable Agent access \(MCP\)|— AbleTime MCP needs a personal access token|— AbleTime plan does not include API access|— AbleTime rejected this credential/;
|
|
15564
16279
|
}
|
|
15565
16280
|
});
|
|
15566
16281
|
|
|
@@ -15584,16 +16299,16 @@ __export(abletime_exports, {
|
|
|
15584
16299
|
updateAbleTimeTask: () => updateAbleTimeTask,
|
|
15585
16300
|
verifyAbleTimeConnection: () => verifyAbleTimeConnection
|
|
15586
16301
|
});
|
|
15587
|
-
function
|
|
16302
|
+
function asRecord6(value) {
|
|
15588
16303
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15589
16304
|
}
|
|
15590
|
-
function
|
|
16305
|
+
function asString2(value) {
|
|
15591
16306
|
return typeof value === "string" ? value.trim() : "";
|
|
15592
16307
|
}
|
|
15593
16308
|
function firstString(record, keys) {
|
|
15594
16309
|
if (!record) return "";
|
|
15595
16310
|
for (const key of keys) {
|
|
15596
|
-
const value =
|
|
16311
|
+
const value = asString2(record[key]);
|
|
15597
16312
|
if (value) return value;
|
|
15598
16313
|
}
|
|
15599
16314
|
return "";
|
|
@@ -15601,14 +16316,14 @@ function firstString(record, keys) {
|
|
|
15601
16316
|
function firstRecord(record, keys) {
|
|
15602
16317
|
if (!record) return null;
|
|
15603
16318
|
for (const key of keys) {
|
|
15604
|
-
const value =
|
|
16319
|
+
const value = asRecord6(record[key]);
|
|
15605
16320
|
if (value) return value;
|
|
15606
16321
|
}
|
|
15607
16322
|
return null;
|
|
15608
16323
|
}
|
|
15609
|
-
function
|
|
16324
|
+
function asList2(value) {
|
|
15610
16325
|
if (Array.isArray(value)) return value;
|
|
15611
|
-
const record =
|
|
16326
|
+
const record = asRecord6(value);
|
|
15612
16327
|
if (!record) return [];
|
|
15613
16328
|
for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
|
|
15614
16329
|
if (Array.isArray(record[key])) return record[key];
|
|
@@ -15620,21 +16335,21 @@ function labelsOf(record) {
|
|
|
15620
16335
|
if (!Array.isArray(raw)) return [];
|
|
15621
16336
|
return raw.map((item) => {
|
|
15622
16337
|
if (typeof item === "string") return item.trim();
|
|
15623
|
-
const rec =
|
|
16338
|
+
const rec = asRecord6(item);
|
|
15624
16339
|
return rec ? firstString(rec, ["name", "title", "label"]) : "";
|
|
15625
16340
|
}).filter(Boolean);
|
|
15626
16341
|
}
|
|
15627
16342
|
function commentsOf(record) {
|
|
15628
16343
|
const raw = record.comments ?? record.notes ?? record.discussion;
|
|
15629
16344
|
const out = [];
|
|
15630
|
-
for (const item of
|
|
15631
|
-
const rec =
|
|
16345
|
+
for (const item of asList2(raw)) {
|
|
16346
|
+
const rec = asRecord6(item);
|
|
15632
16347
|
if (!rec) continue;
|
|
15633
|
-
const body = firstString(rec, ["body", "text", "comment", "
|
|
16348
|
+
const body = firstString(rec, ["body", "text", "comment", "message", "contentSanitized"]) || textFromAbleTimeDoc(rec.content);
|
|
15634
16349
|
if (!body) continue;
|
|
15635
|
-
const user = firstString(
|
|
16350
|
+
const user = firstString(asRecord6(rec.user) ?? asRecord6(rec.author), ["name", "display_name", "username"]) || firstString(rec, ["user_name", "author", "username"]);
|
|
15636
16351
|
const comment = { body };
|
|
15637
|
-
const id = firstString(rec, ["id", "comment_id"]);
|
|
16352
|
+
const id = firstString(rec, ["id", "comment_id", "commentId"]);
|
|
15638
16353
|
if (id) comment.id = id;
|
|
15639
16354
|
const url = firstString(rec, ["url", "permalink"]);
|
|
15640
16355
|
if (url) comment.url = url;
|
|
@@ -15658,11 +16373,11 @@ function taskUrl(task, host) {
|
|
|
15658
16373
|
return `${base}/tasks/${slug}`;
|
|
15659
16374
|
}
|
|
15660
16375
|
function mapAbleTimeTask(raw, host) {
|
|
15661
|
-
const record =
|
|
16376
|
+
const record = asRecord6(raw);
|
|
15662
16377
|
if (!record) return null;
|
|
15663
16378
|
const nested = firstRecord(record, ["task", "data"]) ?? record;
|
|
15664
|
-
const id = firstString(nested, ["id", "task_id", "taskId"]);
|
|
15665
|
-
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number"]) || id;
|
|
16379
|
+
const id = firstString(nested, ["id", "task_id", "taskId", "timeflowTaskId"]);
|
|
16380
|
+
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number", "taskRef"]) || id;
|
|
15666
16381
|
const title = firstString(nested, ["title", "name"]);
|
|
15667
16382
|
if (!id && !identifier && !title) return null;
|
|
15668
16383
|
const resolvedId = id || identifier;
|
|
@@ -15673,21 +16388,22 @@ function mapAbleTimeTask(raw, host) {
|
|
|
15673
16388
|
identifier: resolvedIdentifier,
|
|
15674
16389
|
title: title || resolvedIdentifier,
|
|
15675
16390
|
url,
|
|
15676
|
-
description: firstString(nested, ["description", "body", "details"]) || void 0,
|
|
15677
|
-
state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
|
|
15678
|
-
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(
|
|
15679
|
-
categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(
|
|
16391
|
+
description: firstString(nested, ["description", "body", "details", "descriptionSanitized"]) || textFromAbleTimeDoc(nested.description) || void 0,
|
|
16392
|
+
state: firstString(nested, ["state", "board_state", "boardState", "status", "taskState"]) || void 0,
|
|
16393
|
+
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord6(nested.project), ["id"]) || void 0,
|
|
16394
|
+
categoryId: firstString(nested, ["category_id", "categoryId", "projectCategoryId"]) || firstString(asRecord6(nested.category), ["id"]) || void 0,
|
|
15680
16395
|
assignee: assigneeOf(nested),
|
|
15681
16396
|
labels: labelsOf(nested),
|
|
15682
16397
|
comments: commentsOf(nested),
|
|
15683
|
-
createdAt: firstString(nested, ["created_at", "createdAt", "created"]) || void 0,
|
|
16398
|
+
createdAt: firstString(nested, ["created_at", "createdAt", "created", "dateCreated"]) || void 0,
|
|
15684
16399
|
updatedAt: firstString(nested, [
|
|
15685
16400
|
"updated_at",
|
|
15686
16401
|
"updatedAt",
|
|
15687
16402
|
"modified_at",
|
|
15688
16403
|
"modifiedAt",
|
|
15689
16404
|
"last_activity_at",
|
|
15690
|
-
"lastActivityAt"
|
|
16405
|
+
"lastActivityAt",
|
|
16406
|
+
"lastUpdate"
|
|
15691
16407
|
]) || void 0
|
|
15692
16408
|
};
|
|
15693
16409
|
}
|
|
@@ -15718,16 +16434,16 @@ function issueAttachmentForAbleTimeTask(task) {
|
|
|
15718
16434
|
};
|
|
15719
16435
|
}
|
|
15720
16436
|
function mapProject(raw) {
|
|
15721
|
-
const record =
|
|
16437
|
+
const record = asRecord6(raw);
|
|
15722
16438
|
if (!record) return null;
|
|
15723
|
-
const id = firstString(record, ["id", "project_id"]);
|
|
15724
|
-
const name = firstString(record, ["name", "title"]);
|
|
16439
|
+
const id = firstString(record, ["id", "project_id", "projectId"]);
|
|
16440
|
+
const name = firstString(record, ["name", "title", "projectName"]);
|
|
15725
16441
|
if (!id && !name) return null;
|
|
15726
|
-
const categories =
|
|
15727
|
-
const rec =
|
|
16442
|
+
const categories = asList2(record.categories ?? record.category).map((item) => {
|
|
16443
|
+
const rec = asRecord6(item);
|
|
15728
16444
|
if (!rec) return null;
|
|
15729
|
-
const categoryId = firstString(rec, ["id", "category_id"]);
|
|
15730
|
-
const categoryName = firstString(rec, ["name", "title"]);
|
|
16445
|
+
const categoryId = firstString(rec, ["id", "category_id", "projectCategoryId", "categoryId"]);
|
|
16446
|
+
const categoryName = firstString(rec, ["name", "title", "categoryName"]);
|
|
15731
16447
|
if (!categoryId && !categoryName) return null;
|
|
15732
16448
|
return { id: categoryId || categoryName, name: categoryName || categoryId };
|
|
15733
16449
|
}).filter((item) => Boolean(item));
|
|
@@ -15739,21 +16455,21 @@ function isOpenTask(task) {
|
|
|
15739
16455
|
}
|
|
15740
16456
|
async function getAbleTimeOrientation(opts) {
|
|
15741
16457
|
const raw = await callAbleTimeTool("orientation", {}, opts);
|
|
15742
|
-
const record =
|
|
16458
|
+
const record = asRecord6(raw);
|
|
15743
16459
|
const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
|
|
15744
16460
|
const viewer = {
|
|
15745
16461
|
id: firstString(viewerRec, ["id", "user_id"]) || void 0,
|
|
15746
16462
|
name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
|
|
15747
16463
|
};
|
|
15748
|
-
const projects =
|
|
16464
|
+
const projects = asList2(
|
|
15749
16465
|
record?.projects ?? record?.current_projects ?? record?.currentProjects
|
|
15750
16466
|
).map(mapProject).filter((item) => Boolean(item));
|
|
15751
|
-
const tasks =
|
|
16467
|
+
const tasks = asList2(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
15752
16468
|
return { viewer, projects, tasks, raw };
|
|
15753
16469
|
}
|
|
15754
16470
|
async function listAbleTimeProjects(opts) {
|
|
15755
16471
|
const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
|
|
15756
|
-
return
|
|
16472
|
+
return asList2(raw).map(mapProject).filter((item) => Boolean(item));
|
|
15757
16473
|
}
|
|
15758
16474
|
async function listAbleTimeTasks(opts) {
|
|
15759
16475
|
const args = {};
|
|
@@ -15763,14 +16479,14 @@ async function listAbleTimeTasks(opts) {
|
|
|
15763
16479
|
}
|
|
15764
16480
|
if (opts?.query) args.query = opts.query;
|
|
15765
16481
|
const raw = await callAbleTimeTool("list_tasks", args, opts);
|
|
15766
|
-
const tasks =
|
|
16482
|
+
const tasks = asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
15767
16483
|
return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
|
|
15768
16484
|
}
|
|
15769
16485
|
async function searchAbleTimeTasks(query, opts) {
|
|
15770
16486
|
const q = query.trim();
|
|
15771
16487
|
if (!q) return [];
|
|
15772
16488
|
const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
|
|
15773
|
-
return
|
|
16489
|
+
return asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
|
|
15774
16490
|
}
|
|
15775
16491
|
async function getAbleTimeTask(id, opts) {
|
|
15776
16492
|
const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
|
|
@@ -15818,7 +16534,7 @@ async function commentAbleTimeTask(input, opts) {
|
|
|
15818
16534
|
{ id, task_id: id, task: id, body, comment: body, text: body },
|
|
15819
16535
|
opts
|
|
15820
16536
|
);
|
|
15821
|
-
const rec =
|
|
16537
|
+
const rec = asRecord6(raw);
|
|
15822
16538
|
return {
|
|
15823
16539
|
id: rec ? firstString(rec, ["id", "comment_id"]) || void 0 : void 0,
|
|
15824
16540
|
body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
|
|
@@ -16008,8 +16724,7 @@ async function listAbleTimeAssignedIssues(opts) {
|
|
|
16008
16724
|
};
|
|
16009
16725
|
}
|
|
16010
16726
|
async function verifyAbleTimeConnection(input) {
|
|
16011
|
-
const token = input.token
|
|
16012
|
-
if (!token) throw new Error("AbleTime personal access token is required");
|
|
16727
|
+
const token = assertAbleTimeCredential(input.token);
|
|
16013
16728
|
const host = input.host?.trim() || void 0;
|
|
16014
16729
|
const orientation = await getAbleTimeOrientation({ token, host });
|
|
16015
16730
|
saveAbleTimeConnection({
|
|
@@ -16025,6 +16740,7 @@ var init_abletime = __esm({
|
|
|
16025
16740
|
"use strict";
|
|
16026
16741
|
init_app_settings();
|
|
16027
16742
|
init_abletime_mcp();
|
|
16743
|
+
init_abletime_rest();
|
|
16028
16744
|
CLOSED_STATES = /* @__PURE__ */ new Set([
|
|
16029
16745
|
"done",
|
|
16030
16746
|
"completed",
|
|
@@ -16596,12 +17312,19 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
16596
17312
|
...instructions
|
|
16597
17313
|
].join("\n");
|
|
16598
17314
|
return {
|
|
16599
|
-
id: (0,
|
|
17315
|
+
id: (0, import_node_crypto10.randomUUID)(),
|
|
16600
17316
|
name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
|
|
16601
17317
|
kind: "transcript",
|
|
16602
17318
|
content: body
|
|
16603
17319
|
};
|
|
16604
17320
|
}
|
|
17321
|
+
async function isolateQuotaFailover(run2) {
|
|
17322
|
+
try {
|
|
17323
|
+
return await run2();
|
|
17324
|
+
} catch {
|
|
17325
|
+
return true;
|
|
17326
|
+
}
|
|
17327
|
+
}
|
|
16605
17328
|
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
16606
17329
|
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
16607
17330
|
const tab = createChatTab({
|
|
@@ -16620,11 +17343,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
16620
17343
|
}
|
|
16621
17344
|
return updateThread(tab.id, patch);
|
|
16622
17345
|
}
|
|
16623
|
-
var
|
|
17346
|
+
var import_node_crypto10, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
|
|
16624
17347
|
var init_quota_failover = __esm({
|
|
16625
17348
|
"src/orchestrator/quota-failover.ts"() {
|
|
16626
17349
|
"use strict";
|
|
16627
|
-
|
|
17350
|
+
import_node_crypto10 = require("crypto");
|
|
16628
17351
|
init_session_quota();
|
|
16629
17352
|
init_app_settings();
|
|
16630
17353
|
init_global_workspace();
|
|
@@ -18935,7 +19658,7 @@ var init_optional_services = __esm({
|
|
|
18935
19658
|
});
|
|
18936
19659
|
|
|
18937
19660
|
// src/plan/plan-present.ts
|
|
18938
|
-
function
|
|
19661
|
+
function asRecord7(v) {
|
|
18939
19662
|
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
18940
19663
|
}
|
|
18941
19664
|
function isPresentPlanToolName(name) {
|
|
@@ -18947,7 +19670,7 @@ function extractPresentedPlan(parts) {
|
|
|
18947
19670
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
18948
19671
|
const p = parts[i];
|
|
18949
19672
|
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
18950
|
-
const input =
|
|
19673
|
+
const input = asRecord7(p.input) ?? {};
|
|
18951
19674
|
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
18952
19675
|
if (!content.trim()) continue;
|
|
18953
19676
|
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
@@ -19382,7 +20105,7 @@ function buildTask(input, now) {
|
|
|
19382
20105
|
const ts = nowIso2(now);
|
|
19383
20106
|
const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
|
|
19384
20107
|
return {
|
|
19385
|
-
id: (0,
|
|
20108
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
19386
20109
|
name: input.name?.trim() || defaultScheduleName(prompt),
|
|
19387
20110
|
prompt,
|
|
19388
20111
|
enabled: input.enabled !== false,
|
|
@@ -19469,11 +20192,11 @@ function recordScheduleRun(id, result) {
|
|
|
19469
20192
|
writeAll2(rows);
|
|
19470
20193
|
return next;
|
|
19471
20194
|
}
|
|
19472
|
-
var
|
|
20195
|
+
var import_node_crypto11, import_node_fs52, import_node_path49, import_croner, DURATION_RE, UNIT_MS;
|
|
19473
20196
|
var init_schedules = __esm({
|
|
19474
20197
|
"src/store/schedules.ts"() {
|
|
19475
20198
|
"use strict";
|
|
19476
|
-
|
|
20199
|
+
import_node_crypto11 = require("crypto");
|
|
19477
20200
|
import_node_fs52 = require("fs");
|
|
19478
20201
|
import_node_path49 = require("path");
|
|
19479
20202
|
import_croner = require("croner");
|
|
@@ -20156,62 +20879,75 @@ var init_orchestrator = __esm({
|
|
|
20156
20879
|
*/
|
|
20157
20880
|
async maybeHandleOrchestrationQuotaFailover(threadId, limitText) {
|
|
20158
20881
|
const thread = findThreadByRef(threadId);
|
|
20159
|
-
if (!thread) return;
|
|
20882
|
+
if (!thread) return false;
|
|
20160
20883
|
const plan = planOrchestrationQuotaFailover(thread, limitText);
|
|
20161
|
-
if (!plan || plan.action === "none") return;
|
|
20884
|
+
if (!plan || plan.action === "none") return false;
|
|
20162
20885
|
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
20163
20886
|
this.haltDrain.add(threadId);
|
|
20164
20887
|
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
20165
|
-
|
|
20166
|
-
|
|
20167
|
-
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
|
|
20178
|
-
|
|
20179
|
-
|
|
20888
|
+
try {
|
|
20889
|
+
setStatus(threadId, "idle", null);
|
|
20890
|
+
appendMessage(threadId, {
|
|
20891
|
+
role: "agent",
|
|
20892
|
+
text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
20893
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
20894
|
+
});
|
|
20895
|
+
this.emit({
|
|
20896
|
+
type: "quota_failover",
|
|
20897
|
+
threadId,
|
|
20898
|
+
action: "wait_reset",
|
|
20899
|
+
message: plan.reason,
|
|
20900
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
20901
|
+
});
|
|
20902
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
20903
|
+
} catch {
|
|
20904
|
+
}
|
|
20905
|
+
return true;
|
|
20180
20906
|
}
|
|
20181
20907
|
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
20182
20908
|
this.haltDrain.add(threadId);
|
|
20183
|
-
|
|
20184
|
-
|
|
20185
|
-
|
|
20186
|
-
|
|
20187
|
-
|
|
20909
|
+
let next;
|
|
20910
|
+
try {
|
|
20911
|
+
next = createQuotaFailoverChat(
|
|
20912
|
+
thread,
|
|
20913
|
+
plan.fallbackAgent,
|
|
20914
|
+
plan.limitText
|
|
20915
|
+
);
|
|
20916
|
+
} catch {
|
|
20917
|
+
return false;
|
|
20918
|
+
}
|
|
20188
20919
|
this.clearQuotaResumeTimer(threadId);
|
|
20189
20920
|
try {
|
|
20190
20921
|
updateThread(threadId, { quotaResumeAt: null });
|
|
20191
20922
|
} catch {
|
|
20192
20923
|
}
|
|
20193
|
-
|
|
20194
|
-
|
|
20195
|
-
|
|
20196
|
-
|
|
20197
|
-
|
|
20198
|
-
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
|
|
20209
|
-
|
|
20210
|
-
|
|
20924
|
+
try {
|
|
20925
|
+
appendMessage(threadId, {
|
|
20926
|
+
role: "agent",
|
|
20927
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
20928
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
20929
|
+
});
|
|
20930
|
+
this.emit({
|
|
20931
|
+
type: "quota_failover",
|
|
20932
|
+
threadId,
|
|
20933
|
+
action: "switch_agent",
|
|
20934
|
+
toThreadId: next.id,
|
|
20935
|
+
message: plan.reason
|
|
20936
|
+
});
|
|
20937
|
+
this.emit({
|
|
20938
|
+
type: "status_changed",
|
|
20939
|
+
threadId: next.id,
|
|
20940
|
+
status: next.status
|
|
20941
|
+
});
|
|
20942
|
+
} catch {
|
|
20943
|
+
}
|
|
20944
|
+
void this.send(
|
|
20211
20945
|
next.id,
|
|
20212
20946
|
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
20213
20947
|
);
|
|
20948
|
+
return true;
|
|
20214
20949
|
}
|
|
20950
|
+
return false;
|
|
20215
20951
|
}
|
|
20216
20952
|
/**
|
|
20217
20953
|
* Cursor-style recovery: after a runner crash, feed the error back as the
|
|
@@ -20935,13 +21671,18 @@ var init_orchestrator = __esm({
|
|
|
20935
21671
|
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
20936
21672
|
} else {
|
|
20937
21673
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
20938
|
-
|
|
21674
|
+
const failedOver = await isolateQuotaFailover(
|
|
21675
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, blob)
|
|
21676
|
+
);
|
|
20939
21677
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
20940
21678
|
detail: detail || failDetail,
|
|
20941
21679
|
assistantText: chatText,
|
|
20942
21680
|
partsCount: parts.length
|
|
20943
21681
|
});
|
|
20944
|
-
if (
|
|
21682
|
+
if (shouldNotifyParentAfterTurnError({
|
|
21683
|
+
quotaFailoverHandled: failedOver,
|
|
21684
|
+
crashContinued: this.crashContinued.has(threadId)
|
|
21685
|
+
})) {
|
|
20945
21686
|
const failed = readThread(threadId);
|
|
20946
21687
|
if (failed) {
|
|
20947
21688
|
notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
|
|
@@ -20965,13 +21706,18 @@ var init_orchestrator = __esm({
|
|
|
20965
21706
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
20966
21707
|
}
|
|
20967
21708
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
20968
|
-
|
|
21709
|
+
const failedOver = await isolateQuotaFailover(
|
|
21710
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, message)
|
|
21711
|
+
);
|
|
20969
21712
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
20970
21713
|
detail: message,
|
|
20971
21714
|
assistantText: "",
|
|
20972
21715
|
partsCount: 0
|
|
20973
21716
|
});
|
|
20974
|
-
if (
|
|
21717
|
+
if (shouldNotifyParentAfterTurnError({
|
|
21718
|
+
quotaFailoverHandled: failedOver,
|
|
21719
|
+
crashContinued: this.crashContinued.has(threadId)
|
|
21720
|
+
})) {
|
|
20975
21721
|
const failed = readThread(threadId);
|
|
20976
21722
|
if (failed) {
|
|
20977
21723
|
notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
|
|
@@ -22051,7 +22797,7 @@ init_system_ca();
|
|
|
22051
22797
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
22052
22798
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
22053
22799
|
var import_zod9 = require("zod");
|
|
22054
|
-
var
|
|
22800
|
+
var import_node_crypto14 = require("crypto");
|
|
22055
22801
|
var import_node_path53 = require("path");
|
|
22056
22802
|
init_orchestrator();
|
|
22057
22803
|
init_worktree();
|
|
@@ -22478,8 +23224,8 @@ async function createGitHubIssue(input, opts) {
|
|
|
22478
23224
|
init_fetch();
|
|
22479
23225
|
|
|
22480
23226
|
// src/integrations/linear-oauth.ts
|
|
22481
|
-
var
|
|
22482
|
-
var
|
|
23227
|
+
var import_node_crypto12 = require("crypto");
|
|
23228
|
+
var import_node_http2 = require("http");
|
|
22483
23229
|
init_app_settings();
|
|
22484
23230
|
init_fetch();
|
|
22485
23231
|
|
|
@@ -22490,7 +23236,7 @@ var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
|
|
|
22490
23236
|
var LINEAR_OAUTH_PORT = 19848;
|
|
22491
23237
|
var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
|
|
22492
23238
|
var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
|
|
22493
|
-
var
|
|
23239
|
+
var REFRESH_SKEW_MS3 = 5 * 6e4;
|
|
22494
23240
|
function linearOAuthCredentials() {
|
|
22495
23241
|
const settings = loadAppSettings();
|
|
22496
23242
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
@@ -22531,7 +23277,7 @@ async function persistLinearTokens(data, viewer) {
|
|
|
22531
23277
|
organizationName: viewer?.organizationName
|
|
22532
23278
|
});
|
|
22533
23279
|
}
|
|
22534
|
-
var
|
|
23280
|
+
var refreshInFlight2 = null;
|
|
22535
23281
|
async function refreshLinearAccessToken(refreshToken) {
|
|
22536
23282
|
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
22537
23283
|
const body = new URLSearchParams({
|
|
@@ -22558,16 +23304,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
22558
23304
|
const refresh = settings.integrations.linearRefreshToken?.trim() || "";
|
|
22559
23305
|
const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
|
|
22560
23306
|
const apiKey = settings.integrations.linearApiKey?.trim() || "";
|
|
22561
|
-
if (access && (!refresh || Date.now() < expiresAt -
|
|
23307
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS3)) {
|
|
22562
23308
|
return access;
|
|
22563
23309
|
}
|
|
22564
23310
|
if (refresh) {
|
|
22565
|
-
if (!
|
|
22566
|
-
|
|
22567
|
-
|
|
23311
|
+
if (!refreshInFlight2) {
|
|
23312
|
+
refreshInFlight2 = refreshLinearAccessToken(refresh).finally(() => {
|
|
23313
|
+
refreshInFlight2 = null;
|
|
22568
23314
|
});
|
|
22569
23315
|
}
|
|
22570
|
-
return
|
|
23316
|
+
return refreshInFlight2.catch((err) => {
|
|
22571
23317
|
if (apiKey) return apiKey;
|
|
22572
23318
|
throw err;
|
|
22573
23319
|
});
|
|
@@ -25461,7 +26207,7 @@ init_worktree();
|
|
|
25461
26207
|
init_paths();
|
|
25462
26208
|
|
|
25463
26209
|
// src/board/board-pins.ts
|
|
25464
|
-
var
|
|
26210
|
+
var import_node_crypto13 = require("crypto");
|
|
25465
26211
|
var import_node_fs55 = require("fs");
|
|
25466
26212
|
var import_node_path51 = require("path");
|
|
25467
26213
|
init_paths();
|
|
@@ -26310,7 +27056,7 @@ async function startMcpServer() {
|
|
|
26310
27056
|
const ident = issue?.identifier ?? ref.trim();
|
|
26311
27057
|
const attachments = issue ? [
|
|
26312
27058
|
{
|
|
26313
|
-
id: (0,
|
|
27059
|
+
id: (0, import_node_crypto14.randomUUID)(),
|
|
26314
27060
|
name: issue.identifier,
|
|
26315
27061
|
kind: "issue",
|
|
26316
27062
|
content: [
|