@sideboard-ai/core 0.1.201 → 0.1.203

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