@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
@@ -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
  }
@@ -9503,7 +9538,7 @@ function applyAgentEvent(parts, event) {
9503
9538
  next.push(
9504
9539
  withPartTimes({
9505
9540
  type: "thinking",
9506
- text: data,
9541
+ text: clipThinkingForStore(data),
9507
9542
  ...event.parentId ? { parentId: event.parentId } : {}
9508
9543
  })
9509
9544
  );
@@ -14428,6 +14463,9 @@ function childHaltNotice(child, status) {
14428
14463
  "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."
14429
14464
  ].join("\n");
14430
14465
  }
14466
+ function shouldNotifyParentAfterTurnError(opts) {
14467
+ return !opts.quotaFailoverHandled && !opts.crashContinued;
14468
+ }
14431
14469
  function shouldNotifyParentOfChildHalt(opts) {
14432
14470
  if (!isIncompleteChildStatus(opts.status)) return false;
14433
14471
  if (!opts.child.parentThreadId) return false;
@@ -15370,6 +15408,645 @@ var init_home_board = __esm({
15370
15408
  }
15371
15409
  });
15372
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
+
15373
16050
  // src/integrations/abletime-mcp.ts
15374
16051
  function abletimeToolUsesPm(name) {
15375
16052
  return ABLETIME_PM_TOOL_NAMES.includes(name);
@@ -15383,22 +16060,49 @@ function abletimeMcpUrl(host, opts) {
15383
16060
  const path = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
15384
16061
  return `${normalizeAbleTimeHost(host)}${path}`;
15385
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
+ }
15386
16089
  function rewriteAbleTimeError(message) {
15387
- 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)) {
15388
16092
  return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
15389
16093
  }
