@sideboard-ai/core 0.1.202 → 0.1.204

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