@sideboard-ai/core 0.1.130 → 0.1.133

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 (51) hide show
  1. package/dist/abletime-DDTV7WO3.js +36 -0
  2. package/dist/abletime-VTP4JR63.js +37 -0
  3. package/dist/agents/cursor-runner.js +1 -1
  4. package/dist/{agents-7BUQY2WX.js → agents-NHS6P25H.js} +8 -6
  5. package/dist/{agents-X2I3RNCY.js → agents-SOU5GPWI.js} +9 -7
  6. package/dist/{app-settings-6RPALX4J.js → app-settings-U5RZKUXG.js} +11 -1
  7. package/dist/{app-settings-E7NEQY7D.js → app-settings-YQ7YX2CC.js} +11 -1
  8. package/dist/chunk-2XXAAIN3.js +463 -0
  9. package/dist/{chunk-VE22NWBD.js → chunk-3UVVWNHF.js} +2 -2
  10. package/dist/{chunk-NV73AO7Q.js → chunk-4YAVXWFR.js} +21 -9
  11. package/dist/{chunk-RSFCB23A.js → chunk-6CRKPD4C.js} +4 -3
  12. package/dist/chunk-6GN5WPYZ.js +31 -0
  13. package/dist/{chunk-ZMUOPTKZ.js → chunk-AK7SWE2U.js} +55 -12
  14. package/dist/chunk-AT346U23.js +468 -0
  15. package/dist/{chunk-YMRT2DU6.js → chunk-ED4UPEJX.js} +25 -50
  16. package/dist/chunk-ELBYOGVA.js +32 -0
  17. package/dist/{chunk-IUJOI7KK.js → chunk-FSCQXMGI.js} +35 -4
  18. package/dist/{chunk-HUYEU4GS.js → chunk-GJBHOJCZ.js} +83 -2
  19. package/dist/{chunk-ZND6XV6J.js → chunk-GKKHXZCT.js} +83 -2
  20. package/dist/{chunk-HZPO3SSZ.js → chunk-IM36S4HZ.js} +4 -3
  21. package/dist/{chunk-EKIDHL2T.js → chunk-KBWND62T.js} +32 -55
  22. package/dist/{chunk-HULESWLI.js → chunk-LTP7GHIF.js} +3 -3
  23. package/dist/chunk-M267JPEA.js +103 -0
  24. package/dist/{chunk-HRSBZIXS.js → chunk-Q6XQXCOM.js} +1 -1
  25. package/dist/chunk-S42XV45P.js +104 -0
  26. package/dist/{chunk-YALVX3UE.js → chunk-TG7YU2KG.js} +157 -40
  27. package/dist/{chunk-FMBLOOAO.js → chunk-VSON7EJ6.js} +155 -39
  28. package/dist/{chunk-UDQI3N47.js → chunk-WEU5M7BW.js} +1 -1
  29. package/dist/{chunk-SYLTXDRF.js → chunk-Y645NFYN.js} +2 -2
  30. package/dist/{chunk-MNL4FSKY.js → chunk-YEIH7P7D.js} +3 -3
  31. package/dist/{connected-teams-ZCOU6KCD.js → connected-teams-4A2AXDNN.js} +3 -1
  32. package/dist/{connected-teams-O6D4PM7A.js → connected-teams-IJUJQ7R6.js} +3 -1
  33. package/dist/{coordinator-prompt-SPJSGAQS.js → coordinator-prompt-DXHEDRRN.js} +3 -3
  34. package/dist/{coordinator-prompt-MEEUOEF2.js → coordinator-prompt-XF7LVOUN.js} +3 -3
  35. package/dist/{global-workspace-KNESLWKG.js → global-workspace-2LO5ATOH.js} +4 -4
  36. package/dist/{global-workspace-BMWP7YZC.js → global-workspace-MLQNULOU.js} +4 -4
  37. package/dist/index.cjs +1410 -425
  38. package/dist/index.d.cts +224 -16
  39. package/dist/index.d.ts +224 -16
  40. package/dist/index.js +461 -292
  41. package/dist/mcp/run-stdio.cjs +1261 -342
  42. package/dist/mcp/run-stdio.js +340 -209
  43. package/dist/oauth-A7XXI2TM.js +11 -0
  44. package/dist/oauth-YJBAURIJ.js +13 -0
  45. package/dist/{orchestrator-3ZSFTG72.js → orchestrator-KR5RA5B7.js} +10 -8
  46. package/dist/{orchestrator-G6DU3AF3.js → orchestrator-P2SYHVLS.js} +11 -9
  47. package/dist/{workspaces-O4QZOASB.js → workspaces-ARWPZG6F.js} +5 -5
  48. package/dist/{workspaces-HSYRYJYV.js → workspaces-MINB764G.js} +5 -5
  49. package/dist/{worktree-ERHC7Q4F.js → worktree-EBZMUUAL.js} +2 -2
  50. package/dist/{worktree-JVGSLSLJ.js → worktree-S7MKMYAT.js} +2 -2
  51. package/package.json +1 -1
