@sideboard-ai/core 0.1.202 → 0.1.203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{abletime-KGPD3UUJ.js → abletime-JFGZBDHH.js} +4 -2
  2. package/dist/{abletime-345SE3B4.js → abletime-SISJEPJ3.js} +4 -2
  3. package/dist/abletime-oauth-7TD5Z2QV.js +265 -0
  4. package/dist/abletime-oauth-OSQYFB3E.js +40 -0
  5. package/dist/abletime-rest-QPJTDLKX.js +25 -0
  6. package/dist/abletime-rest-ZJGWYALM.js +24 -0
  7. package/dist/{agents-OXX3ONVY.js → agents-3755WYQM.js} +4 -4
  8. package/dist/{agents-573ZDIX6.js → agents-ZMWL43SP.js} +4 -4
  9. package/dist/{app-settings-V6IHESEQ.js → app-settings-LGHMNHSP.js} +1 -1
  10. package/dist/{app-settings-AXCURTZJ.js → app-settings-YEPF46IJ.js} +1 -1
  11. package/dist/{chunk-7NKY2OX7.js → chunk-2DVCCLII.js} +78 -62
  12. package/dist/chunk-3PYH2RLA.js +388 -0
  13. package/dist/{chunk-A2TAKYSG.js → chunk-5IXJTUST.js} +4 -4
  14. package/dist/chunk-6VEIKW5C.js +390 -0
  15. package/dist/chunk-BFEBFGCS.js +250 -0
  16. package/dist/{chunk-STPZQR5B.js → chunk-CMGRV36E.js} +1 -1
  17. package/dist/{chunk-PGQGP4FD.js → chunk-FZ6IBUKY.js} +36 -218
  18. package/dist/{chunk-7AHAABKN.js → chunk-FZYYEMET.js} +36 -228
  19. package/dist/{chunk-54RSAMBH.js → chunk-LCU6GBZP.js} +2 -2
  20. package/dist/chunk-MHPINQKY.js +260 -0
  21. package/dist/chunk-PJHFNFXA.js +243 -0
  22. package/dist/{chunk-CXFW5LIH.js → chunk-QFBBUFD4.js} +38 -3
  23. package/dist/{chunk-RPX6VDW7.js → chunk-QSEF2YVQ.js} +1 -1
  24. package/dist/{chunk-LJHC62LZ.js → chunk-TNDUTGDV.js} +2 -2
  25. package/dist/{chunk-27GGXX27.js → chunk-TZNJKZ2A.js} +82 -66
  26. package/dist/{chunk-NIVF5NDN.js → chunk-X2TFUFIS.js} +4 -4
  27. package/dist/{chunk-ISV5IFH3.js → chunk-X743YN5Y.js} +38 -3
  28. package/dist/{coordinator-prompt-TY5FHAXC.js → coordinator-prompt-2B2JA2IE.js} +3 -3
  29. package/dist/{coordinator-prompt-BH4EHFOO.js → coordinator-prompt-U4PEZDAW.js} +3 -3
  30. package/dist/{global-workspace-NLLAPQJI.js → global-workspace-JPJ3FKKY.js} +3 -3
  31. package/dist/{global-workspace-5AZEEQRU.js → global-workspace-PU7ZS3LS.js} +3 -3
  32. package/dist/index.cjs +912 -142
  33. package/dist/index.d.cts +61 -9
  34. package/dist/index.d.ts +61 -9
  35. package/dist/index.js +63 -18
  36. package/dist/mcp/run-stdio.cjs +843 -114
  37. package/dist/mcp/run-stdio.js +22 -20
  38. package/dist/{orchestrator-JNSQDRZI.js → orchestrator-3A6KUTNI.js} +5 -5
  39. package/dist/{orchestrator-NCYKEI3E.js → orchestrator-Q33M24ZJ.js} +6 -6
  40. package/dist/{workspaces-RP76JH3J.js → workspaces-IMG7ZRUQ.js} +3 -3
  41. package/dist/{workspaces-PEWYWVHZ.js → workspaces-U6P6DB44.js} +3 -3
  42. package/dist/{worktree-ZQEUKSNE.js → worktree-FL3UIUKQ.js} +2 -2
  43. package/dist/{worktree-72TZWUSH.js → worktree-JSCDJPBU.js} +2 -2
  44. 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(settings.integrations.abletimeAccessToken?.trim());
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
  }
@@ -17459,6 +17494,645 @@ var init_detect = __esm({
17459
17494
  }
17460
17495
  });
17461
17496
 
