@sideboard-ai/core 0.1.202 → 0.1.204
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-S3UHQZVW.js} +4 -2
- package/dist/{abletime-345SE3B4.js → abletime-TONBQP4O.js} +4 -2
- package/dist/abletime-oauth-AVCEO2DY.js +314 -0
- package/dist/abletime-oauth-SD4SZY5U.js +52 -0
- package/dist/abletime-rest-6ODSXV7G.js +25 -0
- package/dist/abletime-rest-J2OBB4VA.js +24 -0
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-573ZDIX6.js → agents-67K4BQFJ.js} +4 -4
- package/dist/{agents-OXX3ONVY.js → agents-BVOJNHV6.js} +5 -5
- 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-3K257PCS.js +393 -0
- package/dist/{chunk-54RSAMBH.js → chunk-5SMS6QTO.js} +2 -2
- package/dist/chunk-6Q7DFVDQ.js +309 -0
- package/dist/chunk-CYLDJWRK.js +391 -0
- package/dist/{chunk-PGQGP4FD.js → chunk-FO2NTJ7L.js} +37 -219
- package/dist/{chunk-7NKY2OX7.js → chunk-FO45DMJ5.js} +125 -109
- package/dist/{chunk-CQ5R3VPP.js → chunk-KZ7PI7FE.js} +18 -0
- package/dist/{chunk-7AHAABKN.js → chunk-QAVWH2RQ.js} +37 -229
- package/dist/{chunk-CXFW5LIH.js → chunk-QFBBUFD4.js} +38 -3
- package/dist/{chunk-RPX6VDW7.js → chunk-QUW5D62C.js} +34 -20
- package/dist/chunk-SGUVAK66.js +262 -0
- package/dist/{chunk-27GGXX27.js → chunk-ULZLWQBS.js} +128 -112
- package/dist/{chunk-LJHC62LZ.js → chunk-VIPA75ZA.js} +2 -2
- package/dist/chunk-W6VM36YO.js +272 -0
- package/dist/{chunk-NIVF5NDN.js → chunk-WF33LOP4.js} +44 -6
- package/dist/{chunk-STPZQR5B.js → chunk-WVATAIF4.js} +34 -20
- package/dist/{chunk-ISV5IFH3.js → chunk-X743YN5Y.js} +38 -3
- package/dist/{chunk-A2TAKYSG.js → chunk-YOSTWSNC.js} +27 -7
- package/dist/{coordinator-prompt-BH4EHFOO.js → coordinator-prompt-NPT555BO.js} +3 -3
- package/dist/{coordinator-prompt-TY5FHAXC.js → coordinator-prompt-WEPXXLLX.js} +3 -3
- package/dist/{global-workspace-NLLAPQJI.js → global-workspace-2I2T7FL5.js} +3 -3
- package/dist/{global-workspace-5AZEEQRU.js → global-workspace-NSILADOM.js} +3 -3
- package/dist/index.cjs +1137 -213
- package/dist/index.d.cts +87 -13
- package/dist/index.d.ts +87 -13
- package/dist/index.js +94 -19
- package/dist/mcp/run-stdio.cjs +1023 -180
- package/dist/mcp/run-stdio.js +22 -20
- package/dist/{orchestrator-JNSQDRZI.js → orchestrator-CMAVNELS.js} +6 -6
- package/dist/{orchestrator-NCYKEI3E.js → orchestrator-I7GFXLHF.js} +6 -6
- package/dist/{workspaces-RP76JH3J.js → workspaces-BOC6M7BW.js} +3 -3
- package/dist/{workspaces-PEWYWVHZ.js → workspaces-RFGZBUAR.js} +3 -3
- package/dist/{worktree-72TZWUSH.js → worktree-G3ZLM4DC.js} +4 -2
- package/dist/{worktree-ZQEUKSNE.js → worktree-WSUQMMK4.js} +4 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -1792,6 +1792,9 @@ function humanizeAgentFailDetail(detail) {
|
|
|
1792
1792
|
if (/\[resource_exhausted\]|resource_exhausted|findfileswithripgrep/.test(lower)) {
|
|
1793
1793
|
return "Cursor local file search failed (ripgrep / resource_exhausted). Wait a minute and retry; if it keeps happening, check Cursor usage.";
|
|
1794
1794
|
}
|
|
1795
|
+
if (looksLikeCodexMcpError(raw)) {
|
|
1796
|
+
return `${raw} \u2014 skip that vendor MCP and continue (PostHog: Settings \u2192 Connectors + HTTP API).`;
|
|
1797
|
+
}
|
|
1795
1798
|
if (/invalid user api key|invalid api key|not logged in|not authenticated|unauthorized|authentication|please run.*login|codex login|claude auth|cursor api/.test(
|
|
1796
1799
|
lower
|
|
1797
1800
|
)) {
|
|
@@ -1849,9 +1852,24 @@ function turnFailChatText(opts) {
|
|
|
1849
1852
|
|
|
1850
1853
|
${fail6}`;
|
|
1851
1854
|
}
|
|
1855
|
+
function looksLikeCodexMcpError(text7) {
|
|
1856
|
+
const t = text7.trim().toLowerCase();
|
|
1857
|
+
if (!t) return false;
|
|
1858
|
+
if (/authrequired|authorization required/.test(t) && /mcp|posthog|rmcp|connector|plugin/.test(t)) {
|
|
1859
|
+
return true;
|
|
1860
|
+
}
|
|
1861
|
+
if (/\bposthog\b/.test(t) && /mcp|oauth|auth|unauthorized|401|plugin|app/.test(t)) {
|
|
1862
|
+
return true;
|
|
1863
|
+
}
|
|
1864
|
+
if (/\brmcp\b/.test(t) && /fatal|quit|transport|error|died|kill|auth/.test(t)) {
|
|
1865
|
+
return true;
|
|
1866
|
+
}
|
|
1867
|
+
return /mcp[_ -]?(server|tool|error|transport|client)/.test(t) && /fail|error|timeout|disconnect|auth|unauthorized/.test(t);
|
|
1868
|
+
}
|
|
1852
1869
|
function shouldFeedErrorBackToAgent(opts) {
|
|
1853
1870
|
const detail = opts.detail.trim();
|
|
1854
1871
|
const chat = (opts.assistantText ?? "").trim();
|
|
1872
|
+
if (looksLikeCodexMcpError(detail)) return true;
|
|
1855
1873
|
if (looksLikeAgentFailureMessage(detail) || looksLikeAgentFailureMessage(chat)) {
|
|
1856
1874
|
return false;
|
|
1857
1875
|
}
|
|
@@ -3424,6 +3442,9 @@ function normalizeVault(raw) {
|
|
|
3424
3442
|
if (typeof raw.abletimeAccessToken === "string" && raw.abletimeAccessToken.trim()) {
|
|
3425
3443
|
out.abletimeAccessToken = raw.abletimeAccessToken.trim();
|
|
3426
3444
|
}
|
|
3445
|
+
if (typeof raw.abletimeRefreshToken === "string" && raw.abletimeRefreshToken.trim()) {
|
|
3446
|
+
out.abletimeRefreshToken = raw.abletimeRefreshToken.trim();
|
|
3447
|
+
}
|
|
3427
3448
|
if (typeof raw.vercelToken === "string" && raw.vercelToken.trim()) {
|
|
3428
3449
|
out.vercelToken = raw.vercelToken.trim();
|
|
3429
3450
|
}
|
|
@@ -3629,6 +3650,7 @@ function emptyPublicIntegrations() {
|
|
|
3629
3650
|
hasSlackAppToken: false,
|
|
3630
3651
|
hasGithubPat: false,
|
|
3631
3652
|
hasAbleTimeToken: false,
|
|
3653
|
+
hasAbleTimeOAuth: false,
|
|
3632
3654
|
hasVercelToken: false,
|
|
3633
3655
|
hasSupabaseToken: false,
|
|
3634
3656
|
hasPosthogToken: false,
|
|
@@ -3653,6 +3675,7 @@ function toPublicAppSettings(settings) {
|
|
|
3653
3675
|
const slackAppToken = integrations.slackAppToken;
|
|
3654
3676
|
const githubPat = integrations.githubPat;
|
|
3655
3677
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
3678
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
3656
3679
|
const vercelToken = integrations.vercelToken;
|
|
3657
3680
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
3658
3681
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -3665,6 +3688,7 @@ function toPublicAppSettings(settings) {
|
|
|
3665
3688
|
delete integrations.slackAppToken;
|
|
3666
3689
|
delete integrations.githubPat;
|
|
3667
3690
|
delete integrations.abletimeAccessToken;
|
|
3691
|
+
delete integrations.abletimeRefreshToken;
|
|
3668
3692
|
delete integrations.vercelToken;
|
|
3669
3693
|
delete integrations.supabaseAccessToken;
|
|
3670
3694
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -3679,7 +3703,8 @@ function toPublicAppSettings(settings) {
|
|
|
3679
3703
|
hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
|
|
3680
3704
|
hasSlackAppToken: Boolean(slackAppToken?.trim()),
|
|
3681
3705
|
hasGithubPat: Boolean(githubPat?.trim()),
|
|
3682
|
-
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim()),
|
|
3706
|
+
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim() || abletimeRefreshToken?.trim()),
|
|
3707
|
+
hasAbleTimeOAuth: Boolean(abletimeRefreshToken?.trim()),
|
|
3683
3708
|
hasVercelToken: Boolean(vercelToken?.trim()),
|
|
3684
3709
|
hasSupabaseToken: Boolean(supabaseAccessToken?.trim()),
|
|
3685
3710
|
hasPosthogToken: Boolean(posthogPersonalApiKey?.trim()),
|
|
@@ -3817,6 +3842,16 @@ function normalizeIntegrations(raw) {
|
|
|
3817
3842
|
const token = source.abletimeAccessToken.trim();
|
|
3818
3843
|
if (token) out.abletimeAccessToken = token;
|
|
3819
3844
|
}
|
|
3845
|
+
if (typeof source.abletimeRefreshToken === "string") {
|
|
3846
|
+
const token = source.abletimeRefreshToken.trim();
|
|
3847
|
+
if (token) out.abletimeRefreshToken = token;
|
|
3848
|
+
}
|
|
3849
|
+
if (typeof source.abletimeTokenExpiresAt === "number" && Number.isFinite(source.abletimeTokenExpiresAt)) {
|
|
3850
|
+
out.abletimeTokenExpiresAt = source.abletimeTokenExpiresAt;
|
|
3851
|
+
} else if (typeof source.abletimeTokenExpiresAt === "string") {
|
|
3852
|
+
const n = Number(source.abletimeTokenExpiresAt);
|
|
3853
|
+
if (Number.isFinite(n)) out.abletimeTokenExpiresAt = n;
|
|
3854
|
+
}
|
|
3820
3855
|
if (typeof source.abletimeHost === "string") {
|
|
3821
3856
|
const host = source.abletimeHost.trim();
|
|
3822
3857
|
if (host) out.abletimeHost = host;
|
|
@@ -4094,7 +4129,7 @@ function readSettingsFile() {
|
|
|
4094
4129
|
}
|
|
4095
4130
|
function diskHoldsSecrets(disk) {
|
|
4096
4131
|
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
|
|
4132
|
+
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
4133
|
);
|
|
4099
4134
|
}
|
|
4100
4135
|
function mergeVault(disk) {
|
|
@@ -4125,6 +4160,9 @@ function mergeVault(disk) {
|
|
|
4125
4160
|
if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
|
|
4126
4161
|
integrations.abletimeAccessToken = vault.abletimeAccessToken;
|
|
4127
4162
|
}
|
|
4163
|
+
if (vault.abletimeRefreshToken && !integrations.abletimeRefreshToken) {
|
|
4164
|
+
integrations.abletimeRefreshToken = vault.abletimeRefreshToken;
|
|
4165
|
+
}
|
|
4128
4166
|
if (vault.vercelToken && !integrations.vercelToken) {
|
|
4129
4167
|
integrations.vercelToken = vault.vercelToken;
|
|
4130
4168
|
}
|
|
@@ -4149,6 +4187,7 @@ function persistSplit(settings) {
|
|
|
4149
4187
|
const slackAppToken = integrations.slackAppToken;
|
|
4150
4188
|
const githubPat = integrations.githubPat;
|
|
4151
4189
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
4190
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
4152
4191
|
const vercelToken = integrations.vercelToken;
|
|
4153
4192
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
4154
4193
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -4161,6 +4200,7 @@ function persistSplit(settings) {
|
|
|
4161
4200
|
delete integrations.slackAppToken;
|
|
4162
4201
|
delete integrations.githubPat;
|
|
4163
4202
|
delete integrations.abletimeAccessToken;
|
|
4203
|
+
delete integrations.abletimeRefreshToken;
|
|
4164
4204
|
delete integrations.vercelToken;
|
|
4165
4205
|
delete integrations.supabaseAccessToken;
|
|
4166
4206
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -4179,6 +4219,7 @@ function persistSplit(settings) {
|
|
|
4179
4219
|
slackAppToken,
|
|
4180
4220
|
githubPat,
|
|
4181
4221
|
abletimeAccessToken,
|
|
4222
|
+
abletimeRefreshToken,
|
|
4182
4223
|
vercelToken,
|
|
4183
4224
|
supabaseAccessToken,
|
|
4184
4225
|
posthogPersonalApiKey,
|
|
@@ -4365,6 +4406,8 @@ function updateIntegrationsSettings(patch) {
|
|
|
4365
4406
|
if ("abletimeAccessToken" in patch) {
|
|
4366
4407
|
if (patch.abletimeAccessToken == null || patch.abletimeAccessToken.trim() === "") {
|
|
4367
4408
|
delete integrations.abletimeAccessToken;
|
|
4409
|
+
delete integrations.abletimeRefreshToken;
|
|
4410
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
4368
4411
|
delete integrations.abletimeViewerName;
|
|
4369
4412
|
} else {
|
|
4370
4413
|
integrations.abletimeAccessToken = patch.abletimeAccessToken.trim();
|
|
@@ -4715,7 +4758,9 @@ function disconnectLinearConnection() {
|
|
|
4715
4758
|
return saveAppSettings({ ...current, integrations });
|
|
4716
4759
|
}
|
|
4717
4760
|
function isAbleTimeConnected(settings = loadAppSettings()) {
|
|
4718
|
-
return Boolean(
|
|
4761
|
+
return Boolean(
|
|
4762
|
+
settings.integrations.abletimeAccessToken?.trim() || settings.integrations.abletimeRefreshToken?.trim()
|
|
4763
|
+
);
|
|
4719
4764
|
}
|
|
4720
4765
|
function getAbleTimeAccessToken(settings = loadAppSettings()) {
|
|
4721
4766
|
const token = settings.integrations.abletimeAccessToken?.trim();
|
|
@@ -4728,6 +4773,12 @@ function saveAbleTimeConnection(input) {
|
|
|
4728
4773
|
const current = loadAppSettings();
|
|
4729
4774
|
const integrations = { ...current.integrations };
|
|
4730
4775
|
integrations.abletimeAccessToken = input.accessToken.trim();
|
|
4776
|
+
if (input.refreshToken?.trim()) {
|
|
4777
|
+
integrations.abletimeRefreshToken = input.refreshToken.trim();
|
|
4778
|
+
}
|
|
4779
|
+
if (typeof input.expiresIn === "number" && Number.isFinite(input.expiresIn)) {
|
|
4780
|
+
integrations.abletimeTokenExpiresAt = Date.now() + Math.max(0, input.expiresIn) * 1e3;
|
|
4781
|
+
}
|
|
4731
4782
|
if (input.host?.trim()) {
|
|
4732
4783
|
integrations.abletimeHost = input.host.trim();
|
|
4733
4784
|
}
|
|
@@ -4740,6 +4791,8 @@ function disconnectAbleTimeConnection() {
|
|
|
4740
4791
|
const current = loadAppSettings();
|
|
4741
4792
|
const integrations = { ...current.integrations };
|
|
4742
4793
|
delete integrations.abletimeAccessToken;
|
|
4794
|
+
delete integrations.abletimeRefreshToken;
|
|
4795
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
4743
4796
|
delete integrations.abletimeViewerName;
|
|
4744
4797
|
return saveAppSettings({ ...current, integrations });
|
|
4745
4798
|
}
|
|
@@ -6034,6 +6087,7 @@ __export(worktree_exports, {
|
|
|
6034
6087
|
canonicalizeRepoPath: () => canonicalizeRepoPath,
|
|
6035
6088
|
collectTakenTeamSlugs: () => collectTakenTeamSlugs,
|
|
6036
6089
|
commitAll: () => commitAll,
|
|
6090
|
+
connectedPrSelectors: () => connectedPrSelectors,
|
|
6037
6091
|
countUnpushedVsOrigin: () => countUnpushedVsOrigin,
|
|
6038
6092
|
createExistingBranchWorktree: () => createExistingBranchWorktree,
|
|
6039
6093
|
createOrUpdatePr: () => createOrUpdatePr,
|
|
@@ -6367,26 +6421,21 @@ function resolvePrSelectors(thread) {
|
|
|
6367
6421
|
function resolvePrSelector(thread) {
|
|
6368
6422
|
return resolvePrSelectors(thread)[0] ?? null;
|
|
6369
6423
|
}
|
|
6424
|
+
function connectedPrSelectors(thread, currentHeadPrUrl) {
|
|
6425
|
+
const out = [];
|
|
6426
|
+
const push = (value) => {
|
|
6427
|
+
const v = value?.trim();
|
|
6428
|
+
if (!v || out.includes(v)) return;
|
|
6429
|
+
out.push(v);
|
|
6430
|
+
};
|
|
6431
|
+
push(currentHeadPrUrl);
|
|
6432
|
+
for (const selector of resolvePrSelectors(thread)) push(selector);
|
|
6433
|
+
return out;
|
|
6434
|
+
}
|
|
6370
6435
|
async function getPrForHeadBranch(repoPath, branch) {
|
|
6371
6436
|
const head = normalizeGitBranchName(branch);
|
|
6372
6437
|
if (!head || isDefaultishSourceRef(head)) return null;
|
|
6373
6438
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
6374
|
-
const viewArgs = [
|
|
6375
|
-
"pr",
|
|
6376
|
-
"view",
|
|
6377
|
-
head,
|
|
6378
|
-
"--json",
|
|
6379
|
-
"number,title,headRefName,url,isCrossRepository,author"
|
|
6380
|
-
];
|
|
6381
|
-
if (slug) viewArgs.push("--repo", slug);
|
|
6382
|
-
const viewed = await gh(viewArgs, repoPath, { reject: false });
|
|
6383
|
-
if (viewed.exitCode === 0 && viewed.stdout.trim()) {
|
|
6384
|
-
try {
|
|
6385
|
-
return JSON.parse(viewed.stdout);
|
|
6386
|
-
} catch {
|
|
6387
|
-
return null;
|
|
6388
|
-
}
|
|
6389
|
-
}
|
|
6390
6439
|
const listHead = slug ? ghHeadRef(slug, head) : head;
|
|
6391
6440
|
const listArgs = [
|
|
6392
6441
|
"pr",
|
|
@@ -6402,10 +6451,28 @@ async function getPrForHeadBranch(repoPath, branch) {
|
|
|
6402
6451
|
];
|
|
6403
6452
|
if (slug) listArgs.push("--repo", slug);
|
|
6404
6453
|
const listed = await gh(listArgs, repoPath, { reject: false });
|
|
6405
|
-
if (listed.exitCode
|
|
6454
|
+
if (listed.exitCode === 0 && listed.stdout.trim()) {
|
|
6455
|
+
try {
|
|
6456
|
+
const rows = JSON.parse(listed.stdout);
|
|
6457
|
+
if (rows[0]) return rows[0];
|
|
6458
|
+
} catch {
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
const viewArgs = [
|
|
6462
|
+
"pr",
|
|
6463
|
+
"view",
|
|
6464
|
+
head,
|
|
6465
|
+
"--json",
|
|
6466
|
+
"number,title,headRefName,url,isCrossRepository,author,state"
|
|
6467
|
+
];
|
|
6468
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
6469
|
+
const viewed = await gh(viewArgs, repoPath, { reject: false });
|
|
6470
|
+
if (viewed.exitCode !== 0 || !viewed.stdout.trim()) return null;
|
|
6406
6471
|
try {
|
|
6407
|
-
const
|
|
6408
|
-
|
|
6472
|
+
const info = JSON.parse(viewed.stdout);
|
|
6473
|
+
const state = (info.state ?? "OPEN").trim().toUpperCase();
|
|
6474
|
+
if (state !== "OPEN") return null;
|
|
6475
|
+
return info;
|
|
6409
6476
|
} catch {
|
|
6410
6477
|
return null;
|
|
6411
6478
|
}
|
|
@@ -10988,7 +11055,18 @@ function toCodexDisableUserMcpArgs(names) {
|
|
|
10988
11055
|
return args;
|
|
10989
11056
|
}
|
|
10990
11057
|
function toCodexUnattendedAppsArgs() {
|
|
10991
|
-
return [
|
|
11058
|
+
return [
|
|
11059
|
+
"-c",
|
|
11060
|
+
"features.apps=false",
|
|
11061
|
+
"-c",
|
|
11062
|
+
"apps._default.enabled=false",
|
|
11063
|
+
"-c",
|
|
11064
|
+
"apps.posthog.enabled=false",
|
|
11065
|
+
"-c",
|
|
11066
|
+
"features.plugins=false",
|
|
11067
|
+
"-c",
|
|
11068
|
+
"mcp_servers.posthog.enabled=false"
|
|
11069
|
+
];
|
|
10992
11070
|
}
|
|
10993
11071
|
var import_node_fs28, import_node_os11, import_node_path29, import_smol_toml2;
|
|
10994
11072
|
var init_orch_mcp_isolation = __esm({
|
|
@@ -12698,6 +12776,33 @@ var init_agent_git_actions = __esm({
|
|
|
12698
12776
|
function normalizePrState(state) {
|
|
12699
12777
|
return (state ?? "").trim().toUpperCase();
|
|
12700
12778
|
}
|
|
12779
|
+
function sameLoginList(a, b) {
|
|
12780
|
+
const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
12781
|
+
const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
12782
|
+
return left.length === right.length && left.every((login, i) => login === right[i]);
|
|
12783
|
+
}
|
|
12784
|
+
function threadPrMetaPatch(thread, meta) {
|
|
12785
|
+
const prevState = normalizePrState(thread.prState);
|
|
12786
|
+
const nextState = normalizePrState(meta.state);
|
|
12787
|
+
const patch = {};
|
|
12788
|
+
if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
|
|
12789
|
+
if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
|
|
12790
|
+
if (nextState && nextState !== prevState) patch.prState = nextState;
|
|
12791
|
+
const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
|
|
12792
|
+
if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
|
|
12793
|
+
const nextAuthor = meta.authorLogin?.trim() || "";
|
|
12794
|
+
if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
|
|
12795
|
+
patch.prAuthorLogin = nextAuthor;
|
|
12796
|
+
}
|
|
12797
|
+
const nextReviewers = meta.reviewerLogins ?? [];
|
|
12798
|
+
if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
|
|
12799
|
+
patch.prReviewerLogins = nextReviewers;
|
|
12800
|
+
}
|
|
12801
|
+
if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
|
|
12802
|
+
patch.skipAutoArchiveOnMerge = false;
|
|
12803
|
+
}
|
|
12804
|
+
return patch;
|
|
12805
|
+
}
|
|
12701
12806
|
function shouldAutoArchiveOnPrMerge(opts) {
|
|
12702
12807
|
if (!opts.autoArchiveEnabled) return false;
|
|
12703
12808
|
if (opts.isGlobal) return false;
|
|
@@ -15373,6 +15478,697 @@ var init_home_board = __esm({
|
|
|
15373
15478
|
}
|
|
15374
15479
|
});
|
|
15375
15480
|
|
|
15481
|
+
// src/integrations/abletime-oauth.ts
|
|
15482
|
+
var abletime_oauth_exports = {};
|
|
15483
|
+
__export(abletime_oauth_exports, {
|
|
15484
|
+
ABLETIME_OAUTH_CALLBACK_PATH: () => ABLETIME_OAUTH_CALLBACK_PATH,
|
|
15485
|
+
ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
|
|
15486
|
+
ABLETIME_OAUTH_LOCAL_CALLBACK: () => ABLETIME_OAUTH_LOCAL_CALLBACK,
|
|
15487
|
+
ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
|
|
15488
|
+
ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
|
|
15489
|
+
ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
|
|
15490
|
+
AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
|
|
15491
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
15492
|
+
ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
|
|
15493
|
+
ableTimeOAuthBouncePage: () => ableTimeOAuthBouncePage,
|
|
15494
|
+
ableTimeOAuthClientId: () => ableTimeOAuthClientId,
|
|
15495
|
+
ableTimeOAuthLocalBounceUrl: () => ableTimeOAuthLocalBounceUrl,
|
|
15496
|
+
ableTimeOAuthRedirectUri: () => ableTimeOAuthRedirectUri,
|
|
15497
|
+
ableTimeOAuthResource: () => ableTimeOAuthResource,
|
|
15498
|
+
createAbleTimePkce: () => createAbleTimePkce,
|
|
15499
|
+
ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
|
|
15500
|
+
hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
|
|
15501
|
+
isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
|
|
15502
|
+
parseAbleTimeOAuthCallbackUrl: () => parseAbleTimeOAuthCallbackUrl,
|
|
15503
|
+
startAbleTimeOAuth: () => startAbleTimeOAuth
|
|
15504
|
+
});
|
|
15505
|
+
function ableTimeOAuthRedirectUri() {
|
|
15506
|
+
return process.env.SIDEBOARD_ABLETIME_OAUTH_REDIRECT?.trim() || ABLETIME_OAUTH_REDIRECT;
|
|
15507
|
+
}
|
|
15508
|
+
function parseAbleTimeOAuthCallbackUrl(reqUrl) {
|
|
15509
|
+
let url;
|
|
15510
|
+
try {
|
|
15511
|
+
url = new URL(reqUrl, ABLETIME_OAUTH_REDIRECT);
|
|
15512
|
+
} catch {
|
|
15513
|
+
return null;
|
|
15514
|
+
}
|
|
15515
|
+
if (url.pathname !== ABLETIME_OAUTH_CALLBACK_PATH) return null;
|
|
15516
|
+
return url;
|
|
15517
|
+
}
|
|
15518
|
+
function ableTimeOAuthLocalBounceUrl(reqUrl) {
|
|
15519
|
+
const url = parseAbleTimeOAuthCallbackUrl(reqUrl);
|
|
15520
|
+
if (!url) return null;
|
|
15521
|
+
const dest = new URL(ABLETIME_OAUTH_LOCAL_CALLBACK);
|
|
15522
|
+
for (const key of BOUNCE_QUERY_KEYS) {
|
|
15523
|
+
const value = url.searchParams.get(key);
|
|
15524
|
+
if (value) dest.searchParams.set(key, value);
|
|
15525
|
+
}
|
|
15526
|
+
return dest.toString();
|
|
15527
|
+
}
|
|
15528
|
+
function escapeHtml(value) {
|
|
15529
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
15530
|
+
}
|
|
15531
|
+
function ableTimeOAuthBouncePage(localUrl) {
|
|
15532
|
+
const safe = escapeHtml(localUrl);
|
|
15533
|
+
return `<!doctype html><html><head><meta charset="utf-8">
|
|
15534
|
+
<meta http-equiv="refresh" content="0;url=${safe}">
|
|
15535
|
+
<title>Returning to Sideboard</title>
|
|
15536
|
+
<script>location.replace(${JSON.stringify(localUrl)})</script>
|
|
15537
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
15538
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}a{color:#0f7ed4}</style>
|
|
15539
|
+
</head><body>
|
|
15540
|
+
<h1>Returning to Sideboard</h1>
|
|
15541
|
+
<p>AbleTime authorized. Opening the app\u2026</p>
|
|
15542
|
+
<p><a href="${safe}">Open Sideboard</a> if it does not appear.</p>
|
|
15543
|
+
</body></html>`;
|
|
15544
|
+
}
|
|
15545
|
+
function hasBakedAbleTimeOAuth() {
|
|
15546
|
+
return Boolean(BAKED_ABLETIME_OAUTH_CLIENT_ID.trim());
|
|
15547
|
+
}
|
|
15548
|
+
function ableTimeOAuthClientId() {
|
|
15549
|
+
return process.env.SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID?.trim() || BAKED_ABLETIME_OAUTH_CLIENT_ID.trim();
|
|
15550
|
+
}
|
|
15551
|
+
function ableTimeOAuthResource(host) {
|
|
15552
|
+
return `${normalizeAbleTimeHost(host)}/api/public/v2/mcp`;
|
|
15553
|
+
}
|
|
15554
|
+
function createAbleTimePkce() {
|
|
15555
|
+
const verifier = (0, import_node_crypto9.randomBytes)(32).toString("base64url");
|
|
15556
|
+
const challenge = (0, import_node_crypto9.createHash)("sha256").update(verifier).digest("base64url");
|
|
15557
|
+
return { verifier, challenge };
|
|
15558
|
+
}
|
|
15559
|
+
function ableTimeOAuthAuthorizeUrl(clientId, state, codeChallenge, host) {
|
|
15560
|
+
const base = normalizeAbleTimeHost(host);
|
|
15561
|
+
const params = new URLSearchParams({
|
|
15562
|
+
response_type: "code",
|
|
15563
|
+
client_id: clientId,
|
|
15564
|
+
redirect_uri: ableTimeOAuthRedirectUri(),
|
|
15565
|
+
code_challenge: codeChallenge,
|
|
15566
|
+
code_challenge_method: "S256",
|
|
15567
|
+
state,
|
|
15568
|
+
resource: ableTimeOAuthResource(host)
|
|
15569
|
+
});
|
|
15570
|
+
return `${base}/oauth/authorize?${params.toString()}`;
|
|
15571
|
+
}
|
|
15572
|
+
function htmlPage(title, body) {
|
|
15573
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
|
|
15574
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
15575
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
15576
|
+
}
|
|
15577
|
+
async function exchangeAbleTimeToken(body, host) {
|
|
15578
|
+
const url = `${normalizeAbleTimeHost(host)}/api/oauth/token`;
|
|
15579
|
+
const res = await httpFetch(url, {
|
|
15580
|
+
method: "POST",
|
|
15581
|
+
headers: {
|
|
15582
|
+
Accept: "application/json",
|
|
15583
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
15584
|
+
},
|
|
15585
|
+
body
|
|
15586
|
+
});
|
|
15587
|
+
const data = await res.json().catch(() => ({}));
|
|
15588
|
+
if (!res.ok || data.error || !data.access_token?.trim()) {
|
|
15589
|
+
const detail = data.error_description || data.error || `HTTP ${res.status}`;
|
|
15590
|
+
throw new Error(`AbleTime OAuth token exchange failed: ${detail}`);
|
|
15591
|
+
}
|
|
15592
|
+
return data;
|
|
15593
|
+
}
|
|
15594
|
+
function persistAbleTimeTokens(data, input) {
|
|
15595
|
+
return saveAbleTimeConnection({
|
|
15596
|
+
accessToken: data.access_token.trim(),
|
|
15597
|
+
refreshToken: data.refresh_token?.trim(),
|
|
15598
|
+
expiresIn: data.expires_in,
|
|
15599
|
+
host: input?.host,
|
|
15600
|
+
viewerName: input?.viewerName
|
|
15601
|
+
});
|
|
15602
|
+
}
|
|
15603
|
+
async function refreshAbleTimeAccessToken(refreshToken) {
|
|
15604
|
+
const host = getAbleTimeHost();
|
|
15605
|
+
const body = new URLSearchParams({
|
|
15606
|
+
grant_type: "refresh_token",
|
|
15607
|
+
refresh_token: refreshToken,
|
|
15608
|
+
client_id: ableTimeOAuthClientId(),
|
|
15609
|
+
resource: ableTimeOAuthResource(host)
|
|
15610
|
+
});
|
|
15611
|
+
try {
|
|
15612
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
15613
|
+
persistAbleTimeTokens(data, { host });
|
|
15614
|
+
return data.access_token.trim();
|
|
15615
|
+
} catch (err) {
|
|
15616
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15617
|
+
if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
|
|
15618
|
+
disconnectAbleTimeConnection();
|
|
15619
|
+
throw new Error("AbleTime sign-in expired \u2014 connect again in Account settings");
|
|
15620
|
+
}
|
|
15621
|
+
throw err;
|
|
15622
|
+
}
|
|
15623
|
+
}
|
|
15624
|
+
async function ensureAbleTimeAccessToken() {
|
|
15625
|
+
const settings = loadAppSettings();
|
|
15626
|
+
const access = settings.integrations.abletimeAccessToken?.trim() || "";
|
|
15627
|
+
const refresh = settings.integrations.abletimeRefreshToken?.trim() || "";
|
|
15628
|
+
const expiresAt = settings.integrations.abletimeTokenExpiresAt ?? 0;
|
|
15629
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
|
|
15630
|
+
return access;
|
|
15631
|
+
}
|
|
15632
|
+
if (refresh) {
|
|
15633
|
+
if (!refreshInFlight) {
|
|
15634
|
+
refreshInFlight = refreshAbleTimeAccessToken(refresh).finally(() => {
|
|
15635
|
+
refreshInFlight = null;
|
|
15636
|
+
});
|
|
15637
|
+
}
|
|
15638
|
+
return refreshInFlight;
|
|
15639
|
+
}
|
|
15640
|
+
return access || null;
|
|
15641
|
+
}
|
|
15642
|
+
function isAbleTimeOAuthCancelled(err) {
|
|
15643
|
+
if (err instanceof AbleTimeOAuthCancelledError) return true;
|
|
15644
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15645
|
+
return message.includes(ABLETIME_OAUTH_CANCELLED);
|
|
15646
|
+
}
|
|
15647
|
+
async function startAbleTimeOAuth(opts) {
|
|
15648
|
+
if (opts?.signal?.aborted) {
|
|
15649
|
+
throw new AbleTimeOAuthCancelledError();
|
|
15650
|
+
}
|
|
15651
|
+
const clientId = ableTimeOAuthClientId();
|
|
15652
|
+
if (!clientId) {
|
|
15653
|
+
throw new Error(
|
|
15654
|
+
`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.`
|
|
15655
|
+
);
|
|
15656
|
+
}
|
|
15657
|
+
const host = opts?.host?.trim() || void 0;
|
|
15658
|
+
const state = (0, import_node_crypto9.randomBytes)(16).toString("hex");
|
|
15659
|
+
const pkce = createAbleTimePkce();
|
|
15660
|
+
const authorizeUrl = ableTimeOAuthAuthorizeUrl(clientId, state, pkce.challenge, host);
|
|
15661
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
15662
|
+
const code = await new Promise((resolve, reject) => {
|
|
15663
|
+
let settled = false;
|
|
15664
|
+
const finish = (err, value) => {
|
|
15665
|
+
if (settled) return;
|
|
15666
|
+
settled = true;
|
|
15667
|
+
cleanup();
|
|
15668
|
+
if (err) reject(err);
|
|
15669
|
+
else resolve(value);
|
|
15670
|
+
};
|
|
15671
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
15672
|
+
try {
|
|
15673
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${ABLETIME_OAUTH_PORT}`);
|
|
15674
|
+
if (url.pathname !== "/callback") {
|
|
15675
|
+
res.writeHead(404);
|
|
15676
|
+
res.end("Not found");
|
|
15677
|
+
return;
|
|
15678
|
+
}
|
|
15679
|
+
const err = url.searchParams.get("error");
|
|
15680
|
+
const gotState = url.searchParams.get("state");
|
|
15681
|
+
const gotCode = url.searchParams.get("code");
|
|
15682
|
+
if (err) {
|
|
15683
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
15684
|
+
res.end(htmlPage("AbleTime", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
15685
|
+
finish(new AbleTimeOAuthCancelledError(`AbleTime OAuth: ${err}`));
|
|
15686
|
+
return;
|
|
15687
|
+
}
|
|
15688
|
+
if (gotState !== state || !gotCode) {
|
|
15689
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
15690
|
+
res.end(htmlPage("AbleTime", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
15691
|
+
finish(new Error("AbleTime OAuth callback was invalid"));
|
|
15692
|
+
return;
|
|
15693
|
+
}
|
|
15694
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
15695
|
+
res.end(
|
|
15696
|
+
htmlPage(
|
|
15697
|
+
"AbleTime connected",
|
|
15698
|
+
"<h1>AbleTime connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
15699
|
+
)
|
|
15700
|
+
);
|
|
15701
|
+
finish(void 0, gotCode);
|
|
15702
|
+
} catch (e) {
|
|
15703
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
15704
|
+
}
|
|
15705
|
+
});
|
|
15706
|
+
const timer = setTimeout(() => {
|
|
15707
|
+
finish(new Error("AbleTime sign-in timed out \u2014 try again"));
|
|
15708
|
+
}, timeoutMs);
|
|
15709
|
+
const onAbort = () => finish(new AbleTimeOAuthCancelledError());
|
|
15710
|
+
const cleanup = () => {
|
|
15711
|
+
clearTimeout(timer);
|
|
15712
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
15713
|
+
server.close();
|
|
15714
|
+
};
|
|
15715
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
15716
|
+
if (opts?.signal?.aborted) {
|
|
15717
|
+
onAbort();
|
|
15718
|
+
return;
|
|
15719
|
+
}
|
|
15720
|
+
server.on("error", (e) => {
|
|
15721
|
+
finish(
|
|
15722
|
+
e instanceof Error && e.code === "EADDRINUSE" ? new Error(
|
|
15723
|
+
`Port ${ABLETIME_OAUTH_PORT} is in use. Close whatever is bound there, or paste an AbleTime token instead.`
|
|
15724
|
+
) : e instanceof Error ? e : new Error(String(e))
|
|
15725
|
+
);
|
|
15726
|
+
});
|
|
15727
|
+
server.listen(ABLETIME_OAUTH_PORT, "127.0.0.1", () => {
|
|
15728
|
+
void Promise.resolve(opts?.openUrl?.(authorizeUrl)).catch((e) => {
|
|
15729
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
15730
|
+
});
|
|
15731
|
+
});
|
|
15732
|
+
});
|
|
15733
|
+
const body = new URLSearchParams({
|
|
15734
|
+
grant_type: "authorization_code",
|
|
15735
|
+
code,
|
|
15736
|
+
redirect_uri: ableTimeOAuthRedirectUri(),
|
|
15737
|
+
client_id: clientId,
|
|
15738
|
+
code_verifier: pkce.verifier,
|
|
15739
|
+
resource: ableTimeOAuthResource(host)
|
|
15740
|
+
});
|
|
15741
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
15742
|
+
persistAbleTimeTokens(data, { host });
|
|
15743
|
+
const token = data.access_token.trim();
|
|
15744
|
+
try {
|
|
15745
|
+
const { getAbleTimeOrientation: getAbleTimeOrientation2 } = await Promise.resolve().then(() => (init_abletime(), abletime_exports));
|
|
15746
|
+
const orientation = await getAbleTimeOrientation2({ token, host });
|
|
15747
|
+
return saveAbleTimeConnection({
|
|
15748
|
+
accessToken: token,
|
|
15749
|
+
refreshToken: data.refresh_token?.trim(),
|
|
15750
|
+
expiresIn: data.expires_in,
|
|
15751
|
+
host,
|
|
15752
|
+
viewerName: orientation.viewer.name
|
|
15753
|
+
});
|
|
15754
|
+
} catch {
|
|
15755
|
+
return loadAppSettings();
|
|
15756
|
+
}
|
|
15757
|
+
}
|
|
15758
|
+
var import_node_crypto9, import_node_http, ABLETIME_OAUTH_CALLBACK_PATH, ABLETIME_OAUTH_PORT, ABLETIME_OAUTH_LOCAL_CALLBACK, ABLETIME_OAUTH_REDIRECT, BOUNCE_QUERY_KEYS, BAKED_ABLETIME_OAUTH_CLIENT_ID, ABLETIME_OAUTH_RESOURCE, REFRESH_SKEW_MS2, refreshInFlight, ABLETIME_OAUTH_CANCELLED, AbleTimeOAuthCancelledError;
|
|
15759
|
+
var init_abletime_oauth = __esm({
|
|
15760
|
+
"src/integrations/abletime-oauth.ts"() {
|
|
15761
|
+
"use strict";
|
|
15762
|
+
import_node_crypto9 = require("crypto");
|
|
15763
|
+
import_node_http = require("http");
|
|
15764
|
+
init_fetch();
|
|
15765
|
+
init_app_settings();
|
|
15766
|
+
init_abletime_mcp();
|
|
15767
|
+
ABLETIME_OAUTH_CALLBACK_PATH = "/oauth/abletime/callback";
|
|
15768
|
+
ABLETIME_OAUTH_PORT = 19849;
|
|
15769
|
+
ABLETIME_OAUTH_LOCAL_CALLBACK = `http://127.0.0.1:${ABLETIME_OAUTH_PORT}/callback`;
|
|
15770
|
+
ABLETIME_OAUTH_REDIRECT = `https://www.sideboard.cloud${ABLETIME_OAUTH_CALLBACK_PATH}`;
|
|
15771
|
+
BOUNCE_QUERY_KEYS = ["code", "state", "error", "error_description"];
|
|
15772
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID = "https://www.sideboard.cloud/oauth/abletime-client.json";
|
|
15773
|
+
ABLETIME_OAUTH_RESOURCE = `${DEFAULT_ABLETIME_HOST}/api/public/v2/mcp`;
|
|
15774
|
+
REFRESH_SKEW_MS2 = 5 * 6e4;
|
|
15775
|
+
refreshInFlight = null;
|
|
15776
|
+
ABLETIME_OAUTH_CANCELLED = "AbleTime sign-in cancelled";
|
|
15777
|
+
AbleTimeOAuthCancelledError = class extends Error {
|
|
15778
|
+
constructor(message = ABLETIME_OAUTH_CANCELLED) {
|
|
15779
|
+
super(message);
|
|
15780
|
+
this.name = "AbleTimeOAuthCancelledError";
|
|
15781
|
+
}
|
|
15782
|
+
};
|
|
15783
|
+
}
|
|
15784
|
+
});
|
|
15785
|
+
|
|
15786
|
+
// src/integrations/abletime-rest.ts
|
|
15787
|
+
var abletime_rest_exports = {};
|
|
15788
|
+
__export(abletime_rest_exports, {
|
|
15789
|
+
ABLETIME_REST_PREFIX: () => ABLETIME_REST_PREFIX,
|
|
15790
|
+
ableTimeDocFromText: () => ableTimeDocFromText,
|
|
15791
|
+
abletimeRestUrl: () => abletimeRestUrl,
|
|
15792
|
+
callAbleTimeRestTool: () => callAbleTimeRestTool,
|
|
15793
|
+
looksLikeAbleTimeUlid: () => looksLikeAbleTimeUlid,
|
|
15794
|
+
textFromAbleTimeDoc: () => textFromAbleTimeDoc
|
|
15795
|
+
});
|
|
15796
|
+
function asRecord4(value) {
|
|
15797
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15798
|
+
}
|
|
15799
|
+
function asString(value) {
|
|
15800
|
+
return typeof value === "string" ? value.trim() : "";
|
|
15801
|
+
}
|
|
15802
|
+
function asList(value) {
|
|
15803
|
+
if (Array.isArray(value)) return value;
|
|
15804
|
+
const record = asRecord4(value);
|
|
15805
|
+
if (!record) return [];
|
|
15806
|
+
for (const key of ["data", "tasks", "projects", "users", "items", "results"]) {
|
|
15807
|
+
if (Array.isArray(record[key])) return record[key];
|
|
15808
|
+
}
|
|
15809
|
+
return [];
|
|
15810
|
+
}
|
|
15811
|
+
function ableTimeDocFromText(text7) {
|
|
15812
|
+
const trimmed = text7.trim();
|
|
15813
|
+
if (!trimmed) return { type: "doc", content: [] };
|
|
15814
|
+
return {
|
|
15815
|
+
type: "doc",
|
|
15816
|
+
content: [{ type: "paragraph", content: [{ type: "text", text: trimmed }] }]
|
|
15817
|
+
};
|
|
15818
|
+
}
|
|
15819
|
+
function textFromAbleTimeDoc(value) {
|
|
15820
|
+
if (typeof value === "string") return value.trim();
|
|
15821
|
+
const record = asRecord4(value);
|
|
15822
|
+
if (!record) return "";
|
|
15823
|
+
if (typeof record.text === "string" && record.text.trim()) return record.text.trim();
|
|
15824
|
+
const chunks = [];
|
|
15825
|
+
const walk = (node) => {
|
|
15826
|
+
const rec = asRecord4(node);
|
|
15827
|
+
if (!rec) return;
|
|
15828
|
+
if (typeof rec.text === "string" && rec.text) chunks.push(rec.text);
|
|
15829
|
+
if (Array.isArray(rec.content)) rec.content.forEach(walk);
|
|
15830
|
+
};
|
|
15831
|
+
walk(record);
|
|
15832
|
+
return chunks.join("").trim();
|
|
15833
|
+
}
|
|
15834
|
+
function abletimeRestUrl(path, host, query) {
|
|
15835
|
+
const base = `${normalizeAbleTimeHost(host)}${ABLETIME_REST_PREFIX}`;
|
|
15836
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
15837
|
+
const url = new URL(`${base}${suffix}`);
|
|
15838
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
15839
|
+
if (value) url.searchParams.set(key, value);
|
|
15840
|
+
}
|
|
15841
|
+
return url.href;
|
|
15842
|
+
}
|
|
15843
|
+
function looksLikeAbleTimeUlid(id) {
|
|
15844
|
+
return ULID_RE.test(id.trim());
|
|
15845
|
+
}
|
|
15846
|
+
async function abletimeRestRequest(path, opts = {}) {
|
|
15847
|
+
const raw = opts.token ?? getAbleTimeAccessToken();
|
|
15848
|
+
const token = normalizeAbleTimeCredential(raw ?? "");
|
|
15849
|
+
if (!token) {
|
|
15850
|
+
throw new Error("AbleTime is not connected \u2014 paste an API key or personal access token in Account settings");
|
|
15851
|
+
}
|
|
15852
|
+
const host = opts.host ?? (opts.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost());
|
|
15853
|
+
const url = abletimeRestUrl(path, host, opts.query);
|
|
15854
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
15855
|
+
const res = await httpFetch(url, {
|
|
15856
|
+
method,
|
|
15857
|
+
headers: {
|
|
15858
|
+
Accept: "application/json",
|
|
15859
|
+
Authorization: `Bearer ${token}`,
|
|
15860
|
+
...opts.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
15861
|
+
},
|
|
15862
|
+
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
|
|
15863
|
+
});
|
|
15864
|
+
const text7 = await res.text().catch(() => "");
|
|
15865
|
+
if (!res.ok) {
|
|
15866
|
+
throw new Error(
|
|
15867
|
+
rewriteAbleTimeError(
|
|
15868
|
+
`AbleTime API error ${res.status}${text7 ? `: ${text7.slice(0, 240)}` : ""}`
|
|
15869
|
+
)
|
|
15870
|
+
);
|
|
15871
|
+
}
|
|
15872
|
+
if (!text7.trim()) return null;
|
|
15873
|
+
try {
|
|
15874
|
+
return JSON.parse(text7);
|
|
15875
|
+
} catch {
|
|
15876
|
+
throw new Error(`AbleTime API returned a non-JSON response: ${text7.slice(0, 200)}`);
|
|
15877
|
+
}
|
|
15878
|
+
}
|
|
15879
|
+
async function restPages(path, opts, maxPages = 20) {
|
|
15880
|
+
const items = [];
|
|
15881
|
+
let cursor;
|
|
15882
|
+
for (let i = 0; i < maxPages; i += 1) {
|
|
15883
|
+
const raw = await abletimeRestRequest(path, {
|
|
15884
|
+
...opts,
|
|
15885
|
+
query: { ...opts.query, ...cursor ? { cursor } : {} }
|
|
15886
|
+
});
|
|
15887
|
+
items.push(...asList(raw));
|
|
15888
|
+
const page = asRecord4(asRecord4(raw)?.page);
|
|
15889
|
+
const next = page ? asString(page.nextCursor) : "";
|
|
15890
|
+
if (!next) break;
|
|
15891
|
+
cursor = next;
|
|
15892
|
+
}
|
|
15893
|
+
return items;
|
|
15894
|
+
}
|
|
15895
|
+
function userDisplayName(raw) {
|
|
15896
|
+
const rec = asRecord4(raw);
|
|
15897
|
+
if (!rec) return null;
|
|
15898
|
+
const id = asString(rec.userId) || asString(rec.id);
|
|
15899
|
+
const name = [asString(rec.firstName), asString(rec.lastName)].filter(Boolean).join(" ") || asString(rec.username) || asString(rec.email) || asString(rec.name);
|
|
15900
|
+
if (!name) return null;
|
|
15901
|
+
return { id: id || void 0, name };
|
|
15902
|
+
}
|
|
15903
|
+
function restTaskToRaw(raw, users) {
|
|
15904
|
+
const rec = asRecord4(raw);
|
|
15905
|
+
if (!rec) return null;
|
|
15906
|
+
const id = asString(rec.timeflowTaskId) || asString(rec.taskId) || asString(rec.id);
|
|
15907
|
+
const identifier = asString(rec.taskRef) || asString(rec.reference) || id;
|
|
15908
|
+
const title = asString(rec.title);
|
|
15909
|
+
if (!id && !identifier && !title) return null;
|
|
15910
|
+
const assigned = asString(rec.assignedUserId);
|
|
15911
|
+
const assignee = assigned ? users.get(assigned) : void 0;
|
|
15912
|
+
const comments = asList(rec.comments).map((item) => {
|
|
15913
|
+
const comment = asRecord4(item) ?? {};
|
|
15914
|
+
const user = userDisplayName(comment);
|
|
15915
|
+
return {
|
|
15916
|
+
id: asString(comment.commentId) || asString(comment.id),
|
|
15917
|
+
body: textFromAbleTimeDoc(comment.content) || asString(comment.contentSanitized) || asString(comment.body),
|
|
15918
|
+
createdAt: asString(comment.createdAt) || asString(comment.lastUpdate),
|
|
15919
|
+
user: user?.name || asString(comment.username)
|
|
15920
|
+
};
|
|
15921
|
+
});
|
|
15922
|
+
return {
|
|
15923
|
+
id,
|
|
15924
|
+
taskId: id,
|
|
15925
|
+
timeflowTaskId: id,
|
|
15926
|
+
reference: identifier,
|
|
15927
|
+
taskRef: identifier,
|
|
15928
|
+
title: title || identifier,
|
|
15929
|
+
state: asString(rec.taskState) || asString(rec.state),
|
|
15930
|
+
description: textFromAbleTimeDoc(rec.description) || asString(rec.descriptionSanitized),
|
|
15931
|
+
projectId: asString(rec.projectId),
|
|
15932
|
+
categoryId: asString(rec.projectCategoryId) || asString(rec.categoryId),
|
|
15933
|
+
tags: rec.tags,
|
|
15934
|
+
comments,
|
|
15935
|
+
createdAt: asString(rec.dateCreated) || asString(rec.createdAt),
|
|
15936
|
+
updatedAt: asString(rec.lastUpdate) || asString(rec.updatedAt),
|
|
15937
|
+
...assignee ? { assignee } : {}
|
|
15938
|
+
};
|
|
15939
|
+
}
|
|
15940
|
+
function restProjectToRaw(raw) {
|
|
15941
|
+
const rec = asRecord4(raw);
|
|
15942
|
+
if (!rec) return null;
|
|
15943
|
+
const id = asString(rec.projectId) || asString(rec.id);
|
|
15944
|
+
const name = asString(rec.projectName) || asString(rec.name);
|
|
15945
|
+
if (!id && !name) return null;
|
|
15946
|
+
const categories = asList(rec.categories).map((item) => {
|
|
15947
|
+
const cat = asRecord4(item) ?? {};
|
|
15948
|
+
return {
|
|
15949
|
+
id: asString(cat.projectCategoryId) || asString(cat.categoryId) || asString(cat.id),
|
|
15950
|
+
name: asString(cat.categoryName) || asString(cat.name)
|
|
15951
|
+
};
|
|
15952
|
+
});
|
|
15953
|
+
return { id, name, projectId: id, projectName: name, categories };
|
|
15954
|
+
}
|
|
15955
|
+
async function listUsers(opts) {
|
|
15956
|
+
const users = /* @__PURE__ */ new Map();
|
|
15957
|
+
for (const item of await restPages("/users", opts).catch(() => [])) {
|
|
15958
|
+
const user = userDisplayName(item);
|
|
15959
|
+
const rec = asRecord4(item);
|
|
15960
|
+
const id = user?.id || (rec ? asString(rec.userId) : "");
|
|
15961
|
+
if (user && id) users.set(id, user);
|
|
15962
|
+
}
|
|
15963
|
+
return users;
|
|
15964
|
+
}
|
|
15965
|
+
async function listProjectsRaw(opts) {
|
|
15966
|
+
const items = await restPages("/projects", { ...opts, query: { expand: "categories" } });
|
|
15967
|
+
return items.map(restProjectToRaw).filter((item) => Boolean(item));
|
|
15968
|
+
}
|
|
15969
|
+
async function listTasksRaw(opts) {
|
|
15970
|
+
const [users, items] = await Promise.all([
|
|
15971
|
+
listUsers(opts),
|
|
15972
|
+
restPages("/tasks", {
|
|
15973
|
+
...opts,
|
|
15974
|
+
query: {
|
|
15975
|
+
...opts.projectId ? { projectId: opts.projectId } : {}
|
|
15976
|
+
}
|
|
15977
|
+
})
|
|
15978
|
+
]);
|
|
15979
|
+
let tasks = items.map((item) => restTaskToRaw(item, users)).filter((item) => Boolean(item));
|
|
15980
|
+
const needle = opts.query?.trim().toLowerCase();
|
|
15981
|
+
if (needle) {
|
|
15982
|
+
tasks = tasks.filter((task) => {
|
|
15983
|
+
const hay = [task.title, task.reference, task.taskRef, task.id].map((value) => String(value ?? "").toLowerCase()).join(" ");
|
|
15984
|
+
const tags = Array.isArray(task.tags) ? task.tags.join(" ").toLowerCase() : "";
|
|
15985
|
+
return hay.includes(needle) || tags.includes(needle);
|
|
15986
|
+
});
|
|
15987
|
+
}
|
|
15988
|
+
return tasks;
|
|
15989
|
+
}
|
|
15990
|
+
async function resolveTaskId(id, opts) {
|
|
15991
|
+
const wanted = id.trim();
|
|
15992
|
+
if (!wanted) throw new Error("AbleTime task id is required");
|
|
15993
|
+
if (looksLikeAbleTimeUlid(wanted)) return wanted;
|
|
15994
|
+
const tasks = await listTasksRaw(opts);
|
|
15995
|
+
const match = tasks.find((task) => {
|
|
15996
|
+
const ref = String(task.reference ?? task.taskRef ?? "").toLowerCase();
|
|
15997
|
+
const taskId = String(task.id ?? "").toLowerCase();
|
|
15998
|
+
return ref === wanted.toLowerCase() || taskId === wanted.toLowerCase();
|
|
15999
|
+
});
|
|
16000
|
+
if (!match?.id) throw new Error(`AbleTime task not found: ${id}`);
|
|
16001
|
+
return String(match.id);
|
|
16002
|
+
}
|
|
16003
|
+
async function getTaskRaw(id, opts) {
|
|
16004
|
+
const taskId = await resolveTaskId(id, opts);
|
|
16005
|
+
const [users, raw] = await Promise.all([
|
|
16006
|
+
listUsers(opts),
|
|
16007
|
+
abletimeRestRequest(`/tasks/${encodeURIComponent(taskId)}`, {
|
|
16008
|
+
...opts,
|
|
16009
|
+
query: { expand: "comments" }
|
|
16010
|
+
})
|
|
16011
|
+
]);
|
|
16012
|
+
const task = restTaskToRaw(raw, users);
|
|
16013
|
+
if (!task) throw new Error(`AbleTime task not found: ${id}`);
|
|
16014
|
+
return task;
|
|
16015
|
+
}
|
|
16016
|
+
function firstStringArg(args, keys) {
|
|
16017
|
+
for (const key of keys) {
|
|
16018
|
+
const value = asString(args[key]);
|
|
16019
|
+
if (value) return value;
|
|
16020
|
+
}
|
|
16021
|
+
return "";
|
|
16022
|
+
}
|
|
16023
|
+
async function callAbleTimeRestTool(name, args = {}, opts) {
|
|
16024
|
+
const token = normalizeAbleTimeCredential(opts?.token ?? getAbleTimeAccessToken() ?? "");
|
|
16025
|
+
if (!token) {
|
|
16026
|
+
throw new Error("AbleTime is not connected \u2014 paste an API key or connect via browser");
|
|
16027
|
+
}
|
|
16028
|
+
if (ableTimeCredentialKind(token) === "pat") {
|
|
16029
|
+
throw new Error("AbleTime REST transport is for organization keys and OAuth tokens, not apt_\u2026");
|
|
16030
|
+
}
|
|
16031
|
+
const requestOpts = { ...opts, token };
|
|
16032
|
+
switch (name) {
|
|
16033
|
+
case "orientation": {
|
|
16034
|
+
const [users, projects, tasks] = await Promise.all([
|
|
16035
|
+
listUsers(requestOpts),
|
|
16036
|
+
listProjectsRaw(requestOpts),
|
|
16037
|
+
listTasksRaw(requestOpts)
|
|
16038
|
+
]);
|
|
16039
|
+
const viewer = [...users.values()].find((user) => user.name) ?? [...users.values()][0] ?? { name: "AbleTime" };
|
|
16040
|
+
return { viewer, projects, tasks };
|
|
16041
|
+
}
|
|
16042
|
+
case "list_projects":
|
|
16043
|
+
return { data: await listProjectsRaw(requestOpts) };
|
|
16044
|
+
case "list_users":
|
|
16045
|
+
return { data: [...(await listUsers(requestOpts)).values()] };
|
|
16046
|
+
case "list_tasks":
|
|
16047
|
+
return {
|
|
16048
|
+
data: await listTasksRaw({
|
|
16049
|
+
...requestOpts,
|
|
16050
|
+
projectId: firstStringArg(args, ["project_id", "projectId", "project"])
|
|
16051
|
+
})
|
|
16052
|
+
};
|
|
16053
|
+
case "search_tasks":
|
|
16054
|
+
return {
|
|
16055
|
+
data: await listTasksRaw({
|
|
16056
|
+
...requestOpts,
|
|
16057
|
+
query: firstStringArg(args, ["query", "q"])
|
|
16058
|
+
})
|
|
16059
|
+
};
|
|
16060
|
+
case "get_task":
|
|
16061
|
+
return await getTaskRaw(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
16062
|
+
case "create_task": {
|
|
16063
|
+
const title = firstStringArg(args, ["title"]);
|
|
16064
|
+
if (!title) throw new Error("AbleTime task title is required");
|
|
16065
|
+
const projectId = firstStringArg(args, ["project_id", "projectId", "project"]);
|
|
16066
|
+
if (!projectId) throw new Error("AbleTime project is required");
|
|
16067
|
+
const projects = await listProjectsRaw(requestOpts);
|
|
16068
|
+
const project = projects.find(
|
|
16069
|
+
(item) => String(item.id).toLowerCase() === projectId.toLowerCase() || String(item.name).toLowerCase() === projectId.toLowerCase()
|
|
16070
|
+
) ?? projects[0];
|
|
16071
|
+
if (!project) throw new Error("AbleTime has no projects to create a task in");
|
|
16072
|
+
const categories = asList(project.categories);
|
|
16073
|
+
const wantedCategory = firstStringArg(args, ["category_id", "categoryId", "category"]);
|
|
16074
|
+
const category = categories.map((item) => asRecord4(item)).find((item) => {
|
|
16075
|
+
if (!item || !wantedCategory) return false;
|
|
16076
|
+
return asString(item.id).toLowerCase() === wantedCategory.toLowerCase() || asString(item.name).toLowerCase() === wantedCategory.toLowerCase();
|
|
16077
|
+
}) ?? asRecord4(categories[0]);
|
|
16078
|
+
const created = await abletimeRestRequest("/tasks", {
|
|
16079
|
+
...requestOpts,
|
|
16080
|
+
method: "POST",
|
|
16081
|
+
body: {
|
|
16082
|
+
projectId: project.id,
|
|
16083
|
+
projectCategoryId: category ? asString(category.id) : void 0,
|
|
16084
|
+
title
|
|
16085
|
+
}
|
|
16086
|
+
});
|
|
16087
|
+
const state = firstStringArg(args, ["state"]);
|
|
16088
|
+
const createdId = asString(asRecord4(created)?.timeflowTaskId);
|
|
16089
|
+
const description = asString(args.description);
|
|
16090
|
+
if (createdId && description) {
|
|
16091
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}`, {
|
|
16092
|
+
...requestOpts,
|
|
16093
|
+
method: "PATCH",
|
|
16094
|
+
body: { description: ableTimeDocFromText(description) }
|
|
16095
|
+
}).catch(() => void 0);
|
|
16096
|
+
}
|
|
16097
|
+
if (createdId && state && state !== "todo") {
|
|
16098
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}/state`, {
|
|
16099
|
+
...requestOpts,
|
|
16100
|
+
method: "POST",
|
|
16101
|
+
body: { taskState: state }
|
|
16102
|
+
}).catch(() => void 0);
|
|
16103
|
+
}
|
|
16104
|
+
return createdId ? await getTaskRaw(createdId, requestOpts) : restTaskToRaw(created, await listUsers(requestOpts));
|
|
16105
|
+
}
|
|
16106
|
+
case "update_task": {
|
|
16107
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
16108
|
+
const patch = {};
|
|
16109
|
+
const title = firstStringArg(args, ["title"]);
|
|
16110
|
+
if (title) patch.title = title;
|
|
16111
|
+
if (args.description !== void 0) {
|
|
16112
|
+
patch.description = args.description == null || args.description === "" ? null : ableTimeDocFromText(String(args.description));
|
|
16113
|
+
}
|
|
16114
|
+
if (args.tags !== void 0 || args.labels !== void 0) {
|
|
16115
|
+
patch.tags = args.tags ?? args.labels ?? args.tag_names;
|
|
16116
|
+
}
|
|
16117
|
+
if (Object.keys(patch).length > 0) {
|
|
16118
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}`, {
|
|
16119
|
+
...requestOpts,
|
|
16120
|
+
method: "PATCH",
|
|
16121
|
+
body: patch
|
|
16122
|
+
});
|
|
16123
|
+
}
|
|
16124
|
+
return await getTaskRaw(id, requestOpts);
|
|
16125
|
+
}
|
|
16126
|
+
case "set_task_state": {
|
|
16127
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
16128
|
+
const state = firstStringArg(args, ["state", "taskState"]);
|
|
16129
|
+
if (!state) throw new Error("AbleTime task state is required");
|
|
16130
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/state`, {
|
|
16131
|
+
...requestOpts,
|
|
16132
|
+
method: "POST",
|
|
16133
|
+
body: { taskState: state }
|
|
16134
|
+
});
|
|
16135
|
+
return await getTaskRaw(id, requestOpts);
|
|
16136
|
+
}
|
|
16137
|
+
case "create_comment": {
|
|
16138
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
16139
|
+
const body = firstStringArg(args, ["body", "comment", "text"]);
|
|
16140
|
+
if (!body) throw new Error("AbleTime comment body is required");
|
|
16141
|
+
const created = await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/comments`, {
|
|
16142
|
+
...requestOpts,
|
|
16143
|
+
method: "POST",
|
|
16144
|
+
body: { content: ableTimeDocFromText(body) }
|
|
16145
|
+
});
|
|
16146
|
+
const rec = asRecord4(created) ?? {};
|
|
16147
|
+
return {
|
|
16148
|
+
id: asString(rec.commentId) || void 0,
|
|
16149
|
+
body: textFromAbleTimeDoc(rec.content) || body
|
|
16150
|
+
};
|
|
16151
|
+
}
|
|
16152
|
+
case "set_task_dependency":
|
|
16153
|
+
throw new Error(
|
|
16154
|
+
"AbleTime organization API keys cannot set task dependencies on REST yet \u2014 use a personal access token (apt_\u2026) for MCP project-management tools."
|
|
16155
|
+
);
|
|
16156
|
+
default:
|
|
16157
|
+
throw new Error(`AbleTime organization API keys do not support ${name} on REST`);
|
|
16158
|
+
}
|
|
16159
|
+
}
|
|
16160
|
+
var ABLETIME_REST_PREFIX, ULID_RE;
|
|
16161
|
+
var init_abletime_rest = __esm({
|
|
16162
|
+
"src/integrations/abletime-rest.ts"() {
|
|
16163
|
+
"use strict";
|
|
16164
|
+
init_fetch();
|
|
16165
|
+
init_app_settings();
|
|
16166
|
+
init_abletime_mcp();
|
|
16167
|
+
ABLETIME_REST_PREFIX = "/api/public/v2";
|
|
16168
|
+
ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
16169
|
+
}
|
|
16170
|
+
});
|
|
16171
|
+
|
|
15376
16172
|
// src/integrations/abletime-mcp.ts
|
|
15377
16173
|
function abletimeToolUsesPm(name) {
|
|
15378
16174
|
return ABLETIME_PM_TOOL_NAMES.includes(name);
|
|
@@ -15386,22 +16182,63 @@ function abletimeMcpUrl(host, opts) {
|
|
|
15386
16182
|
const path = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
|
|
15387
16183
|
return `${normalizeAbleTimeHost(host)}${path}`;
|
|
15388
16184
|
}
|
|
16185
|
+
function normalizeAbleTimeCredential(raw) {
|
|
16186
|
+
return raw.trim().replace(/^bearer\s+/i, "").trim();
|
|
16187
|
+
}
|
|
16188
|
+
function ableTimeCredentialKind(raw) {
|
|
16189
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
16190
|
+
const lower = token.toLowerCase();
|
|
16191
|
+
if (lower.startsWith(ABLETIME_PAT_PREFIX)) return "pat";
|
|
16192
|
+
if (lower.startsWith(ABLETIME_ORG_KEY_PREFIX)) return "org";
|
|
16193
|
+
if (lower.startsWith(ABLETIME_OAUTH_TOKEN_PREFIX) || token.startsWith("eyJ")) return "oauth";
|
|
16194
|
+
return "unknown";
|
|
16195
|
+
}
|
|
16196
|
+
function ableTimeUsesRest(raw) {
|
|
16197
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
16198
|
+
const kind = ableTimeCredentialKind(token);
|
|
16199
|
+
if (kind === "org") return true;
|
|
16200
|
+
if (kind === "oauth") return token.startsWith("eyJ");
|
|
16201
|
+
return kind === "unknown";
|
|
16202
|
+
}
|
|
16203
|
+
function isAbleTimeRejectedCredential(message) {
|
|
16204
|
+
return /INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|INTEGRATION_PAT_REQUIRED|\b401\b/i.test(
|
|
16205
|
+
message
|
|
16206
|
+
);
|
|
16207
|
+
}
|
|
16208
|
+
function assertAbleTimeMcpCredential(raw) {
|
|
16209
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
16210
|
+
if (!token) {
|
|
16211
|
+
throw new Error("AbleTime personal access token is required");
|
|
16212
|
+
}
|
|
16213
|
+
if (ableTimeCredentialKind(token) === "org") {
|
|
16214
|
+
throw new Error(ORG_KEY_HINT);
|
|
16215
|
+
}
|
|
16216
|
+
return token;
|
|
16217
|
+
}
|
|
16218
|
+
function assertAbleTimeCredential(raw) {
|
|
16219
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
16220
|
+
if (!token) {
|
|
16221
|
+
throw new Error("AbleTime API key or personal access token is required");
|
|
16222
|
+
}
|
|
16223
|
+
return token;
|
|
16224
|
+
}
|
|
15389
16225
|
function rewriteAbleTimeError(message) {
|
|
15390
|
-
if (
|
|
16226
|
+
if (REWRITE_APPLIED.test(message)) return message;
|
|
16227
|
+
if (/INTEGRATION_AGENT_ACCESS_DISABLED/i.test(message)) {
|
|
15391
16228
|
return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
|
|
15392
16229
|
}
|
|
15393
|
-
if (/INTEGRATION_PAT_REQUIRED
|
|
15394
|
-
return `${message} \u2014
|
|
16230
|
+
if (/INTEGRATION_PAT_REQUIRED/i.test(message)) {
|
|
16231
|
+
return `${message} \u2014 ${ORG_KEY_HINT}`;
|
|
15395
16232
|
}
|
|
15396
16233
|
if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
|
|
15397
16234
|
return `${message} \u2014 AbleTime plan does not include API access.`;
|
|
15398
16235
|
}
|
|
15399
16236
|
if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
|
|
15400
|
-
return `${message} \u2014
|
|
16237
|
+
return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
|
|
15401
16238
|
}
|
|
15402
16239
|
return message;
|
|
15403
16240
|
}
|
|
15404
|
-
function
|
|
16241
|
+
function asRecord5(value) {
|
|
15405
16242
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15406
16243
|
}
|
|
15407
16244
|
function parseSseJson(body) {
|
|
@@ -15423,12 +16260,12 @@ function parseSseJson(body) {
|
|
|
15423
16260
|
}
|
|
15424
16261
|
}
|
|
15425
16262
|
function unwrapToolResult(result) {
|
|
15426
|
-
const record =
|
|
16263
|
+
const record = asRecord5(result);
|
|
15427
16264
|
if (!record) return result;
|
|
15428
16265
|
const content = record.content;
|
|
15429
16266
|
if (Array.isArray(content)) {
|
|
15430
16267
|
const texts = content.map((part) => {
|
|
15431
|
-
const item =
|
|
16268
|
+
const item = asRecord5(part);
|
|
15432
16269
|
if (!item) return "";
|
|
15433
16270
|
return typeof item.text === "string" ? item.text : "";
|
|
15434
16271
|
}).filter(Boolean);
|
|
@@ -15450,17 +16287,18 @@ function unwrapToolResult(result) {
|
|
|
15450
16287
|
return result;
|
|
15451
16288
|
}
|
|
15452
16289
|
function textsFromUnknown(value) {
|
|
15453
|
-
const record =
|
|
16290
|
+
const record = asRecord5(value);
|
|
15454
16291
|
if (!record) return "";
|
|
15455
16292
|
if (typeof record.message === "string") return record.message;
|
|
15456
16293
|
if (typeof record.error === "string") return record.error;
|
|
15457
16294
|
return "";
|
|
15458
16295
|
}
|
|
15459
16296
|
async function abletimeMcpRequest(method, params, opts) {
|
|
15460
|
-
const
|
|
15461
|
-
if (!
|
|
16297
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
16298
|
+
if (!rawToken?.trim()) {
|
|
15462
16299
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
15463
16300
|
}
|
|
16301
|
+
const token = assertAbleTimeMcpCredential(rawToken);
|
|
15464
16302
|
const url = abletimeMcpUrl(
|
|
15465
16303
|
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
|
|
15466
16304
|
{ pm: opts?.pm }
|
|
@@ -15496,7 +16334,7 @@ async function abletimeMcpRequest(method, params, opts) {
|
|
|
15496
16334
|
return parseSseJson(body);
|
|
15497
16335
|
}
|
|
15498
16336
|
})();
|
|
15499
|
-
const rpc =
|
|
16337
|
+
const rpc = asRecord5(parsed);
|
|
15500
16338
|
if (rpc?.error) {
|
|
15501
16339
|
const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
|
|
15502
16340
|
throw new Error(rewriteAbleTimeError(detail));
|
|
@@ -15527,6 +16365,11 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
15527
16365
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
15528
16366
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
15529
16367
|
}
|
|
16368
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
16369
|
+
if (rawToken && ableTimeUsesRest(rawToken)) {
|
|
16370
|
+
const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
|
|
16371
|
+
return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
|
|
16372
|
+
}
|
|
15530
16373
|
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
15531
16374
|
const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
|
|
15532
16375
|
return unwrapToolResult(result);
|
|
@@ -15539,10 +16382,14 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
15539
16382
|
await initializeIfNeeded(requestOpts);
|
|
15540
16383
|
return await call();
|
|
15541
16384
|
}
|
|
16385
|
+
if (rawToken && ableTimeUsesRest(rawToken) && isAbleTimeRejectedCredential(message)) {
|
|
16386
|
+
const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
|
|
16387
|
+
return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
|
|
16388
|
+
}
|
|
15542
16389
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
15543
16390
|
}
|
|
15544
16391
|
}
|
|
15545
|
-
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
|
|
16392
|
+
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, ABLETIME_OAUTH_TOKEN_PREFIX, ORG_KEY_HINT, REJECTED_CREDENTIAL_HINT, REWRITE_APPLIED;
|
|
15546
16393
|
var init_abletime_mcp = __esm({
|
|
15547
16394
|
"src/integrations/abletime-mcp.ts"() {
|
|
15548
16395
|
"use strict";
|
|
@@ -15564,6 +16411,12 @@ var init_abletime_mcp = __esm({
|
|
|
15564
16411
|
"archive_task",
|
|
15565
16412
|
"delete_task"
|
|
15566
16413
|
];
|
|
16414
|
+
ABLETIME_PAT_PREFIX = "apt_";
|
|
16415
|
+
ABLETIME_ORG_KEY_PREFIX = "atk_";
|
|
16416
|
+
ABLETIME_OAUTH_TOKEN_PREFIX = "acn_";
|
|
16417
|
+
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.";
|
|
16418
|
+
REJECTED_CREDENTIAL_HINT = "AbleTime rejected this credential (unknown, revoked, or expired). Reconnect via browser, or paste a personal access token (apt_\u2026) from Edit Profile \u2192 API Access. Organization API keys (atk_\u2026) use REST, not MCP.";
|
|
16419
|
+
REWRITE_APPLIED = /— Enable Agent access \(MCP\)|— AbleTime MCP needs a personal access token|— AbleTime plan does not include API access|— AbleTime rejected this credential/;
|
|
15567
16420
|
}
|
|
15568
16421
|
});
|
|
15569
16422
|
|
|
@@ -15587,16 +16440,16 @@ __export(abletime_exports, {
|
|
|
15587
16440
|
updateAbleTimeTask: () => updateAbleTimeTask,
|
|
15588
16441
|
verifyAbleTimeConnection: () => verifyAbleTimeConnection
|
|
15589
16442
|
});
|
|
15590
|
-
function
|
|
16443
|
+
function asRecord6(value) {
|
|
15591
16444
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15592
16445
|
}
|
|
15593
|
-
function
|
|
16446
|
+
function asString2(value) {
|
|
15594
16447
|
return typeof value === "string" ? value.trim() : "";
|
|
15595
16448
|
}
|
|
15596
16449
|
function firstString(record, keys) {
|
|
15597
16450
|
if (!record) return "";
|
|
15598
16451
|
for (const key of keys) {
|
|
15599
|
-
const value =
|
|
16452
|
+
const value = asString2(record[key]);
|
|
15600
16453
|
if (value) return value;
|
|
15601
16454
|
}
|
|
15602
16455
|
return "";
|
|
@@ -15604,14 +16457,14 @@ function firstString(record, keys) {
|
|
|
15604
16457
|
function firstRecord(record, keys) {
|
|
15605
16458
|
if (!record) return null;
|
|
15606
16459
|
for (const key of keys) {
|
|
15607
|
-
const value =
|
|
16460
|
+
const value = asRecord6(record[key]);
|
|
15608
16461
|
if (value) return value;
|
|
15609
16462
|
}
|
|
15610
16463
|
return null;
|
|
15611
16464
|
}
|
|
15612
|
-
function
|
|
16465
|
+
function asList2(value) {
|
|
15613
16466
|
if (Array.isArray(value)) return value;
|
|
15614
|
-
const record =
|
|
16467
|
+
const record = asRecord6(value);
|
|
15615
16468
|
if (!record) return [];
|
|
15616
16469
|
for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
|
|
15617
16470
|
if (Array.isArray(record[key])) return record[key];
|
|
@@ -15623,21 +16476,21 @@ function labelsOf(record) {
|
|
|
15623
16476
|
if (!Array.isArray(raw)) return [];
|
|
15624
16477
|
return raw.map((item) => {
|
|
15625
16478
|
if (typeof item === "string") return item.trim();
|
|
15626
|
-
const rec =
|
|
16479
|
+
const rec = asRecord6(item);
|
|
15627
16480
|
return rec ? firstString(rec, ["name", "title", "label"]) : "";
|
|
15628
16481
|
}).filter(Boolean);
|
|
15629
16482
|
}
|
|
15630
16483
|
function commentsOf(record) {
|
|
15631
16484
|
const raw = record.comments ?? record.notes ?? record.discussion;
|
|
15632
16485
|
const out = [];
|
|
15633
|
-
for (const item of
|
|
15634
|
-
const rec =
|
|
16486
|
+
for (const item of asList2(raw)) {
|
|
16487
|
+
const rec = asRecord6(item);
|
|
15635
16488
|
if (!rec) continue;
|
|
15636
|
-
const body = firstString(rec, ["body", "text", "comment", "
|
|
16489
|
+
const body = firstString(rec, ["body", "text", "comment", "message", "contentSanitized"]) || textFromAbleTimeDoc(rec.content);
|
|
15637
16490
|
if (!body) continue;
|
|
15638
|
-
const user = firstString(
|
|
16491
|
+
const user = firstString(asRecord6(rec.user) ?? asRecord6(rec.author), ["name", "display_name", "username"]) || firstString(rec, ["user_name", "author", "username"]);
|
|
15639
16492
|
const comment = { body };
|
|
15640
|
-
const id = firstString(rec, ["id", "comment_id"]);
|
|
16493
|
+
const id = firstString(rec, ["id", "comment_id", "commentId"]);
|
|
15641
16494
|
if (id) comment.id = id;
|
|
15642
16495
|
const url = firstString(rec, ["url", "permalink"]);
|
|
15643
16496
|
if (url) comment.url = url;
|
|
@@ -15661,11 +16514,11 @@ function taskUrl(task, host) {
|
|
|
15661
16514
|
return `${base}/tasks/${slug}`;
|
|
15662
16515
|
}
|
|
15663
16516
|
function mapAbleTimeTask(raw, host) {
|
|
15664
|
-
const record =
|
|
16517
|
+
const record = asRecord6(raw);
|
|
15665
16518
|
if (!record) return null;
|
|
15666
16519
|
const nested = firstRecord(record, ["task", "data"]) ?? record;
|
|
15667
|
-
const id = firstString(nested, ["id", "task_id", "taskId"]);
|
|
15668
|
-
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number"]) || id;
|
|
16520
|
+
const id = firstString(nested, ["id", "task_id", "taskId", "timeflowTaskId"]);
|
|
16521
|
+
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number", "taskRef"]) || id;
|
|
15669
16522
|
const title = firstString(nested, ["title", "name"]);
|
|
15670
16523
|
if (!id && !identifier && !title) return null;
|
|
15671
16524
|
const resolvedId = id || identifier;
|
|
@@ -15676,21 +16529,22 @@ function mapAbleTimeTask(raw, host) {
|
|
|
15676
16529
|
identifier: resolvedIdentifier,
|
|
15677
16530
|
title: title || resolvedIdentifier,
|
|
15678
16531
|
url,
|
|
15679
|
-
description: firstString(nested, ["description", "body", "details"]) || void 0,
|
|
15680
|
-
state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
|
|
15681
|
-
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(
|
|
15682
|
-
categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(
|
|
16532
|
+
description: firstString(nested, ["description", "body", "details", "descriptionSanitized"]) || textFromAbleTimeDoc(nested.description) || void 0,
|
|
16533
|
+
state: firstString(nested, ["state", "board_state", "boardState", "status", "taskState"]) || void 0,
|
|
16534
|
+
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord6(nested.project), ["id"]) || void 0,
|
|
16535
|
+
categoryId: firstString(nested, ["category_id", "categoryId", "projectCategoryId"]) || firstString(asRecord6(nested.category), ["id"]) || void 0,
|
|
15683
16536
|
assignee: assigneeOf(nested),
|
|
15684
16537
|
labels: labelsOf(nested),
|
|
15685
16538
|
comments: commentsOf(nested),
|
|
15686
|
-
createdAt: firstString(nested, ["created_at", "createdAt", "created"]) || void 0,
|
|
16539
|
+
createdAt: firstString(nested, ["created_at", "createdAt", "created", "dateCreated"]) || void 0,
|
|
15687
16540
|
updatedAt: firstString(nested, [
|
|
15688
16541
|
"updated_at",
|
|
15689
16542
|
"updatedAt",
|
|
15690
16543
|
"modified_at",
|
|
15691
16544
|
"modifiedAt",
|
|
15692
16545
|
"last_activity_at",
|
|
15693
|
-
"lastActivityAt"
|
|
16546
|
+
"lastActivityAt",
|
|
16547
|
+
"lastUpdate"
|
|
15694
16548
|
]) || void 0
|
|
15695
16549
|
};
|
|
15696
16550
|
}
|
|
@@ -15721,16 +16575,16 @@ function issueAttachmentForAbleTimeTask(task) {
|
|
|
15721
16575
|
};
|
|
15722
16576
|
}
|
|
15723
16577
|
function mapProject(raw) {
|
|
15724
|
-
const record =
|
|
16578
|
+
const record = asRecord6(raw);
|
|
15725
16579
|
if (!record) return null;
|
|
15726
|
-
const id = firstString(record, ["id", "project_id"]);
|
|
15727
|
-
const name = firstString(record, ["name", "title"]);
|
|
16580
|
+
const id = firstString(record, ["id", "project_id", "projectId"]);
|
|
16581
|
+
const name = firstString(record, ["name", "title", "projectName"]);
|
|
15728
16582
|
if (!id && !name) return null;
|
|
15729
|
-
const categories =
|
|
15730
|
-
const rec =
|
|
16583
|
+
const categories = asList2(record.categories ?? record.category).map((item) => {
|
|
16584
|
+
const rec = asRecord6(item);
|
|
15731
16585
|
if (!rec) return null;
|
|
15732
|
-
const categoryId = firstString(rec, ["id", "category_id"]);
|
|
15733
|
-
const categoryName = firstString(rec, ["name", "title"]);
|
|
16586
|
+
const categoryId = firstString(rec, ["id", "category_id", "projectCategoryId", "categoryId"]);
|
|
16587
|
+
const categoryName = firstString(rec, ["name", "title", "categoryName"]);
|
|
15734
16588
|
if (!categoryId && !categoryName) return null;
|
|
15735
16589
|
return { id: categoryId || categoryName, name: categoryName || categoryId };
|
|
15736
16590
|
}).filter((item) => Boolean(item));
|
|
@@ -15742,21 +16596,21 @@ function isOpenTask(task) {
|
|
|
15742
16596
|
}
|
|
15743
16597
|
async function getAbleTimeOrientation(opts) {
|
|
15744
16598
|
const raw = await callAbleTimeTool("orientation", {}, opts);
|
|
15745
|
-
const record =
|
|
15746
|
-
const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
|
|
16599
|
+
const record = asRecord6(raw);
|
|
16600
|
+
const viewerRec = firstRecord(record, ["viewer", "principal", "user", "actor", "me", "person"]) ?? record;
|
|
15747
16601
|
const viewer = {
|
|
15748
16602
|
id: firstString(viewerRec, ["id", "user_id"]) || void 0,
|
|
15749
16603
|
name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
|
|
15750
16604
|
};
|
|
15751
|
-
const projects =
|
|
16605
|
+
const projects = asList2(
|
|
15752
16606
|
record?.projects ?? record?.current_projects ?? record?.currentProjects
|
|
15753
16607
|
).map(mapProject).filter((item) => Boolean(item));
|
|
15754
|
-
const tasks =
|
|
16608
|
+
const tasks = asList2(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
15755
16609
|
return { viewer, projects, tasks, raw };
|
|
15756
16610
|
}
|
|
15757
16611
|
async function listAbleTimeProjects(opts) {
|
|
15758
16612
|
const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
|
|
15759
|
-
return
|
|
16613
|
+
return asList2(raw).map(mapProject).filter((item) => Boolean(item));
|
|
15760
16614
|
}
|
|
15761
16615
|
async function listAbleTimeTasks(opts) {
|
|
15762
16616
|
const args = {};
|
|
@@ -15766,14 +16620,14 @@ async function listAbleTimeTasks(opts) {
|
|
|
15766
16620
|
}
|
|
15767
16621
|
if (opts?.query) args.query = opts.query;
|
|
15768
16622
|
const raw = await callAbleTimeTool("list_tasks", args, opts);
|
|
15769
|
-
const tasks =
|
|
16623
|
+
const tasks = asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
15770
16624
|
return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
|
|
15771
16625
|
}
|
|
15772
16626
|
async function searchAbleTimeTasks(query, opts) {
|
|
15773
16627
|
const q = query.trim();
|
|
15774
16628
|
if (!q) return [];
|
|
15775
16629
|
const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
|
|
15776
|
-
return
|
|
16630
|
+
return asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
|
|
15777
16631
|
}
|
|
15778
16632
|
async function getAbleTimeTask(id, opts) {
|
|
15779
16633
|
const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
|
|
@@ -15821,7 +16675,7 @@ async function commentAbleTimeTask(input, opts) {
|
|
|
15821
16675
|
{ id, task_id: id, task: id, body, comment: body, text: body },
|
|
15822
16676
|
opts
|
|
15823
16677
|
);
|
|
15824
|
-
const rec =
|
|
16678
|
+
const rec = asRecord6(raw);
|
|
15825
16679
|
return {
|
|
15826
16680
|
id: rec ? firstString(rec, ["id", "comment_id"]) || void 0 : void 0,
|
|
15827
16681
|
body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
|
|
@@ -16011,8 +16865,7 @@ async function listAbleTimeAssignedIssues(opts) {
|
|
|
16011
16865
|
};
|
|
16012
16866
|
}
|
|
16013
16867
|
async function verifyAbleTimeConnection(input) {
|
|
16014
|
-
const token = input.token
|
|
16015
|
-
if (!token) throw new Error("AbleTime personal access token is required");
|
|
16868
|
+
const token = assertAbleTimeCredential(input.token);
|
|
16016
16869
|
const host = input.host?.trim() || void 0;
|
|
16017
16870
|
const orientation = await getAbleTimeOrientation({ token, host });
|
|
16018
16871
|
saveAbleTimeConnection({
|
|
@@ -16028,6 +16881,7 @@ var init_abletime = __esm({
|
|
|
16028
16881
|
"use strict";
|
|
16029
16882
|
init_app_settings();
|
|
16030
16883
|
init_abletime_mcp();
|
|
16884
|
+
init_abletime_rest();
|
|
16031
16885
|
CLOSED_STATES = /* @__PURE__ */ new Set([
|
|
16032
16886
|
"done",
|
|
16033
16887
|
"completed",
|
|
@@ -16599,12 +17453,19 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
16599
17453
|
...instructions
|
|
16600
17454
|
].join("\n");
|
|
16601
17455
|
return {
|
|
16602
|
-
id: (0,
|
|
17456
|
+
id: (0, import_node_crypto10.randomUUID)(),
|
|
16603
17457
|
name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
|
|
16604
17458
|
kind: "transcript",
|
|
16605
17459
|
content: body
|
|
16606
17460
|
};
|
|
16607
17461
|
}
|
|
17462
|
+
async function isolateQuotaFailover(run2) {
|
|
17463
|
+
try {
|
|
17464
|
+
return await run2();
|
|
17465
|
+
} catch {
|
|
17466
|
+
return true;
|
|
17467
|
+
}
|
|
17468
|
+
}
|
|
16608
17469
|
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
16609
17470
|
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
16610
17471
|
const tab = createChatTab({
|
|
@@ -16623,11 +17484,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
16623
17484
|
}
|
|
16624
17485
|
return updateThread(tab.id, patch);
|
|
16625
17486
|
}
|
|
16626
|
-
var
|
|
17487
|
+
var import_node_crypto10, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
|
|
16627
17488
|
var init_quota_failover = __esm({
|
|
16628
17489
|
"src/orchestrator/quota-failover.ts"() {
|
|
16629
17490
|
"use strict";
|
|
16630
|
-
|
|
17491
|
+
import_node_crypto10 = require("crypto");
|
|
16631
17492
|
init_session_quota();
|
|
16632
17493
|
init_app_settings();
|
|
16633
17494
|
init_global_workspace();
|
|
@@ -18938,7 +19799,7 @@ var init_optional_services = __esm({
|
|
|
18938
19799
|
});
|
|
18939
19800
|
|
|
18940
19801
|
// src/plan/plan-present.ts
|
|
18941
|
-
function
|
|
19802
|
+
function asRecord7(v) {
|
|
18942
19803
|
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
18943
19804
|
}
|
|
18944
19805
|
function isPresentPlanToolName(name) {
|
|
@@ -18950,7 +19811,7 @@ function extractPresentedPlan(parts) {
|
|
|
18950
19811
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
18951
19812
|
const p = parts[i];
|
|
18952
19813
|
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
18953
|
-
const input =
|
|
19814
|
+
const input = asRecord7(p.input) ?? {};
|
|
18954
19815
|
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
18955
19816
|
if (!content.trim()) continue;
|
|
18956
19817
|
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
@@ -19385,7 +20246,7 @@ function buildTask(input, now) {
|
|
|
19385
20246
|
const ts = nowIso2(now);
|
|
19386
20247
|
const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
|
|
19387
20248
|
return {
|
|
19388
|
-
id: (0,
|
|
20249
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
19389
20250
|
name: input.name?.trim() || defaultScheduleName(prompt),
|
|
19390
20251
|
prompt,
|
|
19391
20252
|
enabled: input.enabled !== false,
|
|
@@ -19472,11 +20333,11 @@ function recordScheduleRun(id, result) {
|
|
|
19472
20333
|
writeAll2(rows);
|
|
19473
20334
|
return next;
|
|
19474
20335
|
}
|
|
19475
|
-
var
|
|
20336
|
+
var import_node_crypto11, import_node_fs52, import_node_path49, import_croner, DURATION_RE, UNIT_MS;
|
|
19476
20337
|
var init_schedules = __esm({
|
|
19477
20338
|
"src/store/schedules.ts"() {
|
|
19478
20339
|
"use strict";
|
|
19479
|
-
|
|
20340
|
+
import_node_crypto11 = require("crypto");
|
|
19480
20341
|
import_node_fs52 = require("fs");
|
|
19481
20342
|
import_node_path49 = require("path");
|
|
19482
20343
|
import_croner = require("croner");
|
|
@@ -19754,11 +20615,6 @@ function isPidAlive(pid) {
|
|
|
19754
20615
|
return false;
|
|
19755
20616
|
}
|
|
19756
20617
|
}
|
|
19757
|
-
function sameLoginList(a, b) {
|
|
19758
|
-
const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
19759
|
-
const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
19760
|
-
return left.length === right.length && left.every((login, i) => login === right[i]);
|
|
19761
|
-
}
|
|
19762
20618
|
async function waitForPidExit(pid, timeoutMs) {
|
|
19763
20619
|
const start = Date.now();
|
|
19764
20620
|
while (isPidAlive(pid)) {
|
|
@@ -20165,51 +21021,62 @@ var init_orchestrator = __esm({
|
|
|
20165
21021
|
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
20166
21022
|
this.haltDrain.add(threadId);
|
|
20167
21023
|
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
|
|
20178
|
-
|
|
20179
|
-
|
|
20180
|
-
|
|
20181
|
-
|
|
21024
|
+
try {
|
|
21025
|
+
setStatus(threadId, "idle", null);
|
|
21026
|
+
appendMessage(threadId, {
|
|
21027
|
+
role: "agent",
|
|
21028
|
+
text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
21029
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
21030
|
+
});
|
|
21031
|
+
this.emit({
|
|
21032
|
+
type: "quota_failover",
|
|
21033
|
+
threadId,
|
|
21034
|
+
action: "wait_reset",
|
|
21035
|
+
message: plan.reason,
|
|
21036
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
21037
|
+
});
|
|
21038
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
21039
|
+
} catch {
|
|
21040
|
+
}
|
|
20182
21041
|
return true;
|
|
20183
21042
|
}
|
|
20184
21043
|
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
20185
21044
|
this.haltDrain.add(threadId);
|
|
20186
|
-
|
|
20187
|
-
|
|
20188
|
-
|
|
20189
|
-
|
|
20190
|
-
|
|
21045
|
+
let next;
|
|
21046
|
+
try {
|
|
21047
|
+
next = createQuotaFailoverChat(
|
|
21048
|
+
thread,
|
|
21049
|
+
plan.fallbackAgent,
|
|
21050
|
+
plan.limitText
|
|
21051
|
+
);
|
|
21052
|
+
} catch {
|
|
21053
|
+
return false;
|
|
21054
|
+
}
|
|
20191
21055
|
this.clearQuotaResumeTimer(threadId);
|
|
20192
21056
|
try {
|
|
20193
21057
|
updateThread(threadId, { quotaResumeAt: null });
|
|
20194
21058
|
} catch {
|
|
20195
21059
|
}
|
|
20196
|
-
|
|
20197
|
-
|
|
20198
|
-
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
|
|
20209
|
-
|
|
20210
|
-
|
|
20211
|
-
|
|
20212
|
-
|
|
21060
|
+
try {
|
|
21061
|
+
appendMessage(threadId, {
|
|
21062
|
+
role: "agent",
|
|
21063
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
21064
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
21065
|
+
});
|
|
21066
|
+
this.emit({
|
|
21067
|
+
type: "quota_failover",
|
|
21068
|
+
threadId,
|
|
21069
|
+
action: "switch_agent",
|
|
21070
|
+
toThreadId: next.id,
|
|
21071
|
+
message: plan.reason
|
|
21072
|
+
});
|
|
21073
|
+
this.emit({
|
|
21074
|
+
type: "status_changed",
|
|
21075
|
+
threadId: next.id,
|
|
21076
|
+
status: next.status
|
|
21077
|
+
});
|
|
21078
|
+
} catch {
|
|
21079
|
+
}
|
|
20213
21080
|
void this.send(
|
|
20214
21081
|
next.id,
|
|
20215
21082
|
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
@@ -20940,9 +21807,8 @@ var init_orchestrator = __esm({
|
|
|
20940
21807
|
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
20941
21808
|
} else {
|
|
20942
21809
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
20943
|
-
const failedOver = await
|
|
20944
|
-
threadId,
|
|
20945
|
-
blob
|
|
21810
|
+
const failedOver = await isolateQuotaFailover(
|
|
21811
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, blob)
|
|
20946
21812
|
);
|
|
20947
21813
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
20948
21814
|
detail: detail || failDetail,
|
|
@@ -20976,9 +21842,8 @@ var init_orchestrator = __esm({
|
|
|
20976
21842
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
20977
21843
|
}
|
|
20978
21844
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
20979
|
-
const failedOver = await
|
|
20980
|
-
threadId,
|
|
20981
|
-
message
|
|
21845
|
+
const failedOver = await isolateQuotaFailover(
|
|
21846
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, message)
|
|
20982
21847
|
);
|
|
20983
21848
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
20984
21849
|
detail: message,
|
|
@@ -21588,12 +22453,18 @@ var init_orchestrator = __esm({
|
|
|
21588
22453
|
/** Resolve PR selectors and optionally persist `prUrl` when found. */
|
|
21589
22454
|
async withPrSelector(threadRef) {
|
|
21590
22455
|
const thread = this.requireThread(threadRef);
|
|
21591
|
-
const selectors = resolvePrSelectors(thread);
|
|
21592
22456
|
const cwd = thread.worktreePath;
|
|
21593
22457
|
if (!cwd?.trim()) {
|
|
21594
22458
|
throw new Error(`Thread ${threadRef} has no worktreePath`);
|
|
21595
22459
|
}
|
|
21596
|
-
|
|
22460
|
+
let headUrl = null;
|
|
22461
|
+
try {
|
|
22462
|
+
const head = await getPrForHeadBranch(cwd, thread.branchName);
|
|
22463
|
+
headUrl = head?.url?.trim() || null;
|
|
22464
|
+
} catch {
|
|
22465
|
+
headUrl = null;
|
|
22466
|
+
}
|
|
22467
|
+
return { thread, selectors: connectedPrSelectors(thread, headUrl), cwd };
|
|
21597
22468
|
}
|
|
21598
22469
|
async getPrChecks(threadRef) {
|
|
21599
22470
|
const { selectors, cwd } = await this.withPrSelector(threadRef);
|
|
@@ -21621,29 +22492,16 @@ var init_orchestrator = __esm({
|
|
|
21621
22492
|
async persistPrMetaAndMaybeArchive(thread, meta) {
|
|
21622
22493
|
const prevState = normalizePrState(thread.prState);
|
|
21623
22494
|
const nextState = normalizePrState(meta.state);
|
|
21624
|
-
const
|
|
21625
|
-
|
|
21626
|
-
|
|
21627
|
-
|
|
21628
|
-
|
|
21629
|
-
|
|
21630
|
-
const nextAuthor = meta.authorLogin?.trim() || "";
|
|
21631
|
-
if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
|
|
21632
|
-
patch.prAuthorLogin = nextAuthor;
|
|
21633
|
-
}
|
|
21634
|
-
const nextReviewers = meta.reviewerLogins ?? [];
|
|
21635
|
-
if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
|
|
21636
|
-
patch.prReviewerLogins = nextReviewers;
|
|
22495
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
22496
|
+
const targets = siblings.length > 0 ? siblings : [thread];
|
|
22497
|
+
for (const t of targets) {
|
|
22498
|
+
const patch = threadPrMetaPatch(t, meta);
|
|
22499
|
+
if (Object.keys(patch).length === 0) continue;
|
|
22500
|
+
updateThread(t.id, patch);
|
|
21637
22501
|
}
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
if (Object.keys(patch).length > 0) {
|
|
21642
|
-
updateThread(thread.id, patch);
|
|
21643
|
-
const latest2 = this.requireThread(thread.id);
|
|
21644
|
-
if (!latest2.userSetTitle && meta.title && latest2.title !== meta.title) {
|
|
21645
|
-
updateThread(thread.id, { title: meta.title });
|
|
21646
|
-
}
|
|
22502
|
+
const titled = this.requireThread(thread.id);
|
|
22503
|
+
if (!titled.userSetTitle && meta.title && titled.title !== meta.title) {
|
|
22504
|
+
updateThread(thread.id, { title: meta.title });
|
|
21647
22505
|
}
|
|
21648
22506
|
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
|
|
21649
22507
|
const latest = this.requireThread(thread.id);
|
|
@@ -21657,22 +22515,7 @@ var init_orchestrator = __esm({
|
|
|
21657
22515
|
})) {
|
|
21658
22516
|
return;
|
|
21659
22517
|
}
|
|
21660
|
-
const
|
|
21661
|
-
for (const t of siblings) {
|
|
21662
|
-
const sibPatch = { prState: "MERGED", prIsDraft: false };
|
|
21663
|
-
if (meta.url && meta.url !== t.prUrl) sibPatch.prUrl = meta.url;
|
|
21664
|
-
if (meta.title && meta.title !== t.prTitle) sibPatch.prTitle = meta.title;
|
|
21665
|
-
const sibAuthor = meta.authorLogin?.trim() || "";
|
|
21666
|
-
if (sibAuthor && sibAuthor !== (t.prAuthorLogin ?? "").trim()) {
|
|
21667
|
-
sibPatch.prAuthorLogin = sibAuthor;
|
|
21668
|
-
}
|
|
21669
|
-
const sibReviewers = meta.reviewerLogins ?? [];
|
|
21670
|
-
if (!sameLoginList(sibReviewers, t.prReviewerLogins)) {
|
|
21671
|
-
sibPatch.prReviewerLogins = sibReviewers;
|
|
21672
|
-
}
|
|
21673
|
-
if (Object.keys(sibPatch).length > 0) updateThread(t.id, sibPatch);
|
|
21674
|
-
}
|
|
21675
|
-
for (const t of siblings) {
|
|
22518
|
+
for (const t of threadsSharingWorktree(latest.worktreePath)) {
|
|
21676
22519
|
if (this.requireThread(t.id).status === "archived") continue;
|
|
21677
22520
|
await this.archive(t.id);
|
|
21678
22521
|
}
|
|
@@ -22068,7 +22911,7 @@ init_system_ca();
|
|
|
22068
22911
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
22069
22912
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
22070
22913
|
var import_zod9 = require("zod");
|
|
22071
|
-
var
|
|
22914
|
+
var import_node_crypto14 = require("crypto");
|
|
22072
22915
|
var import_node_path53 = require("path");
|
|
22073
22916
|
init_orchestrator();
|
|
22074
22917
|
init_worktree();
|
|
@@ -22495,8 +23338,8 @@ async function createGitHubIssue(input, opts) {
|
|
|
22495
23338
|
init_fetch();
|
|
22496
23339
|
|
|
22497
23340
|
// src/integrations/linear-oauth.ts
|
|
22498
|
-
var
|
|
22499
|
-
var
|
|
23341
|
+
var import_node_crypto12 = require("crypto");
|
|
23342
|
+
var import_node_http2 = require("http");
|
|
22500
23343
|
init_app_settings();
|
|
22501
23344
|
init_fetch();
|
|
22502
23345
|
|
|
@@ -22507,7 +23350,7 @@ var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
|
|
|
22507
23350
|
var LINEAR_OAUTH_PORT = 19848;
|
|
22508
23351
|
var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
|
|
22509
23352
|
var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
|
|
22510
|
-
var
|
|
23353
|
+
var REFRESH_SKEW_MS3 = 5 * 6e4;
|
|
22511
23354
|
function linearOAuthCredentials() {
|
|
22512
23355
|
const settings = loadAppSettings();
|
|
22513
23356
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
@@ -22548,7 +23391,7 @@ async function persistLinearTokens(data, viewer) {
|
|
|
22548
23391
|
organizationName: viewer?.organizationName
|
|
22549
23392
|
});
|
|
22550
23393
|
}
|
|
22551
|
-
var
|
|
23394
|
+
var refreshInFlight2 = null;
|
|
22552
23395
|
async function refreshLinearAccessToken(refreshToken) {
|
|
22553
23396
|
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
22554
23397
|
const body = new URLSearchParams({
|
|
@@ -22575,16 +23418,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
22575
23418
|
const refresh = settings.integrations.linearRefreshToken?.trim() || "";
|
|
22576
23419
|
const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
|
|
22577
23420
|
const apiKey = settings.integrations.linearApiKey?.trim() || "";
|
|
22578
|
-
if (access && (!refresh || Date.now() < expiresAt -
|
|
23421
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS3)) {
|
|
22579
23422
|
return access;
|
|
22580
23423
|
}
|
|
22581
23424
|
if (refresh) {
|
|
22582
|
-
if (!
|
|
22583
|
-
|
|
22584
|
-
|
|
23425
|
+
if (!refreshInFlight2) {
|
|
23426
|
+
refreshInFlight2 = refreshLinearAccessToken(refresh).finally(() => {
|
|
23427
|
+
refreshInFlight2 = null;
|
|
22585
23428
|
});
|
|
22586
23429
|
}
|
|
22587
|
-
return
|
|
23430
|
+
return refreshInFlight2.catch((err) => {
|
|
22588
23431
|
if (apiKey) return apiKey;
|
|
22589
23432
|
throw err;
|
|
22590
23433
|
});
|
|
@@ -25478,7 +26321,7 @@ init_worktree();
|
|
|
25478
26321
|
init_paths();
|
|
25479
26322
|
|
|
25480
26323
|
// src/board/board-pins.ts
|
|
25481
|
-
var
|
|
26324
|
+
var import_node_crypto13 = require("crypto");
|
|
25482
26325
|
var import_node_fs55 = require("fs");
|
|
25483
26326
|
var import_node_path51 = require("path");
|
|
25484
26327
|
init_paths();
|
|
@@ -26327,7 +27170,7 @@ async function startMcpServer() {
|
|
|
26327
27170
|
const ident = issue?.identifier ?? ref.trim();
|
|
26328
27171
|
const attachments = issue ? [
|
|
26329
27172
|
{
|
|
26330
|
-
id: (0,
|
|
27173
|
+
id: (0, import_node_crypto14.randomUUID)(),
|
|
26331
27174
|
name: issue.identifier,
|
|
26332
27175
|
kind: "issue",
|
|
26333
27176
|
content: [
|