@@ -4,18 +4,20 @@ import {
4
4
  applyConnectedTeamToCli,
5
5
  brightsyMcpServerName,
6
6
  ensureCliTeamTracked,
7
- ensureConnectedBrightsyTeamTokens,
7
+ ensureConnectedBrightsyTeamTokens
8
+ } from "./chunk-ED4UPEJX.js";
9
+ import {
8
10
  loadBrightsyConfig
9
- } from "./chunk-YMRT2DU6.js";
11
+ } from "./chunk-S42XV45P.js";
10
12
  import {
11
13
  isOrchestratorThread
12
- } from "./chunk-MNL4FSKY.js";
14
+ } from "./chunk-YEIH7P7D.js";
13
15
  import {
14
16
  codexUnattendedGitConfigArgs,
15
17
  mergeAgentGitAuthEnv,
16
18
  resolveAgentGitAuthEnv,
17
19
  resolveCodexGitWritableRoots
18
- } from "./chunk-HRSBZIXS.js";
20
+ } from "./chunk-Q6XQXCOM.js";
19
21
  import {
20
22
  enrichPathWithNpmGlobalBin,
21
23
  isConductorBundledCli,
@@ -31,7 +33,7 @@ import {
31
33
  resolveClaudeExecutable,
32
34
  unwrapStrippedElectronLaunch,
33
35
  wrapElectronAsNodeLaunch
34
- } from "./chunk-ZND6XV6J.js";
36
+ } from "./chunk-GKKHXZCT.js";
35
37
  import {
36
38
  appDataDir
37
39
  } from "./chunk-BD2ICC4D.js";
@@ -353,11 +355,40 @@ function humanizeAgentFailDetail(detail) {
353
355
  }