17497
+ // src/integrations/abletime-oauth.ts
17498
+ var abletime_oauth_exports = {};
17499
+ __export(abletime_oauth_exports, {
17500
+ ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
17501
+ ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
17502
+ ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
17503
+ ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
17504
+ AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
17505
+ BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
17506
+ ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
17507
+ ableTimeOAuthClientId: () => ableTimeOAuthClientId,
17508
+ ableTimeOAuthResource: () => ableTimeOAuthResource,
17509
+ createAbleTimePkce: () => createAbleTimePkce,
17510
+ ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
17511
+ hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
17512
+ isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
17513
+ startAbleTimeOAuth: () => startAbleTimeOAuth
17514
+ });
17515
+ function hasBakedAbleTimeOAuth() {
17516
+ return Boolean(BAKED_ABLETIME_OAUTH_CLIENT_ID.trim());
17517
+ }
17518
+ function ableTimeOAuthClientId() {
17519
+ return process.env.SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID?.trim() || BAKED_ABLETIME_OAUTH_CLIENT_ID.trim();
17520
+ }
17521
+ function ableTimeOAuthResource(host) {
17522
+ return `${normalizeAbleTimeHost(host)}/api/public/v2/mcp`;
17523
+ }
17524
+ function createAbleTimePkce() {
17525
+ const verifier = (0, import_node_crypto10.randomBytes)(32).toString("base64url");
17526
+ const challenge = (0, import_node_crypto10.createHash)("sha256").update(verifier).digest("base64url");
17527
+ return { verifier, challenge };
17528
+ }
17529
+ function ableTimeOAuthAuthorizeUrl(clientId, state, codeChallenge, host) {
17530
+ const base = normalizeAbleTimeHost(host);
17531
+ const params = new URLSearchParams({
17532
+ response_type: "code",
17533
+ client_id: clientId,
17534
+ redirect_uri: ABLETIME_OAUTH_REDIRECT,
17535
+ code_challenge: codeChallenge,
17536
+ code_challenge_method: "S256",
17537
+ state,
17538
+ resource: ableTimeOAuthResource(host)
17539
+ });
17540
+ return `${base}/oauth/authorize?${params.toString()}`;
17541
+ }
17542
+ function htmlPage(title, body) {
17543
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
17544
+ <style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
17545
+ h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
17546
+ }
17547
+ async function exchangeAbleTimeToken(body, host) {
17548
+ const url = `${normalizeAbleTimeHost(host)}/api/oauth/token`;
17549
+ const res = await httpFetch(url, {
17550
+ method: "POST",
17551
+ headers: {
17552
+ Accept: "application/json",
17553
+ "Content-Type": "application/x-www-form-urlencoded"
17554
+ },
17555
+ body
17556
+ });
17557
+ const data = await res.json().catch(() => ({}));
17558
+ if (!res.ok || data.error || !data.access_token?.trim()) {
17559
+ const detail = data.error_description || data.error || `HTTP ${res.status}`;
17560
+ throw new Error(`AbleTime OAuth token exchange failed: ${detail}`);
17561
+ }
17562
+ return data;
17563
+ }
17564
+ function persistAbleTimeTokens(data, input) {
17565
+ return saveAbleTimeConnection({
17566
+ accessToken: data.access_token.trim(),
17567
+ refreshToken: data.refresh_token?.trim(),
17568
+ expiresIn: data.expires_in,
17569
+ host: input?.host,
17570
+ viewerName: input?.viewerName
17571
+ });
17572
+ }
17573
+ async function refreshAbleTimeAccessToken(refreshToken) {
17574
+ const host = getAbleTimeHost();
17575
+ const body = new URLSearchParams({
17576
+ grant_type: "refresh_token",
17577
+ refresh_token: refreshToken,
17578
+ client_id: ableTimeOAuthClientId(),
17579
+ resource: ableTimeOAuthResource(host)
17580
+ });
17581
+ try {
17582
+ const data = await exchangeAbleTimeToken(body, host);
17583
+ persistAbleTimeTokens(data, { host });
17584
+ return data.access_token.trim();
17585
+ } catch (err) {
17586
+ const message = err instanceof Error ? err.message : String(err);
17587
+ if (/invalid_grant|invalid_token|unauthorized/i.test(message)) {
17588
+ disconnectAbleTimeConnection();
17589
+ throw new Error("AbleTime sign-in expired \u2014 connect again in Account settings");
17590
+ }
17591
+ throw err;
17592
+ }
17593
+ }
17594
+ async function ensureAbleTimeAccessToken() {
17595
+ const settings = loadAppSettings();
17596
+ const access = settings.integrations.abletimeAccessToken?.trim() || "";
17597
+ const refresh = settings.integrations.abletimeRefreshToken?.trim() || "";
17598
+ const expiresAt = settings.integrations.abletimeTokenExpiresAt ?? 0;
17599
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
17600
+ return access;
17601
+ }
17602
+ if (refresh) {
17603
+ if (!refreshInFlight) {
17604
+ refreshInFlight = refreshAbleTimeAccessToken(refresh).finally(() => {
17605
+ refreshInFlight = null;
17606
+ });
17607
+ }
17608
+ return refreshInFlight;
17609
+ }
17610
+ return access || null;
17611
+ }
17612
+ function isAbleTimeOAuthCancelled(err) {
17613
+ if (err instanceof AbleTimeOAuthCancelledError) return true;
17614
+ const message = err instanceof Error ? err.message : String(err);
17615
+ return message.includes(ABLETIME_OAUTH_CANCELLED);
17616
+ }
17617
+ async function startAbleTimeOAuth(opts) {
17618
+ if (opts?.signal?.aborted) {
17619
+ throw new AbleTimeOAuthCancelledError();
17620
+ }
17621
+ const clientId = ableTimeOAuthClientId();
17622
+ if (!clientId) {
17623
+ throw new Error(
17624
+ `AbleTime browser sign-in needs a Client ID Metadata Document URL. Host ${BAKED_ABLETIME_OAUTH_CLIENT_ID} (see site/oauth/abletime-client.json), or set SIDEBOARD_ABLETIME_OAUTH_CLIENT_ID.`
17625
+ );
17626
+ }
17627
+ const host = opts?.host?.trim() || void 0;
17628
+ const state = (0, import_node_crypto10.randomBytes)(16).toString("hex");
17629
+ const pkce = createAbleTimePkce();
17630
+ const authorizeUrl = ableTimeOAuthAuthorizeUrl(clientId, state, pkce.challenge, host);
17631
+ const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
17632
+ const code = await new Promise((resolve, reject) => {
17633
+ let settled = false;
17634
+ const finish = (err, value) => {
17635
+ if (settled) return;
17636
+ settled = true;
17637
+ cleanup();
17638
+ if (err) reject(err);
17639
+ else resolve(value);
17640
+ };
17641
+ const server = (0, import_node_http.createServer)((req, res) => {
17642
+ try {
17643
+ const url = new URL(req.url || "/", `http://127.0.0.1:${ABLETIME_OAUTH_PORT}`);
17644
+ if (url.pathname !== "/callback") {
17645
+ res.writeHead(404);
17646
+ res.end("Not found");
17647
+ return;
17648
+ }
17649
+ const err = url.searchParams.get("error");
17650
+ const gotState = url.searchParams.get("state");
17651
+ const gotCode = url.searchParams.get("code");
17652
+ if (err) {
17653
+ res.writeHead(400, { "Content-Type": "text/html" });
17654
+ res.end(htmlPage("AbleTime", `<h1>Authorization cancelled</h1><p>${err}</p>`));
17655
+ finish(new AbleTimeOAuthCancelledError(`AbleTime OAuth: ${err}`));
17656
+ return;
17657
+ }
17658
+ if (gotState !== state || !gotCode) {
17659
+ res.writeHead(400, { "Content-Type": "text/html" });
17660
+ res.end(htmlPage("AbleTime", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
17661
+ finish(new Error("AbleTime OAuth callback was invalid"));
17662
+ return;
17663
+ }
17664
+ res.writeHead(200, { "Content-Type": "text/html" });
17665
+ res.end(
17666
+ htmlPage(
17667
+ "AbleTime connected",
17668
+ "<h1>AbleTime connected</h1><p>You can close this tab and return to Sideboard.</p>"
17669
+ )
17670
+ );
17671
+ finish(void 0, gotCode);
17672
+ } catch (e) {
17673
+ finish(e instanceof Error ? e : new Error(String(e)));
17674
+ }
17675
+ });
17676
+ const timer = setTimeout(() => {
17677
+ finish(new Error("AbleTime sign-in timed out \u2014 try again"));
17678
+ }, timeoutMs);
17679
+ const onAbort = () => finish(new AbleTimeOAuthCancelledError());
17680
+ const cleanup = () => {
17681
+ clearTimeout(timer);
17682
+ opts?.signal?.removeEventListener("abort", onAbort);
17683
+ server.close();
17684
+ };
17685
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
17686
+ if (opts?.signal?.aborted) {
17687
+ onAbort();
17688
+ return;
17689
+ }
17690
+ server.on("error", (e) => {
17691
+ finish(
17692
+ e instanceof Error && e.code === "EADDRINUSE" ? new Error(
17693
+ `Port ${ABLETIME_OAUTH_PORT} is in use. Close whatever is bound there, or paste an AbleTime token instead.`
17694
+ ) : e instanceof Error ? e : new Error(String(e))
17695
+ );
17696
+ });
17697
+ server.listen(ABLETIME_OAUTH_PORT, "127.0.0.1", () => {
17698
+ void Promise.resolve(opts?.openUrl?.(authorizeUrl)).catch((e) => {
17699
+ finish(e instanceof Error ? e : new Error(String(e)));
17700
+ });
17701
+ });
17702
+ });
17703
+ const body = new URLSearchParams({
17704
+ grant_type: "authorization_code",
17705
+ code,
17706
+ redirect_uri: ABLETIME_OAUTH_REDIRECT,
17707
+ client_id: clientId,
17708
+ code_verifier: pkce.verifier,
17709
+ resource: ableTimeOAuthResource(host)
17710
+ });
17711
+ const data = await exchangeAbleTimeToken(body, host);
17712
+ persistAbleTimeTokens(data, { host });
17713
+ const token = data.access_token.trim();
17714
+ try {
17715
+ const { getAbleTimeOrientation: getAbleTimeOrientation2 } = await Promise.resolve().then(() => (init_abletime(), abletime_exports));
17716
+ const orientation = await getAbleTimeOrientation2({ token, host });
17717
+ return saveAbleTimeConnection({
17718
+ accessToken: token,
17719
+ refreshToken: data.refresh_token?.trim(),
17720
+ expiresIn: data.expires_in,
17721
+ host,
17722
+ viewerName: orientation.viewer.name
17723
+ });
17724
+ } catch {
17725
+ return loadAppSettings();
17726
+ }
17727
+ }
17728
+ var import_node_crypto10, import_node_http, ABLETIME_OAUTH_PORT, ABLETIME_OAUTH_REDIRECT, BAKED_ABLETIME_OAUTH_CLIENT_ID, ABLETIME_OAUTH_RESOURCE, REFRESH_SKEW_MS2, refreshInFlight, ABLETIME_OAUTH_CANCELLED, AbleTimeOAuthCancelledError;
17729
+ var init_abletime_oauth = __esm({
17730
+ "src/integrations/abletime-oauth.ts"() {
17731
+ "use strict";
17732
+ import_node_crypto10 = require("crypto");
17733
+ import_node_http = require("http");
17734
+ init_fetch();
17735
+ init_app_settings();
17736
+ init_abletime_mcp();
17737
+ ABLETIME_OAUTH_PORT = 19849;
17738
+ ABLETIME_OAUTH_REDIRECT = `http://127.0.0.1:${ABLETIME_OAUTH_PORT}/callback`;
17739
+ BAKED_ABLETIME_OAUTH_CLIENT_ID = "https://www.sideboard.cloud/oauth/abletime-client.json";
17740
+ ABLETIME_OAUTH_RESOURCE = `${DEFAULT_ABLETIME_HOST}/api/public/v2/mcp`;
17741
+ REFRESH_SKEW_MS2 = 5 * 6e4;
17742
+ refreshInFlight = null;
17743
+ ABLETIME_OAUTH_CANCELLED = "AbleTime sign-in cancelled";
17744
+ AbleTimeOAuthCancelledError = class extends Error {
17745
+ constructor(message = ABLETIME_OAUTH_CANCELLED) {
17746
+ super(message);
17747
+ this.name = "AbleTimeOAuthCancelledError";
17748
+ }
17749
+ };
17750
+ }
17751
+ });
17752
+
17753
+ // src/integrations/abletime-rest.ts
17754
+ var abletime_rest_exports = {};
17755
+ __export(abletime_rest_exports, {
17756
+ ABLETIME_REST_PREFIX: () => ABLETIME_REST_PREFIX,
17757
+ ableTimeDocFromText: () => ableTimeDocFromText,
17758
+ abletimeRestUrl: () => abletimeRestUrl,
17759
+ callAbleTimeRestTool: () => callAbleTimeRestTool,
17760
+ looksLikeAbleTimeUlid: () => looksLikeAbleTimeUlid,
17761
+ textFromAbleTimeDoc: () => textFromAbleTimeDoc
17762
+ });
17763
+ function asRecord5(value) {
17764
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17765
+ }
17766
+ function asString(value) {
17767
+ return typeof value === "string" ? value.trim() : "";
17768
+ }
17769
+ function asList(value) {
17770
+ if (Array.isArray(value)) return value;
17771
+ const record = asRecord5(value);
17772
+ if (!record) return [];
17773
+ for (const key of ["data", "tasks", "projects", "users", "items", "results"]) {
17774
+ if (Array.isArray(record[key])) return record[key];
17775
+ }
17776
+ return [];
17777
+ }
17778
+ function ableTimeDocFromText(text7) {
17779
+ const trimmed = text7.trim();
17780
+ if (!trimmed) return { type: "doc", content: [] };
17781
+ return {
17782
+ type: "doc",
17783
+ content: [{ type: "paragraph", content: [{ type: "text", text: trimmed }] }]
17784
+ };
17785
+ }
17786
+ function textFromAbleTimeDoc(value) {
17787
+ if (typeof value === "string") return value.trim();
17788
+ const record = asRecord5(value);
17789
+ if (!record) return "";
17790
+ if (typeof record.text === "string" && record.text.trim()) return record.text.trim();
17791
+ const chunks = [];
17792
+ const walk = (node) => {
17793
+ const rec = asRecord5(node);
17794
+ if (!rec) return;
17795
+ if (typeof rec.text === "string" && rec.text) chunks.push(rec.text);
17796
+ if (Array.isArray(rec.content)) rec.content.forEach(walk);
17797
+ };
17798
+ walk(record);
17799
+ return chunks.join("").trim();
17800
+ }
17801
+ function abletimeRestUrl(path2, host, query) {
17802
+ const base = `${normalizeAbleTimeHost(host)}${ABLETIME_REST_PREFIX}`;
17803
+ const suffix = path2.startsWith("/") ? path2 : `/${path2}`;
17804
+ const url = new URL(`${base}${suffix}`);
17805
+ for (const [key, value] of Object.entries(query ?? {})) {
17806
+ if (value) url.searchParams.set(key, value);
17807
+ }
17808
+ return url.href;
17809
+ }
17810
+ function looksLikeAbleTimeUlid(id) {
17811
+ return ULID_RE.test(id.trim());
17812
+ }
17813
+ async function abletimeRestRequest(path2, opts = {}) {
17814
+ const raw = opts.token ?? getAbleTimeAccessToken();
17815
+ const token = normalizeAbleTimeCredential(raw ?? "");
17816
+ if (!token) {
17817
+ throw new Error("AbleTime is not connected \u2014 paste an API key or personal access token in Account settings");
17818
+ }
17819
+ const host = opts.host ?? (opts.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost());
17820
+ const url = abletimeRestUrl(path2, host, opts.query);
17821
+ const method = (opts.method ?? "GET").toUpperCase();
17822
+ const res = await httpFetch(url, {
17823
+ method,
17824
+ headers: {
17825
+ Accept: "application/json",
17826
+ Authorization: `Bearer ${token}`,
17827
+ ...opts.body !== void 0 ? { "Content-Type": "application/json" } : {}
17828
+ },
17829
+ ...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
17830
+ });
17831
+ const text7 = await res.text().catch(() => "");
17832
+ if (!res.ok) {
17833
+ throw new Error(
17834
+ rewriteAbleTimeError(
17835
+ `AbleTime API error ${res.status}${text7 ? `: ${text7.slice(0, 240)}` : ""}`
17836
+ )
17837
+ );
17838
+ }
17839
+ if (!text7.trim()) return null;
17840
+ try {
17841
+ return JSON.parse(text7);
17842
+ } catch {
17843
+ throw new Error(`AbleTime API returned a non-JSON response: ${text7.slice(0, 200)}`);
17844
+ }
17845
+ }
17846
+ async function restPages(path2, opts, maxPages = 20) {
17847
+ const items = [];
17848
+ let cursor;
17849
+ for (let i = 0; i < maxPages; i += 1) {
17850
+ const raw = await abletimeRestRequest(path2, {
17851
+ ...opts,
17852
+ query: { ...opts.query, ...cursor ? { cursor } : {} }
17853
+ });
17854
+ items.push(...asList(raw));
17855
+ const page = asRecord5(asRecord5(raw)?.page);
17856
+ const next = page ? asString(page.nextCursor) : "";
17857
+ if (!next) break;
17858
+ cursor = next;
17859
+ }
17860
+ return items;
17861
+ }
17862
+ function userDisplayName(raw) {
17863
+ const rec = asRecord5(raw);
17864
+ if (!rec) return null;
17865
+ const id = asString(rec.userId) || asString(rec.id);
17866
+ const name = [asString(rec.firstName), asString(rec.lastName)].filter(Boolean).join(" ") || asString(rec.username) || asString(rec.email) || asString(rec.name);
17867
+ if (!name) return null;
17868
+ return { id: id || void 0, name };
17869
+ }
17870
+ function restTaskToRaw(raw, users) {
17871
+ const rec = asRecord5(raw);
17872
+ if (!rec) return null;
17873
+ const id = asString(rec.timeflowTaskId) || asString(rec.taskId) || asString(rec.id);
17874
+ const identifier = asString(rec.taskRef) || asString(rec.reference) || id;
17875
+ const title = asString(rec.title);
17876
+ if (!id && !identifier && !title) return null;
17877
+ const assigned = asString(rec.assignedUserId);
17878
+ const assignee = assigned ? users.get(assigned) : void 0;
17879
+ const comments = asList(rec.comments).map((item) => {
17880
+ const comment = asRecord5(item) ?? {};
17881
+ const user = userDisplayName(comment);
17882
+ return {
17883
+ id: asString(comment.commentId) || asString(comment.id),
17884
+ body: textFromAbleTimeDoc(comment.content) || asString(comment.contentSanitized) || asString(comment.body),
17885
+ createdAt: asString(comment.createdAt) || asString(comment.lastUpdate),
17886
+ user: user?.name || asString(comment.username)
17887
+ };
17888
+ });
17889
+ return {
17890
+ id,
17891
+ taskId: id,
17892
+ timeflowTaskId: id,
17893
+ reference: identifier,
17894
+ taskRef: identifier,
17895
+ title: title || identifier,
17896
+ state: asString(rec.taskState) || asString(rec.state),
17897
+ description: textFromAbleTimeDoc(rec.description) || asString(rec.descriptionSanitized),
17898
+ projectId: asString(rec.projectId),
17899
+ categoryId: asString(rec.projectCategoryId) || asString(rec.categoryId),
17900
+ tags: rec.tags,
17901
+ comments,
17902
+ createdAt: asString(rec.dateCreated) || asString(rec.createdAt),
17903
+ updatedAt: asString(rec.lastUpdate) || asString(rec.updatedAt),
17904
+ ...assignee ? { assignee } : {}
17905
+ };
17906
+ }
17907
+ function restProjectToRaw(raw) {
17908
+ const rec = asRecord5(raw);
17909
+ if (!rec) return null;
17910
+ const id = asString(rec.projectId) || asString(rec.id);
17911
+ const name = asString(rec.projectName) || asString(rec.name);
17912
+ if (!id && !name) return null;
17913
+ const categories = asList(rec.categories).map((item) => {
17914
+ const cat = asRecord5(item) ?? {};
17915
+ return {
17916
+ id: asString(cat.projectCategoryId) || asString(cat.categoryId) || asString(cat.id),
17917
+ name: asString(cat.categoryName) || asString(cat.name)
17918
+ };
17919
+ });
17920
+ return { id, name, projectId: id, projectName: name, categories };
17921
+ }
17922
+ async function listUsers(opts) {
17923
+ const users = /* @__PURE__ */ new Map();
17924
+ for (const item of await restPages("/users", opts).catch(() => [])) {
17925
+ const user = userDisplayName(item);
17926
+ const rec = asRecord5(item);
17927
+ const id = user?.id || (rec ? asString(rec.userId) : "");
17928
+ if (user && id) users.set(id, user);
17929
+ }
17930
+ return users;
17931
+ }
17932
+ async function listProjectsRaw(opts) {
17933
+ const items = await restPages("/projects", { ...opts, query: { expand: "categories" } });
17934
+ return items.map(restProjectToRaw).filter((item) => Boolean(item));
17935
+ }
17936
+ async function listTasksRaw(opts) {
17937
+ const [users, items] = await Promise.all([
17938
+ listUsers(opts),
17939
+ restPages("/tasks", {
17940
+ ...opts,
17941
+ query: {
17942
+ ...opts.projectId ? { projectId: opts.projectId } : {}
17943
+ }
17944
+ })
17945
+ ]);
17946
+ let tasks = items.map((item) => restTaskToRaw(item, users)).filter((item) => Boolean(item));
17947
+ const needle = opts.query?.trim().toLowerCase();
17948
+ if (needle) {
17949
+ tasks = tasks.filter((task) => {
17950
+ const hay = [task.title, task.reference, task.taskRef, task.id].map((value) => String(value ?? "").toLowerCase()).join(" ");
17951
+ const tags = Array.isArray(task.tags) ? task.tags.join(" ").toLowerCase() : "";
17952
+ return hay.includes(needle) || tags.includes(needle);
17953
+ });
17954
+ }
17955
+ return tasks;
17956
+ }
17957
+ async function resolveTaskId(id, opts) {
17958
+ const wanted = id.trim();
17959
+ if (!wanted) throw new Error("AbleTime task id is required");
17960
+ if (looksLikeAbleTimeUlid(wanted)) return wanted;
17961
+ const tasks = await listTasksRaw(opts);
17962
+ const match = tasks.find((task) => {
17963
+ const ref = String(task.reference ?? task.taskRef ?? "").toLowerCase();
17964
+ const taskId = String(task.id ?? "").toLowerCase();
17965
+ return ref === wanted.toLowerCase() || taskId === wanted.toLowerCase();
17966
+ });
17967
+ if (!match?.id) throw new Error(`AbleTime task not found: ${id}`);
17968
+ return String(match.id);
17969
+ }
17970
+ async function getTaskRaw(id, opts) {
17971
+ const taskId = await resolveTaskId(id, opts);
17972
+ const [users, raw] = await Promise.all([
17973
+ listUsers(opts),
17974
+ abletimeRestRequest(`/tasks/${encodeURIComponent(taskId)}`, {
17975
+ ...opts,
17976
+ query: { expand: "comments" }
17977
+ })
17978
+ ]);
17979
+ const task = restTaskToRaw(raw, users);
17980
+ if (!task) throw new Error(`AbleTime task not found: ${id}`);
17981
+ return task;
17982
+ }
17983
+ function firstStringArg(args, keys) {
17984
+ for (const key of keys) {
17985
+ const value = asString(args[key]);
17986
+ if (value) return value;
17987
+ }
17988
+ return "";
17989
+ }
17990
+ async function callAbleTimeRestTool(name, args = {}, opts) {
17991
+ const token = normalizeAbleTimeCredential(opts?.token ?? getAbleTimeAccessToken() ?? "");
17992
+ if (ableTimeCredentialKind(token) !== "org") {
17993
+ throw new Error("AbleTime REST transport is for organization API keys (atk_\u2026)");
17994
+ }
17995
+ const requestOpts = { ...opts, token };
17996
+ switch (name) {
17997
+ case "orientation": {
17998
+ const [users, projects, tasks] = await Promise.all([
17999
+ listUsers(requestOpts),
18000
+ listProjectsRaw(requestOpts),
18001
+ listTasksRaw(requestOpts)
18002
+ ]);
18003
+ const viewer = [...users.values()].find((user) => user.name) ?? [...users.values()][0] ?? { name: "AbleTime" };
18004
+ return { viewer, projects, tasks };
18005
+ }
18006
+ case "list_projects":
18007
+ return { data: await listProjectsRaw(requestOpts) };
18008
+ case "list_users":
18009
+ return { data: [...(await listUsers(requestOpts)).values()] };
18010
+ case "list_tasks":
18011
+ return {
18012
+ data: await listTasksRaw({
18013
+ ...requestOpts,
18014
+ projectId: firstStringArg(args, ["project_id", "projectId", "project"])
18015
+ })
18016
+ };
18017
+ case "search_tasks":
18018
+ return {
18019
+ data: await listTasksRaw({
18020
+ ...requestOpts,
18021
+ query: firstStringArg(args, ["query", "q"])
18022
+ })
18023
+ };
18024
+ case "get_task":
18025
+ return await getTaskRaw(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
18026
+ case "create_task": {
18027
+ const title = firstStringArg(args, ["title"]);
18028
+ if (!title) throw new Error("AbleTime task title is required");
18029
+ const projectId = firstStringArg(args, ["project_id", "projectId", "project"]);
18030
+ if (!projectId) throw new Error("AbleTime project is required");
18031
+ const projects = await listProjectsRaw(requestOpts);
18032
+ const project = projects.find(
18033
+ (item) => String(item.id).toLowerCase() === projectId.toLowerCase() || String(item.name).toLowerCase() === projectId.toLowerCase()
18034
+ ) ?? projects[0];
18035
+ if (!project) throw new Error("AbleTime has no projects to create a task in");
18036
+ const categories = asList(project.categories);
18037
+ const wantedCategory = firstStringArg(args, ["category_id", "categoryId", "category"]);
18038
+ const category = categories.map((item) => asRecord5(item)).find((item) => {
18039
+ if (!item || !wantedCategory) return false;
18040
+ return asString(item.id).toLowerCase() === wantedCategory.toLowerCase() || asString(item.name).toLowerCase() === wantedCategory.toLowerCase();
18041
+ }) ?? asRecord5(categories[0]);
18042
+ const created = await abletimeRestRequest("/tasks", {
18043
+ ...requestOpts,
18044
+ method: "POST",
18045
+ body: {
18046
+ projectId: project.id,
18047
+ projectCategoryId: category ? asString(category.id) : void 0,
18048
+ title
18049
+ }
18050
+ });
18051
+ const state = firstStringArg(args, ["state"]);
18052
+ const createdId = asString(asRecord5(created)?.timeflowTaskId);
18053
+ const description = asString(args.description);
18054
+ if (createdId && description) {
18055
+ await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}`, {
18056
+ ...requestOpts,
18057
+ method: "PATCH",
18058
+ body: { description: ableTimeDocFromText(description) }
18059
+ }).catch(() => void 0);
18060
+ }
18061
+ if (createdId && state && state !== "todo") {
18062
+ await abletimeRestRequest(`/tasks/${encodeURIComponent(createdId)}/state`, {
18063
+ ...requestOpts,
18064
+ method: "POST",
18065
+ body: { taskState: state }
18066
+ }).catch(() => void 0);
18067
+ }
18068
+ return createdId ? await getTaskRaw(createdId, requestOpts) : restTaskToRaw(created, await listUsers(requestOpts));
18069
+ }
18070
+ case "update_task": {
18071
+ const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
18072
+ const patch = {};
18073
+ const title = firstStringArg(args, ["title"]);
18074
+ if (title) patch.title = title;
18075
+ if (args.description !== void 0) {
18076
+ patch.description = args.description == null || args.description === "" ? null : ableTimeDocFromText(String(args.description));
18077
+ }
18078
+ if (args.tags !== void 0 || args.labels !== void 0) {
18079
+ patch.tags = args.tags ?? args.labels ?? args.tag_names;
18080
+ }
18081
+ if (Object.keys(patch).length > 0) {
18082
+ await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}`, {
18083
+ ...requestOpts,
18084
+ method: "PATCH",
18085
+ body: patch
18086
+ });
18087
+ }
18088
+ return await getTaskRaw(id, requestOpts);
18089
+ }
18090
+ case "set_task_state": {
18091
+ const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
18092
+ const state = firstStringArg(args, ["state", "taskState"]);
18093
+ if (!state) throw new Error("AbleTime task state is required");
18094
+ await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/state`, {
18095
+ ...requestOpts,
18096
+ method: "POST",
18097
+ body: { taskState: state }
18098
+ });
18099
+ return await getTaskRaw(id, requestOpts);
18100
+ }
18101
+ case "create_comment": {
18102
+ const id = await resolveTaskId(firstStringArg(args, ["id", "task_id", "task"]), requestOpts);
18103
+ const body = firstStringArg(args, ["body", "comment", "text"]);
18104
+ if (!body) throw new Error("AbleTime comment body is required");
18105
+ const created = await abletimeRestRequest(`/tasks/${encodeURIComponent(id)}/comments`, {
18106
+ ...requestOpts,
18107
+ method: "POST",
18108
+ body: { content: ableTimeDocFromText(body) }
18109
+ });
18110
+ const rec = asRecord5(created) ?? {};
18111
+ return {
18112
+ id: asString(rec.commentId) || void 0,
18113
+ body: textFromAbleTimeDoc(rec.content) || body
18114
+ };
18115
+ }
18116
+ case "set_task_dependency":
18117
+ throw new Error(
18118
+ "AbleTime organization API keys cannot set task dependencies on REST yet \u2014 use a personal access token (apt_\u2026) for MCP project-management tools."
18119
+ );
18120
+ default:
18121
+ throw new Error(`AbleTime organization API keys do not support ${name} on REST`);
18122
+ }
18123
+ }
18124
+ var ABLETIME_REST_PREFIX, ULID_RE;
18125
+ var init_abletime_rest = __esm({
18126
+ "src/integrations/abletime-rest.ts"() {
18127
+ "use strict";
18128
+ init_fetch();
18129
+ init_app_settings();
18130
+ init_abletime_mcp();
18131
+ ABLETIME_REST_PREFIX = "/api/public/v2";
18132
+ ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
18133
+ }
18134
+ });
18135
+
17462
18136
  // src/integrations/abletime-mcp.ts
17463
18137
  function abletimeToolUsesPm(name) {
17464
18138
  return ABLETIME_PM_TOOL_NAMES.includes(name);
@@ -17472,22 +18146,49 @@ function abletimeMcpUrl(host, opts) {
17472
18146
  const path2 = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
17473
18147
  return `${normalizeAbleTimeHost(host)}${path2}`;
17474
18148
  }
18149
+ function normalizeAbleTimeCredential(raw) {
18150
+ return raw.trim().replace(/^bearer\s+/i, "").trim();
18151
+ }
18152
+ function ableTimeCredentialKind(raw) {
18153
+ const token = normalizeAbleTimeCredential(raw).toLowerCase();
18154
+ if (token.startsWith(ABLETIME_PAT_PREFIX)) return "pat";
18155
+ if (token.startsWith(ABLETIME_ORG_KEY_PREFIX)) return "org";
18156
+ return "unknown";
18157
+ }
18158
+ function assertAbleTimeMcpCredential(raw) {
18159
+ const token = normalizeAbleTimeCredential(raw);
18160
+ if (!token) {
18161
+ throw new Error("AbleTime personal access token is required");
18162
+ }
18163
+ if (ableTimeCredentialKind(token) === "org") {
18164
+ throw new Error(ORG_KEY_HINT);
18165
+ }
18166
+ return token;
18167
+ }
18168
+ function assertAbleTimeCredential(raw) {
18169
+ const token = normalizeAbleTimeCredential(raw);
18170
+ if (!token) {
18171
+ throw new Error("AbleTime API key or personal access token is required");
18172
+ }
18173
+ return token;
18174
+ }
17475
18175
  function rewriteAbleTimeError(message) {
17476
- if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
18176
+ if (REWRITE_APPLIED.test(message)) return message;
18177
+ if (/INTEGRATION_AGENT_ACCESS_DISABLED/i.test(message)) {
17477
18178
  return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
17478
18179
  }
17479
- if (/INTEGRATION_PAT_REQUIRED|organization api key/i.test(message)) {
17480
- return `${message} \u2014 AbleTime MCP needs a personal access token (apt_\u2026), not an organization API key.`;
18180
+ if (/INTEGRATION_PAT_REQUIRED/i.test(message)) {
18181
+ return `${message} \u2014 ${ORG_KEY_HINT}`;
17481
18182
  }
17482
18183
  if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
17483
18184
  return `${message} \u2014 AbleTime plan does not include API access.`;
17484
18185
  }
17485
18186
  if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
17486
- return `${message} \u2014 Reconnect AbleTime from Account settings (Profile \u2192 API Access).`;
18187
+ return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
17487
18188
  }
17488
18189
  return message;
17489
18190
  }
17490
- function asRecord5(value) {
18191
+ function asRecord6(value) {
17491
18192
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17492
18193
  }
17493
18194
  function parseSseJson(body) {
@@ -17509,12 +18210,12 @@ function parseSseJson(body) {
17509
18210
  }
17510
18211
  }
17511
18212
  function unwrapToolResult(result) {
17512
- const record = asRecord5(result);
18213
+ const record = asRecord6(result);
17513
18214
  if (!record) return result;
17514
18215
  const content = record.content;
17515
18216
  if (Array.isArray(content)) {
17516
18217
  const texts = content.map((part) => {
17517
- const item = asRecord5(part);
18218
+ const item = asRecord6(part);
17518
18219
  if (!item) return "";
17519
18220
  return typeof item.text === "string" ? item.text : "";
17520
18221
  }).filter(Boolean);
@@ -17536,17 +18237,18 @@ function unwrapToolResult(result) {
17536
18237
  return result;
17537
18238
  }
17538
18239
  function textsFromUnknown(value) {
17539
- const record = asRecord5(value);
18240
+ const record = asRecord6(value);
17540
18241
  if (!record) return "";
17541
18242
  if (typeof record.message === "string") return record.message;
17542
18243
  if (typeof record.error === "string") return record.error;
17543
18244
  return "";
17544
18245
  }
17545
18246
  async function abletimeMcpRequest(method, params, opts) {
17546
- const token = (opts?.token ?? getAbleTimeAccessToken())?.trim();
17547
- if (!token) {
18247
+ const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
18248
+ if (!rawToken?.trim()) {
17548
18249
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
17549
18250
  }
18251
+ const token = assertAbleTimeMcpCredential(rawToken);
17550
18252
  const url = abletimeMcpUrl(
17551
18253
  opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
17552
18254
  { pm: opts?.pm }
@@ -17582,7 +18284,7 @@ async function abletimeMcpRequest(method, params, opts) {
17582
18284
  return parseSseJson(body);
17583
18285
  }
17584
18286
  })();
17585
- const rpc = asRecord5(parsed);
18287
+ const rpc = asRecord6(parsed);
17586
18288
  if (rpc?.error) {
17587
18289
  const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
17588
18290
  throw new Error(rewriteAbleTimeError(detail));
@@ -17613,6 +18315,11 @@ async function callAbleTimeTool(name, args = {}, opts) {
17613
18315
  if (!opts?.token && !isAbleTimeConnected()) {
17614
18316
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
17615
18317
  }
18318
+ const rawToken = opts?.token ?? await Promise.resolve().then(() => (init_abletime_oauth(), abletime_oauth_exports)).then((m) => m.ensureAbleTimeAccessToken());
18319
+ if (rawToken && ableTimeCredentialKind(rawToken) === "org") {
18320
+ const { callAbleTimeRestTool: callAbleTimeRestTool2 } = await Promise.resolve().then(() => (init_abletime_rest(), abletime_rest_exports));
18321
+ return callAbleTimeRestTool2(name, args, { ...opts, token: rawToken });
18322
+ }
17616
18323
  const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
17617
18324
  const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
17618
18325
  return unwrapToolResult(result);
@@ -17628,7 +18335,7 @@ async function callAbleTimeTool(name, args = {}, opts) {
17628
18335
  throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
17629
18336
  }
17630
18337
  }
17631
- var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
18338
+ var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES, ABLETIME_PAT_PREFIX, ABLETIME_ORG_KEY_PREFIX, ORG_KEY_HINT, REJECTED_CREDENTIAL_HINT, REWRITE_APPLIED;
17632
18339
  var init_abletime_mcp = __esm({
17633
18340
  "src/integrations/abletime-mcp.ts"() {
17634
18341
  "use strict";
@@ -17650,6 +18357,11 @@ var init_abletime_mcp = __esm({
17650
18357
  "archive_task",
17651
18358
  "delete_task"
17652
18359
  ];
18360
+ ABLETIME_PAT_PREFIX = "apt_";
18361
+ ABLETIME_ORG_KEY_PREFIX = "atk_";
18362
+ ORG_KEY_HINT = "AbleTime MCP needs a personal access token (apt_\u2026) from Edit Profile \u2192 API Access, not an organization API key (atk_\u2026) from Settings \u2192 Integrations \u2192 API Keys.";
18363
+ REJECTED_CREDENTIAL_HINT = "AbleTime rejected this credential (unknown, revoked, or expired). Use a personal access token (apt_\u2026) from Edit Profile \u2192 API Access \u2014 organization API keys (atk_\u2026) cannot call MCP. Tokens are shown once; rotating replaces the previous one.";
18364
+ REWRITE_APPLIED = /— Enable Agent access \(MCP\)|— AbleTime MCP needs a personal access token|— AbleTime plan does not include API access|— AbleTime rejected this credential/;
17653
18365
  }
17654
18366
  });
17655
18367
 
@@ -17673,16 +18385,16 @@ __export(abletime_exports, {
17673
18385
  updateAbleTimeTask: () => updateAbleTimeTask,
17674
18386
  verifyAbleTimeConnection: () => verifyAbleTimeConnection
17675
18387
  });
17676
- function asRecord6(value) {
18388
+ function asRecord7(value) {
17677
18389
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17678
18390
  }
17679
- function asString(value) {
18391
+ function asString2(value) {
17680
18392
  return typeof value === "string" ? value.trim() : "";
17681
18393
  }
17682
18394
  function firstString(record, keys) {
17683
18395
  if (!record) return "";
17684
18396
  for (const key of keys) {
17685
- const value = asString(record[key]);
18397
+ const value = asString2(record[key]);
17686
18398
  if (value) return value;
17687
18399
  }
17688
18400
  return "";
@@ -17690,14 +18402,14 @@ function firstString(record, keys) {
17690
18402
  function firstRecord(record, keys) {
17691
18403
  if (!record) return null;
17692
18404
  for (const key of keys) {
17693
- const value = asRecord6(record[key]);
18405
+ const value = asRecord7(record[key]);
17694
18406
  if (value) return value;
17695
18407
  }
17696
18408
  return null;
17697
18409
  }
17698
- function asList(value) {
18410
+ function asList2(value) {
17699
18411
  if (Array.isArray(value)) return value;
17700
- const record = asRecord6(value);
18412
+ const record = asRecord7(value);
17701
18413
  if (!record) return [];
17702
18414
  for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
17703
18415
  if (Array.isArray(record[key])) return record[key];
@@ -17709,21 +18421,21 @@ function labelsOf(record) {
17709
18421
  if (!Array.isArray(raw)) return [];
17710
18422
  return raw.map((item) => {
17711
18423
  if (typeof item === "string") return item.trim();
17712
- const rec = asRecord6(item);
18424
+ const rec = asRecord7(item);
17713
18425
  return rec ? firstString(rec, ["name", "title", "label"]) : "";
17714
18426
  }).filter(Boolean);
17715
18427
  }
17716
18428
  function commentsOf(record) {
17717
18429
  const raw = record.comments ?? record.notes ?? record.discussion;
17718
18430
  const out = [];
17719
- for (const item of asList(raw)) {
17720
- const rec = asRecord6(item);
18431
+ for (const item of asList2(raw)) {
18432
+ const rec = asRecord7(item);
17721
18433
  if (!rec) continue;
17722
- const body = firstString(rec, ["body", "text", "comment", "content", "message"]);
18434
+ const body = firstString(rec, ["body", "text", "comment", "message", "contentSanitized"]) || textFromAbleTimeDoc(rec.content);
17723
18435
  if (!body) continue;
17724
- const user = firstString(asRecord6(rec.user) ?? asRecord6(rec.author), ["name", "display_name"]) || firstString(rec, ["user_name", "author"]);
18436
+ const user = firstString(asRecord7(rec.user) ?? asRecord7(rec.author), ["name", "display_name", "username"]) || firstString(rec, ["user_name", "author", "username"]);
17725
18437
  const comment = { body };
17726
- const id = firstString(rec, ["id", "comment_id"]);
18438
+ const id = firstString(rec, ["id", "comment_id", "commentId"]);
17727
18439
  if (id) comment.id = id;
17728
18440
  const url = firstString(rec, ["url", "permalink"]);
17729
18441
  if (url) comment.url = url;
@@ -17747,11 +18459,11 @@ function taskUrl(task, host) {
17747
18459
  return `${base}/tasks/${slug}`;
17748
18460
  }
17749
18461
  function mapAbleTimeTask(raw, host) {
17750
- const record = asRecord6(raw);
18462
+ const record = asRecord7(raw);
17751
18463
  if (!record) return null;
17752
18464
  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;
18465
+ const id = firstString(nested, ["id", "task_id", "taskId", "timeflowTaskId"]);
18466
+ const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number", "taskRef"]) || id;
17755
18467
  const title = firstString(nested, ["title", "name"]);
17756
18468
  if (!id && !identifier && !title) return null;
17757
18469
  const resolvedId = id || identifier;
@@ -17762,21 +18474,22 @@ function mapAbleTimeTask(raw, host) {
17762
18474
  identifier: resolvedIdentifier,
17763
18475
  title: title || resolvedIdentifier,
17764
18476
  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(asRecord6(nested.project), ["id"]) || void 0,
17768
- categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(asRecord6(nested.category), ["id"]) || void 0,
18477
+ description: firstString(nested, ["description", "body", "details", "descriptionSanitized"]) || textFromAbleTimeDoc(nested.description) || void 0,
18478
+ state: firstString(nested, ["state", "board_state", "boardState", "status", "taskState"]) || void 0,
18479
+ projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord7(nested.project), ["id"]) || void 0,
18480
+ categoryId: firstString(nested, ["category_id", "categoryId", "projectCategoryId"]) || firstString(asRecord7(nested.category), ["id"]) || void 0,
17769
18481
  assignee: assigneeOf(nested),
17770
18482
  labels: labelsOf(nested),
17771
18483
  comments: commentsOf(nested),
17772
- createdAt: firstString(nested, ["created_at", "createdAt", "created"]) || void 0,
18484
+ createdAt: firstString(nested, ["created_at", "createdAt", "created", "dateCreated"]) || void 0,
17773
18485
  updatedAt: firstString(nested, [
17774
18486
  "updated_at",
17775
18487
  "updatedAt",
17776
18488
  "modified_at",
17777
18489
  "modifiedAt",
17778
18490
  "last_activity_at",
17779
- "lastActivityAt"
18491
+ "lastActivityAt",
18492
+ "lastUpdate"
17780
18493
  ]) || void 0
17781
18494
  };
17782
18495
  }
@@ -17807,16 +18520,16 @@ function issueAttachmentForAbleTimeTask(task) {
17807
18520
  };
17808
18521
  }
17809
18522
  function mapProject(raw) {
17810
- const record = asRecord6(raw);
18523
+ const record = asRecord7(raw);
17811
18524
  if (!record) return null;
17812
- const id = firstString(record, ["id", "project_id"]);
17813
- const name = firstString(record, ["name", "title"]);
18525
+ const id = firstString(record, ["id", "project_id", "projectId"]);
18526
+ const name = firstString(record, ["name", "title", "projectName"]);
17814
18527
  if (!id && !name) return null;
17815
- const categories = asList(record.categories ?? record.category).map((item) => {
17816
- const rec = asRecord6(item);
18528
+ const categories = asList2(record.categories ?? record.category).map((item) => {
18529
+ const rec = asRecord7(item);
17817
18530
  if (!rec) return null;
17818
- const categoryId = firstString(rec, ["id", "category_id"]);
17819
- const categoryName = firstString(rec, ["name", "title"]);
18531
+ const categoryId = firstString(rec, ["id", "category_id", "projectCategoryId", "categoryId"]);
18532
+ const categoryName = firstString(rec, ["name", "title", "categoryName"]);
17820
18533
  if (!categoryId && !categoryName) return null;
17821
18534
  return { id: categoryId || categoryName, name: categoryName || categoryId };
17822
18535
  }).filter((item) => Boolean(item));
@@ -17828,21 +18541,21 @@ function isOpenTask(task) {
17828
18541
  }
17829
18542
  async function getAbleTimeOrientation(opts) {
17830
18543
  const raw = await callAbleTimeTool("orientation", {}, opts);
17831
- const record = asRecord6(raw);
18544
+ const record = asRecord7(raw);
17832
18545
  const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
17833
18546
  const viewer = {
17834
18547
  id: firstString(viewerRec, ["id", "user_id"]) || void 0,
17835
18548
  name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
17836
18549
  };
17837
- const projects = asList(
18550
+ const projects = asList2(
17838
18551
  record?.projects ?? record?.current_projects ?? record?.currentProjects
17839
18552
  ).map(mapProject).filter((item) => Boolean(item));
17840
- const tasks = asList(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
18553
+ const tasks = asList2(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
17841
18554
  return { viewer, projects, tasks, raw };
17842
18555
  }
17843
18556
  async function listAbleTimeProjects(opts) {
17844
18557
  const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
17845
- return asList(raw).map(mapProject).filter((item) => Boolean(item));
18558
+ return asList2(raw).map(mapProject).filter((item) => Boolean(item));
17846
18559
  }
17847
18560
  async function listAbleTimeTasks(opts) {
17848
18561
  const args = {};
@@ -17852,14 +18565,14 @@ async function listAbleTimeTasks(opts) {
17852
18565
  }
17853
18566
  if (opts?.query) args.query = opts.query;
17854
18567
  const raw = await callAbleTimeTool("list_tasks", args, opts);
17855
- const tasks = asList(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
18568
+ const tasks = asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
17856
18569
  return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
17857
18570
  }
17858
18571
  async function searchAbleTimeTasks(query, opts) {
17859
18572
  const q = query.trim();
17860
18573
  if (!q) return [];
17861
18574
  const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
17862
- return asList(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
18575
+ return asList2(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
17863
18576
  }
17864
18577
  async function getAbleTimeTask(id, opts) {
17865
18578
  const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
@@ -17907,7 +18620,7 @@ async function commentAbleTimeTask(input, opts) {
17907
18620
  { id, task_id: id, task: id, body, comment: body, text: body },
17908
18621
  opts
17909
18622
  );
17910
- const rec = asRecord6(raw);
18623
+ const rec = asRecord7(raw);
17911
18624
  return {
17912
18625
  id: rec ? firstString(rec, ["id", "comment_id"]) || void 0 : void 0,
17913
18626
  body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
@@ -18097,8 +18810,7 @@ async function listAbleTimeAssignedIssues(opts) {
18097
18810
  };
18098
18811
  }
18099
18812
  async function verifyAbleTimeConnection(input) {
18100
- const token = input.token.trim();
18101
- if (!token) throw new Error("AbleTime personal access token is required");
18813
+ const token = assertAbleTimeCredential(input.token);
18102
18814
  const host = input.host?.trim() || void 0;
18103
18815
  const orientation = await getAbleTimeOrientation({ token, host });
18104
18816
  saveAbleTimeConnection({
@@ -18114,6 +18826,7 @@ var init_abletime = __esm({
18114
18826
  "use strict";
18115
18827
  init_app_settings();
18116
18828
  init_abletime_mcp();
18829
+ init_abletime_rest();
18117
18830
  CLOSED_STATES = /* @__PURE__ */ new Set([
18118
18831
  "done",
18119
18832
  "completed",
@@ -18697,12 +19410,19 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
18697
19410
  ...instructions
18698
19411
  ].join("\n");
18699
19412
  return {
18700
- id: (0, import_node_crypto10.randomUUID)(),
19413
+ id: (0, import_node_crypto11.randomUUID)(),
18701
19414
  name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
18702
19415
  kind: "transcript",
18703
19416
  content: body
18704
19417
  };
18705
19418
  }
19419
+ async function isolateQuotaFailover(run2) {
19420
+ try {
19421
+ return await run2();
19422
+ } catch {
19423
+ return true;
19424
+ }
19425
+ }
18706
19426
  function createQuotaFailoverChat(from, fallbackAgent, limitText) {
18707
19427
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
18708
19428
  const tab = createChatTab({
@@ -18721,11 +19441,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
18721
19441
  }
18722
19442
  return updateThread(tab.id, patch);
18723
19443
  }
18724
- var import_node_crypto10, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
19444
+ var import_node_crypto11, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
18725
19445
  var init_quota_failover = __esm({
18726
19446
  "src/orchestrator/quota-failover.ts"() {
18727
19447
  "use strict";
18728
- import_node_crypto10 = require("crypto");
19448
+ import_node_crypto11 = require("crypto");
18729
19449
  init_session_quota();
18730
19450
  init_app_settings();
18731
19451
  init_global_workspace();
@@ -20978,16 +21698,16 @@ function normalizeServiceOrigin(raw, fallback) {
20978
21698
  return fallback;
20979
21699
  }
20980
21700
  }
20981
- function asRecord7(value) {
21701
+ function asRecord8(value) {
20982
21702
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
20983
21703
  }
20984
- function asString2(value) {
21704
+ function asString3(value) {
20985
21705
  return typeof value === "string" ? value.trim() : "";
20986
21706
  }
20987
21707
  function firstString2(record, keys) {
20988
21708
  if (!record) return "";
20989
21709
  for (const key of keys) {
20990
- const value = asString2(record[key]);
21710
+ const value = asString3(record[key]);
20991
21711
  if (value) return value;
20992
21712
  }
20993
21713
  return "";
@@ -21011,7 +21731,7 @@ async function authorizedGet(url, token) {
21011
21731
  });
21012
21732
  const body = await readJson(res);
21013
21733
  if (!res.ok) {
21014
- const record = asRecord7(body);
21734
+ const record = asRecord8(body);
21015
21735
  const message = firstString2(record, ["message", "error", "error_description", "detail"]) || (typeof body === "string" ? body.trim().slice(0, 160) : "");
21016
21736
  throw new Error(
21017
21737
  message ? `${res.status}: ${message}` : `Request failed (${res.status})`
@@ -21025,28 +21745,28 @@ async function verifyOptionalService(input) {
21025
21745
  const spec = optionalServiceSpec(input.id);
21026
21746
  try {
21027
21747
  if (input.id === "vercel") {
21028
- const body2 = asRecord7(await authorizedGet("https://api.vercel.com/v2/user", token));
21029
- const user = asRecord7(body2?.user) ?? body2;
21748
+ const body2 = asRecord8(await authorizedGet("https://api.vercel.com/v2/user", token));
21749
+ const user = asRecord8(body2?.user) ?? body2;
21030
21750
  const name2 = firstString2(user, ["username", "name", "email"]) || "Vercel user";
21031
21751
  return { viewerName: name2 };
21032
21752
  }
21033
21753
  if (input.id === "supabase") {
21034
21754
  const body2 = await authorizedGet("https://api.supabase.com/v1/organizations", token);
21035
21755
  const list2 = Array.isArray(body2) ? body2 : [];
21036
- const first2 = asRecord7(list2[0]);
21756
+ const first2 = asRecord8(list2[0]);
21037
21757
  const name2 = firstString2(first2, ["name", "slug"]) || "Supabase";
21038
21758
  return { viewerName: list2.length > 1 ? `${name2} +${list2.length - 1}` : name2 };
21039
21759
  }
21040
21760
  if (input.id === "posthog") {
21041
21761
  const host2 = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://us.posthog.com");
21042
- const body2 = asRecord7(await authorizedGet(`${host2}/api/users/@me/`, token));
21762
+ const body2 = asRecord8(await authorizedGet(`${host2}/api/users/@me/`, token));
21043
21763
  const name2 = firstString2(body2, ["email", "distinct_id"]) || [firstString2(body2, ["first_name"]), firstString2(body2, ["last_name"])].filter(Boolean).join(" ") || "PostHog user";
21044
21764
  return { viewerName: name2, host: host2 };
21045
21765
  }
21046
21766
  const host = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://sentry.io");
21047
21767
  const body = await authorizedGet(`${host}/api/0/organizations/`, token);
21048
21768
  const list = Array.isArray(body) ? body : [];
21049
- const first = asRecord7(list[0]);
21769
+ const first = asRecord8(list[0]);
21050
21770
  const name = firstString2(first, ["slug", "name"]) || "Sentry";
21051
21771
  return { viewerName: list.length > 1 ? `${name} +${list.length - 1}` : name, host };
21052
21772
  } catch (err) {
@@ -21233,7 +21953,7 @@ var init_optional_services = __esm({
21233
21953
  });
21234
21954
 
21235
21955
  // src/plan/plan-present.ts
21236
- function asRecord8(v) {
21956
+ function asRecord9(v) {
21237
21957
  return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
21238
21958
  }
21239
21959
  function isPresentPlanToolName(name) {
@@ -21245,7 +21965,7 @@ function extractPresentedPlan(parts) {
21245
21965
  for (let i = parts.length - 1; i >= 0; i--) {
21246
21966
  const p = parts[i];
21247
21967
  if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
21248
- const input = asRecord8(p.input) ?? {};
21968
+ const input = asRecord9(p.input) ?? {};
21249
21969
  const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
21250
21970
  if (!content.trim()) continue;
21251
21971
  const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
@@ -21928,51 +22648,62 @@ var init_orchestrator = __esm({
21928
22648
  if (plan.action === "wait_reset" && plan.resumeAt) {
21929
22649
  this.haltDrain.add(threadId);
21930
22650
  this.scheduleQuotaResume(threadId, plan.resumeAt);
21931
- setStatus(threadId, "idle", null);
21932
- appendMessage(threadId, {
21933
- role: "agent",
21934
- text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
21935
- ts: (/* @__PURE__ */ new Date()).toISOString()
21936
- });
21937
- this.emit({
21938
- type: "quota_failover",
21939
- threadId,
21940
- action: "wait_reset",
21941
- message: plan.reason,
21942
- resumeAt: plan.resumeAt.toISOString()
21943
- });
21944
- this.emit({ type: "status_changed", threadId, status: "idle" });
22651
+ try {
22652
+ setStatus(threadId, "idle", null);
22653
+ appendMessage(threadId, {
22654
+ role: "agent",
22655
+ text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
22656
+ ts: (/* @__PURE__ */ new Date()).toISOString()
22657
+ });
22658
+ this.emit({
22659
+ type: "quota_failover",
22660
+ threadId,
22661
+ action: "wait_reset",
22662
+ message: plan.reason,
22663
+ resumeAt: plan.resumeAt.toISOString()
22664
+ });
22665
+ this.emit({ type: "status_changed", threadId, status: "idle" });
22666
+ } catch {
22667
+ }
21945
22668
  return true;
21946
22669
  }
21947
22670
  if (plan.action === "switch_agent" && plan.fallbackAgent) {
21948
22671
  this.haltDrain.add(threadId);
21949
- const next = createQuotaFailoverChat(
21950
- thread,
21951
- plan.fallbackAgent,
21952
- plan.limitText
21953
- );
22672
+ let next;
22673
+ try {
22674
+ next = createQuotaFailoverChat(
22675
+ thread,
22676
+ plan.fallbackAgent,
22677
+ plan.limitText
22678
+ );
22679
+ } catch {
22680
+ return false;
22681
+ }
21954
22682
  this.clearQuotaResumeTimer(threadId);
21955
22683
  try {
21956
22684
  updateThread(threadId, { quotaResumeAt: null });
21957
22685
  } catch {
21958
22686
  }
21959
- appendMessage(threadId, {
21960
- role: "agent",
21961
- text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
21962
- ts: (/* @__PURE__ */ new Date()).toISOString()
21963
- });
21964
- this.emit({
21965
- type: "quota_failover",
21966
- threadId,
21967
- action: "switch_agent",
21968
- toThreadId: next.id,
21969
- message: plan.reason
21970
- });
21971
- this.emit({
21972
- type: "status_changed",
21973
- threadId: next.id,
21974
- status: next.status
21975
- });
22687
+ try {
22688
+ appendMessage(threadId, {
22689
+ role: "agent",
22690
+ text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
22691
+ ts: (/* @__PURE__ */ new Date()).toISOString()
22692
+ });
22693
+ this.emit({
22694
+ type: "quota_failover",
22695
+ threadId,
22696
+ action: "switch_agent",
22697
+ toThreadId: next.id,
22698
+ message: plan.reason
22699
+ });
22700
+ this.emit({
22701
+ type: "status_changed",
22702
+ threadId: next.id,
22703
+ status: next.status
22704
+ });
22705
+ } catch {
22706
+ }
21976
22707
  void this.send(
21977
22708
  next.id,
21978
22709
  QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
@@ -22703,9 +23434,8 @@ var init_orchestrator = __esm({
22703
23434
  this.maybeEnqueueJobContinue(threadId, chatText, parts);
22704
23435
  } else {
22705
23436
  const blob = [chatText, detail].filter(Boolean).join("\n");
22706
- const failedOver = await this.maybeHandleOrchestrationQuotaFailover(
22707
- threadId,
22708
- blob
23437
+ const failedOver = await isolateQuotaFailover(
23438
+ () => this.maybeHandleOrchestrationQuotaFailover(threadId, blob)
22709
23439
  );
22710
23440
  this.maybeEnqueueCrashContinue(threadId, {
22711
23441
  detail: detail || failDetail,
@@ -22739,9 +23469,8 @@ var init_orchestrator = __esm({
22739
23469
  this.emit({ type: "status_changed", threadId, status: "error" });
22740
23470
  }
22741
23471
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
22742
- const failedOver = await this.maybeHandleOrchestrationQuotaFailover(
22743
- threadId,
22744
- message
23472
+ const failedOver = await isolateQuotaFailover(
23473
+ () => this.maybeHandleOrchestrationQuotaFailover(threadId, message)
22745
23474
  );
22746
23475
  this.maybeEnqueueCrashContinue(threadId, {
22747
23476
  detail: message,
@@ -23953,10 +24682,18 @@ var index_exports = {};
23953
24682
  __export(index_exports, {
23954
24683
  ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
23955
24684
  ABLETIME_MCP_PM_PATH: () => ABLETIME_MCP_PM_PATH,
24685
+ ABLETIME_OAUTH_CANCELLED: () => ABLETIME_OAUTH_CANCELLED,
24686
+ ABLETIME_OAUTH_PORT: () => ABLETIME_OAUTH_PORT,
24687
+ ABLETIME_OAUTH_REDIRECT: () => ABLETIME_OAUTH_REDIRECT,
24688
+ ABLETIME_OAUTH_RESOURCE: () => ABLETIME_OAUTH_RESOURCE,
24689
+ ABLETIME_ORG_KEY_PREFIX: () => ABLETIME_ORG_KEY_PREFIX,
24690
+ ABLETIME_PAT_PREFIX: () => ABLETIME_PAT_PREFIX,
23956
24691
  ABLETIME_PM_TOOL_NAMES: () => ABLETIME_PM_TOOL_NAMES,
23957
24692
  AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
23958
24693
  AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
23959
24694
  ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
24695
+ AbleTimeOAuthCancelledError: () => AbleTimeOAuthCancelledError,
24696
+ BAKED_ABLETIME_OAUTH_CLIENT_ID: () => BAKED_ABLETIME_OAUTH_CLIENT_ID,
23960
24697
  BAKED_SLACK_RELAY_URL: () => BAKED_SLACK_RELAY_URL,
23961
24698
  BRIGHTSY_MCP_ALLOWED_TOOLS: () => BRIGHTSY_MCP_ALLOWED_TOOLS,
23962
24699
  BRIGHTSY_SUMMARIZE_CONTEXT_TOOL: () => BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
@@ -24045,7 +24782,11 @@ __export(index_exports, {
24045
24782
  WORKTREE_GITHUB_MCP_TOOLS: () => WORKTREE_GITHUB_MCP_TOOLS,
24046
24783
  WORKTREE_LINEAR_MCP_TOOLS: () => WORKTREE_LINEAR_MCP_TOOLS,
24047
24784
  WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
24785
+ ableTimeCredentialKind: () => ableTimeCredentialKind,
24048
24786
  ableTimeDependencyTarget: () => ableTimeDependencyTarget,
24787
+ ableTimeOAuthAuthorizeUrl: () => ableTimeOAuthAuthorizeUrl,
24788
+ ableTimeOAuthClientId: () => ableTimeOAuthClientId,
24789
+ ableTimeOAuthResource: () => ableTimeOAuthResource,
24049
24790
  abletimeMcpRequest: () => abletimeMcpRequest,
24050
24791
  abletimeMcpUrl: () => abletimeMcpUrl,
24051
24792
  abletimeToolUsesPm: () => abletimeToolUsesPm,
@@ -24076,6 +24817,8 @@ __export(index_exports, {
24076
24817
  applyThreadIntoMain: () => applyThreadIntoMain,
24077
24818
  applyTurnUsage: () => applyTurnUsage,
24078
24819
  armSchedules: () => armSchedules,
24820
+ assertAbleTimeCredential: () => assertAbleTimeCredential,
24821
+ assertAbleTimeMcpCredential: () => assertAbleTimeMcpCredential,
24079
24822
  assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
24080
24823
  attachmentFromAbsolutePath: () => attachmentFromAbsolutePath,
24081
24824
  attachmentsFromBuffers: () => attachmentsFromBuffers,
@@ -24155,6 +24898,7 @@ __export(index_exports, {
24155
24898
  countCacheControlBlocks: () => countCacheControlBlocks,
24156
24899
  countUnpushedVsOrigin: () => countUnpushedVsOrigin,
24157
24900
  cowboyModeEnabled: () => cowboyModeEnabled,
24901
+ createAbleTimePkce: () => createAbleTimePkce,
24158
24902
  createAbleTimeTask: () => createAbleTimeTask,
24159
24903
  createChatTab: () => createChatTab,
24160
24904
  createEmptyThread: () => createEmptyThread,
@@ -24194,6 +24938,7 @@ __export(index_exports, {
24194
24938
  encodeBrightsyTarget: () => encodeBrightsyTarget,
24195
24939
  enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
24196
24940
  enrichWorkspacesWithGithub: () => enrichWorkspacesWithGithub,
24941
+ ensureAbleTimeAccessToken: () => ensureAbleTimeAccessToken,
24197
24942
  ensureAbleTimeTask: () => ensureAbleTimeTask,
24198
24943
  ensureAgentPath: () => ensureAgentPath,
24199
24944
  ensureBrightsyLocalConfigFresh: () => ensureBrightsyLocalConfigFresh,
@@ -24340,6 +25085,7 @@ __export(index_exports, {
24340
25085
  groupHomeBoardWorktrees: () => groupHomeBoardWorktrees,
24341
25086
  handleSlackInbound: () => handleSlackInbound,
24342
25087
  harnessEnvKey: () => harnessEnvKey,
25088
+ hasBakedAbleTimeOAuth: () => hasBakedAbleTimeOAuth,
24343
25089
  hasBakedLinearOAuth: () => hasBakedLinearOAuth,
24344
25090
  hasBakedSlackOAuth: () => hasBakedSlackOAuth,
24345
25091
  hasConductorHook: () => hasConductorHook,
@@ -24364,6 +25110,7 @@ __export(index_exports, {
24364
25110
  invalidateThreadListCache: () => invalidateThreadListCache,
24365
25111
  invalidateThreadRecord: () => invalidateThreadRecord,
24366
25112
  isAbleTimeConnected: () => isAbleTimeConnected,
25113
+ isAbleTimeOAuthCancelled: () => isAbleTimeOAuthCancelled,
24367
25114
  isAskUserToolName: () => isAskUserToolName,
24368
25115
  isBrightsyConnected: () => isBrightsyConnected,
24369
25116
  isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
@@ -24493,6 +25240,7 @@ __export(index_exports, {
24493
25240
  nextPastedTextName: () => nextPastedTextName,
24494
25241
  nextThinkingEffort: () => nextThinkingEffort,
24495
25242
  nonInteractiveGitProcessEnv: () => nonInteractiveGitProcessEnv,
25243
+ normalizeAbleTimeCredential: () => normalizeAbleTimeCredential,
24496
25244
  normalizeAbleTimeHost: () => normalizeAbleTimeHost,
24497
25245
  normalizeCodeSelection: () => normalizeCodeSelection,
24498
25246
  normalizeLinearRelationType: () => normalizeLinearRelationType,
@@ -24674,6 +25422,7 @@ __export(index_exports, {
24674
25422
  stackMergeReadiness: () => stackMergeReadiness,
24675
25423
  stageAbsolutePathsAsAttachments: () => stageAbsolutePathsAsAttachments,
24676
25424
  stageBuffersAsAttachments: () => stageBuffersAsAttachments,
25425
+ startAbleTimeOAuth: () => startAbleTimeOAuth,
24677
25426
  startDevServer: () => startDevServer,
24678
25427
  startLinearOAuth: () => startLinearOAuth,
24679
25428
  startMcpServer: () => startMcpServer,
@@ -24976,8 +25725,8 @@ function issueActivityKind(issue, sinceIso) {
24976
25725
  }
24977
25726
 
24978
25727
  // src/integrations/linear-oauth.ts
24979
- var import_node_crypto11 = require("crypto");
24980
- var import_node_http = require("http");
25728
+ var import_node_crypto12 = require("crypto");
25729
+ var import_node_http2 = require("http");
24981
25730
  init_app_settings();
24982
25731
  init_fetch();
24983
25732
 
@@ -24995,7 +25744,7 @@ var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
24995
25744
  var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
24996
25745
  var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
24997
25746
  var LINEAR_OAUTH_SCOPES = "read,write";
24998
- var REFRESH_SKEW_MS2 = 5 * 6e4;
25747
+ var REFRESH_SKEW_MS3 = 5 * 6e4;
24999
25748
  function linearOAuthCredentials() {
25000
25749
  const settings = loadAppSettings();
25001
25750
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
@@ -25008,8 +25757,8 @@ function linearOAuthCredentials() {
25008
25757
  return { clientId, clientSecret };
25009
25758
  }
25010
25759
  function createLinearPkce() {
25011
- const verifier = (0, import_node_crypto11.randomBytes)(32).toString("base64url");
25012
- const challenge = (0, import_node_crypto11.createHash)("sha256").update(verifier).digest("base64url");
25760
+ const verifier = (0, import_node_crypto12.randomBytes)(32).toString("base64url");
25761
+ const challenge = (0, import_node_crypto12.createHash)("sha256").update(verifier).digest("base64url");
25013
25762
  return { verifier, challenge };
25014
25763
  }
25015
25764
  function linearOAuthAuthorizeUrl(clientId, state, codeChallenge) {
@@ -25033,7 +25782,7 @@ function linearAuthorizationHeader(token) {
25033
25782
  if (t.startsWith("lin_api_")) return t;
25034
25783
  return `Bearer ${t}`;
25035
25784
  }
25036
- function htmlPage(title, body) {
25785
+ function htmlPage2(title, body) {
25037
25786
  return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
25038
25787
  <style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
25039
25788
  h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
@@ -25079,7 +25828,7 @@ async function fetchLinearViewer(accessToken) {
25079
25828
  organizationName: viewer?.organization?.name?.trim() || void 0
25080
25829
  };
25081
25830
  }
25082
- var refreshInFlight = null;
25831
+ var refreshInFlight2 = null;
25083
25832
  async function refreshLinearAccessToken(refreshToken) {
25084
25833
  const { clientId, clientSecret } = linearOAuthCredentials();
25085
25834
  const body = new URLSearchParams({
@@ -25106,16 +25855,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
25106
25855
  const refresh = settings.integrations.linearRefreshToken?.trim() || "";
25107
25856
  const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
25108
25857
  const apiKey = settings.integrations.linearApiKey?.trim() || "";
25109
- if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
25858
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS3)) {
25110
25859
  return access;
25111
25860
  }
25112
25861
  if (refresh) {
25113
- if (!refreshInFlight) {
25114
- refreshInFlight = refreshLinearAccessToken(refresh).finally(() => {
25115
- refreshInFlight = null;
25862
+ if (!refreshInFlight2) {
25863
+ refreshInFlight2 = refreshLinearAccessToken(refresh).finally(() => {
25864
+ refreshInFlight2 = null;
25116
25865
  });
25117
25866
  }
25118
- return refreshInFlight.catch((err) => {
25867
+ return refreshInFlight2.catch((err) => {
25119
25868
  if (apiKey) return apiKey;
25120
25869
  throw err;
25121
25870
  });
@@ -25140,7 +25889,7 @@ async function startLinearOAuth(opts) {
25140
25889
  throw new LinearOAuthCancelledError();
25141
25890
  }
25142
25891
  const { clientId, clientSecret } = linearOAuthCredentials();
25143
- const state = (0, import_node_crypto11.randomBytes)(16).toString("hex");
25892
+ const state = (0, import_node_crypto12.randomBytes)(16).toString("hex");
25144
25893
  const pkce = createLinearPkce();
25145
25894
  const authorizeUrl = linearOAuthAuthorizeUrl(clientId, state, pkce.challenge);
25146
25895
  const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
@@ -25153,7 +25902,7 @@ async function startLinearOAuth(opts) {
25153
25902
  if (err) reject(err);
25154
25903
  else resolve(value);
25155
25904
  };
25156
- const server = (0, import_node_http.createServer)((req, res) => {
25905
+ const server = (0, import_node_http2.createServer)((req, res) => {
25157
25906
  try {
25158
25907
  const url = new URL(req.url || "/", `http://127.0.0.1:${LINEAR_OAUTH_PORT}`);
25159
25908
  if (url.pathname !== "/callback") {
@@ -25166,19 +25915,19 @@ async function startLinearOAuth(opts) {
25166
25915
  const gotCode = url.searchParams.get("code");
25167
25916
  if (err) {
25168
25917
  res.writeHead(400, { "Content-Type": "text/html" });
25169
- res.end(htmlPage("Linear", `<h1>Authorization cancelled</h1><p>${err}</p>`));
25918
+ res.end(htmlPage2("Linear", `<h1>Authorization cancelled</h1><p>${err}</p>`));
25170
25919
  finish(new LinearOAuthCancelledError(`Linear OAuth: ${err}`));
25171
25920
  return;
25172
25921
  }
25173
25922
  if (gotState !== state || !gotCode) {
25174
25923
  res.writeHead(400, { "Content-Type": "text/html" });
25175
- res.end(htmlPage("Linear", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
25924
+ res.end(htmlPage2("Linear", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
25176
25925
  finish(new Error("Linear OAuth callback was invalid"));
25177
25926
  return;
25178
25927
  }
25179
25928
  res.writeHead(200, { "Content-Type": "text/html" });
25180
25929
  res.end(
25181
- htmlPage(
25930
+ htmlPage2(
25182
25931
  "Linear connected",
25183
25932
  "<h1>Linear connected</h1><p>You can close this tab and return to Sideboard.</p>"
25184
25933
  )
@@ -26591,6 +27340,7 @@ async function createGitHubIssue(input, opts) {
26591
27340
  // src/index.ts
26592
27341
  init_abletime();
26593
27342
  init_abletime_mcp();
27343
+ init_abletime_oauth();
26594
27344
 
26595
27345
  // src/integrations/issues.ts
26596
27346
  init_worktree();
@@ -26993,7 +27743,7 @@ function buildPathRefAttachment(input) {
26993
27743
  init_stage_files();
26994
27744
 
26995
27745
  // src/composer/pasted-text.ts
26996
- var import_node_crypto12 = require("crypto");
27746
+ var import_node_crypto13 = require("crypto");
26997
27747
  var PASTE_ATTACH_MIN_CHARS = 1200;
26998
27748
  var PASTE_ATTACH_MIN_LINES = 15;
26999
27749
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
@@ -27020,7 +27770,7 @@ function nextPastedTextName(existing) {
27020
27770
  }
27021
27771
  function buildPastedTextAttachment(text7, opts) {
27022
27772
  return {
27023
- id: opts?.id ?? (0, import_node_crypto12.randomUUID)(),
27773
+ id: opts?.id ?? (0, import_node_crypto13.randomUUID)(),
27024
27774
  name: opts?.name ?? "Pasted text #1.txt",
27025
27775
  kind: "file",
27026
27776
  path: opts?.path,
@@ -27051,7 +27801,7 @@ function planQuestionsSignature(questions) {
27051
27801
  return `${q.question}${opts}`;
27052
27802
  }).join("");
27053
27803
  }
27054
- function asRecord9(v) {
27804
+ function asRecord10(v) {
27055
27805
  return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
27056
27806
  }
27057
27807
  function parseOptions(raw) {
@@ -27062,7 +27812,7 @@ function parseOptions(raw) {
27062
27812
  out.push({ label: item.trim() });
27063
27813
  continue;
27064
27814
  }
27065
- const o = asRecord9(item);
27815
+ const o = asRecord10(item);
27066
27816
  if (!o) continue;
27067
27817
  const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
27068
27818
  if (!label) continue;
@@ -27072,7 +27822,7 @@ function parseOptions(raw) {
27072
27822
  return out;
27073
27823
  }
27074
27824
  function parseOneQuestion(raw) {
27075
- const o = asRecord9(raw);
27825
+ const o = asRecord10(raw);
27076
27826
  if (!o) return null;
27077
27827
  const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
27078
27828
  if (!question) return null;
@@ -27083,7 +27833,7 @@ function parseOneQuestion(raw) {
27083
27833
  return { question, header, multiSelect, options };
27084
27834
  }
27085
27835
  function parsePlanQuestionsInput(input) {
27086
- const root = asRecord9(input);
27836
+ const root = asRecord10(input);
27087
27837
  if (!root) return [];
27088
27838
  const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
27089
27839
  const out = [];
@@ -27174,7 +27924,7 @@ init_orphan_cleanup();
27174
27924
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
27175
27925
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
27176
27926
  var import_zod9 = require("zod");
27177
- var import_node_crypto14 = require("crypto");
27927
+ var import_node_crypto15 = require("crypto");
27178
27928
  var import_node_path53 = require("path");
27179
27929
  init_orchestrator();
27180
27930
  init_worktree();
@@ -28901,7 +29651,7 @@ init_worktree();
28901
29651
  init_paths();
28902
29652
 
28903
29653
  // src/board/board-pins.ts
28904
- var import_node_crypto13 = require("crypto");
29654
+ var import_node_crypto14 = require("crypto");
28905
29655
  var import_node_fs55 = require("fs");
28906
29656
  var import_node_path51 = require("path");
28907
29657
  init_paths();
@@ -28933,7 +29683,7 @@ function addBoardPin(input) {
28933
29683
  const ref = input.ref.trim();
28934
29684
  if (!ref) throw new Error("Board item needs a ref");
28935
29685
  const pin = {
28936
- id: (0, import_node_crypto13.randomUUID)(),
29686
+ id: (0, import_node_crypto14.randomUUID)(),
28937
29687
  kind: input.kind,
28938
29688
  ref,
28939
29689
  repoPath: input.repoPath.trim(),
@@ -29767,7 +30517,7 @@ async function startMcpServer() {
29767
30517
  const ident = issue?.identifier ?? ref.trim();
29768
30518
  const attachments = issue ? [
29769
30519
  {
29770
- id: (0, import_node_crypto14.randomUUID)(),
30520
+ id: (0, import_node_crypto15.randomUUID)(),
29771
30521
  name: issue.identifier,
29772
30522
  kind: "issue",
29773
30523
  content: [
@@ -30722,7 +31472,7 @@ init_orch_mcp_isolation();
30722
31472
  init_workspaces2();
30723
31473
 
30724
31474
  // src/slack/oauth.ts
30725
- var import_node_crypto15 = require("crypto");
31475
+ var import_node_crypto16 = require("crypto");
30726
31476
  init_app_settings();
30727
31477
  init_workspaces2();
30728
31478
 
@@ -30946,7 +31696,7 @@ async function startSlackOAuth(opts) {
30946
31696
  throw new SlackOAuthCancelledError();
30947
31697
  }
30948
31698
  const { clientId } = slackOAuthCredentials();
30949
- const state = (0, import_node_crypto15.randomBytes)(16).toString("hex");
31699
+ const state = (0, import_node_crypto16.randomBytes)(16).toString("hex");
30950
31700
  const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
30951
31701
  const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
30952
31702
  const pollIntervalMs = opts?.pollIntervalMs ?? 400;
@@ -31228,7 +31978,7 @@ init_workspaces2();
31228
31978
  init_reply_target();
31229
31979
 
31230
31980
  // src/slack/relay-protocol.ts
31231
- var import_node_crypto16 = require("crypto");
31981
+ var import_node_crypto17 = require("crypto");
31232
31982
  var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
31233
31983
  var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
31234
31984
  function slackRelaySessionKey(teamId, userId, deviceId) {
@@ -31238,7 +31988,7 @@ function slackRelayUserKey(teamId, userId) {
31238
31988
  return `${teamId.trim()}:${userId.trim()}`;
31239
31989
  }
31240
31990
  function newSlackRelayEventId() {
31241
- return (0, import_node_crypto16.randomBytes)(12).toString("hex");
31991
+ return (0, import_node_crypto17.randomBytes)(12).toString("hex");
31242
31992
  }
31243
31993
  function parseSlackRelayClientMessage(raw) {
31244
31994
  try {
@@ -31311,7 +32061,7 @@ function parseSlackRelayServerMessage(raw) {
31311
32061
 
31312
32062
  // src/slack/public-dns.ts
31313
32063
  var import_node_dns = require("dns");
31314
- var import_node_http2 = require("http");
32064
+ var import_node_http3 = require("http");
31315
32065
  var import_node_https = require("https");
31316
32066
  var PUBLIC_DNS = ["1.1.1.1", "8.8.8.8"];
31317
32067
  function finishLookup(options, callback, address, family) {
@@ -32355,7 +33105,7 @@ var SlackRelayHub = class {
32355
33105
  };
32356
33106
 
32357
33107
  // src/slack/relay-server.ts
32358
- var import_node_http3 = require("http");
33108
+ var import_node_http4 = require("http");
32359
33109
  var import_ws3 = require("ws");
32360
33110
 
32361
33111
  // src/slack/relay-static.ts
@@ -32527,7 +33277,7 @@ async function startSlackRelayServer(opts) {
32527
33277
  sendJson(res, 200, { ok: true, ...value.payload });
32528
33278
  return true;
32529
33279
  };
32530
- const httpServer = (0, import_node_http3.createServer)((req, res) => {
33280
+ const httpServer = (0, import_node_http4.createServer)((req, res) => {
32531
33281
  void (async () => {
32532
33282
  const reqUrl = req.url || "/";
32533
33283
  if (await handleCallback(reqUrl, res)) return;
@@ -32664,10 +33414,18 @@ init_outbound_watch();
32664
33414
  0 && (module.exports = {
32665
33415
  ABLETIME_MCP_PATH,
32666
33416
  ABLETIME_MCP_PM_PATH,
33417
+ ABLETIME_OAUTH_CANCELLED,
33418
+ ABLETIME_OAUTH_PORT,
33419
+ ABLETIME_OAUTH_REDIRECT,
33420
+ ABLETIME_OAUTH_RESOURCE,
33421
+ ABLETIME_ORG_KEY_PREFIX,
33422
+ ABLETIME_PAT_PREFIX,
32667
33423
  ABLETIME_PM_TOOL_NAMES,
32668
33424
  AGENT_GIT_ACTIONS,
32669
33425
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
32670
33426
  ATTACHMENTS_DIR,
33427
+ AbleTimeOAuthCancelledError,
33428
+ BAKED_ABLETIME_OAUTH_CLIENT_ID,
32671
33429
  BAKED_SLACK_RELAY_URL,
32672
33430
  BRIGHTSY_MCP_ALLOWED_TOOLS,
32673
33431
  BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
@@ -32756,7 +33514,11 @@ init_outbound_watch();
32756
33514
  WORKTREE_GITHUB_MCP_TOOLS,
32757
33515
  WORKTREE_LINEAR_MCP_TOOLS,
32758
33516
  WORKTREE_MCP_TOOLS,
33517
+ ableTimeCredentialKind,
32759
33518
  ableTimeDependencyTarget,
33519
+ ableTimeOAuthAuthorizeUrl,
33520
+ ableTimeOAuthClientId,
33521
+ ableTimeOAuthResource,
32760
33522
  abletimeMcpRequest,
32761
33523
  abletimeMcpUrl,
32762
33524
  abletimeToolUsesPm,
@@ -32787,6 +33549,8 @@ init_outbound_watch();
32787
33549
  applyThreadIntoMain,
32788
33550
  applyTurnUsage,
32789
33551
  armSchedules,
33552
+ assertAbleTimeCredential,
33553
+ assertAbleTimeMcpCredential,
32790
33554
  assertOrchestratorCapableAgent,
32791
33555
  attachmentFromAbsolutePath,
32792
33556
  attachmentsFromBuffers,
@@ -32866,6 +33630,7 @@ init_outbound_watch();
32866
33630
  countCacheControlBlocks,
32867
33631
  countUnpushedVsOrigin,
32868
33632
  cowboyModeEnabled,
33633
+ createAbleTimePkce,
32869
33634
  createAbleTimeTask,
32870
33635
  createChatTab,
32871
33636
  createEmptyThread,
@@ -32905,6 +33670,7 @@ init_outbound_watch();
32905
33670
  encodeBrightsyTarget,
32906
33671
  enrichPathWithNpmGlobalBin,
32907
33672
  enrichWorkspacesWithGithub,
33673
+ ensureAbleTimeAccessToken,
32908
33674
  ensureAbleTimeTask,
32909
33675
  ensureAgentPath,
32910
33676
  ensureBrightsyLocalConfigFresh,
@@ -33051,6 +33817,7 @@ init_outbound_watch();
33051
33817
  groupHomeBoardWorktrees,
33052
33818
  handleSlackInbound,
33053
33819
  harnessEnvKey,
33820
+ hasBakedAbleTimeOAuth,
33054
33821
  hasBakedLinearOAuth,
33055
33822
  hasBakedSlackOAuth,
33056
33823
  hasConductorHook,
@@ -33075,6 +33842,7 @@ init_outbound_watch();
33075
33842
  invalidateThreadListCache,
33076
33843
  invalidateThreadRecord,
33077
33844
  isAbleTimeConnected,
33845
+ isAbleTimeOAuthCancelled,
33078
33846
  isAskUserToolName,
33079
33847
  isBrightsyConnected,
33080
33848
  isBrightsyNdjsonLine,
@@ -33204,6 +33972,7 @@ init_outbound_watch();
33204
33972
  nextPastedTextName,
33205
33973
  nextThinkingEffort,
33206
33974
  nonInteractiveGitProcessEnv,
33975
+ normalizeAbleTimeCredential,
33207
33976
  normalizeAbleTimeHost,
33208
33977
  normalizeCodeSelection,
33209
33978
  normalizeLinearRelationType,
@@ -33385,6 +34154,7 @@ init_outbound_watch();
33385
34154
  stackMergeReadiness,
33386
34155
  stageAbsolutePathsAsAttachments,
33387
34156
  stageBuffersAsAttachments,
34157
+ startAbleTimeOAuth,
33388
34158
  startDevServer,
33389
34159
  startLinearOAuth,
33390
34160
  startMcpServer,