@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/index.cjs
CHANGED
|
@@ -929,6 +929,9 @@ function normalizeVault(raw) {
|
|
|
929
929
|
if (typeof raw.abletimeAccessToken === "string" && raw.abletimeAccessToken.trim()) {
|
|
930
930
|
out.abletimeAccessToken = raw.abletimeAccessToken.trim();
|
|
931
931
|
}
|
|
932
|
+
if (typeof raw.abletimeRefreshToken === "string" && raw.abletimeRefreshToken.trim()) {
|
|
933
|
+
out.abletimeRefreshToken = raw.abletimeRefreshToken.trim();
|
|
934
|
+
}
|
|
932
935
|
if (typeof raw.vercelToken === "string" && raw.vercelToken.trim()) {
|
|
933
936
|
out.vercelToken = raw.vercelToken.trim();
|
|
934
937
|
}
|
|
@@ -1134,6 +1137,7 @@ function emptyPublicIntegrations() {
|
|
|
1134
1137
|
hasSlackAppToken: false,
|
|
1135
1138
|
hasGithubPat: false,
|
|
1136
1139
|
hasAbleTimeToken: false,
|
|
1140
|
+
hasAbleTimeOAuth: false,
|
|
1137
1141
|
hasVercelToken: false,
|
|
1138
1142
|
hasSupabaseToken: false,
|
|
1139
1143
|
hasPosthogToken: false,
|
|
@@ -1158,6 +1162,7 @@ function toPublicAppSettings(settings) {
|
|
|
1158
1162
|
const slackAppToken = integrations.slackAppToken;
|
|
1159
1163
|
const githubPat = integrations.githubPat;
|
|
1160
1164
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
1165
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
1161
1166
|
const vercelToken = integrations.vercelToken;
|
|
1162
1167
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
1163
1168
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -1170,6 +1175,7 @@ function toPublicAppSettings(settings) {
|
|
|
1170
1175
|
delete integrations.slackAppToken;
|
|
1171
1176
|
delete integrations.githubPat;
|
|
1172
1177
|
delete integrations.abletimeAccessToken;
|
|
1178
|
+
delete integrations.abletimeRefreshToken;
|
|
1173
1179
|
delete integrations.vercelToken;
|
|
1174
1180
|
delete integrations.supabaseAccessToken;
|
|
1175
1181
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -1184,7 +1190,8 @@ function toPublicAppSettings(settings) {
|
|
|
1184
1190
|
hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
|
|
1185
1191
|
hasSlackAppToken: Boolean(slackAppToken?.trim()),
|
|
1186
1192
|
hasGithubPat: Boolean(githubPat?.trim()),
|
|
1187
|
-
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim()),
|
|
1193
|
+
hasAbleTimeToken: Boolean(abletimeAccessToken?.trim() || abletimeRefreshToken?.trim()),
|
|
1194
|
+
hasAbleTimeOAuth: Boolean(abletimeRefreshToken?.trim()),
|
|
1188
1195
|
hasVercelToken: Boolean(vercelToken?.trim()),
|
|
1189
1196
|
hasSupabaseToken: Boolean(supabaseAccessToken?.trim()),
|
|
1190
1197
|
hasPosthogToken: Boolean(posthogPersonalApiKey?.trim()),
|
|
@@ -1322,6 +1329,16 @@ function normalizeIntegrations(raw) {
|
|
|
1322
1329
|
const token = source.abletimeAccessToken.trim();
|
|
1323
1330
|
if (token) out.abletimeAccessToken = token;
|
|
1324
1331
|
}
|
|
1332
|
+
if (typeof source.abletimeRefreshToken === "string") {
|
|
1333
|
+
const token = source.abletimeRefreshToken.trim();
|
|
1334
|
+
if (token) out.abletimeRefreshToken = token;
|
|
1335
|
+
}
|
|
1336
|
+
if (typeof source.abletimeTokenExpiresAt === "number" && Number.isFinite(source.abletimeTokenExpiresAt)) {
|
|
1337
|
+
out.abletimeTokenExpiresAt = source.abletimeTokenExpiresAt;
|
|
1338
|
+
} else if (typeof source.abletimeTokenExpiresAt === "string") {
|
|
1339
|
+
const n = Number(source.abletimeTokenExpiresAt);
|
|
1340
|
+
if (Number.isFinite(n)) out.abletimeTokenExpiresAt = n;
|
|
1341
|
+
}
|
|
1325
1342
|
if (typeof source.abletimeHost === "string") {
|
|
1326
1343
|
const host = source.abletimeHost.trim();
|
|
1327
1344
|
if (host) out.abletimeHost = host;
|
|
@@ -1599,7 +1616,7 @@ function readSettingsFile() {
|
|
|
1599
1616
|
}
|
|
1600
1617
|
function diskHoldsSecrets(disk) {
|
|
1601
1618
|
return Boolean(
|
|
1602
|
-
disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || disk.integrations.abletimeAccessToken || disk.integrations.vercelToken || disk.integrations.supabaseAccessToken || disk.integrations.posthogPersonalApiKey || disk.integrations.sentryAuthToken || Object.keys(disk.environment).length > 0
|
|
1619
|
+
disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || disk.integrations.abletimeAccessToken || disk.integrations.abletimeRefreshToken || disk.integrations.vercelToken || disk.integrations.supabaseAccessToken || disk.integrations.posthogPersonalApiKey || disk.integrations.sentryAuthToken || Object.keys(disk.environment).length > 0
|
|
1603
1620
|
);
|
|
1604
1621
|
}
|
|
1605
1622
|
function mergeVault(disk) {
|
|
@@ -1630,6 +1647,9 @@ function mergeVault(disk) {
|
|
|
1630
1647
|
if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
|
|
1631
1648
|
integrations.abletimeAccessToken = vault.abletimeAccessToken;
|
|
1632
1649
|
}
|
|
1650
|
+
if (vault.abletimeRefreshToken && !integrations.abletimeRefreshToken) {
|
|
1651
|
+
integrations.abletimeRefreshToken = vault.abletimeRefreshToken;
|
|
1652
|
+
}
|
|
1633
1653
|
if (vault.vercelToken && !integrations.vercelToken) {
|
|
1634
1654
|
integrations.vercelToken = vault.vercelToken;
|
|
1635
1655
|
}
|
|
@@ -1654,6 +1674,7 @@ function persistSplit(settings) {
|
|
|
1654
1674
|
const slackAppToken = integrations.slackAppToken;
|
|
1655
1675
|
const githubPat = integrations.githubPat;
|
|
1656
1676
|
const abletimeAccessToken = integrations.abletimeAccessToken;
|
|
1677
|
+
const abletimeRefreshToken = integrations.abletimeRefreshToken;
|
|
1657
1678
|
const vercelToken = integrations.vercelToken;
|
|
1658
1679
|
const supabaseAccessToken = integrations.supabaseAccessToken;
|
|
1659
1680
|
const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
|
|
@@ -1666,6 +1687,7 @@ function persistSplit(settings) {
|
|
|
1666
1687
|
delete integrations.slackAppToken;
|
|
1667
1688
|
delete integrations.githubPat;
|
|
1668
1689
|
delete integrations.abletimeAccessToken;
|
|
1690
|
+
delete integrations.abletimeRefreshToken;
|
|
1669
1691
|
delete integrations.vercelToken;
|
|
1670
1692
|
delete integrations.supabaseAccessToken;
|
|
1671
1693
|
delete integrations.posthogPersonalApiKey;
|
|
@@ -1684,6 +1706,7 @@ function persistSplit(settings) {
|
|
|
1684
1706
|
slackAppToken,
|
|
1685
1707
|
githubPat,
|
|
1686
1708
|
abletimeAccessToken,
|
|
1709
|
+
abletimeRefreshToken,
|
|
1687
1710
|
vercelToken,
|
|
1688
1711
|
supabaseAccessToken,
|
|
1689
1712
|
posthogPersonalApiKey,
|
|
@@ -1870,6 +1893,8 @@ function updateIntegrationsSettings(patch) {
|
|
|
1870
1893
|
if ("abletimeAccessToken" in patch) {
|
|
1871
1894
|
if (patch.abletimeAccessToken == null || patch.abletimeAccessToken.trim() === "") {
|
|
1872
1895
|
delete integrations.abletimeAccessToken;
|
|
1896
|
+
delete integrations.abletimeRefreshToken;
|
|
1897
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
1873
1898
|
delete integrations.abletimeViewerName;
|
|
1874
1899
|
} else {
|
|
1875
1900
|
integrations.abletimeAccessToken = patch.abletimeAccessToken.trim();
|
|
@@ -2220,7 +2245,9 @@ function disconnectLinearConnection() {
|
|
|
2220
2245
|
return saveAppSettings({ ...current, integrations });
|
|
2221
2246
|
}
|
|
2222
2247
|
function isAbleTimeConnected(settings = loadAppSettings()) {
|
|
2223
|
-
return Boolean(
|
|
2248
|
+
return Boolean(
|
|
2249
|
+
settings.integrations.abletimeAccessToken?.trim() || settings.integrations.abletimeRefreshToken?.trim()
|
|
2250
|
+
);
|
|
2224
2251
|
}
|
|
2225
2252
|
function getAbleTimeAccessToken(settings = loadAppSettings()) {
|
|
2226
2253
|
const token = settings.integrations.abletimeAccessToken?.trim();
|
|
@@ -2233,6 +2260,12 @@ function saveAbleTimeConnection(input) {
|
|
|
2233
2260
|
const current = loadAppSettings();
|
|
2234
2261
|
const integrations = { ...current.integrations };
|
|
2235
2262
|
integrations.abletimeAccessToken = input.accessToken.trim();
|
|
2263
|
+
if (input.refreshToken?.trim()) {
|
|
2264
|
+
integrations.abletimeRefreshToken = input.refreshToken.trim();
|
|
2265
|
+
}
|
|
2266
|
+
if (typeof input.expiresIn === "number" && Number.isFinite(input.expiresIn)) {
|
|
2267
|
+
integrations.abletimeTokenExpiresAt = Date.now() + Math.max(0, input.expiresIn) * 1e3;
|
|
2268
|
+
}
|
|
2236
2269
|
if (input.host?.trim()) {
|
|
2237
2270
|
integrations.abletimeHost = input.host.trim();
|
|
2238
2271
|
}
|
|
@@ -2245,6 +2278,8 @@ function disconnectAbleTimeConnection() {
|
|
|
2245
2278
|
const current = loadAppSettings();
|
|
2246
2279
|
const integrations = { ...current.integrations };
|
|
2247
2280
|
delete integrations.abletimeAccessToken;
|
|
2281
|
+
delete integrations.abletimeRefreshToken;
|
|
2282
|
+
delete integrations.abletimeTokenExpiresAt;
|
|
2248
2283
|
delete integrations.abletimeViewerName;
|
|
2249
2284
|
return saveAppSettings({ ...current, integrations });
|
|
2250
2285
|
}
|
|
@@ -3263,6 +3298,9 @@ function humanizeAgentFailDetail(detail) {
|
|
|
3263
3298
|
if (/\[resource_exhausted\]|resource_exhausted|findfileswithripgrep/.test(lower)) {
|
|
3264
3299
|
return "Cursor local file search failed (ripgrep / resource_exhausted). Wait a minute and retry; if it keeps happening, check Cursor usage.";
|
|
3265
3300
|
}
|
|
3301
|
+
if (looksLikeCodexMcpError(raw)) {
|
|
3302
|
+
return `${raw} \u2014 skip that vendor MCP and continue (PostHog: Settings \u2192 Connectors + HTTP API).`;
|
|
3303
|
+
}
|
|
3266
3304
|
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(
|
|
3267
3305
|
lower
|
|
3268
3306
|
)) {
|
|
@@ -3320,9 +3358,24 @@ function turnFailChatText(opts) {
|
|
|
3320
3358
|
|
|
3321
3359
|
${fail6}`;
|
|
3322
3360
|
}
|
|
3361
|
+
function looksLikeCodexMcpError(text7) {
|
|
3362
|
+
const t = text7.trim().toLowerCase();
|
|
3363
|
+
if (!t) return false;
|
|
3364
|
+
if (/authrequired|authorization required/.test(t) && /mcp|posthog|rmcp|connector|plugin/.test(t)) {
|
|
3365
|
+
return true;
|
|
3366
|
+
}
|
|
3367
|
+
if (/\bposthog\b/.test(t) && /mcp|oauth|auth|unauthorized|401|plugin|app/.test(t)) {
|
|
3368
|
+
return true;
|
|
3369
|
+
}
|
|
3370
|
+
if (/\brmcp\b/.test(t) && /fatal|quit|transport|error|died|kill|auth/.test(t)) {
|
|
3371
|
+
return true;
|
|
3372
|
+
}
|
|
3373
|
+
return /mcp[_ -]?(server|tool|error|transport|client)/.test(t) && /fail|error|timeout|disconnect|auth|unauthorized/.test(t);
|
|
3374
|
+
}
|
|
3323
3375
|
function shouldFeedErrorBackToAgent(opts) {
|
|
3324
3376
|
const detail = opts.detail.trim();
|
|
3325
3377
|
const chat = (opts.assistantText ?? "").trim();
|
|
3378
|
+
if (looksLikeCodexMcpError(detail)) return true;
|
|
3326
3379
|
if (looksLikeAgentFailureMessage(detail) || looksLikeAgentFailureMessage(chat)) {
|
|
3327
3380
|
return false;
|
|
3328
3381
|
}
|
|
@@ -7693,6 +7746,7 @@ __export(worktree_exports, {
|
|
|
7693
7746
|
canonicalizeRepoPath: () => canonicalizeRepoPath,
|
|
7694
7747
|
collectTakenTeamSlugs: () => collectTakenTeamSlugs,
|
|
7695
7748
|
commitAll: () => commitAll,
|
|
7749
|
+
connectedPrSelectors: () => connectedPrSelectors,
|
|
7696
7750
|
countUnpushedVsOrigin: () => countUnpushedVsOrigin,
|
|
7697
7751
|
createExistingBranchWorktree: () => createExistingBranchWorktree,
|
|
7698
7752
|
createOrUpdatePr: () => createOrUpdatePr,
|
|
@@ -8026,26 +8080,21 @@ function resolvePrSelectors(thread) {
|
|
|
8026
8080
|
function resolvePrSelector(thread) {
|
|
8027
8081
|
return resolvePrSelectors(thread)[0] ?? null;
|
|
8028
8082
|
}
|
|
8083
|
+
function connectedPrSelectors(thread, currentHeadPrUrl) {
|
|
8084
|
+
const out = [];
|
|
8085
|
+
const push = (value) => {
|
|
8086
|
+
const v = value?.trim();
|
|
8087
|
+
if (!v || out.includes(v)) return;
|
|
8088
|
+
out.push(v);
|
|
8089
|
+
};
|
|
8090
|
+
push(currentHeadPrUrl);
|
|
8091
|
+
for (const selector of resolvePrSelectors(thread)) push(selector);
|
|
8092
|
+
return out;
|
|
8093
|
+
}
|
|
8029
8094
|
async function getPrForHeadBranch(repoPath, branch) {
|
|
8030
8095
|
const head = normalizeGitBranchName(branch);
|
|
8031
8096
|
if (!head || isDefaultishSourceRef(head)) return null;
|
|
8032
8097
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
8033
|
-
const viewArgs = [
|
|
8034
|
-
"pr",
|
|
8035
|
-
"view",
|
|
8036
|
-
head,
|
|
8037
|
-
"--json",
|
|
8038
|
-
"number,title,headRefName,url,isCrossRepository,author"
|
|
8039
|
-
];
|
|
8040
|
-
if (slug) viewArgs.push("--repo", slug);
|
|
8041
|
-
const viewed = await gh(viewArgs, repoPath, { reject: false });
|
|
8042
|
-
if (viewed.exitCode === 0 && viewed.stdout.trim()) {
|
|
8043
|
-
try {
|
|
8044
|
-
return JSON.parse(viewed.stdout);
|
|
8045
|
-
} catch {
|
|
8046
|
-
return null;
|
|
8047
|
-
}
|
|
8048
|
-
}
|
|
8049
8098
|
const listHead = slug ? ghHeadRef(slug, head) : head;
|
|
8050
8099
|
const listArgs = [
|
|
8051
8100
|
"pr",
|
|
@@ -8061,10 +8110,28 @@ async function getPrForHeadBranch(repoPath, branch) {
|
|
|
8061
8110
|
];
|
|
8062
8111
|
if (slug) listArgs.push("--repo", slug);
|
|
8063
8112
|
const listed = await gh(listArgs, repoPath, { reject: false });
|
|
8064
|
-
if (listed.exitCode
|
|
8113
|
+
if (listed.exitCode === 0 && listed.stdout.trim()) {
|
|
8114
|
+
try {
|
|
8115
|
+
const rows = JSON.parse(listed.stdout);
|
|
8116
|
+
if (rows[0]) return rows[0];
|
|
8117
|
+
} catch {
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
const viewArgs = [
|
|
8121
|
+
"pr",
|
|
8122
|
+
"view",
|
|
8123
|
+
head,
|
|
8124
|
+
"--json",
|
|
8125
|
+
"number,title,headRefName,url,isCrossRepository,author,state"
|
|
8126
|
+
];
|
|
8127
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
8128
|
+
const viewed = await gh(viewArgs, repoPath, { reject: false });
|
|
8129
|
+
if (viewed.exitCode !== 0 || !viewed.stdout.trim()) return null;
|
|
8065
8130
|
try {
|
|
8066
|
-
const
|
|
8067
|
-
|
|
8131
|
+
const info = JSON.parse(viewed.stdout);
|
|
8132
|
+
const state = (info.state ?? "OPEN").trim().toUpperCase();
|
|
8133
|
+
if (state !== "OPEN") return null;
|
|
8134
|
+
return info;
|
|
8068
8135
|
} catch {
|
|
8069
8136
|
return null;
|
|
8070
8137
|
}
|
|
@@ -12834,7 +12901,18 @@ function toCodexDisableUserMcpArgs(names) {
|
|
|
12834
12901
|
return args;
|
|
12835
12902
|
}
|
|
12836
12903
|
function toCodexUnattendedAppsArgs() {
|
|
12837
|
-
return [
|
|
12904
|
+
return [
|
|
12905
|
+
"-c",
|
|
12906
|
+
"features.apps=false",
|
|
12907
|
+
"-c",
|
|
12908
|
+
"apps._default.enabled=false",
|
|
12909
|
+
"-c",
|
|
12910
|
+
"apps.posthog.enabled=false",
|
|
12911
|
+
"-c",
|
|
12912
|
+
"features.plugins=false",
|
|
12913
|
+
"-c",
|
|
12914
|
+
"mcp_servers.posthog.enabled=false"
|
|
12915
|
+
];
|
|
12838
12916
|
}
|
|
12839
12917
|
var import_node_fs32, import_node_os11, import_node_path33, import_smol_toml2;
|
|
12840
12918
|
var init_orch_mcp_isolation = __esm({
|
|
@@ -14585,11 +14663,14 @@ function staleOk(now) {
|
|
|
14585
14663
|
if (now - lastGood.at > CLAUDE_USAGE_STALE_OK_MS) return null;
|
|
14586
14664
|
return lastGood.value;
|
|
14587
14665
|
}
|
|
14666
|
+
function reuseLast() {
|
|
14667
|
+
return lastGood?.value ?? null;
|
|
14668
|
+
}
|
|
14588
14669
|
async function getClaudePlanUsage(opts = {}) {
|
|
14589
14670
|
const now = opts.now ?? Date.now();
|
|
14671
|
+
if (now < errorUntil) return reuseLast() ?? staleOk(now);
|
|
14590
14672
|
if (!opts.force) {
|
|
14591
14673
|
if (cache && now - cache.at < CLAUDE_USAGE_TTL_MS) return cache.value;
|
|
14592
|
-
if (now < errorUntil) return staleOk(now);
|
|
14593
14674
|
}
|
|
14594
14675
|
if (inflight) return inflight;
|
|
14595
14676
|
inflight = (async () => {
|
|
@@ -14620,6 +14701,12 @@ async function getClaudePlanUsage(opts = {}) {
|
|
|
14620
14701
|
cache = { at: now, value: null };
|
|
14621
14702
|
return null;
|
|
14622
14703
|
}
|
|
14704
|
+
if (res.status === 429) {
|
|
14705
|
+
errorUntil = now + CLAUDE_USAGE_ERROR_TTL_MS;
|
|
14706
|
+
const last = reuseLast();
|
|
14707
|
+
if (last) cache = { at: now, value: last };
|
|
14708
|
+
return last;
|
|
14709
|
+
}
|
|
14623
14710
|
if (!res.ok) {
|
|
14624
14711
|
errorUntil = now + CLAUDE_USAGE_ERROR_TTL_MS;
|
|
14625
14712
|
return staleOk(now);
|
|
@@ -15380,6 +15467,33 @@ var init_agent_git_actions = __esm({
|
|
|
15380
15467
|
function normalizePrState(state) {
|
|
15381
15468
|
return (state ?? "").trim().toUpperCase();
|
|
15382
15469
|
}
|
|
15470
|
+
function sameLoginList(a, b) {
|
|
15471
|
+
const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
15472
|
+
const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
15473
|
+
return left.length === right.length && left.every((login, i) => login === right[i]);
|
|
15474
|
+
}
|
|
15475
|
+
function threadPrMetaPatch(thread, meta) {
|
|
15476
|
+
const prevState = normalizePrState(thread.prState);
|
|
15477
|
+
const nextState = normalizePrState(meta.state);
|
|
15478
|
+
const patch = {};
|
|
15479
|
+
if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
|
|
15480
|
+
if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
|
|
15481
|
+
if (nextState && nextState !== prevState) patch.prState = nextState;
|
|
15482
|
+
const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
|
|
15483
|
+
if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
|
|
15484
|
+
const nextAuthor = meta.authorLogin?.trim() || "";
|
|
15485
|
+
if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
|
|
15486
|
+
patch.prAuthorLogin = nextAuthor;
|
|
15487
|
+
}
|
|
15488
|
+
const nextReviewers = meta.reviewerLogins ?? [];
|
|
15489
|
+
if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
|
|
15490
|
+
patch.prReviewerLogins = nextReviewers;
|
|
15491
|
+
}
|
|
15492
|
+
if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
|
|
15493
|
+
patch.skipAutoArchiveOnMerge = false;
|
|
15494
|
+
}
|
|
15495
|
+
return patch;
|
|
15496
|
+
}
|
|
15383
15497
|
function shouldAutoArchiveOnPrMerge(opts) {
|
|
15384
15498
|
if (!opts.autoArchiveEnabled) return false;
|
|
15385
15499
|
if (opts.isGlobal) return false;
|
|
@@ -17459,6 +17573,697 @@ var init_detect = __esm({
|
|
|
17459
17573
|
}
|
|
17460
17574
|
});
|
|
17461
17575
|
|
|
17576
|
+
// src/integrations/abletime-oauth.ts
|
|
17577
|
+
var abletime_oauth_exports = {};
|
|
17578
|
+
__export(abletime_oauth_exports, {
|
|
17579
|
+
ABLETIME_OAUTH_CALLBACK_PATH: () => ABLETIME_OAUTH_CALLBACK_PATH,
|
|
17580
|
+
ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
|
|
17581
|
+
ABLETIME_OAUTH_LOCAL_CALLBACK: () => ABLETIME_OAUTH_LOCAL_CALLBACK,
|
|
17582
|
+
ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
|
|
17583
|
+
ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
|
|
17584
|
+
ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
|
|
17585
|
+
AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
|
|
17586
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
17587
|
+
ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
|
|
17588
|
+
ableTimeOAuthBouncePage: () => ableTimeOAuthBouncePage,
|
|
17589
|
+
ableTimeOAuthClientId: () => ableTimeOAuthClientId,
|
|
17590
|
+
ableTimeOAuthLocalBounceUrl: () => ableTimeOAuthLocalBounceUrl,
|
|
17591
|
+
ableTimeOAuthRedirectUri: () => ableTimeOAuthRedirectUri,
|
|
17592
|
+
ableTimeOAuthResource: () => ableTimeOAuthResource,
|
|
17593
|
+
createAbleTimePkce: () => createAbleTimePkce,
|
|
17594
|
+
ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
|
|
17595
|
+
hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
|
|
17596
|
+
isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
|
|
17597
|
+
parseAbleTimeOAuthCallbackUrl: () => parseAbleTimeOAuthCallbackUrl,
|
|
17598
|
+
startAbleTimeOAuth: () => startAbleTimeOAuth
|
|
17599
|
+
});
|
|
17600
|
+
function ableTimeOAuthRedirectUri() {
|
|
17601
|
+
return process.env.SIDEBOARD_ABLETIME_OAUTH_REDIRECT?.trim() || ABLETIME_OAUTH_REDIRECT;
|
|
17602
|
+
}
|
|
17603
|
+
function parseAbleTimeOAuthCallbackUrl(reqUrl) {
|
|
17604
|
+
let url;
|
|
17605
|
+
try {
|
|
17606
|
+
url = new URL(reqUrl, ABLETIME_OAUTH_REDIRECT);
|
|
17607
|
+
} catch {
|
|
17608
|
+
return null;
|
|
17609
|
+
}
|
|
17610
|
+
if (url.pathname !== ABLETIME_OAUTH_CALLBACK_PATH) return null;
|
|
17611
|
+
return url;
|
|
17612
|
+
}
|
|
17613
|
+
function ableTimeOAuthLocalBounceUrl(reqUrl) {
|
|
17614
|
+
const url = parseAbleTimeOAuthCallbackUrl(reqUrl);
|
|
17615
|
+
if (!url) return null;
|
|
17616
|
+
const dest = new URL(ABLETIME_OAUTH_LOCAL_CALLBACK);
|
|
17617
|
+
for (const key of BOUNCE_QUERY_KEYS) {
|
|
17618
|
+
const value = url.searchParams.get(key);
|
|
17619
|
+
if (value) dest.searchParams.set(key, value);
|
|
17620
|
+
}
|
|
17621
|
+
return dest.toString();
|
|
17622
|
+
}
|
|
17623
|
+
function escapeHtml(value) {
|
|
17624
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
17625
|
+
}
|
|
17626
|
+
function ableTimeOAuthBouncePage(localUrl) {
|
|
17627
|
+
const safe = escapeHtml(localUrl);
|
|
17628
|
+
return `<!doctype html><html><head><meta charset="utf-8">
|
|
17629
|
+
<meta http-equiv="refresh" content="0;url=${safe}">
|
|
17630
|
+
<title>Returning to Sideboard</title>
|
|
17631
|
+
<script>location.replace(${JSON.stringify(localUrl)})</script>
|
|
17632
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
17633
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}a{color:#0f7ed4}</style>
|
|
17634
|
+
</head><body>
|
|
17635
|
+
<h1>Returning to Sideboard</h1>
|
|
17636
|
+
<p>AbleTime authorized. Opening the app\u2026</p>
|
|
17637
|
+
<p><a href="${safe}">Open Sideboard</a> if it does not appear.</p>
|
|
17638
|
+
</body></html>`;
|
|
17639
|
+
}
|
|
17640
|
+
function hasBakedAbleTimeOAuth() {
|
|
17641
|
+
return Boolean(BAKED_ABLETIME_OAUTH_CLIENT_ID.trim());
|
|
17642
|
+
}
|
|
17643
|
+
function ableTimeOAuthClientId() {
|
|
17644
|
+
return process.env.SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID?.trim() || BAKED_ABLETIME_OAUTH_CLIENT_ID.trim();
|
|
17645
|
+
}
|
|
17646
|
+
function ableTimeOAuthResource(host) {
|
|
17647
|
+
return `${normalizeAbleTimeHost(host)}/api/public/v2/mcp`;
|
|
17648
|
+
}
|
|
17649
|
+
function createAbleTimePkce() {
|
|
17650
|
+
const verifier = (0, import_node_crypto10.randomBytes)(32).toString("base64url");
|
|
17651
|
+
const challenge = (0, import_node_crypto10.createHash)("sha256").update(verifier).digest("base64url");
|
|
17652
|
+
return { verifier, challenge };
|
|
17653
|
+
}
|
|
17654
|
+
function ableTimeOAuthAuthorizeUrl(clientId, state, codeChallenge, host) {
|
|
17655
|
+
const base = normalizeAbleTimeHost(host);
|
|
17656
|
+
const params = new URLSearchParams({
|
|
17657
|
+
response_type: "code",
|
|
17658
|
+
client_id: clientId,
|
|
17659
|
+
redirect_uri: ableTimeOAuthRedirectUri(),
|
|
17660
|
+
code_challenge: codeChallenge,
|
|
17661
|
+
code_challenge_method: "S256",
|
|
17662
|
+
state,
|
|
17663
|
+
resource: ableTimeOAuthResource(host)
|
|
17664
|
+
});
|
|
17665
|
+
return `${base}/oauth/authorize?${params.toString()}`;
|
|
17666
|
+
}
|
|
17667
|
+
function htmlPage(title, body) {
|
|
17668
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
|
|
17669
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
17670
|
+
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
17671
|
+
}
|
|
17672
|
+
async function exchangeAbleTimeToken(body, host) {
|
|
17673
|
+
const url = `${normalizeAbleTimeHost(host)}/api/oauth/token`;
|
|
17674
|
+
const res = await httpFetch(url, {
|
|
17675
|
+
method: "POST",
|
|
17676
|
+
headers: {
|
|
17677
|
+
Accept: "application/json",
|
|
17678
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
17679
|
+
},
|
|
17680
|
+
body
|
|
17681
|
+
});
|
|
17682
|
+
const data = await res.json().catch(() => ({}));
|
|
17683
|
+
if (!res.ok || data.error || !data.access_token?.trim()) {
|
|
17684
|
+
const detail = data.error_description || data.error || `HTTP ${res.status}`;
|
|
17685
|
+
throw new Error(`AbleTime OAuth token exchange failed: ${detail}`);
|
|
17686
|
+
}
|
|
17687
|
+
return data;
|
|
17688
|
+
}
|
|
17689
|
+
function persistAbleTimeTokens(data, input) {
|
|
17690
|
+
return saveAbleTimeConnection({
|
|
17691
|
+
accessToken: data.access_token.trim(),
|
|
17692
|
+
refreshToken: data.refresh_token?.trim(),
|
|
17693
|
+
expiresIn: data.expires_in,
|
|
17694
|
+
host: input?.host,
|
|
17695
|
+
viewerName: input?.viewerName
|
|
17696
|
+
});
|
|
17697
|
+
}
|
|
17698
|
+
async function refreshAbleTimeAccessToken(refreshToken) {
|
|
17699
|
+
const host = getAbleTimeHost();
|
|
17700
|
+
const body = new URLSearchParams({
|
|
17701
|
+
grant_type: "refresh_token",
|
|
17702
|
+
refresh_token: refreshToken,
|
|
17703
|
+
client_id: ableTimeOAuthClientId(),
|
|
17704
|
+
resource: ableTimeOAuthResource(host)
|
|
17705
|
+
});
|
|
17706
|
+
try {
|
|
17707
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
17708
|
+
persistAbleTimeTokens(data, { host });
|
|
17709
|
+
return data.access_token.trim();
|
|
17710
|
+
} catch (err) {
|
|
17711
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17712
|
+
if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
|
|
17713
|
+
disconnectAbleTimeConnection();
|
|
17714
|
+
throw new Error("AbleTime sign-in expired \u2014 connect again in Account settings");
|
|
17715
|
+
}
|
|
17716
|
+
throw err;
|
|
17717
|
+
}
|
|
17718
|
+
}
|
|
17719
|
+
async function ensureAbleTimeAccessToken() {
|
|
17720
|
+
const settings = loadAppSettings();
|
|
17721
|
+
const access = settings.integrations.abletimeAccessToken?.trim() || "";
|
|
17722
|
+
const refresh = settings.integrations.abletimeRefreshToken?.trim() || "";
|
|
17723
|
+
const expiresAt = settings.integrations.abletimeTokenExpiresAt ?? 0;
|
|
17724
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
|
|
17725
|
+
return access;
|
|
17726
|
+
}
|
|
17727
|
+
if (refresh) {
|
|
17728
|
+
if (!refreshInFlight) {
|
|
17729
|
+
refreshInFlight = refreshAbleTimeAccessToken(refresh).finally(() => {
|
|
17730
|
+
refreshInFlight = null;
|
|
17731
|
+
});
|
|
17732
|
+
}
|
|
17733
|
+
return refreshInFlight;
|
|
17734
|
+
}
|
|
17735
|
+
return access || null;
|
|
17736
|
+
}
|
|
17737
|
+
function isAbleTimeOAuthCancelled(err) {
|
|
17738
|
+
if (err instanceof AbleTimeOAuthCancelledError) return true;
|
|
17739
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17740
|
+
return message.includes(ABLETIME_OAUTH_CANCELLED);
|
|
17741
|
+
}
|
|
17742
|
+
async function startAbleTimeOAuth(opts) {
|
|
17743
|
+
if (opts?.signal?.aborted) {
|
|
17744
|
+
throw new AbleTimeOAuthCancelledError();
|
|
17745
|
+
}
|
|
17746
|
+
const clientId = ableTimeOAuthClientId();
|
|
17747
|
+
if (!clientId) {
|
|
17748
|
+
throw new Error(
|
|
17749
|
+
`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.`
|
|
17750
|
+
);
|
|
17751
|
+
}
|
|
17752
|
+
const host = opts?.host?.trim() || void 0;
|
|
17753
|
+
const state = (0, import_node_crypto10.randomBytes)(16).toString("hex");
|
|
17754
|
+
const pkce = createAbleTimePkce();
|
|
17755
|
+
const authorizeUrl = ableTimeOAuthAuthorizeUrl(clientId, state, pkce.challenge, host);
|
|
17756
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
17757
|
+
const code = await new Promise((resolve, reject) => {
|
|
17758
|
+
let settled = false;
|
|
17759
|
+
const finish = (err, value) => {
|
|
17760
|
+
if (settled) return;
|
|
17761
|
+
settled = true;
|
|
17762
|
+
cleanup();
|
|
17763
|
+
if (err) reject(err);
|
|
17764
|
+
else resolve(value);
|
|
17765
|
+
};
|
|
17766
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
17767
|
+
try {
|
|
17768
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${ABLETIME_OAUTH_PORT}`);
|
|
17769
|
+
if (url.pathname !== "/callback") {
|
|
17770
|
+
res.writeHead(404);
|
|
17771
|
+
res.end("Not found");
|
|
17772
|
+
return;
|
|
17773
|
+
}
|
|
17774
|
+
const err = url.searchParams.get("error");
|
|
17775
|
+
const gotState = url.searchParams.get("state");
|
|
17776
|
+
const gotCode = url.searchParams.get("code");
|
|
17777
|
+
if (err) {
|
|
17778
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
17779
|
+
res.end(htmlPage("AbleTime", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
17780
|
+
finish(new AbleTimeOAuthCancelledError(`AbleTime OAuth: ${err}`));
|
|
17781
|
+
return;
|
|
17782
|
+
}
|
|
17783
|
+
if (gotState !== state || !gotCode) {
|
|
17784
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
17785
|
+
res.end(htmlPage("AbleTime", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
17786
|
+
finish(new Error("AbleTime OAuth callback was invalid"));
|
|
17787
|
+
return;
|
|
17788
|
+
}
|
|
17789
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
17790
|
+
res.end(
|
|
17791
|
+
htmlPage(
|
|
17792
|
+
"AbleTime connected",
|
|
17793
|
+
"<h1>AbleTime connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
17794
|
+
)
|
|
17795
|
+
);
|
|
17796
|
+
finish(void 0, gotCode);
|
|
17797
|
+
} catch (e) {
|
|
17798
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
17799
|
+
}
|
|
17800
|
+
});
|
|
17801
|
+
const timer = setTimeout(() => {
|
|
17802
|
+
finish(new Error("AbleTime sign-in timed out \u2014 try again"));
|
|
17803
|
+
}, timeoutMs);
|
|
17804
|
+
const onAbort = () => finish(new AbleTimeOAuthCancelledError());
|
|
17805
|
+
const cleanup = () => {
|
|
17806
|
+
clearTimeout(timer);
|
|
17807
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
17808
|
+
server.close();
|
|
17809
|
+
};
|
|
17810
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
17811
|
+
if (opts?.signal?.aborted) {
|
|
17812
|
+
onAbort();
|
|
17813
|
+
return;
|
|
17814
|
+
}
|
|
17815
|
+
server.on("error", (e) => {
|
|
17816
|
+
finish(
|
|
17817
|
+
e instanceof Error && e.code === "EADDRINUSE" ? new Error(
|
|
17818
|
+
`Port ${ABLETIME_OAUTH_PORT} is in use. Close whatever is bound there, or paste an AbleTime token instead.`
|
|
17819
|
+
) : e instanceof Error ? e : new Error(String(e))
|
|
17820
|
+
);
|
|
17821
|
+
});
|
|
17822
|
+
server.listen(ABLETIME_OAUTH_PORT, "127.0.0.1", () => {
|
|
17823
|
+
void Promise.resolve(opts?.openUrl?.(authorizeUrl)).catch((e) => {
|
|
17824
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
17825
|
+
});
|
|
17826
|
+
});
|
|
17827
|
+
});
|
|
17828
|
+
const body = new URLSearchParams({
|
|
17829
|
+
grant_type: "authorization_code",
|
|
17830
|
+
code,
|
|
17831
|
+
redirect_uri: ableTimeOAuthRedirectUri(),
|
|
17832
|
+
client_id: clientId,
|
|
17833
|
+
code_verifier: pkce.verifier,
|
|
17834
|
+
resource: ableTimeOAuthResource(host)
|
|
17835
|
+
});
|
|
17836
|
+
const data = await exchangeAbleTimeToken(body, host);
|
|
17837
|
+
persistAbleTimeTokens(data, { host });
|
|
17838
|
+
const token = data.access_token.trim();
|
|
17839
|
+
try {
|
|
17840
|
+
const { getAbleTimeOrientation: getAbleTimeOrientation2 } = await Promise.resolve().then(() => (init_abletime(), abletime_exports));
|
|
17841
|
+
const orientation = await getAbleTimeOrientation2({ token, host });
|
|
17842
|
+
return saveAbleTimeConnection({
|
|
17843
|
+
accessToken: token,
|
|
17844
|
+
refreshToken: data.refresh_token?.trim(),
|
|
17845
|
+
expiresIn: data.expires_in,
|
|
17846
|
+
host,
|
|
17847
|
+
viewerName: orientation.viewer.name
|
|
17848
|
+
});
|
|
17849
|
+
} catch {
|
|
17850
|
+
return loadAppSettings();
|
|
17851
|
+
}
|
|
17852
|
+
}
|
|
17853
|
+
var import_node_crypto10, 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;
|
|
17854
|
+
var init_abletime_oauth = __esm({
|
|
17855
|
+
"src/integrations/abletime-oauth.ts"() {
|
|
17856
|
+
"use strict";
|
|
17857
|
+
import_node_crypto10 = require("crypto");
|
|
17858
|
+
import_node_http = require("http");
|
|
17859
|
+
init_fetch();
|
|
17860
|
+
init_app_settings();
|
|
17861
|
+
init_abletime_mcp();
|
|
17862
|
+
ABLETIME_OAUTH_CALLBACK_PATH = "/oauth/abletime/callback";
|
|
17863
|
+
ABLETIME_OAUTH_PORT = 19849;
|
|
17864
|
+
ABLETIME_OAUTH_LOCAL_CALLBACK = `http://127.0.0.1:${ABLETIME_OAUTH_PORT}/callback`;
|
|
17865
|
+
ABLETIME_OAUTH_REDIRECT = `https://www.sideboard.cloud${ABLETIME_OAUTH_CALLBACK_PATH}`;
|
|
17866
|
+
BOUNCE_QUERY_KEYS = ["code", "state", "error", "error_description"];
|
|
17867
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID = "https://www.sideboard.cloud/oauth/abletime-client.json";
|
|
17868
|
+
ABLETIME_OAUTH_RESOURCE = `${DEFAULT_ABLETIME_HOST}/api/public/v2/mcp`;
|
|
17869
|
+
REFRESH_SKEW_MS2 = 5 * 6e4;
|
|
17870
|
+
refreshInFlight = null;
|
|
17871
|
+
ABLETIME_OAUTH_CANCELLED = "AbleTime sign-in cancelled";
|
|
17872
|
+
AbleTimeOAuthCancelledError = class extends Error {
|
|
17873
|
+
constructor(message = ABLETIME_OAUTH_CANCELLED) {
|
|
17874
|
+
super(message);
|
|
17875
|
+
this.name = "AbleTimeOAuthCancelledError";
|
|
17876
|
+
}
|
|
17877
|
+
};
|
|
17878
|
+
}
|
|
17879
|
+
});
|
|
17880
|
+
|
|
17881
|
+
// src/integrations/abletime-rest.ts
|
|
17882
|
+
var abletime_rest_exports = {};
|
|
17883
|
+
__export(abletime_rest_exports, {
|
|
17884
|
+
ABLETIME_REST_PREFIX: () => ABLETIME_REST_PREFIX,
|
|
17885
|
+
ableTimeDocFromText: () => ableTimeDocFromText,
|
|
17886
|
+
abletimeRestUrl: () => abletimeRestUrl,
|
|
17887
|
+
callAbleTimeRestTool: () => callAbleTimeRestTool,
|
|
17888
|
+
looksLikeAbleTimeUlid: () => looksLikeAbleTimeUlid,
|
|
17889
|
+
textFromAbleTimeDoc: () => textFromAbleTimeDoc
|
|
17890
|
+
});
|
|
17891
|
+
function asRecord5(value) {
|
|
17892
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17893
|
+
}
|
|
17894
|
+
function asString(value) {
|
|
17895
|
+
return typeof value === "string" ? value.trim() : "";
|
|
17896
|
+
}
|
|
17897
|
+
function asList(value) {
|
|
17898
|
+
if (Array.isArray(value)) return value;
|
|
17899
|
+
const record = asRecord5(value);
|
|
17900
|
+
if (!record) return [];
|
|
17901
|
+
for (const key of ["data", "tasks", "projects", "users", "items", "results"]) {
|
|
17902
|
+
if (Array.isArray(record[key])) return record[key];
|
|
17903
|
+
}
|
|
17904
|
+
return [];
|
|
17905
|
+
}
|
|
17906
|
+
function ableTimeDocFromText(text7) {
|
|
17907
|
+
const trimmed = text7.trim();
|
|
17908
|
+
if (!trimmed) return { type: "doc", content: [] };
|
|
17909
|
+
return {
|
|
17910
|
+
type: "doc",
|
|
17911
|
+
content: [{ type: "paragraph", content: [{ type: "text", text: trimmed }] }]
|
|
17912
|
+
};
|
|
17913
|
+
}
|
|
17914
|
+
function textFromAbleTimeDoc(value) {
|
|
17915
|
+
if (typeof value === "string") return value.trim();
|
|
17916
|
+
const record = asRecord5(value);
|
|
17917
|
+
if (!record) return "";
|
|
17918
|
+
if (typeof record.text === "string" && record.text.trim()) return record.text.trim();
|
|
17919
|
+
const chunks = [];
|
|
17920
|
+
const walk = (node) => {
|
|
17921
|
+
const rec = asRecord5(node);
|
|
17922
|
+
if (!rec) return;
|
|
17923
|
+
if (typeof rec.text === "string" && rec.text) chunks.push(rec.text);
|
|
17924
|
+
if (Array.isArray(rec.content)) rec.content.forEach(walk);
|
|
17925
|
+
};
|
|
17926
|
+
walk(record);
|
|
17927
|
+
return chunks.join("").trim();
|
|
17928
|
+
}
|
|
17929
|
+
function abletimeRestUrl(path2, host, query) {
|
|
17930
|
+
const base = `${normalizeAbleTimeHost(host)}${ABLETIME_REST_PREFIX}`;
|
|
17931
|
+
const suffix = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
17932
|
+
const url = new URL(`${base}${suffix}`);
|
|
17933
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
17934
|
+
if (value) url.searchParams.set(key, value);
|
|
17935
|
+
}
|
|
17936
|
+
return url.href;
|
|
17937
|
+
}
|
|
17938
|
+
function looksLikeAbleTimeUlid(id) {
|
|
17939
|
+
return ULID_RE.test(id.trim());
|
|
17940
|
+
}
|
|
17941
|
+
async function abletimeRestRequest(path2, opts = {}) {
|
|
17942
|
+
const raw = opts.token ?? getAbleTimeAccessToken();
|
|
17943
|
+
const token = normalizeAbleTimeCredential(raw ?? "");
|
|
17944
|
+
if (!token) {
|
|
17945
|
+
throw new Error("AbleTime is not connected \u2014 paste an API key or personal access token in Account settings");
|
|
17946
|
+
}
|
|
17947
|
+
const host = opts.host ?? (opts.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost());
|
|
17948
|
+
const url = abletimeRestUrl(path2, host, opts.query);
|
|
17949
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
17950
|
+
const res = await httpFetch(url, {
|
|
17951
|
+
method,
|
|
17952
|
+
headers: {
|
|
17953
|
+
Accept: "application/json",
|
|
17954
|
+
Authorization: `Bearer ${token}`,
|
|
17955
|
+
...opts.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
17956
|
+
},
|
|
17957
|
+
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
|
|
17958
|
+
});
|
|
17959
|
+
const text7 = await res.text().catch(() => "");
|
|
17960
|
+
if (!res.ok) {
|
|
17961
|
+
throw new Error(
|
|
17962
|
+
rewriteAbleTimeError(
|
|
17963
|
+
`AbleTime API error ${res.status}${text7 ? `: ${text7.slice(0, 240)}` : ""}`
|
|
17964
|
+
)
|
|
17965
|
+
);
|
|
17966
|
+
}
|
|
17967
|
+
if (!text7.trim()) return null;
|
|
17968
|
+
try {
|
|
17969
|
+
return JSON.parse(text7);
|
|
17970
|
+
} catch {
|
|
17971
|
+
throw new Error(`AbleTime API returned a non-JSON response: ${text7.slice(0, 200)}`);
|
|
17972
|
+
}
|
|
17973
|
+
}
|
|
17974
|
+
async function restPages(path2, opts, maxPages = 20) {
|
|
17975
|
+
const items = [];
|
|
17976
|
+
let cursor;
|
|
17977
|
+
for (let i = 0; i < maxPages; i += 1) {
|
|
17978
|
+
const raw = await abletimeRestRequest(path2, {
|
|
17979
|
+
...opts,
|
|
17980
|
+
query: { ...opts.query, ...cursor ? { cursor } : {} }
|
|
17981
|
+
});
|
|
17982
|
+
items.push(...asList(raw));
|
|
17983
|
+
const page = asRecord5(asRecord5(raw)?.page);
|
|
17984
|
+
const next = page ? asString(page.nextCursor) : "";
|
|
17985
|
+
if (!next) break;
|
|
17986
|
+
cursor = next;
|
|
17987
|
+
}
|
|
17988
|
+
return items;
|
|
17989
|
+
}
|
|
17990
|
+
function userDisplayName(raw) {
|
|
17991
|
+
const rec = asRecord5(raw);
|
|
17992
|
+
if (!rec) return null;
|
|
17993
|
+
const id = asString(rec.userId) || asString(rec.id);
|
|
17994
|
+
const name = [asString(rec.firstName), asString(rec.lastName)].filter(Boolean).join(" ") || asString(rec.username) || asString(rec.email) || asString(rec.name);
|
|
17995
|
+
if (!name) return null;
|
|
17996
|
+
return { id: id || void 0, name };
|
|
17997
|
+
}
|
|
17998
|
+
function restTaskToRaw(raw, users) {
|
|
17999
|
+
const rec = asRecord5(raw);
|
|
18000
|
+
if (!rec) return null;
|
|
18001
|
+
const id = asString(rec.timeflowTaskId) || asString(rec.taskId) || asString(rec.id);
|
|
18002
|
+
const identifier = asString(rec.taskRef) || asString(rec.reference) || id;
|
|
18003
|
+
const title = asString(rec.title);
|
|
18004
|
+
if (!id && !identifier && !title) return null;
|
|
18005
|
+
const assigned = asString(rec.assignedUserId);
|
|
18006
|
+
const assignee = assigned ? users.get(assigned) : void 0;
|
|
18007
|
+
const comments = asList(rec.comments).map((item) => {
|
|
18008
|
+
const comment = asRecord5(item) ?? {};
|
|
18009
|
+
const user = userDisplayName(comment);
|
|
18010
|
+
return {
|
|
18011
|
+
id: asString(comment.commentId) || asString(comment.id),
|
|
18012
|
+
body: textFromAbleTimeDoc(comment.content) || asString(comment.contentSanitized) || asString(comment.body),
|
|
18013
|
+
createdAt: asString(comment.createdAt) || asString(comment.lastUpdate),
|
|
18014
|
+
user: user?.name || asString(comment.username)
|
|
18015
|
+
};
|
|
18016
|
+
});
|
|
18017
|
+
return {
|
|
18018
|
+
id,
|
|
18019
|
+
taskId: id,
|
|
18020
|
+
timeflowTaskId: id,
|
|
18021
|
+
reference: identifier,
|
|
18022
|
+
taskRef: identifier,
|
|
18023
|
+
title: title || identifier,
|
|
18024
|
+
state: asString(rec.taskState) || asString(rec.state),
|
|
18025
|
+
description: textFromAbleTimeDoc(rec.description) || asString(rec.descriptionSanitized),
|
|
18026
|
+
projectId: asString(rec.projectId),
|
|
18027
|
+
categoryId: asString(rec.projectCategoryId) || asString(rec.categoryId),
|
|
18028
|
+
tags: rec.tags,
|
|
18029
|
+
comments,
|
|
18030
|
+
createdAt: asString(rec.dateCreated) || asString(rec.createdAt),
|
|
18031
|
+
updatedAt: asString(rec.lastUpdate) || asString(rec.updatedAt),
|
|
18032
|
+
...assignee ? { assignee } : {}
|
|
18033
|
+
};
|
|
18034
|
+
}
|
|
18035
|
+
function restProjectToRaw(raw) {
|
|
18036
|
+
const rec = asRecord5(raw);
|
|
18037
|
+
if (!rec) return null;
|
|
18038
|
+
const id = asString(rec.projectId) || asString(rec.id);
|
|
18039
|
+
const name = asString(rec.projectName) || asString(rec.name);
|
|
18040
|
+
if (!id && !name) return null;
|
|
18041
|
+
const categories = asList(rec.categories).map((item) => {
|
|
18042
|
+
const cat = asRecord5(item) ?? {};
|
|
18043
|
+
return {
|
|
18044
|
+
id: asString(cat.projectCategoryId) || asString(cat.categoryId) || asString(cat.id),
|
|
18045
|
+
name: asString(cat.categoryName) || asString(cat.name)
|
|
18046
|
+
};
|
|
18047
|
+
});
|
|
18048
|
+
return { id, name, projectId: id, projectName: name, categories };
|
|
18049
|
+
}
|
|
18050
|
+
async function listUsers(opts) {
|
|
18051
|
+
const users = /* @__PURE__ */ new Map();
|
|
18052
|
+
for (const item of await restPages("/users", opts).catch(() => [])) {
|
|
18053
|
+
const user = userDisplayName(item);
|
|
18054
|
+
const rec = asRecord5(item);
|
|
18055
|
+
const id = user?.id || (rec ? asString(rec.userId) : "");
|
|
18056
|
+
if (user && id) users.set(id, user);
|
|
18057
|
+
}
|
|
18058
|
+
return users;
|
|
18059
|
+
}
|
|
18060
|
+
async function listProjectsRaw(opts) {
|
|
18061
|
+
const items = await restPages("/projects", { ...opts, query: { expand: "categories" } });
|
|
18062
|
+
return items.map(restProjectToRaw).filter((item) => Boolean(item));
|
|
18063
|
+
}
|
|
18064
|
+
async function listTasksRaw(opts) {
|
|
18065
|
+
const [users, items] = await Promise.all([
|
|
18066
|
+
listUsers(opts),
|
|
18067
|
+
restPages("/tasks", {
|
|
18068
|
+
...opts,
|
|
18069
|
+
query: {
|
|
18070
|
+
...opts.projectId ? { projectId: opts.projectId } : {}
|
|
18071
|
+
}
|
|
18072
|
+
})
|
|
18073
|
+
]);
|
|
18074
|
+
let tasks = items.map((item) => restTaskToRaw(item, users)).filter((item) => Boolean(item));
|
|
18075
|
+
const needle = opts.query?.trim().toLowerCase();
|
|
18076
|
+
if (needle) {
|
|
18077
|
+
tasks = tasks.filter((task) => {
|
|
18078
|
+
const hay = [task.title, task.reference, task.taskRef, task.id].map((value) => String(value ?? "").toLowerCase()).join(" ");
|
|
18079
|
+
const tags = Array.isArray(task.tags) ? task.tags.join(" ").toLowerCase() : "";
|
|
18080
|
+
return hay.includes(needle) || tags.includes(needle);
|
|
18081
|
+
});
|
|
18082
|
+
}
|
|
18083
|
+
return tasks;
|
|
18084
|
+
}
|
|
18085
|
+
async function resolveTaskId(id, opts) {
|
|
18086
|
+
const wanted = id.trim();
|
|
18087
|
+
if (!wanted) throw new Error("AbleTime task id is required");
|
|
18088
|
+
if (looksLikeAbleTimeUlid(wanted)) return wanted;
|
|
18089
|
+
const tasks = await listTasksRaw(opts);
|
|
18090
|
+
const match = tasks.find((task) => {
|
|
18091
|
+
const ref = String(task.reference ?? task.taskRef ?? "").toLowerCase();
|
|
18092
|
+
const taskId = String(task.id ?? "").toLowerCase();
|
|
18093
|
+
return ref === wanted.toLowerCase() || taskId === wanted.toLowerCase();
|
|
18094
|
+
});
|
|
18095
|
+
if (!match?.id) throw new Error(`AbleTime task not found: ${id}`);
|
|
18096
|
+
return String(match.id);
|
|
18097
|
+
}
|
|
18098
|
+
async function getTaskRaw(id, opts) {
|
|
18099
|
+
const taskId = await resolveTaskId(id, opts);
|
|
18100
|
+
const [users, raw] = await Promise.all([
|
|
18101
|
+
listUsers(opts),
|
|
18102
|
+
abletimeRestRequest(`/tasks/${encodeURIComponent(taskId)}`, {
|
|
18103
|
+
...opts,
|
|
18104
|
+
query: { expand: "comments" }
|
|
18105
|
+
})
|
|
18106
|
+
]);
|
|
18107
|
+
const task = restTaskToRaw(raw, users);
|
|
18108
|
+
if (!task) throw new Error(`AbleTime task not found: ${id}`);
|
|
18109
|
+
return task;
|
|
18110
|
+
}
|
|
18111
|
+
function firstStringArg(args, keys) {
|
|
18112
|
+
for (const key of keys) {
|
|
18113
|
+
const value = asString(args[key]);
|
|
18114
|
+
if (value) return value;
|
|
18115
|
+
}
|
|
18116
|
+
return "";
|
|
18117
|
+
}
|
|
18118
|
+
async function callAbleTimeRestTool(name, args = {}, opts) {
|
|
18119
|
+
const token = normalizeAbleTimeCredential(opts?.token ?? getAbleTimeAccessToken() ?? "");
|
|
18120
|
+
if (!token) {
|
|
18121
|
+
throw new Error("AbleTime is not connected \u2014 paste an API key or connect via browser");
|
|
18122
|
+
}
|
|
18123
|
+
if (ableTimeCredentialKind(token) === "pat") {
|
|
18124
|
+
throw new Error("AbleTime REST transport is for organization keys and OAuth tokens, not apt_\u2026");
|
|
18125
|
+
}
|
|
18126
|
+
const requestOpts = { ...opts, token };
|
|
18127
|
+
switch (name) {
|
|
18128
|
+
case "orientation": {
|
|
18129
|
+
const [users, projects, tasks] = await Promise.all([
|
|
18130
|
+
listUsers(requestOpts),
|
|
18131
|
+
listProjectsRaw(requestOpts),
|
|
18132
|
+
listTasksRaw(requestOpts)
|
|
18133
|
+
]);
|
|
18134
|
+
const viewer = [...users.values()].find((user) => user.name) ?? [...users.values()][0] ?? { name: "AbleTime" };
|
|
18135
|
+
return { viewer, projects, tasks };
|
|
18136
|
+
}
|
|
18137
|
+
case "list_projects":
|
|
18138
|
+
return { data: await listProjectsRaw(requestOpts) };
|
|
18139
|
+
case "list_users":
|
|
18140
|
+
return { data: [...(await listUsers(requestOpts)).values()] };
|
|
18141
|
+
case "list_tasks":
|
|
18142
|
+
return {
|
|
18143
|
+
data: await listTasksRaw({
|
|
18144
|
+
...requestOpts,
|
|
18145
|
+
projectId: firstStringArg(args, ["project_id", "projectId", "project"])
|
|
18146
|
+
})
|
|
18147
|
+
};
|
|
18148
|
+
case "search_tasks":
|
|
18149
|
+
return {
|
|
18150
|
+
data: await listTasksRaw({
|
|
18151
|
+
...requestOpts,
|
|
18152
|
+
query: firstStringArg(args, ["query", "q"])
|
|
18153
|
+
})
|
|
18154
|
+
};
|
|
18155
|
+
case "get_task":
|
|
18156
|
+
return await getTaskRaw(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18157
|
+
case "create_task": {
|
|
18158
|
+
const title = firstStringArg(args, ["title"]);
|
|
18159
|
+
if (!title) throw new Error("AbleTime task title is required");
|
|
18160
|
+
const projectId = firstStringArg(args, ["project_id", "projectId", "project"]);
|
|
18161
|
+
if (!projectId) throw new Error("AbleTime project is required");
|
|
18162
|
+
const projects = await listProjectsRaw(requestOpts);
|
|
18163
|
+
const project = projects.find(
|
|
18164
|
+
(item) => String(item.id).toLowerCase() === projectId.toLowerCase() || String(item.name).toLowerCase() === projectId.toLowerCase()
|
|
18165
|
+
) ?? projects[0];
|
|
18166
|
+
if (!project) throw new Error("AbleTime has no projects to create a task in");
|
|
18167
|
+
const categories = asList(project.categories);
|
|
18168
|
+
const wantedCategory = firstStringArg(args, ["category_id", "categoryId", "category"]);
|
|
18169
|
+
const category = categories.map((item) => asRecord5(item)).find((item) => {
|
|
18170
|
+
if (!item || !wantedCategory) return false;
|
|
18171
|
+
return asString(item.id).toLowerCase() === wantedCategory.toLowerCase() || asString(item.name).toLowerCase() === wantedCategory.toLowerCase();
|
|
18172
|
+
}) ?? asRecord5(categories[0]);
|
|
18173
|
+
const created = await abletimeRestRequest("/tasks", {
|
|
18174
|
+
...requestOpts,
|
|
18175
|
+
method: "POST",
|
|
18176
|
+
body: {
|
|
18177
|
+
projectId: project.id,
|
|
18178
|
+
projectCategoryId: category ? asString(category.id) : void 0,
|
|
18179
|
+
title
|
|
18180
|
+
}
|
|
18181
|
+
});
|
|
18182
|
+
const state = firstStringArg(args, ["state"]);
|
|
18183
|
+
const createdId = asString(asRecord5(created)?.timeflowTaskId);
|
|
18184
|
+
const description = asString(args.description);
|
|
18185
|
+
if (createdId && description) {
|
|
18186
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}`, {
|
|
18187
|
+
...requestOpts,
|
|
18188
|
+
method: "PATCH",
|
|
18189
|
+
body: { description: ableTimeDocFromText(description) }
|
|
18190
|
+
}).catch(() => void 0);
|
|
18191
|
+
}
|
|
18192
|
+
if (createdId && state && state !== "todo") {
|
|
18193
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}/state`, {
|
|
18194
|
+
...requestOpts,
|
|
18195
|
+
method: "POST",
|
|
18196
|
+
body: { taskState: state }
|
|
18197
|
+
}).catch(() => void 0);
|
|
18198
|
+
}
|
|
18199
|
+
return createdId ? await getTaskRaw(createdId, requestOpts) : restTaskToRaw(created, await listUsers(requestOpts));
|
|
18200
|
+
}
|
|
18201
|
+
case "update_task": {
|
|
18202
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18203
|
+
const patch = {};
|
|
18204
|
+
const title = firstStringArg(args, ["title"]);
|
|
18205
|
+
if (title) patch.title = title;
|
|
18206
|
+
if (args.description !== void 0) {
|
|
18207
|
+
patch.description = args.description == null || args.description === "" ? null : ableTimeDocFromText(String(args.description));
|
|
18208
|
+
}
|
|
18209
|
+
if (args.tags !== void 0 || args.labels !== void 0) {
|
|
18210
|
+
patch.tags = args.tags ?? args.labels ?? args.tag_names;
|
|
18211
|
+
}
|
|
18212
|
+
if (Object.keys(patch).length > 0) {
|
|
18213
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}`, {
|
|
18214
|
+
...requestOpts,
|
|
18215
|
+
method: "PATCH",
|
|
18216
|
+
body: patch
|
|
18217
|
+
});
|
|
18218
|
+
}
|
|
18219
|
+
return await getTaskRaw(id, requestOpts);
|
|
18220
|
+
}
|
|
18221
|
+
case "set_task_state": {
|
|
18222
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18223
|
+
const state = firstStringArg(args, ["state", "taskState"]);
|
|
18224
|
+
if (!state) throw new Error("AbleTime task state is required");
|
|
18225
|
+
await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/state`, {
|
|
18226
|
+
...requestOpts,
|
|
18227
|
+
method: "POST",
|
|
18228
|
+
body: { taskState: state }
|
|
18229
|
+
});
|
|
18230
|
+
return await getTaskRaw(id, requestOpts);
|
|
18231
|
+
}
|
|
18232
|
+
case "create_comment": {
|
|
18233
|
+
const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
|
|
18234
|
+
const body = firstStringArg(args, ["body", "comment", "text"]);
|
|
18235
|
+
if (!body) throw new Error("AbleTime comment body is required");
|
|
18236
|
+
const created = await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/comments`, {
|
|
18237
|
+
...requestOpts,
|
|
18238
|
+
method: "POST",
|
|
18239
|
+
body: { content: ableTimeDocFromText(body) }
|
|
18240
|
+
});
|
|
18241
|
+
const rec = asRecord5(created) ?? {};
|
|
18242
|
+
return {
|
|
18243
|
+
id: asString(rec.commentId) || void 0,
|
|
18244
|
+
body: textFromAbleTimeDoc(rec.content) || body
|
|
18245
|
+
};
|
|
18246
|
+
}
|
|
18247
|
+
case "set_task_dependency":
|
|
18248
|
+
throw new Error(
|
|
18249
|
+
"AbleTime organization API keys cannot set task dependencies on REST yet \u2014 use a personal access token (apt_\u2026) for MCP project-management tools."
|
|
18250
|
+
);
|
|
18251
|
+
default:
|
|
18252
|
+
throw new Error(`AbleTime organization API keys do not support ${name} on REST`);
|
|
18253
|
+
}
|
|
18254
|
+
}
|
|
18255
|
+
var ABLETIME_REST_PREFIX, ULID_RE;
|
|
18256
|
+
var init_abletime_rest = __esm({
|
|
18257
|
+
"src/integrations/abletime-rest.ts"() {
|
|
18258
|
+
"use strict";
|
|
18259
|
+
init_fetch();
|
|
18260
|
+
init_app_settings();
|
|
18261
|
+
init_abletime_mcp();
|
|
18262
|
+
ABLETIME_REST_PREFIX = "/api/public/v2";
|
|
18263
|
+
ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
18264
|
+
}
|
|
18265
|
+
});
|
|
18266
|
+
|
|
17462
18267
|
// src/integrations/abletime-mcp.ts
|
|
17463
18268
|
function abletimeToolUsesPm(name) {
|
|
17464
18269
|
return ABLETIME_PM_TOOL_NAMES.includes(name);
|
|
@@ -17472,22 +18277,63 @@ function abletimeMcpUrl(host, opts) {
|
|
|
17472
18277
|
const path2 = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
|
|
17473
18278
|
return `${normalizeAbleTimeHost(host)}${path2}`;
|
|
17474
18279
|
}
|
|
18280
|
+
function normalizeAbleTimeCredential(raw) {
|
|
18281
|
+
return raw.trim().replace(/^bearer\s+/i, "").trim();
|
|
18282
|
+
}
|
|
18283
|
+
function ableTimeCredentialKind(raw) {
|
|
18284
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
18285
|
+
const lower = token.toLowerCase();
|
|
18286
|
+
if (lower.startsWith(ABLETIME_PAT_PREFIX)) return "pat";
|
|
18287
|
+
if (lower.startsWith(ABLETIME_ORG_KEY_PREFIX)) return "org";
|
|
18288
|
+
if (lower.startsWith(ABLETIME_OAUTH_TOKEN_PREFIX) || token.startsWith("eyJ")) return "oauth";
|
|
18289
|
+
return "unknown";
|
|
18290
|
+
}
|
|
18291
|
+
function ableTimeUsesRest(raw) {
|
|
18292
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
18293
|
+
const kind = ableTimeCredentialKind(token);
|
|
18294
|
+
if (kind === "org") return true;
|
|
18295
|
+
if (kind === "oauth") return token.startsWith("eyJ");
|
|
18296
|
+
return kind === "unknown";
|
|
18297
|
+
}
|
|
18298
|
+
function isAbleTimeRejectedCredential(message) {
|
|
18299
|
+
return /INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|INTEGRATION_PAT_REQUIRED|\b401\b/i.test(
|
|
18300
|
+
message
|
|
18301
|
+
);
|
|
18302
|
+
}
|
|
18303
|
+
function assertAbleTimeMcpCredential(raw) {
|
|
18304
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
18305
|
+
if (!token) {
|
|
18306
|
+
throw new Error("AbleTime personal access token is required");
|
|
18307
|
+
}
|
|
18308
|
+
if (ableTimeCredentialKind(token) === "org") {
|
|
18309
|
+
throw new Error(ORG_KEY_HINT);
|
|
18310
|
+
}
|
|
18311
|
+
return token;
|
|
18312
|
+
}
|
|
18313
|
+
function assertAbleTimeCredential(raw) {
|
|
18314
|
+
const token = normalizeAbleTimeCredential(raw);
|
|
18315
|
+
if (!token) {
|
|
18316
|
+
throw new Error("AbleTime API key or personal access token is required");
|
|
18317
|
+
}
|
|
18318
|
+
return token;
|
|
18319
|
+
}
|
|
17475
18320
|
function rewriteAbleTimeError(message) {
|
|
17476
|
-
if (
|
|
18321
|
+
if (REWRITE_APPLIED.test(message)) return message;
|
|
18322
|
+
if (/INTEGRATION_AGENT_ACCESS_DISABLED/i.test(message)) {
|
|
17477
18323
|
return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
|
|
17478
18324
|
}
|
|
17479
|
-
if (/INTEGRATION_PAT_REQUIRED
|
|
17480
|
-
return `${message} \u2014
|
|
18325
|
+
if (/INTEGRATION_PAT_REQUIRED/i.test(message)) {
|
|
18326
|
+
return `${message} \u2014 ${ORG_KEY_HINT}`;
|
|
17481
18327
|
}
|
|
17482
18328
|
if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
|
|
17483
18329
|
return `${message} \u2014 AbleTime plan does not include API access.`;
|
|
17484
18330
|
}
|
|
17485
18331
|
if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
|
|
17486
|
-
return `${message} \u2014
|
|
18332
|
+
return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
|
|
17487
18333
|
}
|
|
17488
18334
|
return message;
|
|
17489
18335
|
}
|
|
17490
|
-
function
|
|
18336
|
+
function asRecord6(value) {
|
|
17491
18337
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17492
18338
|
}
|
|
17493
18339
|
function parseSseJson(body) {
|
|
@@ -17509,12 +18355,12 @@ function parseSseJson(body) {
|
|
|
17509
18355
|
}
|
|
17510
18356
|
}
|
|
17511
18357
|
function unwrapToolResult(result) {
|
|
17512
|
-
const record =
|
|
18358
|
+
const record = asRecord6(result);
|
|
17513
18359
|
if (!record) return result;
|
|
17514
18360
|
const content = record.content;
|
|
17515
18361
|
if (Array.isArray(content)) {
|
|
17516
18362
|
const texts = content.map((part) => {
|
|
17517
|
-
const item =
|
|
18363
|
+
const item = asRecord6(part);
|
|
17518
18364
|
if (!item) return "";
|
|
17519
18365
|
return typeof item.text === "string" ? item.text : "";
|
|
17520
18366
|
}).filter(Boolean);
|
|
@@ -17536,17 +18382,18 @@ function unwrapToolResult(result) {
|
|
|
17536
18382
|
return result;
|
|
17537
18383
|
}
|
|
17538
18384
|
function textsFromUnknown(value) {
|
|
17539
|
-
const record =
|
|
18385
|
+
const record = asRecord6(value);
|
|
17540
18386
|
if (!record) return "";
|
|
17541
18387
|
if (typeof record.message === "string") return record.message;
|
|
17542
18388
|
if (typeof record.error === "string") return record.error;
|
|
17543
18389
|
return "";
|
|
17544
18390
|
}
|
|
17545
18391
|
async function abletimeMcpRequest(method, params, opts) {
|
|
17546
|
-
const
|
|
17547
|
-
if (!
|
|
18392
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
18393
|
+
if (!rawToken?.trim()) {
|
|
17548
18394
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
17549
18395
|
}
|
|
18396
|
+
const token = assertAbleTimeMcpCredential(rawToken);
|
|
17550
18397
|
const url = abletimeMcpUrl(
|
|
17551
18398
|
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
|
|
17552
18399
|
{ pm: opts?.pm }
|
|
@@ -17582,7 +18429,7 @@ async function abletimeMcpRequest(method, params, opts) {
|
|
|
17582
18429
|
return parseSseJson(body);
|
|
17583
18430
|
}
|
|
17584
18431
|
})();
|
|
17585
|
-
const rpc =
|
|
18432
|
+
const rpc = asRecord6(parsed);
|
|
17586
18433
|
if (rpc?.error) {
|
|
17587
18434
|
const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
|
|
17588
18435
|
throw new Error(rewriteAbleTimeError(detail));
|
|
@@ -17613,6 +18460,11 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
17613
18460
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
17614
18461
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
17615
18462
|
}
|
|
18463
|
+
const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
|
|
18464
|
+
if (rawToken && ableTimeUsesRest(rawToken)) {
|
|
18465
|
+
const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
|
|
18466
|
+
return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
|
|
18467
|
+
}
|
|
17616
18468
|
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
17617
18469
|
const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
|
|
17618
18470
|
return unwrapToolResult(result);
|
|
@@ -17625,10 +18477,14 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
17625
18477
|
await initializeIfNeeded(requestOpts);
|
|
17626
18478
|
return await call();
|
|
17627
18479
|
}
|
|
18480
|
+
if (rawToken && ableTimeUsesRest(rawToken) && isAbleTimeRejectedCredential(message)) {
|
|
18481
|
+
const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
|
|
18482
|
+
return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
|
|
18483
|
+
}
|
|
17628
18484
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
17629
18485
|
}
|
|
17630
18486
|
}
|
|
17631
|
-
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
|
|
18487
|
+
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;
|
|
17632
18488
|
var init_abletime_mcp = __esm({
|
|
17633
18489
|
"src/integrations/abletime-mcp.ts"() {
|
|
17634
18490
|
"use strict";
|
|
@@ -17650,6 +18506,12 @@ var init_abletime_mcp = __esm({
|
|
|
17650
18506
|
"archive_task",
|
|
17651
18507
|
"delete_task"
|
|
17652
18508
|
];
|
|
18509
|
+
ABLETIME_PAT_PREFIX = "apt_";
|
|
18510
|
+
ABLETIME_ORG_KEY_PREFIX = "atk_";
|
|
18511
|
+
ABLETIME_OAUTH_TOKEN_PREFIX = "acn_";
|
|
18512
|
+
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.";
|
|
18513
|
+
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.";
|
|
18514
|
+
REWRITE_APPLIED = /— Enable Agent access \(MCP\)|— AbleTime MCP needs a personal access token|— AbleTime plan does not include API access|— AbleTime rejected this credential/;
|
|
17653
18515
|
}
|
|
17654
18516
|
});
|
|
17655
18517
|
|
|
@@ -17673,16 +18535,16 @@ __export(abletime_exports, {
|
|
|
17673
18535
|
updateAbleTimeTask: () => updateAbleTimeTask,
|
|
17674
18536
|
verifyAbleTimeConnection: () => verifyAbleTimeConnection
|
|
17675
18537
|
});
|
|
17676
|
-
function
|
|
18538
|
+
function asRecord7(value) {
|
|
17677
18539
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
17678
18540
|
}
|
|
17679
|
-
function
|
|
18541
|
+
function asString2(value) {
|
|
17680
18542
|
return typeof value === "string" ? value.trim() : "";
|
|
17681
18543
|
}
|
|
17682
18544
|
function firstString(record, keys) {
|
|
17683
18545
|
if (!record) return "";
|
|
17684
18546
|
for (const key of keys) {
|
|
17685
|
-
const value =
|
|
18547
|
+
const value = asString2(record[key]);
|
|
17686
18548
|
if (value) return value;
|
|
17687
18549
|
}
|
|
17688
18550
|
return "";
|
|
@@ -17690,14 +18552,14 @@ function firstString(record, keys) {
|
|
|
17690
18552
|
function firstRecord(record, keys) {
|
|
17691
18553
|
if (!record) return null;
|
|
17692
18554
|
for (const key of keys) {
|
|
17693
|
-
const value =
|
|
18555
|
+
const value = asRecord7(record[key]);
|
|
17694
18556
|
if (value) return value;
|
|
17695
18557
|
}
|
|
17696
18558
|
return null;
|
|
17697
18559
|
}
|
|
17698
|
-
function
|
|
18560
|
+
function asList2(value) {
|
|
17699
18561
|
if (Array.isArray(value)) return value;
|
|
17700
|
-
const record =
|
|
18562
|
+
const record = asRecord7(value);
|
|
17701
18563
|
if (!record) return [];
|
|
17702
18564
|
for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
|
|
17703
18565
|
if (Array.isArray(record[key])) return record[key];
|
|
@@ -17709,21 +18571,21 @@ function labelsOf(record) {
|
|
|
17709
18571
|
if (!Array.isArray(raw)) return [];
|
|
17710
18572
|
return raw.map((item) => {
|
|
17711
18573
|
if (typeof item === "string") return item.trim();
|
|
17712
|
-
const rec =
|
|
18574
|
+
const rec = asRecord7(item);
|
|
17713
18575
|
return rec ? firstString(rec, ["name", "title", "label"]) : "";
|
|
17714
18576
|
}).filter(Boolean);
|
|
17715
18577
|
}
|
|
17716
18578
|
function commentsOf(record) {
|
|
17717
18579
|
const raw = record.comments ?? record.notes ?? record.discussion;
|
|
17718
18580
|
const out = [];
|
|
17719
|
-
for (const item of
|
|
17720
|
-
const rec =
|
|
18581
|
+
for (const item of asList2(raw)) {
|
|
18582
|
+
const rec = asRecord7(item);
|
|
17721
18583
|
if (!rec) continue;
|
|
17722
|
-
const body = firstString(rec, ["body", "text", "comment", "
|
|
18584
|
+
const body = firstString(rec, ["body", "text", "comment", "message", "contentSanitized"]) || textFromAbleTimeDoc(rec.content);
|
|
17723
18585
|
if (!body) continue;
|
|
17724
|
-
const user = firstString(
|
|
18586
|
+
const user = firstString(asRecord7(rec.user) ?? asRecord7(rec.author), ["name", "display_name", "username"]) || firstString(rec, ["user_name", "author", "username"]);
|
|
17725
18587
|
const comment = { body };
|
|
17726
|
-
const id = firstString(rec, ["id", "comment_id"]);
|
|
18588
|
+
const id = firstString(rec, ["id", "comment_id", "commentId"]);
|
|
17727
18589
|
if (id) comment.id = id;
|
|
17728
18590
|
const url = firstString(rec, ["url", "permalink"]);
|
|
17729
18591
|
if (url) comment.url = url;
|
|
@@ -17747,11 +18609,11 @@ function taskUrl(task, host) {
|
|
|
17747
18609
|
return `${base}/tasks/${slug}`;
|
|
17748
18610
|
}
|
|
17749
18611
|
function mapAbleTimeTask(raw, host) {
|
|
17750
|
-
const record =
|
|
18612
|
+
const record = asRecord7(raw);
|
|
17751
18613
|
if (!record) return null;
|
|
17752
18614
|
const nested = firstRecord(record, ["task", "data"]) ?? record;
|
|
17753
|
-
const id = firstString(nested, ["id", "task_id", "taskId"]);
|
|
17754
|
-
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number"]) || id;
|
|
18615
|
+
const id = firstString(nested, ["id", "task_id", "taskId", "timeflowTaskId"]);
|
|
18616
|
+
const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number", "taskRef"]) || id;
|
|
17755
18617
|
const title = firstString(nested, ["title", "name"]);
|
|
17756
18618
|
if (!id && !identifier && !title) return null;
|
|
17757
18619
|
const resolvedId = id || identifier;
|
|
@@ -17762,21 +18624,22 @@ function mapAbleTimeTask(raw, host) {
|
|
|
17762
18624
|
identifier: resolvedIdentifier,
|
|
17763
18625
|
title: title || resolvedIdentifier,
|
|
17764
18626
|
url,
|
|
17765
|
-
description: firstString(nested, ["description", "body", "details"]) || void 0,
|
|
17766
|
-
state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
|
|
17767
|
-
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(
|
|
17768
|
-
categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(
|
|
18627
|
+
description: firstString(nested, ["description", "body", "details", "descriptionSanitized"]) || textFromAbleTimeDoc(nested.description) || void 0,
|
|
18628
|
+
state: firstString(nested, ["state", "board_state", "boardState", "status", "taskState"]) || void 0,
|
|
18629
|
+
projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord7(nested.project), ["id"]) || void 0,
|
|
18630
|
+
categoryId: firstString(nested, ["category_id", "categoryId", "projectCategoryId"]) || firstString(asRecord7(nested.category), ["id"]) || void 0,
|
|
17769
18631
|
assignee: assigneeOf(nested),
|
|
17770
18632
|
labels: labelsOf(nested),
|
|
17771
18633
|
comments: commentsOf(nested),
|
|
17772
|
-
createdAt: firstString(nested, ["created_at", "createdAt", "created"]) || void 0,
|
|
18634
|
+
createdAt: firstString(nested, ["created_at", "createdAt", "created", "dateCreated"]) || void 0,
|
|
17773
18635
|
updatedAt: firstString(nested, [
|
|
17774
18636
|
"updated_at",
|
|
17775
18637
|
"updatedAt",
|
|
17776
18638
|
"modified_at",
|
|
17777
18639
|
"modifiedAt",
|
|
17778
18640
|
"last_activity_at",
|
|
17779
|
-
"lastActivityAt"
|
|
18641
|
+
"lastActivityAt",
|
|
18642
|
+
"lastUpdate"
|
|
17780
18643
|
]) || void 0
|
|
17781
18644
|
};
|
|
17782
18645
|
}
|
|
@@ -17807,16 +18670,16 @@ function issueAttachmentForAbleTimeTask(task) {
|
|
|
17807
18670
|
};
|
|
17808
18671
|
}
|
|
17809
18672
|
function mapProject(raw) {
|
|
17810
|
-
const record =
|
|
18673
|
+
const record = asRecord7(raw);
|
|
17811
18674
|
if (!record) return null;
|
|
17812
|
-
const id = firstString(record, ["id", "project_id"]);
|
|
17813
|
-
const name = firstString(record, ["name", "title"]);
|
|
18675
|
+
const id = firstString(record, ["id", "project_id", "projectId"]);
|
|
18676
|
+
const name = firstString(record, ["name", "title", "projectName"]);
|
|
17814
18677
|
if (!id && !name) return null;
|
|
17815
|
-
const categories =
|
|
17816
|
-
const rec =
|
|
18678
|
+
const categories = asList2(record.categories ?? record.category).map((item) => {
|
|
18679
|
+
const rec = asRecord7(item);
|
|
17817
18680
|
if (!rec) return null;
|
|
17818
|
-
const categoryId = firstString(rec, ["id", "category_id"]);
|
|
17819
|
-
const categoryName = firstString(rec, ["name", "title"]);
|
|
18681
|
+
const categoryId = firstString(rec, ["id", "category_id", "projectCategoryId", "categoryId"]);
|
|
18682
|
+
const categoryName = firstString(rec, ["name", "title", "categoryName"]);
|
|
17820
18683
|
if (!categoryId && !categoryName) return null;
|
|
17821
18684
|
return { id: categoryId || categoryName, name: categoryName || categoryId };
|
|
17822
18685
|
}).filter((item) => Boolean(item));
|
|
@@ -17828,21 +18691,21 @@ function isOpenTask(task) {
|
|
|
17828
18691
|
}
|
|
17829
18692
|
async function getAbleTimeOrientation(opts) {
|
|
17830
18693
|
const raw = await callAbleTimeTool("orientation", {}, opts);
|
|
17831
|
-
const record =
|
|
17832
|
-
const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
|
|
18694
|
+
const record = asRecord7(raw);
|
|
18695
|
+
const viewerRec = firstRecord(record, ["viewer", "principal", "user", "actor", "me", "person"]) ?? record;
|
|
17833
18696
|
const viewer = {
|
|
17834
18697
|
id: firstString(viewerRec, ["id", "user_id"]) || void 0,
|
|
17835
18698
|
name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
|
|
17836
18699
|
};
|
|
17837
|
-
const projects =
|
|
18700
|
+
const projects = asList2(
|
|
17838
18701
|
record?.projects ?? record?.current_projects ?? record?.currentProjects
|
|
17839
18702
|
).map(mapProject).filter((item) => Boolean(item));
|
|
17840
|
-
const tasks =
|
|
18703
|
+
const tasks = asList2(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
17841
18704
|
return { viewer, projects, tasks, raw };
|
|
17842
18705
|
}
|
|
17843
18706
|
async function listAbleTimeProjects(opts) {
|
|
17844
18707
|
const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
|
|
17845
|
-
return
|
|
18708
|
+
return asList2(raw).map(mapProject).filter((item) => Boolean(item));
|
|
17846
18709
|
}
|
|
17847
18710
|
async function listAbleTimeTasks(opts) {
|
|
17848
18711
|
const args = {};
|
|
@@ -17852,14 +18715,14 @@ async function listAbleTimeTasks(opts) {
|
|
|
17852
18715
|
}
|
|
17853
18716
|
if (opts?.query) args.query = opts.query;
|
|
17854
18717
|
const raw = await callAbleTimeTool("list_tasks", args, opts);
|
|
17855
|
-
const tasks =
|
|
18718
|
+
const tasks = asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
|
|
17856
18719
|
return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
|
|
17857
18720
|
}
|
|
17858
18721
|
async function searchAbleTimeTasks(query, opts) {
|
|
17859
18722
|
const q = query.trim();
|
|
17860
18723
|
if (!q) return [];
|
|
17861
18724
|
const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
|
|
17862
|
-
return
|
|
18725
|
+
return asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
|
|
17863
18726
|
}
|
|
17864
18727
|
async function getAbleTimeTask(id, opts) {
|
|
17865
18728
|
const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
|
|
@@ -17907,7 +18770,7 @@ async function commentAbleTimeTask(input, opts) {
|
|
|
17907
18770
|
{ id, task_id: id, task: id, body, comment: body, text: body },
|
|
17908
18771
|
opts
|
|
17909
18772
|
);
|
|
17910
|
-
const rec =
|
|
18773
|
+
const rec = asRecord7(raw);
|
|
17911
18774
|
return {
|
|
17912
18775
|
id: rec ? firstString(rec, ["id", "comment_id"]) || void 0 : void 0,
|
|
17913
18776
|
body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
|
|
@@ -18097,8 +18960,7 @@ async function listAbleTimeAssignedIssues(opts) {
|
|
|
18097
18960
|
};
|
|
18098
18961
|
}
|
|
18099
18962
|
async function verifyAbleTimeConnection(input) {
|
|
18100
|
-
const token = input.token
|
|
18101
|
-
if (!token) throw new Error("AbleTime personal access token is required");
|
|
18963
|
+
const token = assertAbleTimeCredential(input.token);
|
|
18102
18964
|
const host = input.host?.trim() || void 0;
|
|
18103
18965
|
const orientation = await getAbleTimeOrientation({ token, host });
|
|
18104
18966
|
saveAbleTimeConnection({
|
|
@@ -18114,6 +18976,7 @@ var init_abletime = __esm({
|
|
|
18114
18976
|
"use strict";
|
|
18115
18977
|
init_app_settings();
|
|
18116
18978
|
init_abletime_mcp();
|
|
18979
|
+
init_abletime_rest();
|
|
18117
18980
|
CLOSED_STATES = /* @__PURE__ */ new Set([
|
|
18118
18981
|
"done",
|
|
18119
18982
|
"completed",
|
|
@@ -18697,12 +19560,19 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
18697
19560
|
...instructions
|
|
18698
19561
|
].join("\n");
|
|
18699
19562
|
return {
|
|
18700
|
-
id: (0,
|
|
19563
|
+
id: (0, import_node_crypto11.randomUUID)(),
|
|
18701
19564
|
name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
|
|
18702
19565
|
kind: "transcript",
|
|
18703
19566
|
content: body
|
|
18704
19567
|
};
|
|
18705
19568
|
}
|
|
19569
|
+
async function isolateQuotaFailover(run2) {
|
|
19570
|
+
try {
|
|
19571
|
+
return await run2();
|
|
19572
|
+
} catch {
|
|
19573
|
+
return true;
|
|
19574
|
+
}
|
|
19575
|
+
}
|
|
18706
19576
|
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
18707
19577
|
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
18708
19578
|
const tab = createChatTab({
|
|
@@ -18721,11 +19591,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
18721
19591
|
}
|
|
18722
19592
|
return updateThread(tab.id, patch);
|
|
18723
19593
|
}
|
|
18724
|
-
var
|
|
19594
|
+
var import_node_crypto11, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
|
|
18725
19595
|
var init_quota_failover = __esm({
|
|
18726
19596
|
"src/orchestrator/quota-failover.ts"() {
|
|
18727
19597
|
"use strict";
|
|
18728
|
-
|
|
19598
|
+
import_node_crypto11 = require("crypto");
|
|
18729
19599
|
init_session_quota();
|
|
18730
19600
|
init_app_settings();
|
|
18731
19601
|
init_global_workspace();
|
|
@@ -20978,16 +21848,16 @@ function normalizeServiceOrigin(raw, fallback) {
|
|
|
20978
21848
|
return fallback;
|
|
20979
21849
|
}
|
|
20980
21850
|
}
|
|
20981
|
-
function
|
|
21851
|
+
function asRecord8(value) {
|
|
20982
21852
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20983
21853
|
}
|
|
20984
|
-
function
|
|
21854
|
+
function asString3(value) {
|
|
20985
21855
|
return typeof value === "string" ? value.trim() : "";
|
|
20986
21856
|
}
|
|
20987
21857
|
function firstString2(record, keys) {
|
|
20988
21858
|
if (!record) return "";
|
|
20989
21859
|
for (const key of keys) {
|
|
20990
|
-
const value =
|
|
21860
|
+
const value = asString3(record[key]);
|
|
20991
21861
|
if (value) return value;
|
|
20992
21862
|
}
|
|
20993
21863
|
return "";
|
|
@@ -21011,7 +21881,7 @@ async function authorizedGet(url, token) {
|
|
|
21011
21881
|
});
|
|
21012
21882
|
const body = await readJson(res);
|
|
21013
21883
|
if (!res.ok) {
|
|
21014
|
-
const record =
|
|
21884
|
+
const record = asRecord8(body);
|
|
21015
21885
|
const message = firstString2(record, ["message", "error", "error_description", "detail"]) || (typeof body === "string" ? body.trim().slice(0, 160) : "");
|
|
21016
21886
|
throw new Error(
|
|
21017
21887
|
message ? `${res.status}: ${message}` : `Request failed (${res.status})`
|
|
@@ -21025,28 +21895,28 @@ async function verifyOptionalService(input) {
|
|
|
21025
21895
|
const spec = optionalServiceSpec(input.id);
|
|
21026
21896
|
try {
|
|
21027
21897
|
if (input.id === "vercel") {
|
|
21028
|
-
const body2 =
|
|
21029
|
-
const user =
|
|
21898
|
+
const body2 = asRecord8(await authorizedGet("https://api.vercel.com/v2/user", token));
|
|
21899
|
+
const user = asRecord8(body2?.user) ?? body2;
|
|
21030
21900
|
const name2 = firstString2(user, ["username", "name", "email"]) || "Vercel user";
|
|
21031
21901
|
return { viewerName: name2 };
|
|
21032
21902
|
}
|
|
21033
21903
|
if (input.id === "supabase") {
|
|
21034
21904
|
const body2 = await authorizedGet("https://api.supabase.com/v1/organizations", token);
|
|
21035
21905
|
const list2 = Array.isArray(body2) ? body2 : [];
|
|
21036
|
-
const first2 =
|
|
21906
|
+
const first2 = asRecord8(list2[0]);
|
|
21037
21907
|
const name2 = firstString2(first2, ["name", "slug"]) || "Supabase";
|
|
21038
21908
|
return { viewerName: list2.length > 1 ? `${name2} +${list2.length - 1}` : name2 };
|
|
21039
21909
|
}
|
|
21040
21910
|
if (input.id === "posthog") {
|
|
21041
21911
|
const host2 = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://us.posthog.com");
|
|
21042
|
-
const body2 =
|
|
21912
|
+
const body2 = asRecord8(await authorizedGet(`${host2}/api/users/@me/`, token));
|
|
21043
21913
|
const name2 = firstString2(body2, ["email", "distinct_id"]) || [firstString2(body2, ["first_name"]), firstString2(body2, ["last_name"])].filter(Boolean).join(" ") || "PostHog user";
|
|
21044
21914
|
return { viewerName: name2, host: host2 };
|
|
21045
21915
|
}
|
|
21046
21916
|
const host = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://sentry.io");
|
|
21047
21917
|
const body = await authorizedGet(`${host}/api/0/organizations/`, token);
|
|
21048
21918
|
const list = Array.isArray(body) ? body : [];
|
|
21049
|
-
const first =
|
|
21919
|
+
const first = asRecord8(list[0]);
|
|
21050
21920
|
const name = firstString2(first, ["slug", "name"]) || "Sentry";
|
|
21051
21921
|
return { viewerName: list.length > 1 ? `${name} +${list.length - 1}` : name, host };
|
|
21052
21922
|
} catch (err) {
|
|
@@ -21233,7 +22103,7 @@ var init_optional_services = __esm({
|
|
|
21233
22103
|
});
|
|
21234
22104
|
|
|
21235
22105
|
// src/plan/plan-present.ts
|
|
21236
|
-
function
|
|
22106
|
+
function asRecord9(v) {
|
|
21237
22107
|
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
21238
22108
|
}
|
|
21239
22109
|
function isPresentPlanToolName(name) {
|
|
@@ -21245,7 +22115,7 @@ function extractPresentedPlan(parts) {
|
|
|
21245
22115
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
21246
22116
|
const p = parts[i];
|
|
21247
22117
|
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
21248
|
-
const input =
|
|
22118
|
+
const input = asRecord9(p.input) ?? {};
|
|
21249
22119
|
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
21250
22120
|
if (!content.trim()) continue;
|
|
21251
22121
|
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
@@ -21517,11 +22387,6 @@ function isPidAlive(pid) {
|
|
|
21517
22387
|
return false;
|
|
21518
22388
|
}
|
|
21519
22389
|
}
|
|
21520
|
-
function sameLoginList(a, b) {
|
|
21521
|
-
const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
21522
|
-
const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
|
|
21523
|
-
return left.length === right.length && left.every((login, i) => login === right[i]);
|
|
21524
|
-
}
|
|
21525
22390
|
async function waitForPidExit(pid, timeoutMs) {
|
|
21526
22391
|
const start = Date.now();
|
|
21527
22392
|
while (isPidAlive(pid)) {
|
|
@@ -21928,51 +22793,62 @@ var init_orchestrator = __esm({
|
|
|
21928
22793
|
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
21929
22794
|
this.haltDrain.add(threadId);
|
|
21930
22795
|
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
|
|
22796
|
+
try {
|
|
22797
|
+
setStatus(threadId, "idle", null);
|
|
22798
|
+
appendMessage(threadId, {
|
|
22799
|
+
role: "agent",
|
|
22800
|
+
text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
22801
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
22802
|
+
});
|
|
22803
|
+
this.emit({
|
|
22804
|
+
type: "quota_failover",
|
|
22805
|
+
threadId,
|
|
22806
|
+
action: "wait_reset",
|
|
22807
|
+
message: plan.reason,
|
|
22808
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
22809
|
+
});
|
|
22810
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
22811
|
+
} catch {
|
|
22812
|
+
}
|
|
21945
22813
|
return true;
|
|
21946
22814
|
}
|
|
21947
22815
|
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
21948
22816
|
this.haltDrain.add(threadId);
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
22817
|
+
let next;
|
|
22818
|
+
try {
|
|
22819
|
+
next = createQuotaFailoverChat(
|
|
22820
|
+
thread,
|
|
22821
|
+
plan.fallbackAgent,
|
|
22822
|
+
plan.limitText
|
|
22823
|
+
);
|
|
22824
|
+
} catch {
|
|
22825
|
+
return false;
|
|
22826
|
+
}
|
|
21954
22827
|
this.clearQuotaResumeTimer(threadId);
|
|
21955
22828
|
try {
|
|
21956
22829
|
updateThread(threadId, { quotaResumeAt: null });
|
|
21957
22830
|
} catch {
|
|
21958
22831
|
}
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
22832
|
+
try {
|
|
22833
|
+
appendMessage(threadId, {
|
|
22834
|
+
role: "agent",
|
|
22835
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
22836
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
22837
|
+
});
|
|
22838
|
+
this.emit({
|
|
22839
|
+
type: "quota_failover",
|
|
22840
|
+
threadId,
|
|
22841
|
+
action: "switch_agent",
|
|
22842
|
+
toThreadId: next.id,
|
|
22843
|
+
message: plan.reason
|
|
22844
|
+
});
|
|
22845
|
+
this.emit({
|
|
22846
|
+
type: "status_changed",
|
|
22847
|
+
threadId: next.id,
|
|
22848
|
+
status: next.status
|
|
22849
|
+
});
|
|
22850
|
+
} catch {
|
|
22851
|
+
}
|
|
21976
22852
|
void this.send(
|
|
21977
22853
|
next.id,
|
|
21978
22854
|
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
@@ -22703,9 +23579,8 @@ var init_orchestrator = __esm({
|
|
|
22703
23579
|
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
22704
23580
|
} else {
|
|
22705
23581
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
22706
|
-
const failedOver = await
|
|
22707
|
-
threadId,
|
|
22708
|
-
blob
|
|
23582
|
+
const failedOver = await isolateQuotaFailover(
|
|
23583
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, blob)
|
|
22709
23584
|
);
|
|
22710
23585
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
22711
23586
|
detail: detail || failDetail,
|
|
@@ -22739,9 +23614,8 @@ var init_orchestrator = __esm({
|
|
|
22739
23614
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
22740
23615
|
}
|
|
22741
23616
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
22742
|
-
const failedOver = await
|
|
22743
|
-
threadId,
|
|
22744
|
-
message
|
|
23617
|
+
const failedOver = await isolateQuotaFailover(
|
|
23618
|
+
() => this.maybeHandleOrchestrationQuotaFailover(threadId, message)
|
|
22745
23619
|
);
|
|
22746
23620
|
this.maybeEnqueueCrashContinue(threadId, {
|
|
22747
23621
|
detail: message,
|
|
@@ -23351,12 +24225,18 @@ var init_orchestrator = __esm({
|
|
|
23351
24225
|
/** Resolve PR selectors and optionally persist `prUrl` when found. */
|
|
23352
24226
|
async withPrSelector(threadRef) {
|
|
23353
24227
|
const thread = this.requireThread(threadRef);
|
|
23354
|
-
const selectors = resolvePrSelectors(thread);
|
|
23355
24228
|
const cwd = thread.worktreePath;
|
|
23356
24229
|
if (!cwd?.trim()) {
|
|
23357
24230
|
throw new Error(`Thread ${threadRef} has no worktreePath`);
|
|
23358
24231
|
}
|
|
23359
|
-
|
|
24232
|
+
let headUrl = null;
|
|
24233
|
+
try {
|
|
24234
|
+
const head = await getPrForHeadBranch(cwd, thread.branchName);
|
|
24235
|
+
headUrl = head?.url?.trim() || null;
|
|
24236
|
+
} catch {
|
|
24237
|
+
headUrl = null;
|
|
24238
|
+
}
|
|
24239
|
+
return { thread, selectors: connectedPrSelectors(thread, headUrl), cwd };
|
|
23360
24240
|
}
|
|
23361
24241
|
async getPrChecks(threadRef) {
|
|
23362
24242
|
const { selectors, cwd } = await this.withPrSelector(threadRef);
|
|
@@ -23384,29 +24264,16 @@ var init_orchestrator = __esm({
|
|
|
23384
24264
|
async persistPrMetaAndMaybeArchive(thread, meta) {
|
|
23385
24265
|
const prevState = normalizePrState(thread.prState);
|
|
23386
24266
|
const nextState = normalizePrState(meta.state);
|
|
23387
|
-
const
|
|
23388
|
-
|
|
23389
|
-
|
|
23390
|
-
|
|
23391
|
-
|
|
23392
|
-
|
|
23393
|
-
const nextAuthor = meta.authorLogin?.trim() || "";
|
|
23394
|
-
if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
|
|
23395
|
-
patch.prAuthorLogin = nextAuthor;
|
|
23396
|
-
}
|
|
23397
|
-
const nextReviewers = meta.reviewerLogins ?? [];
|
|
23398
|
-
if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
|
|
23399
|
-
patch.prReviewerLogins = nextReviewers;
|
|
23400
|
-
}
|
|
23401
|
-
if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
|
|
23402
|
-
patch.skipAutoArchiveOnMerge = false;
|
|
24267
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
24268
|
+
const targets = siblings.length > 0 ? siblings : [thread];
|
|
24269
|
+
for (const t of targets) {
|
|
24270
|
+
const patch = threadPrMetaPatch(t, meta);
|
|
24271
|
+
if (Object.keys(patch).length === 0) continue;
|
|
24272
|
+
updateThread(t.id, patch);
|
|
23403
24273
|
}
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
if (!latest2.userSetTitle && meta.title && latest2.title !== meta.title) {
|
|
23408
|
-
updateThread(thread.id, { title: meta.title });
|
|
23409
|
-
}
|
|
24274
|
+
const titled = this.requireThread(thread.id);
|
|
24275
|
+
if (!titled.userSetTitle && meta.title && titled.title !== meta.title) {
|
|
24276
|
+
updateThread(thread.id, { title: meta.title });
|
|
23410
24277
|
}
|
|
23411
24278
|
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
|
|
23412
24279
|
const latest = this.requireThread(thread.id);
|
|
@@ -23420,22 +24287,7 @@ var init_orchestrator = __esm({
|
|
|
23420
24287
|
})) {
|
|
23421
24288
|
return;
|
|
23422
24289
|
}
|
|
23423
|
-
const
|
|
23424
|
-
for (const t of siblings) {
|
|
23425
|
-
const sibPatch = { prState: "MERGED", prIsDraft: false };
|
|
23426
|
-
if (meta.url && meta.url !== t.prUrl) sibPatch.prUrl = meta.url;
|
|
23427
|
-
if (meta.title && meta.title !== t.prTitle) sibPatch.prTitle = meta.title;
|
|
23428
|
-
const sibAuthor = meta.authorLogin?.trim() || "";
|
|
23429
|
-
if (sibAuthor && sibAuthor !== (t.prAuthorLogin ?? "").trim()) {
|
|
23430
|
-
sibPatch.prAuthorLogin = sibAuthor;
|
|
23431
|
-
}
|
|
23432
|
-
const sibReviewers = meta.reviewerLogins ?? [];
|
|
23433
|
-
if (!sameLoginList(sibReviewers, t.prReviewerLogins)) {
|
|
23434
|
-
sibPatch.prReviewerLogins = sibReviewers;
|
|
23435
|
-
}
|
|
23436
|
-
if (Object.keys(sibPatch).length > 0) updateThread(t.id, sibPatch);
|
|
23437
|
-
}
|
|
23438
|
-
for (const t of siblings) {
|
|
24290
|
+
for (const t of threadsSharingWorktree(latest.worktreePath)) {
|
|
23439
24291
|
if (this.requireThread(t.id).status === "archived") continue;
|
|
23440
24292
|
await this.archive(t.id);
|
|
23441
24293
|
}
|
|
@@ -23953,10 +24805,21 @@ var index_exports = {};
|
|
|
23953
24805
|
__export(index_exports, {
|
|
23954
24806
|
ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
|
|
23955
24807
|
ABLETIME_MCP_PM_PATH: () => ABLETIME_MCP_PM_PATH,
|
|
24808
|
+
ABLETIME_OAUTH_CALLBACK_PATH: () => ABLETIME_OAUTH_CALLBACK_PATH,
|
|
24809
|
+
ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
|
|
24810
|
+
ABLETIME_OAUTH_LOCAL_CALLBACK: () => ABLETIME_OAUTH_LOCAL_CALLBACK,
|
|
24811
|
+
ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
|
|
24812
|
+
ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
|
|
24813
|
+
ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
|
|
24814
|
+
ABLETIME_OAUTH_TOKEN_PREFIX: () => ABLETIME_OAUTH_TOKEN_PREFIX,
|
|
24815
|
+
ABLETIME_ORG_KEY_PREFIX: () => ABLETIME_ORG_KEY_PREFIX,
|
|
24816
|
+
ABLETIME_PAT_PREFIX: () => ABLETIME_PAT_PREFIX,
|
|
23956
24817
|
ABLETIME_PM_TOOL_NAMES: () => ABLETIME_PM_TOOL_NAMES,
|
|
23957
24818
|
AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
|
|
23958
24819
|
AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
23959
24820
|
ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
|
|
24821
|
+
AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
|
|
24822
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
23960
24823
|
BAKED_SLACK_RELAY_URL: () => BAKED_SLACK_RELAY_URL,
|
|
23961
24824
|
BRIGHTSY_MCP_ALLOWED_TOOLS: () => BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
23962
24825
|
BRIGHTSY_SUMMARIZE_CONTEXT_TOOL: () => BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
|
|
@@ -24045,7 +24908,15 @@ __export(index_exports, {
|
|
|
24045
24908
|
WORKTREE_GITHUB_MCP_TOOLS: () => WORKTREE_GITHUB_MCP_TOOLS,
|
|
24046
24909
|
WORKTREE_LINEAR_MCP_TOOLS: () => WORKTREE_LINEAR_MCP_TOOLS,
|
|
24047
24910
|
WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
|
|
24911
|
+
ableTimeCredentialKind: () => ableTimeCredentialKind,
|
|
24048
24912
|
ableTimeDependencyTarget: () => ableTimeDependencyTarget,
|
|
24913
|
+
ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
|
|
24914
|
+
ableTimeOAuthBouncePage: () => ableTimeOAuthBouncePage,
|
|
24915
|
+
ableTimeOAuthClientId: () => ableTimeOAuthClientId,
|
|
24916
|
+
ableTimeOAuthLocalBounceUrl: () => ableTimeOAuthLocalBounceUrl,
|
|
24917
|
+
ableTimeOAuthRedirectUri: () => ableTimeOAuthRedirectUri,
|
|
24918
|
+
ableTimeOAuthResource: () => ableTimeOAuthResource,
|
|
24919
|
+
ableTimeUsesRest: () => ableTimeUsesRest,
|
|
24049
24920
|
abletimeMcpRequest: () => abletimeMcpRequest,
|
|
24050
24921
|
abletimeMcpUrl: () => abletimeMcpUrl,
|
|
24051
24922
|
abletimeToolUsesPm: () => abletimeToolUsesPm,
|
|
@@ -24076,6 +24947,8 @@ __export(index_exports, {
|
|
|
24076
24947
|
applyThreadIntoMain: () => applyThreadIntoMain,
|
|
24077
24948
|
applyTurnUsage: () => applyTurnUsage,
|
|
24078
24949
|
armSchedules: () => armSchedules,
|
|
24950
|
+
assertAbleTimeCredential: () => assertAbleTimeCredential,
|
|
24951
|
+
assertAbleTimeMcpCredential: () => assertAbleTimeMcpCredential,
|
|
24079
24952
|
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
24080
24953
|
attachmentFromAbsolutePath: () => attachmentFromAbsolutePath,
|
|
24081
24954
|
attachmentsFromBuffers: () => attachmentsFromBuffers,
|
|
@@ -24148,6 +25021,7 @@ __export(index_exports, {
|
|
|
24148
25021
|
connectOptionalService: () => connectOptionalService,
|
|
24149
25022
|
connectSlackToken: () => connectSlackToken,
|
|
24150
25023
|
connectedOptionalServices: () => connectedOptionalServices,
|
|
25024
|
+
connectedPrSelectors: () => connectedPrSelectors,
|
|
24151
25025
|
contextTokens: () => contextTokens,
|
|
24152
25026
|
coordinatorSystemPrompt: () => coordinatorSystemPrompt,
|
|
24153
25027
|
coordinatorTurnReminder: () => coordinatorTurnReminder,
|
|
@@ -24155,6 +25029,7 @@ __export(index_exports, {
|
|
|
24155
25029
|
countCacheControlBlocks: () => countCacheControlBlocks,
|
|
24156
25030
|
countUnpushedVsOrigin: () => countUnpushedVsOrigin,
|
|
24157
25031
|
cowboyModeEnabled: () => cowboyModeEnabled,
|
|
25032
|
+
createAbleTimePkce: () => createAbleTimePkce,
|
|
24158
25033
|
createAbleTimeTask: () => createAbleTimeTask,
|
|
24159
25034
|
createChatTab: () => createChatTab,
|
|
24160
25035
|
createEmptyThread: () => createEmptyThread,
|
|
@@ -24194,6 +25069,7 @@ __export(index_exports, {
|
|
|
24194
25069
|
encodeBrightsyTarget: () => encodeBrightsyTarget,
|
|
24195
25070
|
enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
|
|
24196
25071
|
enrichWorkspacesWithGithub: () => enrichWorkspacesWithGithub,
|
|
25072
|
+
ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
|
|
24197
25073
|
ensureAbleTimeTask: () => ensureAbleTimeTask,
|
|
24198
25074
|
ensureAgentPath: () => ensureAgentPath,
|
|
24199
25075
|
ensureBrightsyLocalConfigFresh: () => ensureBrightsyLocalConfigFresh,
|
|
@@ -24340,6 +25216,7 @@ __export(index_exports, {
|
|
|
24340
25216
|
groupHomeBoardWorktrees: () => groupHomeBoardWorktrees,
|
|
24341
25217
|
handleSlackInbound: () => handleSlackInbound,
|
|
24342
25218
|
harnessEnvKey: () => harnessEnvKey,
|
|
25219
|
+
hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
|
|
24343
25220
|
hasBakedLinearOAuth: () => hasBakedLinearOAuth,
|
|
24344
25221
|
hasBakedSlackOAuth: () => hasBakedSlackOAuth,
|
|
24345
25222
|
hasConductorHook: () => hasConductorHook,
|
|
@@ -24364,6 +25241,8 @@ __export(index_exports, {
|
|
|
24364
25241
|
invalidateThreadListCache: () => invalidateThreadListCache,
|
|
24365
25242
|
invalidateThreadRecord: () => invalidateThreadRecord,
|
|
24366
25243
|
isAbleTimeConnected: () => isAbleTimeConnected,
|
|
25244
|
+
isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
|
|
25245
|
+
isAbleTimeRejectedCredential: () => isAbleTimeRejectedCredential,
|
|
24367
25246
|
isAskUserToolName: () => isAskUserToolName,
|
|
24368
25247
|
isBrightsyConnected: () => isBrightsyConnected,
|
|
24369
25248
|
isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
|
|
@@ -24493,6 +25372,7 @@ __export(index_exports, {
|
|
|
24493
25372
|
nextPastedTextName: () => nextPastedTextName,
|
|
24494
25373
|
nextThinkingEffort: () => nextThinkingEffort,
|
|
24495
25374
|
nonInteractiveGitProcessEnv: () => nonInteractiveGitProcessEnv,
|
|
25375
|
+
normalizeAbleTimeCredential: () => normalizeAbleTimeCredential,
|
|
24496
25376
|
normalizeAbleTimeHost: () => normalizeAbleTimeHost,
|
|
24497
25377
|
normalizeCodeSelection: () => normalizeCodeSelection,
|
|
24498
25378
|
normalizeLinearRelationType: () => normalizeLinearRelationType,
|
|
@@ -24515,6 +25395,7 @@ __export(index_exports, {
|
|
|
24515
25395
|
originFetchBranch: () => originFetchBranch,
|
|
24516
25396
|
originGhRepoEnv: () => originGhRepoEnv,
|
|
24517
25397
|
packagedDetachedJobPath: () => packagedDetachedJobPath,
|
|
25398
|
+
parseAbleTimeOAuthCallbackUrl: () => parseAbleTimeOAuthCallbackUrl,
|
|
24518
25399
|
parseClaudeUsagePayload: () => parseClaudeUsagePayload,
|
|
24519
25400
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
24520
25401
|
parseDurationMs: () => parseDurationMs,
|
|
@@ -24674,6 +25555,7 @@ __export(index_exports, {
|
|
|
24674
25555
|
stackMergeReadiness: () => stackMergeReadiness,
|
|
24675
25556
|
stageAbsolutePathsAsAttachments: () => stageAbsolutePathsAsAttachments,
|
|
24676
25557
|
stageBuffersAsAttachments: () => stageBuffersAsAttachments,
|
|
25558
|
+
startAbleTimeOAuth: () => startAbleTimeOAuth,
|
|
24677
25559
|
startDevServer: () => startDevServer,
|
|
24678
25560
|
startLinearOAuth: () => startLinearOAuth,
|
|
24679
25561
|
startMcpServer: () => startMcpServer,
|
|
@@ -24976,8 +25858,8 @@ function issueActivityKind(issue, sinceIso) {
|
|
|
24976
25858
|
}
|
|
24977
25859
|
|
|
24978
25860
|
// src/integrations/linear-oauth.ts
|
|
24979
|
-
var
|
|
24980
|
-
var
|
|
25861
|
+
var import_node_crypto12 = require("crypto");
|
|
25862
|
+
var import_node_http2 = require("http");
|
|
24981
25863
|
init_app_settings();
|
|
24982
25864
|
init_fetch();
|
|
24983
25865
|
|
|
@@ -24995,7 +25877,7 @@ var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
|
|
|
24995
25877
|
var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
|
|
24996
25878
|
var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
|
|
24997
25879
|
var LINEAR_OAUTH_SCOPES = "read,write";
|
|
24998
|
-
var
|
|
25880
|
+
var REFRESH_SKEW_MS3 = 5 * 6e4;
|
|
24999
25881
|
function linearOAuthCredentials() {
|
|
25000
25882
|
const settings = loadAppSettings();
|
|
25001
25883
|
const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
|
|
@@ -25008,8 +25890,8 @@ function linearOAuthCredentials() {
|
|
|
25008
25890
|
return { clientId, clientSecret };
|
|
25009
25891
|
}
|
|
25010
25892
|
function createLinearPkce() {
|
|
25011
|
-
const verifier = (0,
|
|
25012
|
-
const challenge = (0,
|
|
25893
|
+
const verifier = (0, import_node_crypto12.randomBytes)(32).toString("base64url");
|
|
25894
|
+
const challenge = (0, import_node_crypto12.createHash)("sha256").update(verifier).digest("base64url");
|
|
25013
25895
|
return { verifier, challenge };
|
|
25014
25896
|
}
|
|
25015
25897
|
function linearOAuthAuthorizeUrl(clientId, state, codeChallenge) {
|
|
@@ -25033,7 +25915,7 @@ function linearAuthorizationHeader(token) {
|
|
|
25033
25915
|
if (t.startsWith("lin_api_")) return t;
|
|
25034
25916
|
return `Bearer ${t}`;
|
|
25035
25917
|
}
|
|
25036
|
-
function
|
|
25918
|
+
function htmlPage2(title, body) {
|
|
25037
25919
|
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
|
|
25038
25920
|
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
25039
25921
|
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
@@ -25079,7 +25961,7 @@ async function fetchLinearViewer(accessToken) {
|
|
|
25079
25961
|
organizationName: viewer?.organization?.name?.trim() || void 0
|
|
25080
25962
|
};
|
|
25081
25963
|
}
|
|
25082
|
-
var
|
|
25964
|
+
var refreshInFlight2 = null;
|
|
25083
25965
|
async function refreshLinearAccessToken(refreshToken) {
|
|
25084
25966
|
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
25085
25967
|
const body = new URLSearchParams({
|
|
@@ -25106,16 +25988,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
|
|
|
25106
25988
|
const refresh = settings.integrations.linearRefreshToken?.trim() || "";
|
|
25107
25989
|
const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
|
|
25108
25990
|
const apiKey = settings.integrations.linearApiKey?.trim() || "";
|
|
25109
|
-
if (access && (!refresh || Date.now() < expiresAt -
|
|
25991
|
+
if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS3)) {
|
|
25110
25992
|
return access;
|
|
25111
25993
|
}
|
|
25112
25994
|
if (refresh) {
|
|
25113
|
-
if (!
|
|
25114
|
-
|
|
25115
|
-
|
|
25995
|
+
if (!refreshInFlight2) {
|
|
25996
|
+
refreshInFlight2 = refreshLinearAccessToken(refresh).finally(() => {
|
|
25997
|
+
refreshInFlight2 = null;
|
|
25116
25998
|
});
|
|
25117
25999
|
}
|
|
25118
|
-
return
|
|
26000
|
+
return refreshInFlight2.catch((err) => {
|
|
25119
26001
|
if (apiKey) return apiKey;
|
|
25120
26002
|
throw err;
|
|
25121
26003
|
});
|
|
@@ -25140,7 +26022,7 @@ async function startLinearOAuth(opts) {
|
|
|
25140
26022
|
throw new LinearOAuthCancelledError();
|
|
25141
26023
|
}
|
|
25142
26024
|
const { clientId, clientSecret } = linearOAuthCredentials();
|
|
25143
|
-
const state = (0,
|
|
26025
|
+
const state = (0, import_node_crypto12.randomBytes)(16).toString("hex");
|
|
25144
26026
|
const pkce = createLinearPkce();
|
|
25145
26027
|
const authorizeUrl = linearOAuthAuthorizeUrl(clientId, state, pkce.challenge);
|
|
25146
26028
|
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
@@ -25153,7 +26035,7 @@ async function startLinearOAuth(opts) {
|
|
|
25153
26035
|
if (err) reject(err);
|
|
25154
26036
|
else resolve(value);
|
|
25155
26037
|
};
|
|
25156
|
-
const server = (0,
|
|
26038
|
+
const server = (0, import_node_http2.createServer)((req, res) => {
|
|
25157
26039
|
try {
|
|
25158
26040
|
const url = new URL(req.url || "/", `http://127.0.0.1:${LINEAR_OAUTH_PORT}`);
|
|
25159
26041
|
if (url.pathname !== "/callback") {
|
|
@@ -25166,19 +26048,19 @@ async function startLinearOAuth(opts) {
|
|
|
25166
26048
|
const gotCode = url.searchParams.get("code");
|
|
25167
26049
|
if (err) {
|
|
25168
26050
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
25169
|
-
res.end(
|
|
26051
|
+
res.end(htmlPage2("Linear", `<h1>Authorization cancelled</h1><p>${err}</p>`));
|
|
25170
26052
|
finish(new LinearOAuthCancelledError(`Linear OAuth: ${err}`));
|
|
25171
26053
|
return;
|
|
25172
26054
|
}
|
|
25173
26055
|
if (gotState !== state || !gotCode) {
|
|
25174
26056
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
25175
|
-
res.end(
|
|
26057
|
+
res.end(htmlPage2("Linear", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
|
|
25176
26058
|
finish(new Error("Linear OAuth callback was invalid"));
|
|
25177
26059
|
return;
|
|
25178
26060
|
}
|
|
25179
26061
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
25180
26062
|
res.end(
|
|
25181
|
-
|
|
26063
|
+
htmlPage2(
|
|
25182
26064
|
"Linear connected",
|
|
25183
26065
|
"<h1>Linear connected</h1><p>You can close this tab and return to Sideboard.</p>"
|
|
25184
26066
|
)
|
|
@@ -26591,6 +27473,7 @@ async function createGitHubIssue(input, opts) {
|
|
|
26591
27473
|
// src/index.ts
|
|
26592
27474
|
init_abletime();
|
|
26593
27475
|
init_abletime_mcp();
|
|
27476
|
+
init_abletime_oauth();
|
|
26594
27477
|
|
|
26595
27478
|
// src/integrations/issues.ts
|
|
26596
27479
|
init_worktree();
|
|
@@ -26993,7 +27876,7 @@ function buildPathRefAttachment(input) {
|
|
|
26993
27876
|
init_stage_files();
|
|
26994
27877
|
|
|
26995
27878
|
// src/composer/pasted-text.ts
|
|
26996
|
-
var
|
|
27879
|
+
var import_node_crypto13 = require("crypto");
|
|
26997
27880
|
var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
26998
27881
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
26999
27882
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
@@ -27020,7 +27903,7 @@ function nextPastedTextName(existing) {
|
|
|
27020
27903
|
}
|
|
27021
27904
|
function buildPastedTextAttachment(text7, opts) {
|
|
27022
27905
|
return {
|
|
27023
|
-
id: opts?.id ?? (0,
|
|
27906
|
+
id: opts?.id ?? (0, import_node_crypto13.randomUUID)(),
|
|
27024
27907
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
27025
27908
|
kind: "file",
|
|
27026
27909
|
path: opts?.path,
|
|
@@ -27051,7 +27934,7 @@ function planQuestionsSignature(questions) {
|
|
|
27051
27934
|
return `${q.question}${opts}`;
|
|
27052
27935
|
}).join("");
|
|
27053
27936
|
}
|
|
27054
|
-
function
|
|
27937
|
+
function asRecord10(v) {
|
|
27055
27938
|
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
27056
27939
|
}
|
|
27057
27940
|
function parseOptions(raw) {
|
|
@@ -27062,7 +27945,7 @@ function parseOptions(raw) {
|
|
|
27062
27945
|
out.push({ label: item.trim() });
|
|
27063
27946
|
continue;
|
|
27064
27947
|
}
|
|
27065
|
-
const o =
|
|
27948
|
+
const o = asRecord10(item);
|
|
27066
27949
|
if (!o) continue;
|
|
27067
27950
|
const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
|
|
27068
27951
|
if (!label) continue;
|
|
@@ -27072,7 +27955,7 @@ function parseOptions(raw) {
|
|
|
27072
27955
|
return out;
|
|
27073
27956
|
}
|
|
27074
27957
|
function parseOneQuestion(raw) {
|
|
27075
|
-
const o =
|
|
27958
|
+
const o = asRecord10(raw);
|
|
27076
27959
|
if (!o) return null;
|
|
27077
27960
|
const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
|
|
27078
27961
|
if (!question) return null;
|
|
@@ -27083,7 +27966,7 @@ function parseOneQuestion(raw) {
|
|
|
27083
27966
|
return { question, header, multiSelect, options };
|
|
27084
27967
|
}
|
|
27085
27968
|
function parsePlanQuestionsInput(input) {
|
|
27086
|
-
const root =
|
|
27969
|
+
const root = asRecord10(input);
|
|
27087
27970
|
if (!root) return [];
|
|
27088
27971
|
const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
|
|
27089
27972
|
const out = [];
|
|
@@ -27174,7 +28057,7 @@ init_orphan_cleanup();
|
|
|
27174
28057
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
27175
28058
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
27176
28059
|
var import_zod9 = require("zod");
|
|
27177
|
-
var
|
|
28060
|
+
var import_node_crypto15 = require("crypto");
|
|
27178
28061
|
var import_node_path53 = require("path");
|
|
27179
28062
|
init_orchestrator();
|
|
27180
28063
|
init_worktree();
|
|
@@ -28901,7 +29784,7 @@ init_worktree();
|
|
|
28901
29784
|
init_paths();
|
|
28902
29785
|
|
|
28903
29786
|
// src/board/board-pins.ts
|
|
28904
|
-
var
|
|
29787
|
+
var import_node_crypto14 = require("crypto");
|
|
28905
29788
|
var import_node_fs55 = require("fs");
|
|
28906
29789
|
var import_node_path51 = require("path");
|
|
28907
29790
|
init_paths();
|
|
@@ -28933,7 +29816,7 @@ function addBoardPin(input) {
|
|
|
28933
29816
|
const ref = input.ref.trim();
|
|
28934
29817
|
if (!ref) throw new Error("Board item needs a ref");
|
|
28935
29818
|
const pin = {
|
|
28936
|
-
id: (0,
|
|
29819
|
+
id: (0, import_node_crypto14.randomUUID)(),
|
|
28937
29820
|
kind: input.kind,
|
|
28938
29821
|
ref,
|
|
28939
29822
|
repoPath: input.repoPath.trim(),
|
|
@@ -29767,7 +30650,7 @@ async function startMcpServer() {
|
|
|
29767
30650
|
const ident = issue?.identifier ?? ref.trim();
|
|
29768
30651
|
const attachments = issue ? [
|
|
29769
30652
|
{
|
|
29770
|
-
id: (0,
|
|
30653
|
+
id: (0, import_node_crypto15.randomUUID)(),
|
|
29771
30654
|
name: issue.identifier,
|
|
29772
30655
|
kind: "issue",
|
|
29773
30656
|
content: [
|
|
@@ -30722,7 +31605,7 @@ init_orch_mcp_isolation();
|
|
|
30722
31605
|
init_workspaces2();
|
|
30723
31606
|
|
|
30724
31607
|
// src/slack/oauth.ts
|
|
30725
|
-
var
|
|
31608
|
+
var import_node_crypto16 = require("crypto");
|
|
30726
31609
|
init_app_settings();
|
|
30727
31610
|
init_workspaces2();
|
|
30728
31611
|
|
|
@@ -30746,11 +31629,11 @@ var SLACK_OAUTH_REDIRECT = `https://relay.sideboard.cloud${SLACK_OAUTH_CALLBACK_
|
|
|
30746
31629
|
function slackOAuthRedirectUri() {
|
|
30747
31630
|
return process.env.SIDEBOARD_SLACK_OAUTH_REDIRECT?.trim() || SLACK_OAUTH_REDIRECT;
|
|
30748
31631
|
}
|
|
30749
|
-
function
|
|
31632
|
+
function escapeHtml2(value) {
|
|
30750
31633
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
30751
31634
|
}
|
|
30752
31635
|
function slackOAuthHtmlPage(title, body) {
|
|
30753
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>${
|
|
31636
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml2(title)}</title>
|
|
30754
31637
|
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
|
|
30755
31638
|
h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
|
|
30756
31639
|
}
|
|
@@ -30946,7 +31829,7 @@ async function startSlackOAuth(opts) {
|
|
|
30946
31829
|
throw new SlackOAuthCancelledError();
|
|
30947
31830
|
}
|
|
30948
31831
|
const { clientId } = slackOAuthCredentials();
|
|
30949
|
-
const state = (0,
|
|
31832
|
+
const state = (0, import_node_crypto16.randomBytes)(16).toString("hex");
|
|
30950
31833
|
const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
|
|
30951
31834
|
const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
|
|
30952
31835
|
const pollIntervalMs = opts?.pollIntervalMs ?? 400;
|
|
@@ -31228,7 +32111,7 @@ init_workspaces2();
|
|
|
31228
32111
|
init_reply_target();
|
|
31229
32112
|
|
|
31230
32113
|
// src/slack/relay-protocol.ts
|
|
31231
|
-
var
|
|
32114
|
+
var import_node_crypto17 = require("crypto");
|
|
31232
32115
|
var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
|
|
31233
32116
|
var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
|
|
31234
32117
|
function slackRelaySessionKey(teamId, userId, deviceId) {
|
|
@@ -31238,7 +32121,7 @@ function slackRelayUserKey(teamId, userId) {
|
|
|
31238
32121
|
return `${teamId.trim()}:${userId.trim()}`;
|
|
31239
32122
|
}
|
|
31240
32123
|
function newSlackRelayEventId() {
|
|
31241
|
-
return (0,
|
|
32124
|
+
return (0, import_node_crypto17.randomBytes)(12).toString("hex");
|
|
31242
32125
|
}
|
|
31243
32126
|
function parseSlackRelayClientMessage(raw) {
|
|
31244
32127
|
try {
|
|
@@ -31311,7 +32194,7 @@ function parseSlackRelayServerMessage(raw) {
|
|
|
31311
32194
|
|
|
31312
32195
|
// src/slack/public-dns.ts
|
|
31313
32196
|
var import_node_dns = require("dns");
|
|
31314
|
-
var
|
|
32197
|
+
var import_node_http3 = require("http");
|
|
31315
32198
|
var import_node_https = require("https");
|
|
31316
32199
|
var PUBLIC_DNS = ["1.1.1.1", "8.8.8.8"];
|
|
31317
32200
|
function finishLookup(options, callback, address, family) {
|
|
@@ -32355,7 +33238,7 @@ var SlackRelayHub = class {
|
|
|
32355
33238
|
};
|
|
32356
33239
|
|
|
32357
33240
|
// src/slack/relay-server.ts
|
|
32358
|
-
var
|
|
33241
|
+
var import_node_http4 = require("http");
|
|
32359
33242
|
var import_ws3 = require("ws");
|
|
32360
33243
|
|
|
32361
33244
|
// src/slack/relay-static.ts
|
|
@@ -32434,6 +33317,7 @@ async function tryServeStatic(req, res, root) {
|
|
|
32434
33317
|
}
|
|
32435
33318
|
|
|
32436
33319
|
// src/slack/relay-server.ts
|
|
33320
|
+
init_abletime_oauth();
|
|
32437
33321
|
function sendHtml(res, status, title, body) {
|
|
32438
33322
|
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
32439
33323
|
res.end(slackOAuthHtmlPage(title, body));
|
|
@@ -32468,7 +33352,7 @@ async function startSlackRelayServer(opts) {
|
|
|
32468
33352
|
res,
|
|
32469
33353
|
400,
|
|
32470
33354
|
"Slack",
|
|
32471
|
-
`<h1>Authorization cancelled</h1><p>${
|
|
33355
|
+
`<h1>Authorization cancelled</h1><p>${escapeHtml2(error)}</p>`
|
|
32472
33356
|
);
|
|
32473
33357
|
return true;
|
|
32474
33358
|
}
|
|
@@ -32507,7 +33391,7 @@ async function startSlackRelayServer(opts) {
|
|
|
32507
33391
|
} catch (err) {
|
|
32508
33392
|
const message = err instanceof Error ? err.message : String(err);
|
|
32509
33393
|
pending.put(state, { ok: false, error: message });
|
|
32510
|
-
sendHtml(res, 400, "Slack", `<h1>Could not connect Slack</h1><p>${
|
|
33394
|
+
sendHtml(res, 400, "Slack", `<h1>Could not connect Slack</h1><p>${escapeHtml2(message)}</p>`);
|
|
32511
33395
|
}
|
|
32512
33396
|
return true;
|
|
32513
33397
|
};
|
|
@@ -32527,11 +33411,21 @@ async function startSlackRelayServer(opts) {
|
|
|
32527
33411
|
sendJson(res, 200, { ok: true, ...value.payload });
|
|
32528
33412
|
return true;
|
|
32529
33413
|
};
|
|
32530
|
-
const httpServer = (0,
|
|
33414
|
+
const httpServer = (0, import_node_http4.createServer)((req, res) => {
|
|
32531
33415
|
void (async () => {
|
|
32532
33416
|
const reqUrl = req.url || "/";
|
|
32533
33417
|
if (await handleCallback(reqUrl, res)) return;
|
|
32534
33418
|
if (handleResult(reqUrl, res)) return;
|
|
33419
|
+
const ableTimeBounce = ableTimeOAuthLocalBounceUrl(reqUrl);
|
|
33420
|
+
if (ableTimeBounce && (req.method === "GET" || req.method === "HEAD")) {
|
|
33421
|
+
res.writeHead(200, {
|
|
33422
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
33423
|
+
"Cache-Control": "no-store",
|
|
33424
|
+
Refresh: `0;url=${ableTimeBounce}`
|
|
33425
|
+
});
|
|
33426
|
+
res.end(req.method === "HEAD" ? "" : ableTimeOAuthBouncePage(ableTimeBounce));
|
|
33427
|
+
return;
|
|
33428
|
+
}
|
|
32535
33429
|
if (req.url === "/health") {
|
|
32536
33430
|
sendJson(res, 200, {
|
|
32537
33431
|
ok: true,
|
|
@@ -32664,10 +33558,21 @@ init_outbound_watch();
|
|
|
32664
33558
|
0 && (module.exports = {
|
|
32665
33559
|
ABLETIME_MCP_PATH,
|
|
32666
33560
|
ABLETIME_MCP_PM_PATH,
|
|
33561
|
+
ABLETIME_OAUTH_CALLBACK_PATH,
|
|
33562
|
+
ABLETIME_OAUTH_CANCELLED,
|
|
33563
|
+
ABLETIME_OAUTH_LOCAL_CALLBACK,
|
|
33564
|
+
ABLETIME_OAUTH_PORT,
|
|
33565
|
+
ABLETIME_OAUTH_REDIRECT,
|
|
33566
|
+
ABLETIME_OAUTH_RESOURCE,
|
|
33567
|
+
ABLETIME_OAUTH_TOKEN_PREFIX,
|
|
33568
|
+
ABLETIME_ORG_KEY_PREFIX,
|
|
33569
|
+
ABLETIME_PAT_PREFIX,
|
|
32667
33570
|
ABLETIME_PM_TOOL_NAMES,
|
|
32668
33571
|
AGENT_GIT_ACTIONS,
|
|
32669
33572
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
32670
33573
|
ATTACHMENTS_DIR,
|
|
33574
|
+
AbleTimeOAuthCancelledError,
|
|
33575
|
+
BAKED_ABLETIME_OAUTH_CLIENT_ID,
|
|
32671
33576
|
BAKED_SLACK_RELAY_URL,
|
|
32672
33577
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
32673
33578
|
BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
|
|
@@ -32756,7 +33661,15 @@ init_outbound_watch();
|
|
|
32756
33661
|
WORKTREE_GITHUB_MCP_TOOLS,
|
|
32757
33662
|
WORKTREE_LINEAR_MCP_TOOLS,
|
|
32758
33663
|
WORKTREE_MCP_TOOLS,
|
|
33664
|
+
ableTimeCredentialKind,
|
|
32759
33665
|
ableTimeDependencyTarget,
|
|
33666
|
+
ableTimeOAuthAuthorizeUrl,
|
|
33667
|
+
ableTimeOAuthBouncePage,
|
|
33668
|
+
ableTimeOAuthClientId,
|
|
33669
|
+
ableTimeOAuthLocalBounceUrl,
|
|
33670
|
+
ableTimeOAuthRedirectUri,
|
|
33671
|
+
ableTimeOAuthResource,
|
|
33672
|
+
ableTimeUsesRest,
|
|
32760
33673
|
abletimeMcpRequest,
|
|
32761
33674
|
abletimeMcpUrl,
|
|
32762
33675
|
abletimeToolUsesPm,
|
|
@@ -32787,6 +33700,8 @@ init_outbound_watch();
|
|
|
32787
33700
|
applyThreadIntoMain,
|
|
32788
33701
|
applyTurnUsage,
|
|
32789
33702
|
armSchedules,
|
|
33703
|
+
assertAbleTimeCredential,
|
|
33704
|
+
assertAbleTimeMcpCredential,
|
|
32790
33705
|
assertOrchestratorCapableAgent,
|
|
32791
33706
|
attachmentFromAbsolutePath,
|
|
32792
33707
|
attachmentsFromBuffers,
|
|
@@ -32859,6 +33774,7 @@ init_outbound_watch();
|
|
|
32859
33774
|
connectOptionalService,
|
|
32860
33775
|
connectSlackToken,
|
|
32861
33776
|
connectedOptionalServices,
|
|
33777
|
+
connectedPrSelectors,
|
|
32862
33778
|
contextTokens,
|
|
32863
33779
|
coordinatorSystemPrompt,
|
|
32864
33780
|
coordinatorTurnReminder,
|
|
@@ -32866,6 +33782,7 @@ init_outbound_watch();
|
|
|
32866
33782
|
countCacheControlBlocks,
|
|
32867
33783
|
countUnpushedVsOrigin,
|
|
32868
33784
|
cowboyModeEnabled,
|
|
33785
|
+
createAbleTimePkce,
|
|
32869
33786
|
createAbleTimeTask,
|
|
32870
33787
|
createChatTab,
|
|
32871
33788
|
createEmptyThread,
|
|
@@ -32905,6 +33822,7 @@ init_outbound_watch();
|
|
|
32905
33822
|
encodeBrightsyTarget,
|
|
32906
33823
|
enrichPathWithNpmGlobalBin,
|
|
32907
33824
|
enrichWorkspacesWithGithub,
|
|
33825
|
+
ensureAbleTimeAccessToken,
|
|
32908
33826
|
ensureAbleTimeTask,
|
|
32909
33827
|
ensureAgentPath,
|
|
32910
33828
|
ensureBrightsyLocalConfigFresh,
|
|
@@ -33051,6 +33969,7 @@ init_outbound_watch();
|
|
|
33051
33969
|
groupHomeBoardWorktrees,
|
|
33052
33970
|
handleSlackInbound,
|
|
33053
33971
|
harnessEnvKey,
|
|
33972
|
+
hasBakedAbleTimeOAuth,
|
|
33054
33973
|
hasBakedLinearOAuth,
|
|
33055
33974
|
hasBakedSlackOAuth,
|
|
33056
33975
|
hasConductorHook,
|
|
@@ -33075,6 +33994,8 @@ init_outbound_watch();
|
|
|
33075
33994
|
invalidateThreadListCache,
|
|
33076
33995
|
invalidateThreadRecord,
|
|
33077
33996
|
isAbleTimeConnected,
|
|
33997
|
+
isAbleTimeOAuthCancelled,
|
|
33998
|
+
isAbleTimeRejectedCredential,
|
|
33078
33999
|
isAskUserToolName,
|
|
33079
34000
|
isBrightsyConnected,
|
|
33080
34001
|
isBrightsyNdjsonLine,
|
|
@@ -33204,6 +34125,7 @@ init_outbound_watch();
|
|
|
33204
34125
|
nextPastedTextName,
|
|
33205
34126
|
nextThinkingEffort,
|
|
33206
34127
|
nonInteractiveGitProcessEnv,
|
|
34128
|
+
normalizeAbleTimeCredential,
|
|
33207
34129
|
normalizeAbleTimeHost,
|
|
33208
34130
|
normalizeCodeSelection,
|
|
33209
34131
|
normalizeLinearRelationType,
|
|
@@ -33226,6 +34148,7 @@ init_outbound_watch();
|
|
|
33226
34148
|
originFetchBranch,
|
|
33227
34149
|
originGhRepoEnv,
|
|
33228
34150
|
packagedDetachedJobPath,
|
|
34151
|
+
parseAbleTimeOAuthCallbackUrl,
|
|
33229
34152
|
parseClaudeUsagePayload,
|
|
33230
34153
|
parseCursorRunnerLine,
|
|
33231
34154
|
parseDurationMs,
|
|
@@ -33385,6 +34308,7 @@ init_outbound_watch();
|
|
|
33385
34308
|
stackMergeReadiness,
|
|
33386
34309
|
stageAbsolutePathsAsAttachments,
|
|
33387
34310
|
stageBuffersAsAttachments,
|
|
34311
|
+
startAbleTimeOAuth,
|
|
33388
34312
|
startDevServer,
|
|
33389
34313
|
startLinearOAuth,
|
|
33390
34314
|
startMcpServer,
|