354
356
  function turnFailChatText(opts) {
355
357
  const chat = opts.assistantText.trim();
356
- if (chat) return chat;
357
- if (opts.exitCode === 0) return "";
358
+ if (opts.exitCode === 0) return chat;
358
359
  const detail = opts.detail.trim();
359
- if (detail) return humanizeAgentFailDetail(detail);
360
- return formatTurnExitError(opts.exitCode ?? 1, "");
360
+ const fail = detail ? humanizeAgentFailDetail(detail) : formatTurnExitError(opts.exitCode ?? 1, "");
361
+ if (!chat) return fail;
362
+ const failCore = fail.replace(/^exit\s*\d+:\s*/i, "").trim();
363
+ if (looksLikeAgentFailureMessage(chat) || failCore && chat.includes(failCore) || chat.includes(fail)) {
364
+ return chat;
365
+ }
366
+ return `${chat}
367
+
368
+ ${fail}`;
369
+ }
370
+ function shouldFeedErrorBackToAgent(opts) {
371
+ const detail = opts.detail.trim();
372
+ const chat = (opts.assistantText ?? "").trim();
373
+ if (looksLikeAgentFailureMessage(detail) || looksLikeAgentFailureMessage(chat)) {
374
+ return false;
375
+ }
376
+ if (looksLikeInvalidAgentSession(detail) || looksLikeV8Oom(detail) || looksLikeRetryableRunnerCrash(detail)) {
377
+ return true;
378
+ }
379
+ if (/without details|segfault|sig(?:segv|abrt|ill)|fatal error/i.test(detail)) {
380
+ return true;
381
+ }
382
+ return !chat && (opts.partsCount ?? 0) === 0;
383
+ }
384
+ function formatAgentErrorContinuePrompt(detail) {
385
+ const fail = humanizeAgentFailDetail(detail.trim()) || "the agent process exited without details";
386
+ return [
387
+ "The previous agent process ended before it finished. Error:",
388
+ fail,
389
+ "",
390
+ "Continue from where you left off. Use the error to recover \u2014 do not restart the whole task unless the error requires it. If you cannot continue, say why."
391
+ ].join("\n");
361
392
  }