15390
- if (/INTEGRATION_PAT_REQUIRED|organization api key/i.test(message)) {
15391
- 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}`;
15392
16096
  }
15393
16097
  if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
15394
16098
  return `${message} \u2014 AbleTime plan does not include API access.`;
15395
16099
  }
15396
16100
  if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
15397
- return `${message} \u2014 Reconnect AbleTime from Account settings (Profile \u2192 API Access).`;
16101
+ return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
15398
16102
  }
15399
16103
  return message;
15400
16104
  }
15401
- function asRecord4(value) {
16105
+ function asRecord5(value) {
15402
16106
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15403
16107
  }
15404
16108
  function parseSseJson(body) {
@@ -15420,12 +16124,12 @@ function parseSseJson(body) {
15420
16124
  }
15421
16125
  }
15422
16126
  function unwrapToolResult(result) {
15423
- const record = asRecord4(result);
16127
+ const record = asRecord5(result);
15424
16128
  if (!record) return result;
15425
16129
  const content = record.content;
15426
16130
  if (Array.isArray(content)) {
15427
16131
  const texts = content.map((part) => {
15428
- const item = asRecord4(part);
16132
+ const item = asRecord5(part);
15429
16133
  if (!item) return "";
15430
16134
  return typeof item.text === "string" ? item.text : "";
15431
16135
  }).filter(Boolean);
@@ -15447,17 +16151,18 @@ function unwrapToolResult(result) {
15447
16151
  return result;
15448
16152
  }
15449
16153
  function textsFromUnknown(value) {
15450
- const record = asRecord4(value);
16154
+ const record = asRecord5(value);
15451
16155
  if (!record) return "";
15452
16156
  if (typeof record.message === "string") return record.message;
15453
16157
  if (typeof record.error === "string") return record.error;
15454
16158
  return "";
15455
16159
  }
15456
16160
  async function abletimeMcpRequest(method, params, opts) {
15457
- const token = (opts?.token ?? getAbleTimeAccessToken())?.trim();
15458
- 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()) {
15459
16163
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
15460
16164
  }
16165
+ const token = assertAbleTimeMcpCredential(rawToken);
15461
16166
  const url = abletimeMcpUrl(
15462
16167
  opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
15463
16168
  { pm: opts?.pm }
@@ -15493,7 +16198,7 @@ async function abletimeMcpRequest(method, params, opts) {
15493
16198
  return parseSseJson(body);
15494
16199
  }
15495
16200
  })();
15496
- const rpc = asRecord4(parsed);
16201
+ const rpc = asRecord5(parsed);
15497
16202
  if (rpc?.error) {
15498
16203
  const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
15499
16204
  throw new Error(rewriteAbleTimeError(detail));
@@ -15524,6 +16229,11 @@ async function callAbleTimeTool(name, args = {}, opts) {
15524
16229
  if (!opts?.token && !isAbleTimeConnected()) {
15525
16230
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
15526
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
+ }
15527
16237
  const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
15528
16238
  const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
15529
16239
  return unwrapToolResult(result);
@@ -15539,7 +16249,7 @@ async function callAbleTimeTool(name, args = {}, opts) {
15539
16249
  throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
15540
16250
  }
15541
16251
  }
15542
- 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;
15543
16253
  var init_abletime_mcp = __esm({
15544
16254
  "src/integrations/abletime-mcp.ts"() {
15545
16255
  "use strict";
@@ -15561,6 +16271,11 @@ var init_abletime_mcp = __esm({
15561
16271
  "archive_task",
15562
16272
  "delete_task"
15563
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/;
15564
16279
  }
15565
16280
  });
15566
16281
 
@@ -15584,16 +16299,16 @@ __export(abletime_exports, {
15584
16299
  updateAbleTimeTask: () => updateAbleTimeTask,
15585
16300
  verifyAbleTimeConnection: () => verifyAbleTimeConnection
15586
16301
  });
15587
- function asRecord5(value) {
16302
+ function asRecord6(value) {
15588
16303
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15589
16304
  }
15590
- function asString(value) {
16305
+ function asString2(value) {
15591
16306
  return typeof value === "string" ? value.trim() : "";
15592
16307
  }
15593
16308
  function firstString(record, keys) {
15594
16309
  if (!record) return "";
15595
16310
  for (const key of keys) {
15596
- const value = asString(record[key]);
16311
+ const value = asString2(record[key]);
15597
16312
  if (value) return value;
15598
16313
  }
15599
16314
  return "";
@@ -15601,14 +16316,14 @@ function firstString(record, keys) {
15601
16316
  function firstRecord(record, keys) {
15602
16317
  if (!record) return null;
15603
16318
  for (const key of keys) {
15604
- const value = asRecord5(record[key]);
16319
+ const value = asRecord6(record[key]);
15605
16320
  if (value) return value;
15606
16321
  }
15607
16322
  return null;
15608
16323
  }
15609
- function asList(value) {
16324
+ function asList2(value) {
15610
16325
  if (Array.isArray(value)) return value;
15611
- const record = asRecord5(value);
16326
+ const record = asRecord6(value);
15612
16327
  if (!record) return [];
15613
16328
  for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
15614
16329
  if (Array.isArray(record[key])) return record[key];
@@ -15620,21 +16335,21 @@ function labelsOf(record) {
15620
16335
  if (!Array.isArray(raw)) return [];
15621
16336
  return raw.map((item) => {
15622
16337
  if (typeof item === "string") return item.trim();
15623
- const rec = asRecord5(item);
16338
+ const rec = asRecord6(item);
15624
16339
  return rec ? firstString(rec, ["name", "title", "label"]) : "";
15625
16340
  }).filter(Boolean);
15626
16341
  }
15627
16342
  function commentsOf(record) {
15628
16343
  const raw = record.comments ?? record.notes ?? record.discussion;
15629
16344
  const out = [];
15630
- for (const item of asList(raw)) {
15631
- const rec = asRecord5(item);
16345
+ for (const item of asList2(raw)) {
16346
+ const rec = asRecord6(item);
15632
16347
  if (!rec) continue;
15633
- const body = firstString(rec, ["body", "text", "comment", "content", "message"]);
16348
+ const body = firstString(rec, ["body", "text", "comment", "message", "contentSanitized"]) || textFromAbleTimeDoc(rec.content);
15634
16349
  if (!body) continue;
15635
- 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"]);
15636
16351
  const comment = { body };
15637
- const id = firstString(rec, ["id", "comment_id"]);
16352
+ const id = firstString(rec, ["id", "comment_id", "commentId"]);
15638
16353
  if (id) comment.id = id;
15639
16354
  const url = firstString(rec, ["url", "permalink"]);
15640
16355
  if (url) comment.url = url;
@@ -15658,11 +16373,11 @@ function taskUrl(task, host) {
15658
16373
  return `${base}/tasks/${slug}`;
15659
16374
  }
15660
16375
  function mapAbleTimeTask(raw, host) {
15661
- const record = asRecord5(raw);
16376
+ const record = asRecord6(raw);
15662
16377
  if (!record) return null;
15663
16378
  const nested = firstRecord(record, ["task", "data"]) ?? record;
15664
- const id = firstString(nested, ["id", "task_id", "taskId"]);
15665
- 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;
15666
16381
  const title = firstString(nested, ["title", "name"]);
15667
16382
  if (!id && !identifier && !title) return null;
15668
16383
  const resolvedId = id || identifier;
@@ -15673,21 +16388,22 @@ function mapAbleTimeTask(raw, host) {
15673
16388
  identifier: resolvedIdentifier,
15674
16389
  title: title || resolvedIdentifier,
15675
16390
  url,
15676
- description: firstString(nested, ["description", "body", "details"]) || void 0,
15677
- state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
15678
- projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord5(nested.project), ["id"]) || void 0,
15679
- 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,
15680
16395
  assignee: assigneeOf(nested),
15681
16396
  labels: labelsOf(nested),
15682
16397
  comments: commentsOf(nested),
15683
- createdAt: firstString(nested, ["created_at", "createdAt", "created"]) || void 0,
16398
+ createdAt: firstString(nested, ["created_at", "createdAt", "created", "dateCreated"]) || void 0,
15684
16399
  updatedAt: firstString(nested, [
15685
16400
  "updated_at",
15686
16401
  "updatedAt",
15687
16402
  "modified_at",
15688
16403
  "modifiedAt",
15689
16404
  "last_activity_at",
15690
- "lastActivityAt"
16405
+ "lastActivityAt",
16406
+ "lastUpdate"
15691
16407
  ]) || void 0
15692
16408
  };
15693
16409
  }
@@ -15718,16 +16434,16 @@ function issueAttachmentForAbleTimeTask(task) {
15718
16434
  };
15719
16435
  }
15720
16436
  function mapProject(raw) {
15721
- const record = asRecord5(raw);
16437
+ const record = asRecord6(raw);
15722
16438
  if (!record) return null;
15723
- const id = firstString(record, ["id", "project_id"]);
15724
- const name = firstString(record, ["name", "title"]);
16439
+ const id = firstString(record, ["id", "project_id", "projectId"]);
16440
+ const name = firstString(record, ["name", "title", "projectName"]);
15725
16441
  if (!id && !name) return null;
15726
- const categories = asList(record.categories ?? record.category).map((item) => {
15727
- const rec = asRecord5(item);
16442
+ const categories = asList2(record.categories ?? record.category).map((item) => {
16443
+ const rec = asRecord6(item);
15728
16444
  if (!rec) return null;
15729
- const categoryId = firstString(rec, ["id", "category_id"]);
15730
- const categoryName = firstString(rec, ["name", "title"]);
16445
+ const categoryId = firstString(rec, ["id", "category_id", "projectCategoryId", "categoryId"]);
16446
+ const categoryName = firstString(rec, ["name", "title", "categoryName"]);
15731
16447
  if (!categoryId && !categoryName) return null;
15732
16448
  return { id: categoryId || categoryName, name: categoryName || categoryId };
15733
16449
  }).filter((item) => Boolean(item));
@@ -15739,21 +16455,21 @@ function isOpenTask(task) {
15739
16455
  }
15740
16456
  async function getAbleTimeOrientation(opts) {
15741
16457
  const raw = await callAbleTimeTool("orientation", {}, opts);
15742
- const record = asRecord5(raw);
16458
+ const record = asRecord6(raw);
15743
16459
  const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
15744
16460
  const viewer = {
15745
16461
  id: firstString(viewerRec, ["id", "user_id"]) || void 0,
15746
16462
  name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
15747
16463
  };
15748
- const projects = asList(
16464
+ const projects = asList2(
15749
16465
  record?.projects ?? record?.current_projects ?? record?.currentProjects
15750
16466
  ).map(mapProject).filter((item) => Boolean(item));
15751
- 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));
15752
16468
  return { viewer, projects, tasks, raw };
15753
16469
  }
15754
16470
  async function listAbleTimeProjects(opts) {
15755
16471
  const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
15756
- return asList(raw).map(mapProject).filter((item) => Boolean(item));
16472
+ return asList2(raw).map(mapProject).filter((item) => Boolean(item));
15757
16473
  }
15758
16474
  async function listAbleTimeTasks(opts) {
15759
16475
  const args = {};
@@ -15763,14 +16479,14 @@ async function listAbleTimeTasks(opts) {
15763
16479
  }
15764
16480
  if (opts?.query) args.query = opts.query;
15765
16481
  const raw = await callAbleTimeTool("list_tasks", args, opts);
15766
- 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));
15767
16483
  return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
15768
16484
  }
15769
16485
  async function searchAbleTimeTasks(query, opts) {
15770
16486
  const q = query.trim();
15771
16487
  if (!q) return [];
15772
16488
  const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
15773
- 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);
15774
16490
  }
15775
16491
  async function getAbleTimeTask(id, opts) {
15776
16492
  const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
@@ -15818,7 +16534,7 @@ async function commentAbleTimeTask(input, opts) {
15818
16534
  { id, task_id: id, task: id, body, comment: body, text: body },
15819
16535
  opts
15820
16536
  );
15821
- const rec = asRecord5(raw);
16537
+ const rec = asRecord6(raw);
15822
16538
  return {
15823
16539
  id: rec ? firstString(rec, ["id", "comment_id"]) || void 0 : void 0,
15824
16540
  body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
@@ -16008,8 +16724,7 @@ async function listAbleTimeAssignedIssues(opts) {
16008
16724
  };
16009
16725
  }
16010
16726
  async function verifyAbleTimeConnection(input) {
16011
- const token = input.token.trim();
16012
- if (!token) throw new Error("AbleTime personal access token is required");
16727
+ const token = assertAbleTimeCredential(input.token);
16013
16728
  const host = input.host?.trim() || void 0;
16014
16729
  const orientation = await getAbleTimeOrientation({ token, host });
16015
16730
  saveAbleTimeConnection({
@@ -16025,6 +16740,7 @@ var init_abletime = __esm({
16025
16740
  "use strict";
16026
16741
  init_app_settings();
16027
16742
  init_abletime_mcp();
16743
+ init_abletime_rest();
16028
16744
  CLOSED_STATES = /* @__PURE__ */ new Set([
16029
16745
  "done",
16030
16746
  "completed",
@@ -16596,12 +17312,19 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
16596
17312
  ...instructions
16597
17313
  ].join("\n");
16598
17314
  return {
16599
- id: (0, import_node_crypto9.randomUUID)(),
17315
+ id: (0, import_node_crypto10.randomUUID)(),
16600
17316
  name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
16601
17317
  kind: "transcript",
16602
17318
  content: body
16603
17319
  };
16604
17320
  }
17321
+ async function isolateQuotaFailover(run2) {
17322
+ try {
17323
+ return await run2();
17324
+ } catch {
17325
+ return true;
17326
+ }
17327
+ }
16605
17328
  function createQuotaFailoverChat(from, fallbackAgent, limitText) {
16606
17329
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
16607
17330
  const tab = createChatTab({
@@ -16620,11 +17343,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
16620
17343
  }
16621
17344
  return updateThread(tab.id, patch);
16622
17345
  }
16623
- var import_node_crypto9, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
17346
+ var import_node_crypto10, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
16624
17347
  var init_quota_failover = __esm({
16625
17348
  "src/orchestrator/quota-failover.ts"() {
16626
17349
  "use strict";
16627
- import_node_crypto9 = require("crypto");
17350
+ import_node_crypto10 = require("crypto");
16628
17351
  init_session_quota();
16629
17352
  init_app_settings();
16630
17353
  init_global_workspace();
@@ -18935,7 +19658,7 @@ var init_optional_services = __esm({
18935
19658
  });
18936
19659
 
18937
19660
  // src/plan/plan-present.ts
18938
- function asRecord6(v) {
19661
+ function asRecord7(v) {
18939
19662
  return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
18940
19663
  }
18941
19664
  function isPresentPlanToolName(name) {
@@ -18947,7 +19670,7 @@ function extractPresentedPlan(parts) {
18947
19670
  for (let i = parts.length - 1; i >= 0; i--) {
18948
19671
  const p = parts[i];
18949
19672
  if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
18950
- const input = asRecord6(p.input) ?? {};
19673
+ const input = asRecord7(p.input) ?? {};
18951
19674
  const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
18952
19675
  if (!content.trim()) continue;
18953
19676
  const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
@@ -19382,7 +20105,7 @@ function buildTask(input, now) {
19382
20105
  const ts = nowIso2(now);
19383
20106
  const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
19384
20107
  return {
19385
- id: (0, import_node_crypto10.randomUUID)(),
20108
+ id: (0, import_node_crypto11.randomUUID)(),
19386
20109
  name: input.name?.trim() || defaultScheduleName(prompt),
19387
20110
  prompt,
19388
20111
  enabled: input.enabled !== false,
@@ -19469,11 +20192,11 @@ function recordScheduleRun(id, result) {
19469
20192
  writeAll2(rows);
19470
20193
  return next;
19471
20194
  }
19472
- 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;
19473
20196
  var init_schedules = __esm({
19474
20197
  "src/store/schedules.ts"() {
19475
20198
  "use strict";
19476
- import_node_crypto10 = require("crypto");
20199
+ import_node_crypto11 = require("crypto");
19477
20200
  import_node_fs52 = require("fs");
19478
20201
  import_node_path49 = require("path");
19479
20202
  import_croner = require("croner");
@@ -20156,62 +20879,75 @@ var init_orchestrator = __esm({
20156
20879
  */
20157
20880
  async maybeHandleOrchestrationQuotaFailover(threadId, limitText) {
20158
20881
  const thread = findThreadByRef(threadId);
20159
- if (!thread) return;
20882
+ if (!thread) return false;
20160
20883
  const plan = planOrchestrationQuotaFailover(thread, limitText);
20161
- if (!plan || plan.action === "none") return;
20884
+ if (!plan || plan.action === "none") return false;
20162
20885
  if (plan.action === "wait_reset" && plan.resumeAt) {
20163
20886
  this.haltDrain.add(threadId);
20164
20887
  this.scheduleQuotaResume(threadId, plan.resumeAt);
20165
- setStatus(threadId, "idle", null);
20166
- appendMessage(threadId, {
20167
- role: "agent",
20168
- text: `Sideboard will auto-retry this chat around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
20169
- ts: (/* @__PURE__ */ new Date()).toISOString()
20170
- });
20171
- this.emit({
20172
- type: "quota_failover",
20173
- threadId,
20174
- action: "wait_reset",
20175
- message: plan.reason,
20176
- resumeAt: plan.resumeAt.toISOString()
20177
- });
20178
- this.emit({ type: "status_changed", threadId, status: "idle" });
20179
- return;
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
+ }
20905
+ return true;
20180
20906
  }
