@sideboard-ai/core 0.1.202 → 0.1.203

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