362
393
  function formatTurnExitError(exitCode, stderrSummary) {
363
394
  const code = exitCode ?? 1;
@@ -1495,6 +1526,7 @@ function teamEnv(team) {
1495
1526
  const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
1496
1527
  return {
1497
1528
  BRIGHTSY_API_TOKEN: team.access_token,
1529
+ ...team.refresh_token ? { BRIGHTSY_REFRESH_TOKEN: team.refresh_token } : {},
1498
1530
  BRIGHTSY_ACCOUNT_ID: team.id,
1499
1531
  BRIGHTSY_API_URL: endpoint
1500
1532
  };
@@ -1757,11 +1789,17 @@ var CLAUDE_CHROME_ALLOWED_TOOLS = [
1757
1789
  "Skill(claude-in-chrome)"
1758
1790
  ];
1759
1791
  var CLAUDE_PROMPT_ARG_MAX = 2e5;
1792
+ var mcpListCache = null;
1760
1793
  async function loadMcpServers() {
1794
+ if (mcpListCache && Date.now() - mcpListCache.at < 3e4) {
1795
+ return mcpListCache.servers;
1796
+ }
1761
1797
  const claude = resolveClaudeExecutable();
1762
1798
  const mcpText = await run(claude, ["mcp", "list"], { reject: false });
1763
- return parseMcpList(`${mcpText.stdout}
1799
+ const servers = parseMcpList(`${mcpText.stdout}
1764
1800
  ${mcpText.stderr}`);
1801
+ mcpListCache = { at: Date.now(), servers };
1802
+ return servers;
1765
1803
  }
1766
1804
  function usageFromClaude(usage) {
1767
1805
  if (!usage) return null;
@@ -2004,7 +2042,7 @@ var claudeAdapter = {
2004
2042
  );
2005
2043
  }
2006
2044
  const mode = permissionMode(thread);
2007
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-KNESLWKG.js");
2045
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-2LO5ATOH.js");
2008
2046
  const isOrchestrator = isOrchestratorThread2(thread);
2009
2047
  const injectedServers = await buildInjectedMcpServers({
2010
2048
  includeSideboard: true,
@@ -3295,10 +3333,13 @@ var opencodeAdapter = {
3295
3333
  const part = obj.part;
3296
3334
  const id = part?.tool_use_id ?? part?.id ?? obj.tool_use_id ?? obj.id;
3297
3335
  if (!id) return null;
3336
+ const flagged = obj.isError === true || obj.is_error === true;
3337
+ const errText = formatUnknownDetail(obj.error);
3298
3338
  return {
3299
3339
  type: "tool_result",
3300
3340
  id,
3301
- content: part?.output ?? part?.content ?? obj.output ?? obj.content
3341
+ content: part?.output ?? part?.content ?? obj.output ?? obj.content ?? (errText || void 0),
3342
+ isError: flagged || Boolean(errText)
3302
3343
  };
3303
3344
  }
3304
3345
  if (obj.type === "step_finish" || obj.type === "step-finish") {
@@ -3822,6 +3863,8 @@ export {
3822
3863
  looksLikeAgentFailureMessage,
3823
3864
  fallbackTurnFailDetail,
3824
3865
  turnFailChatText,
3866
+ shouldFeedErrorBackToAgent,
3867
+ formatAgentErrorContinuePrompt,
3825
3868
  formatTurnExitError,
3826
3869
  sumUsageList,
3827
3870
  applyTurnUsage,
@@ -0,0 +1,468 @@
1
+ import {
2
+ httpFetch
3
+ } from "./chunk-ELBYOGVA.js";
4
+ import {
5
+ getAbleTimeAccessToken,
6
+ getAbleTimeHost,
7
+ isAbleTimeConnected,
8
+ saveAbleTimeConnection
9
+ } from "./chunk-GJBHOJCZ.js";
10
+
11
+ // src/integrations/abletime-mcp.ts
12
+ var DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
13
+ var ABLETIME_MCP_PATH = "/api/public/v2/mcp";
14
+ var MCP_PROTOCOL_VERSION = "2025-03-26";
15
+ function normalizeAbleTimeHost(raw) {
16
+ const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
17
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
18
+ return withScheme.replace(/\/+$/, "");
19
+ }
20
+ function abletimeMcpUrl(host) {
21
+ return `${normalizeAbleTimeHost(host)}${ABLETIME_MCP_PATH}`;
22
+ }
23
+ function rewriteAbleTimeError(message) {
24
+ if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
25
+ return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
26
+ }
27
+ if (/INTEGRATION_PAT_REQUIRED|organization api key/i.test(message)) {
28
+ return `${message} \u2014 AbleTime MCP needs a personal access token (apt_\u2026), not an organization API key.`;
29
+ }
30
+ if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
31
+ return `${message} \u2014 AbleTime plan does not include API access.`;
32
+ }
33
+ if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
34
+ return `${message} \u2014 Reconnect AbleTime from Account settings (Profile \u2192 API Access).`;
35
+ }
36
+ return message;
37
+ }
38
+ function asRecord(value) {
39
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
40
+ }
41
+ function parseSseJson(body) {
42
+ const blocks = body.split(/\n\n+/);
43
+ let last;
44
+ for (const block of blocks) {
45
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).filter((line) => line && line !== "[DONE]").join("\n");
46
+ if (!data) continue;
47
+ try {
48
+ last = JSON.parse(data);
49
+ } catch {
50
+ }
51
+ }
52
+ if (last !== void 0) return last;
53
+ try {
54
+ return JSON.parse(body);
55
+ } catch {
56
+ throw new Error(`AbleTime MCP returned a non-JSON response: ${body.slice(0, 200)}`);
57
+ }
58
+ }
59
+ function unwrapToolResult(result) {
60
+ const record = asRecord(result);
61
+ if (!record) return result;
62
+ const content = record.content;
63
+ if (Array.isArray(content)) {
64
+ const texts = content.map((part) => {
65
+ const item = asRecord(part);
66
+ if (!item) return "";
67
+ return typeof item.text === "string" ? item.text : "";
68
+ }).filter(Boolean);
69
+ if (texts.length === 1) {
70
+ const text = texts[0];
71
+ try {
72
+ return JSON.parse(text);
73
+ } catch {
74
+ return text;
75
+ }
76
+ }
77
+ if (texts.length > 1) return texts.join("\n");
78
+ }
79
+ if (record.structuredContent != null) return record.structuredContent;
80
+ if (record.isError === true) {
81
+ const message = typeof record.message === "string" ? record.message : textsFromUnknown(result) || "AbleTime tool failed";
82
+ throw new Error(message);
83
+ }
84
+ return result;
85
+ }
86
+ function textsFromUnknown(value) {
87
+ const record = asRecord(value);
88
+ if (!record) return "";
89
+ if (typeof record.message === "string") return record.message;
90
+ if (typeof record.error === "string") return record.error;
91
+ return "";
92
+ }
93
+ async function abletimeMcpRequest(method, params, opts) {
94
+ const token = (opts?.token ?? getAbleTimeAccessToken())?.trim();
95
+ if (!token) {
96
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
97
+ }
98
+ const url = abletimeMcpUrl(
99
+ opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost())
100
+ );
101
+ const res = await httpFetch(url, {
102
+ method: "POST",
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ Accept: "application/json, text/event-stream",
106
+ Authorization: `Bearer ${token}`,
107
+ "MCP-Protocol-Version": MCP_PROTOCOL_VERSION
108
+ },
109
+ body: JSON.stringify({
110
+ jsonrpc: "2.0",
111
+ id: 1,
112
+ method,
113
+ ...params === void 0 ? {} : { params }
114
+ })
115
+ });
116
+ const body = await res.text().catch(() => "");
117
+ if (!res.ok) {
118
+ throw new Error(
119
+ rewriteAbleTimeError(
120
+ `AbleTime MCP error ${res.status}${body ? `: ${body.slice(0, 240)}` : ""}`
121
+ )
122
+ );
123
+ }
124
+ const contentType = res.headers.get("content-type") ?? "";
125
+ const parsed = contentType.includes("text/event-stream") ? parseSseJson(body) : (() => {
126
+ try {
127
+ return JSON.parse(body);
128
+ } catch {
129
+ return parseSseJson(body);
130
+ }
131
+ })();
132
+ const rpc = asRecord(parsed);
133
+ if (rpc?.error) {
134
+ const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
135
+ throw new Error(rewriteAbleTimeError(detail));
136
+ }
137
+ if (rpc && "result" in rpc) return rpc.result;
138
+ return parsed;
139
+ }
140
+ async function initializeIfNeeded(opts) {
141
+ try {
142
+ await abletimeMcpRequest(
143
+ "initialize",
144
+ {
145
+ protocolVersion: MCP_PROTOCOL_VERSION,
146
+ capabilities: {},
147
+ clientInfo: { name: "sideboard", version: "1.0.0" }
148
+ },
149
+ opts
150
+ );
151
+ } catch (err) {
152
+ const message = err instanceof Error ? err.message : String(err);
153
+ if (/already initialized|not (required|supported)|method not found|-32601/i.test(message)) {
154
+ return;
155
+ }
156
+ throw err;
157
+ }
158
+ }
159
+ async function callAbleTimeTool(name, args = {}, opts) {
160
+ if (!opts?.token && !isAbleTimeConnected()) {
161
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
162
+ }
163
+ const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, opts).then((result) => {
164
+ return unwrapToolResult(result);
165
+ });
166
+ try {
167
+ return await call();
168
+ } catch (err) {
169
+ const message = err instanceof Error ? err.message : String(err);
170
+ if (/initialize|session|not initialized|-32000/i.test(message)) {
171
+ await initializeIfNeeded(opts);
172
+ return await call();
173
+ }
174
+ throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
175
+ }
176
+ }
177
+
178
+ // src/integrations/abletime.ts
179
+ var CLOSED_STATES = /* @__PURE__ */ new Set([
180
+ "done",
181
+ "completed",
182
+ "canceled",
183
+ "cancelled",
184
+ "archived",
185
+ "closed"
186
+ ]);
187
+ function asRecord2(value) {
188
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
189
+ }
190
+ function asString(value) {
191
+ return typeof value === "string" ? value.trim() : "";
192
+ }
193
+ function firstString(record, keys) {
194
+ if (!record) return "";
195
+ for (const key of keys) {
196
+ const value = asString(record[key]);
197
+ if (value) return value;
198
+ }
199
+ return "";
200
+ }
201
+ function firstRecord(record, keys) {
202
+ if (!record) return null;
203
+ for (const key of keys) {
204
+ const value = asRecord2(record[key]);
205
+ if (value) return value;
206
+ }
207
+ return null;
208
+ }
209
+ function asList(value) {
210
+ if (Array.isArray(value)) return value;
211
+ const record = asRecord2(value);
212
+ if (!record) return [];
213
+ for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
214
+ if (Array.isArray(record[key])) return record[key];
215
+ }
216
+ return [];
217
+ }
218
+ function labelsOf(record) {
219
+ const raw = record.tags ?? record.labels ?? record.tag_names;
220
+ if (!Array.isArray(raw)) return [];
221
+ return raw.map((item) => {
222
+ if (typeof item === "string") return item.trim();
223
+ const rec = asRecord2(item);
224
+ return rec ? firstString(rec, ["name", "title", "label"]) : "";
225
+ }).filter(Boolean);
226
+ }
227
+ function assigneeOf(record) {
228
+ const nested = firstRecord(record, ["assignee", "assigned_to", "user", "owner"]);
229
+ const name = firstString(nested, ["name", "display_name", "full_name", "email"]) || firstString(record, ["assignee_name", "assigneeName"]);
230
+ if (!name) return void 0;
231
+ const id = firstString(nested, ["id", "user_id"]);
232
+ return { id: id || void 0, name };
233
+ }
234
+ function taskUrl(task, host) {
235
+ const base = normalizeAbleTimeHost(host);
236
+ const slug = encodeURIComponent(task.identifier || task.id);
237
+ return `${base}/tasks/${slug}`;
238
+ }
239
+ function mapAbleTimeTask(raw, host) {
240
+ const record = asRecord2(raw);
241
+ if (!record) return null;
242
+ const nested = firstRecord(record, ["task", "data"]) ?? record;
243
+ const id = firstString(nested, ["id", "task_id", "taskId"]);
244
+ const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number"]) || id;
245
+ const title = firstString(nested, ["title", "name"]);
246
+ if (!id && !identifier && !title) return null;
247
+ const resolvedId = id || identifier;
248
+ const resolvedIdentifier = identifier || id;
249
+ const url = firstString(nested, ["url", "permalink", "html_url"]) || taskUrl({ id: resolvedId, identifier: resolvedIdentifier }, host);
250
+ return {
251
+ id: resolvedId,
252
+ identifier: resolvedIdentifier,
253
+ title: title || resolvedIdentifier,
254
+ url,
255
+ description: firstString(nested, ["description", "body", "details"]) || void 0,
256
+ state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
257
+ projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord2(nested.project), ["id"]) || void 0,
258
+ categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(asRecord2(nested.category), ["id"]) || void 0,
259
+ assignee: assigneeOf(nested),
260
+ labels: labelsOf(nested)
261
+ };
262
+ }
263
+ function toAbleTimeIssueInfo(task) {
264
+ return {
265
+ id: task.id || task.identifier,
266
+ identifier: task.identifier,
267
+ title: task.title,
268
+ url: task.url,
269
+ labels: task.labels,
270
+ provider: "abletime",
271
+ assignee: task.assignee?.name,
272
+ assignees: task.assignee?.name ? [task.assignee.name] : void 0
273
+ };
274
+ }
275
+ function issueAttachmentForAbleTimeTask(task) {
276
+ return {
277
+ id: `abletime-${task.id || task.identifier}`,
278
+ name: task.identifier || task.title,
279
+ kind: "issue",
280
+ content: [
281
+ `Linked issue: ${task.identifier} \u2014 ${task.title}`,
282
+ task.url ? `URL: ${task.url}` : null,
283
+ task.labels.length ? `Labels: ${task.labels.join(", ")}` : null
284
+ ].filter(Boolean).join("\n")
285
+ };
286
+ }
287
+ function mapProject(raw) {
288
+ const record = asRecord2(raw);
289
+ if (!record) return null;
290
+ const id = firstString(record, ["id", "project_id"]);
291
+ const name = firstString(record, ["name", "title"]);
292
+ if (!id && !name) return null;
293
+ const categories = asList(record.categories ?? record.category).map((item) => {
294
+ const rec = asRecord2(item);
295
+ if (!rec) return null;
296
+ const categoryId = firstString(rec, ["id", "category_id"]);
297
+ const categoryName = firstString(rec, ["name", "title"]);
298
+ if (!categoryId && !categoryName) return null;
299
+ return { id: categoryId || categoryName, name: categoryName || categoryId };
300
+ }).filter((item) => Boolean(item));
301
+ return { id: id || name, name: name || id, categories };
302
+ }
303
+ function isOpenTask(task) {
304
+ const state = (task.state ?? "").toLowerCase();
305
+ return !state || !CLOSED_STATES.has(state);
306
+ }
307
+ async function getAbleTimeOrientation(opts) {
308
+ const raw = await callAbleTimeTool("orientation", {}, opts);
309
+ const record = asRecord2(raw);
310
+ const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
311
+ const viewer = {
312
+ id: firstString(viewerRec, ["id", "user_id"]) || void 0,
313
+ name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
314
+ };
315
+ const projects = asList(
316
+ record?.projects ?? record?.current_projects ?? record?.currentProjects
317
+ ).map(mapProject).filter((item) => Boolean(item));
318
+ const tasks = asList(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
319
+ return { viewer, projects, tasks, raw };
320
+ }
321
+ async function listAbleTimeProjects(opts) {
322
+ const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
323
+ return asList(raw).map(mapProject).filter((item) => Boolean(item));
324
+ }
325
+ async function listAbleTimeTasks(opts) {
326
+ const args = {};
327
+ if (opts?.projectId) {
328
+ args.project_id = opts.projectId;
329
+ args.project = opts.projectId;
330
+ }
331
+ if (opts?.query) args.query = opts.query;
332
+ const raw = await callAbleTimeTool("list_tasks", args, opts);
333
+ const tasks = asList(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
334
+ return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
335
+ }
336
+ async function searchAbleTimeTasks(query, opts) {
337
+ const q = query.trim();
338
+ if (!q) return [];
339
+ const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
340
+ return asList(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
341
+ }
342
+ async function getAbleTimeTask(id, opts) {
343
+ const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
344
+ const task = mapAbleTimeTask(raw, opts?.host);
345
+ if (!task) throw new Error(`AbleTime task not found: ${id}`);
346
+ return task;
347
+ }
348
+ async function createAbleTimeTask(input, opts) {
349
+ const title = input.title.trim();
350
+ if (!title) throw new Error("AbleTime task title is required");
351
+ const project = await resolveAbleTimeProject(input.projectId, opts);
352
+ const category = resolveAbleTimeCategory(project, input.categoryId);
353
+ const raw = await callAbleTimeTool(
354
+ "create_task",
355
+ {
356
+ title,
357
+ description: input.description?.trim() || void 0,
358
+ state: input.state ?? "todo",
359
+ project: project.id,
360
+ project_id: project.id,
361
+ category: category?.id,
362
+ category_id: category?.id
363
+ },
364
+ opts
365
+ );
366
+ const task = mapAbleTimeTask(raw, opts?.host);
367
+ if (!task) throw new Error("AbleTime create_task returned no task");
368
+ return task;
369
+ }
370
+ async function resolveAbleTimeProject(projectId, opts) {
371
+ const wanted = projectId?.trim().toLowerCase();
372
+ const fromOrientation = (await getAbleTimeOrientation(opts).catch(() => null))?.projects ?? [];
373
+ const listed = fromOrientation.length > 0 ? fromOrientation : await listAbleTimeProjects(opts);
374
+ if (listed.length === 0) {
375
+ throw new Error(
376
+ rewriteAbleTimeError(
377
+ "AbleTime has no projects to create a task in \u2014 create a project in AbleTime first."
378
+ )
379
+ );
380
+ }
381
+ if (!wanted) return listed[0];
382
+ const found = listed.find(
383
+ (project) => project.id.toLowerCase() === wanted || project.name.toLowerCase() === wanted
384
+ );
385
+ if (!found) {
386
+ const names = listed.map((project) => project.name).join(", ") || "(none)";
387
+ throw new Error(`AbleTime project not found: ${projectId}. Known: ${names}`);
388
+ }
389
+ return found;
390
+ }
391
+ function resolveAbleTimeCategory(project, categoryId) {
392
+ if (project.categories.length === 0) return void 0;
393
+ const wanted = categoryId?.trim().toLowerCase();
394
+ if (!wanted) return project.categories[0];
395
+ return project.categories.find(
396
+ (category) => category.id.toLowerCase() === wanted || category.name.toLowerCase() === wanted
397
+ ) ?? project.categories[0];
398
+ }
399
+ function titlesMatch(a, b) {
400
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
401
+ }
402
+ async function ensureAbleTimeTask(input, opts) {
403
+ const title = input.title.trim();
404
+ if (!title) throw new Error("AbleTime task title is required");
405
+ const searched = await searchAbleTimeTasks(title, opts).catch(() => []);
406
+ const exact = searched.find((task) => titlesMatch(task.identifier, title)) || searched.find((task) => titlesMatch(task.title, title));
407
+ if (exact) return { ...exact, created: false };
408
+ const created = await createAbleTimeTask(
409
+ {
410
+ title,
411
+ description: input.description,
412
+ projectId: input.projectId,
413
+ categoryId: input.categoryId,
414
+ state: "todo"
415
+ },
416
+ opts
417
+ );
418
+ return { ...created, created: true };
419
+ }
420
+ async function listAbleTimeAssignedIssues(opts) {
421
+ if (!opts?.token && !isAbleTimeConnected()) {
422
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
423
+ }
424
+ const [orientation, listed] = await Promise.all([
425
+ getAbleTimeOrientation(opts).catch(() => null),
426
+ listAbleTimeTasks(opts)
427
+ ]);
428
+ const issues = listed.map(toAbleTimeIssueInfo);
429
+ return {
430
+ viewer: orientation?.viewer ?? {},
431
+ issues
432
+ };
433
+ }
434
+ async function verifyAbleTimeConnection(input) {
435
+ const token = input.token.trim();
436
+ if (!token) throw new Error("AbleTime personal access token is required");
437
+ const host = input.host?.trim() || void 0;
438
+ const orientation = await getAbleTimeOrientation({ token, host });
439
+ saveAbleTimeConnection({
440
+ accessToken: token,
441
+ host,
442
+ viewerName: orientation.viewer.name
443
+ });
444
+ return orientation.viewer;
445
+ }
446
+
447
+ export {
448
+ DEFAULT_ABLETIME_HOST,
449
+ ABLETIME_MCP_PATH,
450
+ normalizeAbleTimeHost,
451
+ abletimeMcpUrl,
452
+ rewriteAbleTimeError,
453
+ abletimeMcpRequest,
454
+ callAbleTimeTool,
455
+ taskUrl,
456
+ mapAbleTimeTask,
457
+ toAbleTimeIssueInfo,
458
+ issueAttachmentForAbleTimeTask,
459
+ getAbleTimeOrientation,
460
+ listAbleTimeProjects,
461
+ listAbleTimeTasks,
462
+ searchAbleTimeTasks,
463
+ getAbleTimeTask,
464
+ createAbleTimeTask,
465
+ ensureAbleTimeTask,
466
+ listAbleTimeAssignedIssues,
467
+ verifyAbleTimeConnection
468
+ };