@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
@@ -0,0 +1,272 @@
1
+ import {
2
+ httpFetch
3
+ } from "./chunk-N7WZ6NIN.js";
4
+ import {
5
+ getAbleTimeHost,
6
+ isAbleTimeConnected
7
+ } from "./chunk-QFBBUFD4.js";
8
+
9
+ // src/integrations/abletime-mcp.ts
10
+ var DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
11
+ var ABLETIME_MCP_PATH = "/api/public/v2/mcp";
12
+ var ABLETIME_MCP_PM_PATH = "/api/public/v2/mcp/pm";
13
+ var MCP_PROTOCOL_VERSION = "2025-03-26";
14
+ var ABLETIME_PM_TOOL_NAMES = [
15
+ "set_task_assignee",
16
+ "set_task_priority",
17
+ "set_task_stage",
18
+ "set_task_dependency",
19
+ "set_task_blocked",
20
+ "set_task_locked",
21
+ "set_task_schedule",
22
+ "clear_task_schedule",
23
+ "archive_task",
24
+ "delete_task"
25
+ ];
26
+ function abletimeToolUsesPm(name) {
27
+ return ABLETIME_PM_TOOL_NAMES.includes(name);
28
+ }
29
+ function normalizeAbleTimeHost(raw) {
30
+ const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
31
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
32
+ return withScheme.replace(/\/+$/, "");
33
+ }
34
+ function abletimeMcpUrl(host, opts) {
35
+ const path = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
36
+ return `${normalizeAbleTimeHost(host)}${path}`;
37
+ }
38
+ var ABLETIME_PAT_PREFIX = "apt_";
39
+ var ABLETIME_ORG_KEY_PREFIX = "atk_";
40
+ var ABLETIME_OAUTH_TOKEN_PREFIX = "acn_";
41
+ var 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.";
42
+ var 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.";
43
+ var REWRITE_APPLIED = /— Enable Agent access \(MCP\)|— AbleTime MCP needs a personal access token|— AbleTime plan does not include API access|— AbleTime rejected this credential/;
44
+ function normalizeAbleTimeCredential(raw) {
45
+ return raw.trim().replace(/^bearer\s+/i, "").trim();
46
+ }
47
+ function ableTimeCredentialKind(raw) {
48
+ const token = normalizeAbleTimeCredential(raw);
49
+ const lower = token.toLowerCase();
50
+ if (lower.startsWith(ABLETIME_PAT_PREFIX)) return "pat";
51
+ if (lower.startsWith(ABLETIME_ORG_KEY_PREFIX)) return "org";
52
+ if (lower.startsWith(ABLETIME_OAUTH_TOKEN_PREFIX) || token.startsWith("eyJ")) return "oauth";
53
+ return "unknown";
54
+ }
55
+ function ableTimeUsesRest(raw) {
56
+ const token = normalizeAbleTimeCredential(raw);
57
+ const kind = ableTimeCredentialKind(token);
58
+ if (kind === "org") return true;
59
+ if (kind === "oauth") return token.startsWith("eyJ");
60
+ return kind === "unknown";
61
+ }
62
+ function isAbleTimeRejectedCredential(message) {
63
+ return /INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|INTEGRATION_PAT_REQUIRED|\b401\b/i.test(
64
+ message
65
+ );
66
+ }
67
+ function assertAbleTimeMcpCredential(raw) {
68
+ const token = normalizeAbleTimeCredential(raw);
69
+ if (!token) {
70
+ throw new Error("AbleTime personal access token is required");
71
+ }
72
+ if (ableTimeCredentialKind(token) === "org") {
73
+ throw new Error(ORG_KEY_HINT);
74
+ }
75
+ return token;
76
+ }
77
+ function assertAbleTimeCredential(raw) {
78
+ const token = normalizeAbleTimeCredential(raw);
79
+ if (!token) {
80
+ throw new Error("AbleTime API key or personal access token is required");
81
+ }
82
+ return token;
83
+ }
84
+ function rewriteAbleTimeError(message) {
85
+ if (REWRITE_APPLIED.test(message)) return message;
86
+ if (/INTEGRATION_AGENT_ACCESS_DISABLED/i.test(message)) {
87
+ return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
88
+ }
89
+ if (/INTEGRATION_PAT_REQUIRED/i.test(message)) {
90
+ return `${message} \u2014 ${ORG_KEY_HINT}`;
91
+ }
92
+ if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
93
+ return `${message} \u2014 AbleTime plan does not include API access.`;
94
+ }
95
+ if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
96
+ return `${message} \u2014 ${REJECTED_CREDENTIAL_HINT}`;
97
+ }
98
+ return message;
99
+ }
100
+ function asRecord(value) {
101
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
102
+ }
103
+ function parseSseJson(body) {
104
+ const blocks = body.split(/\n\n+/);
105
+ let last;
106
+ for (const block of blocks) {
107
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).filter((line) => line && line !== "[DONE]").join("\n");
108
+ if (!data) continue;
109
+ try {
110
+ last = JSON.parse(data);
111
+ } catch {
112
+ }
113
+ }
114
+ if (last !== void 0) return last;
115
+ try {
116
+ return JSON.parse(body);
117
+ } catch {
118
+ throw new Error(`AbleTime MCP returned a non-JSON response: ${body.slice(0, 200)}`);
119
+ }
120
+ }
121
+ function unwrapToolResult(result) {
122
+ const record = asRecord(result);
123
+ if (!record) return result;
124
+ const content = record.content;
125
+ if (Array.isArray(content)) {
126
+ const texts = content.map((part) => {
127
+ const item = asRecord(part);
128
+ if (!item) return "";
129
+ return typeof item.text === "string" ? item.text : "";
130
+ }).filter(Boolean);
131
+ if (texts.length === 1) {
132
+ const text = texts[0];
133
+ try {
134
+ return JSON.parse(text);
135
+ } catch {
136
+ return text;
137
+ }
138
+ }
139
+ if (texts.length > 1) return texts.join("\n");
140
+ }
141
+ if (record.structuredContent != null) return record.structuredContent;
142
+ if (record.isError === true) {
143
+ const message = typeof record.message === "string" ? record.message : textsFromUnknown(result) || "AbleTime tool failed";
144
+ throw new Error(message);
145
+ }
146
+ return result;
147
+ }
148
+ function textsFromUnknown(value) {
149
+ const record = asRecord(value);
150
+ if (!record) return "";
151
+ if (typeof record.message === "string") return record.message;
152
+ if (typeof record.error === "string") return record.error;
153
+ return "";
154
+ }
155
+ async function abletimeMcpRequest(method, params, opts) {
156
+ const rawToken = opts?.token ?? await import("./abletime-oauth-SD4SZY5U.js").then((m) => m.ensureAbleTimeAccessToken());
157
+ if (!rawToken?.trim()) {
158
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
159
+ }
160
+ const token = assertAbleTimeMcpCredential(rawToken);
161
+ const url = abletimeMcpUrl(
162
+ opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
163
+ { pm: opts?.pm }
164
+ );
165
+ const res = await httpFetch(url, {
166
+ method: "POST",
167
+ headers: {
168
+ "Content-Type": "application/json",
169
+ Accept: "application/json, text/event-stream",
170
+ Authorization: `Bearer ${token}`,
171
+ "MCP-Protocol-Version": MCP_PROTOCOL_VERSION
172
+ },
173
+ body: JSON.stringify({
174
+ jsonrpc: "2.0",
175
+ id: 1,
176
+ method,
177
+ ...params === void 0 ? {} : { params }
178
+ })
179
+ });
180
+ const body = await res.text().catch(() => "");
181
+ if (!res.ok) {
182
+ throw new Error(
183
+ rewriteAbleTimeError(
184
+ `AbleTime MCP error ${res.status}${body ? `: ${body.slice(0, 240)}` : ""}`
185
+ )
186
+ );
187
+ }
188
+ const contentType = res.headers.get("content-type") ?? "";
189
+ const parsed = contentType.includes("text/event-stream") ? parseSseJson(body) : (() => {
190
+ try {
191
+ return JSON.parse(body);
192
+ } catch {
193
+ return parseSseJson(body);
194
+ }
195
+ })();
196
+ const rpc = asRecord(parsed);
197
+ if (rpc?.error) {
198
+ const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
199
+ throw new Error(rewriteAbleTimeError(detail));
200
+ }
201
+ if (rpc && "result" in rpc) return rpc.result;
202
+ return parsed;
203
+ }
204
+ async function initializeIfNeeded(opts) {
205
+ try {
206
+ await abletimeMcpRequest(
207
+ "initialize",
208
+ {
209
+ protocolVersion: MCP_PROTOCOL_VERSION,
210
+ capabilities: {},
211
+ clientInfo: { name: "sideboard", version: "1.0.0" }
212
+ },
213
+ opts
214
+ );
215
+ } catch (err) {
216
+ const message = err instanceof Error ? err.message : String(err);
217
+ if (/already initialized|not (required|supported)|method not found|-32601/i.test(message)) {
218
+ return;
219
+ }
220
+ throw err;
221
+ }
222
+ }
223
+ async function callAbleTimeTool(name, args = {}, opts) {
224
+ if (!opts?.token && !isAbleTimeConnected()) {
225
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
226
+ }
227
+ const rawToken = opts?.token ?? await import("./abletime-oauth-SD4SZY5U.js").then((m) => m.ensureAbleTimeAccessToken());
228
+ if (rawToken && ableTimeUsesRest(rawToken)) {
229
+ const { callAbleTimeRestTool } = await import("./abletime-rest-J2OBB4VA.js");
230
+ return callAbleTimeRestTool(name, args, { ...opts, token: rawToken });
231
+ }
232
+ const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
233
+ const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
234
+ return unwrapToolResult(result);
235
+ });
236
+ try {
237
+ return await call();
238
+ } catch (err) {
239
+ const message = err instanceof Error ? err.message : String(err);
240
+ if (/initialize|session|not initialized|-32000/i.test(message)) {
241
+ await initializeIfNeeded(requestOpts);
242
+ return await call();
243
+ }
244
+ if (rawToken && ableTimeUsesRest(rawToken) && isAbleTimeRejectedCredential(message)) {
245
+ const { callAbleTimeRestTool } = await import("./abletime-rest-J2OBB4VA.js");
246
+ return callAbleTimeRestTool(name, args, { ...opts, token: rawToken });
247
+ }
248
+ throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
249
+ }
250
+ }
251
+
252
+ export {
253
+ DEFAULT_ABLETIME_HOST,
254
+ ABLETIME_MCP_PATH,
255
+ ABLETIME_MCP_PM_PATH,
256
+ ABLETIME_PM_TOOL_NAMES,
257
+ abletimeToolUsesPm,
258
+ normalizeAbleTimeHost,
259
+ abletimeMcpUrl,
260
+ ABLETIME_PAT_PREFIX,
261
+ ABLETIME_ORG_KEY_PREFIX,
262
+ ABLETIME_OAUTH_TOKEN_PREFIX,
263
+ normalizeAbleTimeCredential,
264
+ ableTimeCredentialKind,
265
+ ableTimeUsesRest,
266
+ isAbleTimeRejectedCredential,
267
+ assertAbleTimeMcpCredential,
268
+ assertAbleTimeCredential,
269
+ rewriteAbleTimeError,
270
+ abletimeMcpRequest,
271
+ callAbleTimeTool
272
+ };
@@ -2,7 +2,7 @@
2
2
 