20181
20907
  if (plan.action === "switch_agent" && plan.fallbackAgent) {
20182
20908
  this.haltDrain.add(threadId);
20183
- const next = createQuotaFailoverChat(
20184
- thread,
20185
- plan.fallbackAgent,
20186
- plan.limitText
20187
- );
20909
+ let next;
20910
+ try {
20911
+ next = createQuotaFailoverChat(
20912
+ thread,
20913
+ plan.fallbackAgent,
20914
+ plan.limitText
20915
+ );
20916
+ } catch {
20917
+ return false;
20918
+ }
20188
20919
  this.clearQuotaResumeTimer(threadId);
20189
20920
  try {
20190
20921
  updateThread(threadId, { quotaResumeAt: null });
20191
20922
  } catch {
20192
20923
  }
20193
- appendMessage(threadId, {
20194
- role: "agent",
20195
- text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
20196
- ts: (/* @__PURE__ */ new Date()).toISOString()
20197
- });
20198
- this.emit({
20199
- type: "quota_failover",
20200
- threadId,
20201
- action: "switch_agent",
20202
- toThreadId: next.id,
20203
- message: plan.reason
20204
- });
20205
- this.emit({
20206
- type: "status_changed",
20207
- threadId: next.id,
20208
- status: next.status
20209
- });
20210
- await this.send(
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
+ }
20944
+ void this.send(
20211
20945
  next.id,
20212
20946
  QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
20213
20947
  );
20948
+ return true;
20214
20949
  }
20950
+ return false;
20215
20951
  }
20216
20952
  /**
20217
20953
  * Cursor-style recovery: after a runner crash, feed the error back as the
@@ -20935,13 +21671,18 @@ var init_orchestrator = __esm({
20935
21671
  this.maybeEnqueueJobContinue(threadId, chatText, parts);
20936
21672
  } else {
20937
21673
  const blob = [chatText, detail].filter(Boolean).join("\n");
20938
- void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
21674
+ const failedOver = await isolateQuotaFailover(
21675
+ () => this.maybeHandleOrchestrationQuotaFailover(threadId, blob)
21676
+ );
20939
21677
  this.maybeEnqueueCrashContinue(threadId, {
20940
21678
  detail: detail || failDetail,
20941
21679
  assistantText: chatText,
20942
21680
  partsCount: parts.length
20943
21681
  });
20944
- if (!this.crashContinued.has(threadId)) {
21682
+ if (shouldNotifyParentAfterTurnError({
21683
+ quotaFailoverHandled: failedOver,
21684
+ crashContinued: this.crashContinued.has(threadId)
21685
+ })) {
20945
21686
  const failed = readThread(threadId);
20946
21687
  if (failed) {
20947
21688
  notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
@@ -20965,13 +21706,18 @@ var init_orchestrator = __esm({
20965
21706
  this.emit({ type: "status_changed", threadId, status: "error" });
20966
21707
  }
20967
21708
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
20968
- void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
21709
+ const failedOver = await isolateQuotaFailover(
21710
+ () => this.maybeHandleOrchestrationQuotaFailover(threadId, message)
21711
+ );
20969
21712
  this.maybeEnqueueCrashContinue(threadId, {
20970
21713
  detail: message,
20971
21714
  assistantText: "",
20972
21715
  partsCount: 0
20973
21716
  });
20974
- if (!this.crashContinued.has(threadId)) {
21717
+ if (shouldNotifyParentAfterTurnError({
21718
+ quotaFailoverHandled: failedOver,
21719
+ crashContinued: this.crashContinued.has(threadId)
21720
+ })) {
20975
21721
  const failed = readThread(threadId);
20976
21722
  if (failed) {
20977
21723
  notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
@@ -22051,7 +22797,7 @@ init_system_ca();
22051
22797
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
22052
22798
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
22053
22799
  var import_zod9 = require("zod");
22054
- var import_node_crypto13 = require("crypto");
22800
+ var import_node_crypto14 = require("crypto");
22055
22801
  var import_node_path53 = require("path");
22056
22802
  init_orchestrator();
22057
22803
  init_worktree();
@@ -22478,8 +23224,8 @@ async function createGitHubIssue(input, opts) {
22478
23224
  init_fetch();
22479
23225
 
22480
23226
  // src/integrations/linear-oauth.ts
22481
- var import_node_crypto11 = require("crypto");
22482
- var import_node_http = require("http");
23227
+ var import_node_crypto12 = require("crypto");
23228
+ var import_node_http2 = require("http");
22483
23229
  init_app_settings();
22484
23230
  init_fetch();
22485
23231
 
@@ -22490,7 +23236,7 @@ var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
22490
23236
  var LINEAR_OAUTH_PORT = 19848;
22491
23237
  var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
22492
23238
  var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
22493
- var REFRESH_SKEW_MS2 = 5 * 6e4;
23239
+ var REFRESH_SKEW_MS3 = 5 * 6e4;
22494
23240
  function linearOAuthCredentials() {
22495
23241
  const settings = loadAppSettings();
22496
23242
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
@@ -22531,7 +23277,7 @@ async function persistLinearTokens(data, viewer) {
22531
23277
  organizationName: viewer?.organizationName
22532
23278
  });
22533
23279
  }
22534
- var refreshInFlight = null;
23280
+ var refreshInFlight2 = null;
22535
23281
  async function refreshLinearAccessToken(refreshToken) {
22536
23282
  const { clientId, clientSecret } = linearOAuthCredentials();
22537
23283
  const body = new URLSearchParams({
@@ -22558,16 +23304,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
22558
23304
  const refresh = settings.integrations.linearRefreshToken?.trim() || "";
22559
23305
  const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
22560
23306
  const apiKey = settings.integrations.linearApiKey?.trim() || "";
22561
- if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
23307
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS3)) {
22562
23308
  return access;
22563
23309
  }
22564
23310
  if (refresh) {
22565
- if (!refreshInFlight) {
22566
- refreshInFlight = refreshLinearAccessToken(refresh).finally(() => {
22567
- refreshInFlight = null;
23311
+ if (!refreshInFlight2) {
23312
+ refreshInFlight2 = refreshLinearAccessToken(refresh).finally(() => {
23313
+ refreshInFlight2 = null;
22568
23314
  });
22569
23315
  }
22570
- return refreshInFlight.catch((err) => {
23316
+ return refreshInFlight2.catch((err) => {
22571
23317
  if (apiKey) return apiKey;
22572
23318
  throw err;
22573
23319
  });
@@ -25461,7 +26207,7 @@ init_worktree();
25461
26207
  init_paths();
25462
26208
 
25463
26209
  // src/board/board-pins.ts
25464
- var import_node_crypto12 = require("crypto");
26210
+ var import_node_crypto13 = require("crypto");
25465
26211
  var import_node_fs55 = require("fs");
25466
26212
  var import_node_path51 = require("path");
25467
26213
  init_paths();
@@ -26310,7 +27056,7 @@ async function startMcpServer() {
26310
27056
  const ident = issue?.identifier ?? ref.trim();
26311
27057
  const attachments = issue ? [
26312
27058
  {
26313
- id: (0, import_node_crypto13.randomUUID)(),
27059
+ id: (0, import_node_crypto14.randomUUID)(),
26314
27060
  name: issue.identifier,
26315
27061
  kind: "issue",
26316
27062
  content: [