3
3
  import {
4
4
  isOrchestratorThread
5
- } from "./chunk-54RSAMBH.js";
5
+ } from "./chunk-5SMS6QTO.js";
6
6
  import {
7
7
  applyConnectedTeamToCli,
8
8
  brightsyMcpServerName,
@@ -20,7 +20,7 @@ import {
20
20
  mergeAgentGitAuthEnv,
21
21
  resolveAgentGitAuthEnv,
22
22
  resolveCodexGitWritableRoots
23
- } from "./chunk-RPX6VDW7.js";
23
+ } from "./chunk-QUW5D62C.js";
24
24
  import {
25
25
  enrichPathWithNpmGlobalBin,
26
26
  isConductorBundledCli,
@@ -38,7 +38,7 @@ import {
38
38
  resolveClaudeExecutable,
39
39
  unwrapStrippedElectronLaunch,
40
40
  wrapElectronAsNodeLaunch
41
- } from "./chunk-ISV5IFH3.js";
41
+ } from "./chunk-X743YN5Y.js";
42
42
  import {
43
43
  appDataDir
44
44
  } from "./chunk-EQNRWDLN.js";
@@ -410,6 +410,9 @@ function humanizeAgentFailDetail(detail) {
410
410
  if (/\[resource_exhausted\]|resource_exhausted|findfileswithripgrep/.test(lower)) {
411
411
  return "Cursor local file search failed (ripgrep / resource_exhausted). Wait a minute and retry; if it keeps happening, check Cursor usage.";
412
412
  }
413
+ if (looksLikeCodexMcpError(raw)) {
414
+ return `${raw} \u2014 skip that vendor MCP and continue (PostHog: Settings \u2192 Connectors + HTTP API).`;
415
+ }
413
416
  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(
414
417
  lower
415
418
  )) {
@@ -467,9 +470,24 @@ function turnFailChatText(opts) {
467
470
 
468
471
  ${fail}`;
469
472
  }
473
+ function looksLikeCodexMcpError(text) {
474
+ const t = text.trim().toLowerCase();
475
+ if (!t) return false;
476
+ if (/authrequired|authorization required/.test(t) && /mcp|posthog|rmcp|connector|plugin/.test(t)) {
477
+ return true;
478
+ }
479
+ if (/\bposthog\b/.test(t) && /mcp|oauth|auth|unauthorized|401|plugin|app/.test(t)) {
480
+ return true;
481
+ }
482
+ if (/\brmcp\b/.test(t) && /fatal|quit|transport|error|died|kill|auth/.test(t)) {
483
+ return true;
484
+ }
485
+ return /mcp[_ -]?(server|tool|error|transport|client)/.test(t) && /fail|error|timeout|disconnect|auth|unauthorized/.test(t);
486
+ }
470
487
  function shouldFeedErrorBackToAgent(opts) {
471
488
  const detail = opts.detail.trim();
472
489
  const chat = (opts.assistantText ?? "").trim();
490
+ if (looksLikeCodexMcpError(detail)) return true;
473
491
  if (looksLikeAgentFailureMessage(detail) || looksLikeAgentFailureMessage(chat)) {
474
492
  return false;
475
493
  }
@@ -2423,7 +2441,7 @@ var claudeAdapter = {
2423
2441
  );
2424
2442
  }
2425
2443
  const mode = permissionMode(thread);
2426
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-NLLAPQJI.js");
2444
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-2I2T7FL5.js");
2427
2445
  const isOrchestrator = isOrchestratorThread2(thread);
2428
2446
  const injectedServers = await buildInjectedMcpServers({
2429
2447
  includeSideboard: true,
@@ -2755,7 +2773,18 @@ function toCodexDisableUserMcpArgs(names) {
2755
2773
  return args;
2756
2774
  }
2757
2775
  function toCodexUnattendedAppsArgs() {
2758
- return ["-c", "features.apps=false", "-c", "apps._default.enabled=false"];
2776
+ return [
2777
+ "-c",
2778
+ "features.apps=false",
2779
+ "-c",
2780
+ "apps._default.enabled=false",
2781
+ "-c",
2782
+ "apps.posthog.enabled=false",
2783
+ "-c",
2784
+ "features.plugins=false",
2785
+ "-c",
2786
+ "mcp_servers.posthog.enabled=false"
2787
+ ];
2759
2788
  }
2760
2789
 
2761
2790
  // src/agents/codex.ts
@@ -4422,11 +4451,14 @@ function staleOk(now) {
4422
4451
  if (now - lastGood.at > CLAUDE_USAGE_STALE_OK_MS) return null;
4423
4452
  return lastGood.value;
4424
4453
  }
4454
+ function reuseLast() {
4455
+ return lastGood?.value ?? null;
4456
+ }
4425
4457
  async function getClaudePlanUsage(opts = {}) {
4426
4458
  const now = opts.now ?? Date.now();
4459
+ if (now < errorUntil) return reuseLast() ?? staleOk(now);
4427
4460
  if (!opts.force) {
4428
4461
  if (cache && now - cache.at < CLAUDE_USAGE_TTL_MS) return cache.value;
4429
- if (now < errorUntil) return staleOk(now);
4430
4462
  }
4431
4463
  if (inflight) return inflight;
4432
4464
  inflight = (async () => {
@@ -4457,6 +4489,12 @@ async function getClaudePlanUsage(opts = {}) {
4457
4489
  cache = { at: now, value: null };
4458
4490
  return null;
4459
4491
  }
4492
+ if (res.status === 429) {
4493
+ errorUntil = now + CLAUDE_USAGE_ERROR_TTL_MS;
4494
+ const last = reuseLast();
4495
+ if (last) cache = { at: now, value: last };
4496
+ return last;
4497
+ }
4460
4498
  if (!res.ok) {
4461
4499
  errorUntil = now + CLAUDE_USAGE_ERROR_TTL_MS;
4462
4500
  return staleOk(now);
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  getGithubGitAuthMode,
9
9
  getGithubPat
10
- } from "./chunk-CXFW5LIH.js";
10
+ } from "./chunk-QFBBUFD4.js";
11
11
  import {
12
12
  listThreads
13
13
  } from "./chunk-DFUES6QU.js";
@@ -2154,26 +2154,21 @@ function resolvePrSelectors(thread) {
2154
2154
  function resolvePrSelector(thread) {
2155
2155
  return resolvePrSelectors(thread)[0] ?? null;
2156
2156
  }
2157
+ function connectedPrSelectors(thread, currentHeadPrUrl) {
2158
+ const out = [];
2159
+ const push = (value) => {
2160
+ const v = value?.trim();
2161
+ if (!v || out.includes(v)) return;
2162
+ out.push(v);
2163
+ };
2164
+ push(currentHeadPrUrl);
2165
+ for (const selector of resolvePrSelectors(thread)) push(selector);
2166
+ return out;
2167
+ }
2157
2168
  async function getPrForHeadBranch(repoPath, branch) {
2158
2169
  const head = normalizeGitBranchName(branch);
2159
2170
  if (!head || isDefaultishSourceRef(head)) return null;
2160
2171
  const slug = await resolveGithubRepoSlug(repoPath);
2161
- const viewArgs = [
2162
- "pr",
2163
- "view",
2164
- head,
2165
- "--json",
2166
- "number,title,headRefName,url,isCrossRepository,author"
2167
- ];
2168
- if (slug) viewArgs.push("--repo", slug);
2169
- const viewed = await gh(viewArgs, repoPath, { reject: false });
2170
- if (viewed.exitCode === 0 && viewed.stdout.trim()) {
2171
- try {
2172
- return JSON.parse(viewed.stdout);
2173
- } catch {
2174
- return null;
2175
- }
2176
- }
2177
2172
  const listHead = slug ? ghHeadRef(slug, head) : head;
2178
2173
  const listArgs = [
2179
2174
  "pr",
@@ -2189,10 +2184,28 @@ async function getPrForHeadBranch(repoPath, branch) {
2189
2184
  ];
2190
2185
  if (slug) listArgs.push("--repo", slug);
2191
2186
  const listed = await gh(listArgs, repoPath, { reject: false });
2192
- if (listed.exitCode !== 0 || !listed.stdout.trim()) return null;
2187
+ if (listed.exitCode === 0 && listed.stdout.trim()) {
2188
+ try {
2189
+ const rows = JSON.parse(listed.stdout);
2190
+ if (rows[0]) return rows[0];
2191
+ } catch {
2192
+ }
2193
+ }
2194
+ const viewArgs = [
2195
+ "pr",
2196
+ "view",
2197
+ head,
2198
+ "--json",
2199
+ "number,title,headRefName,url,isCrossRepository,author,state"
2200
+ ];
2201
+ if (slug) viewArgs.push("--repo", slug);
2202
+ const viewed = await gh(viewArgs, repoPath, { reject: false });
2203
+ if (viewed.exitCode !== 0 || !viewed.stdout.trim()) return null;
2193
2204
  try {
2194
- const rows = JSON.parse(listed.stdout);
2195
- return rows[0] ?? null;
2205
+ const info = JSON.parse(viewed.stdout);
2206
+ const state = (info.state ?? "OPEN").trim().toUpperCase();
2207
+ if (state !== "OPEN") return null;
2208
+ return info;
2196
2209
  } catch {
2197
2210
  return null;
2198
2211
  }
@@ -3313,6 +3326,7 @@ export {
3313
3326
  isDefaultishSourceRef,
3314
3327
  resolvePrSelectors,
3315
3328
  resolvePrSelector,
3329
+ connectedPrSelectors,
3316
3330
  getPrForHeadBranch,
3317
3331
  detectLocalMergeConflicts,
3318
3332
  getPrChecks,
@@ -356,6 +356,9 @@ function normalizeVault(raw) {
356
356
  if (typeof raw.abletimeAccessToken === "string" && raw.abletimeAccessToken.trim()) {
357
357
  out.abletimeAccessToken = raw.abletimeAccessToken.trim();
358
358
  }
359
+ if (typeof raw.abletimeRefreshToken === "string" && raw.abletimeRefreshToken.trim()) {
360
+ out.abletimeRefreshToken = raw.abletimeRefreshToken.trim();
361
+ }
359
362
  if (typeof raw.vercelToken === "string" && raw.vercelToken.trim()) {
360
363
  out.vercelToken = raw.vercelToken.trim();
361
364
  }
@@ -437,6 +440,7 @@ function emptyPublicIntegrations() {
437
440
  hasSlackAppToken: false,
438
441
  hasGithubPat: false,
439
442
  hasAbleTimeToken: false,
443
+ hasAbleTimeOAuth: false,
440
444
  hasVercelToken: false,
441
445
  hasSupabaseToken: false,
442
446
  hasPosthogToken: false,
@@ -461,6 +465,7 @@ function toPublicAppSettings(settings) {
461
465
  const slackAppToken = integrations.slackAppToken;
462
466
  const githubPat = integrations.githubPat;
463
467
  const abletimeAccessToken = integrations.abletimeAccessToken;
468
+ const abletimeRefreshToken = integrations.abletimeRefreshToken;
464
469
  const vercelToken = integrations.vercelToken;
465
470
  const supabaseAccessToken = integrations.supabaseAccessToken;
466
471
  const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
@@ -473,6 +478,7 @@ function toPublicAppSettings(settings) {
473
478
  delete integrations.slackAppToken;
474
479
  delete integrations.githubPat;
475
480
  delete integrations.abletimeAccessToken;
481
+ delete integrations.abletimeRefreshToken;
476
482
  delete integrations.vercelToken;
477
483
  delete integrations.supabaseAccessToken;
478
484
  delete integrations.posthogPersonalApiKey;
@@ -487,7 +493,8 @@ function toPublicAppSettings(settings) {
487
493
  hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
488
494
  hasSlackAppToken: Boolean(slackAppToken?.trim()),
489
495
  hasGithubPat: Boolean(githubPat?.trim()),
490
- hasAbleTimeToken: Boolean(abletimeAccessToken?.trim()),
496
+ hasAbleTimeToken: Boolean(abletimeAccessToken?.trim() || abletimeRefreshToken?.trim()),
497
+ hasAbleTimeOAuth: Boolean(abletimeRefreshToken?.trim()),
491
498
  hasVercelToken: Boolean(vercelToken?.trim()),
492
499
  hasSupabaseToken: Boolean(supabaseAccessToken?.trim()),
493
500
  hasPosthogToken: Boolean(posthogPersonalApiKey?.trim()),
@@ -633,6 +640,16 @@ function normalizeIntegrations(raw) {
633
640
  const token = source.abletimeAccessToken.trim();
634
641
  if (token) out.abletimeAccessToken = token;
635
642
  }
643
+ if (typeof source.abletimeRefreshToken === "string") {
644
+ const token = source.abletimeRefreshToken.trim();
645
+ if (token) out.abletimeRefreshToken = token;
646
+ }
647
+ if (typeof source.abletimeTokenExpiresAt === "number" && Number.isFinite(source.abletimeTokenExpiresAt)) {
648
+ out.abletimeTokenExpiresAt = source.abletimeTokenExpiresAt;
649
+ } else if (typeof source.abletimeTokenExpiresAt === "string") {
650
+ const n = Number(source.abletimeTokenExpiresAt);
651
+ if (Number.isFinite(n)) out.abletimeTokenExpiresAt = n;
652
+ }
636
653
  if (typeof source.abletimeHost === "string") {
637
654
  const host = source.abletimeHost.trim();
638
655
  if (host) out.abletimeHost = host;
@@ -921,7 +938,7 @@ function readSettingsFile() {
921
938
  }
922
939
  function diskHoldsSecrets(disk) {
923
940
  return Boolean(
924
- 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
941
+ 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
925
942
  );
926
943
  }
927
944
  function mergeVault(disk) {
@@ -952,6 +969,9 @@ function mergeVault(disk) {
952
969
  if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
953
970
  integrations.abletimeAccessToken = vault.abletimeAccessToken;
954
971
  }
972
+ if (vault.abletimeRefreshToken && !integrations.abletimeRefreshToken) {
973
+ integrations.abletimeRefreshToken = vault.abletimeRefreshToken;
974
+ }
955
975
  if (vault.vercelToken && !integrations.vercelToken) {
956
976
  integrations.vercelToken = vault.vercelToken;
957
977
  }
@@ -976,6 +996,7 @@ function persistSplit(settings) {
976
996
  const slackAppToken = integrations.slackAppToken;
977
997
  const githubPat = integrations.githubPat;
978
998
  const abletimeAccessToken = integrations.abletimeAccessToken;
999
+ const abletimeRefreshToken = integrations.abletimeRefreshToken;
979
1000
  const vercelToken = integrations.vercelToken;
980
1001
  const supabaseAccessToken = integrations.supabaseAccessToken;
981
1002
  const posthogPersonalApiKey = integrations.posthogPersonalApiKey;
@@ -988,6 +1009,7 @@ function persistSplit(settings) {
988
1009
  delete integrations.slackAppToken;
989
1010
  delete integrations.githubPat;
990
1011
  delete integrations.abletimeAccessToken;
1012
+ delete integrations.abletimeRefreshToken;
991
1013
  delete integrations.vercelToken;
992
1014
  delete integrations.supabaseAccessToken;
993
1015
  delete integrations.posthogPersonalApiKey;
@@ -1006,6 +1028,7 @@ function persistSplit(settings) {
1006
1028
  slackAppToken,
1007
1029
  githubPat,
1008
1030
  abletimeAccessToken,
1031
+ abletimeRefreshToken,
1009
1032
  vercelToken,
1010
1033
  supabaseAccessToken,
1011
1034
  posthogPersonalApiKey,
@@ -1192,6 +1215,8 @@ function updateIntegrationsSettings(patch) {
1192
1215
  if ("abletimeAccessToken" in patch) {
1193
1216
  if (patch.abletimeAccessToken == null || patch.abletimeAccessToken.trim() === "") {
1194
1217
  delete integrations.abletimeAccessToken;
1218
+ delete integrations.abletimeRefreshToken;
1219
+ delete integrations.abletimeTokenExpiresAt;
1195
1220
  delete integrations.abletimeViewerName;
1196
1221
  } else {
1197
1222
  integrations.abletimeAccessToken = patch.abletimeAccessToken.trim();
@@ -1544,7 +1569,9 @@ function disconnectLinearConnection() {
1544
1569
  return saveAppSettings({ ...current, integrations });
1545
1570
  }
1546
1571
  function isAbleTimeConnected(settings = loadAppSettings()) {
1547
- return Boolean(settings.integrations.abletimeAccessToken?.trim());
1572
+ return Boolean(
1573
+ settings.integrations.abletimeAccessToken?.trim() || settings.integrations.abletimeRefreshToken?.trim()
1574
+ );
1548
1575
  }
1549
1576
  function getAbleTimeAccessToken(settings = loadAppSettings()) {
1550
1577
  const token = settings.integrations.abletimeAccessToken?.trim();
@@ -1557,6 +1584,12 @@ function saveAbleTimeConnection(input) {
1557
1584
  const current = loadAppSettings();
1558
1585
  const integrations = { ...current.integrations };
1559
1586
  integrations.abletimeAccessToken = input.accessToken.trim();
1587
+ if (input.refreshToken?.trim()) {
1588
+ integrations.abletimeRefreshToken = input.refreshToken.trim();
1589
+ }
1590
+ if (typeof input.expiresIn === "number" && Number.isFinite(input.expiresIn)) {
1591
+ integrations.abletimeTokenExpiresAt = Date.now() + Math.max(0, input.expiresIn) * 1e3;
1592
+ }
1560
1593
  if (input.host?.trim()) {
1561
1594
  integrations.abletimeHost = input.host.trim();
1562
1595
  }
@@ -1569,6 +1602,8 @@ function disconnectAbleTimeConnection() {
1569
1602
  const current = loadAppSettings();
1570
1603
  const integrations = { ...current.integrations };
1571
1604
  delete integrations.abletimeAccessToken;
1605
+ delete integrations.abletimeRefreshToken;
1606
+ delete integrations.abletimeTokenExpiresAt;
1572
1607
  delete integrations.abletimeViewerName;
1573
1608
  return saveAppSettings({ ...current, integrations });
1574
1609
  }