@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.
- package/dist/abletime-DDTV7WO3.js +36 -0
- package/dist/abletime-VTP4JR63.js +37 -0
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-7BUQY2WX.js → agents-NHS6P25H.js} +8 -6
- package/dist/{agents-X2I3RNCY.js → agents-SOU5GPWI.js} +9 -7
- package/dist/{app-settings-6RPALX4J.js → app-settings-U5RZKUXG.js} +11 -1
- package/dist/{app-settings-E7NEQY7D.js → app-settings-YQ7YX2CC.js} +11 -1
- package/dist/chunk-2XXAAIN3.js +463 -0
- package/dist/{chunk-VE22NWBD.js → chunk-3UVVWNHF.js} +2 -2
- package/dist/{chunk-NV73AO7Q.js → chunk-4YAVXWFR.js} +21 -9
- package/dist/{chunk-RSFCB23A.js → chunk-6CRKPD4C.js} +4 -3
- package/dist/chunk-6GN5WPYZ.js +31 -0
- package/dist/{chunk-ZMUOPTKZ.js → chunk-AK7SWE2U.js} +55 -12
- package/dist/chunk-AT346U23.js +468 -0
- package/dist/{chunk-YMRT2DU6.js → chunk-ED4UPEJX.js} +25 -50
- package/dist/chunk-ELBYOGVA.js +32 -0
- package/dist/{chunk-IUJOI7KK.js → chunk-FSCQXMGI.js} +35 -4
- package/dist/{chunk-HUYEU4GS.js → chunk-GJBHOJCZ.js} +83 -2
- package/dist/{chunk-ZND6XV6J.js → chunk-GKKHXZCT.js} +83 -2
- package/dist/{chunk-HZPO3SSZ.js → chunk-IM36S4HZ.js} +4 -3
- package/dist/{chunk-EKIDHL2T.js → chunk-KBWND62T.js} +32 -55
- package/dist/{chunk-HULESWLI.js → chunk-LTP7GHIF.js} +3 -3
- package/dist/chunk-M267JPEA.js +103 -0
- package/dist/{chunk-HRSBZIXS.js → chunk-Q6XQXCOM.js} +1 -1
- package/dist/chunk-S42XV45P.js +104 -0
- package/dist/{chunk-YALVX3UE.js → chunk-TG7YU2KG.js} +157 -40
- package/dist/{chunk-FMBLOOAO.js → chunk-VSON7EJ6.js} +155 -39
- package/dist/{chunk-UDQI3N47.js → chunk-WEU5M7BW.js} +1 -1
- package/dist/{chunk-SYLTXDRF.js → chunk-Y645NFYN.js} +2 -2
- package/dist/{chunk-MNL4FSKY.js → chunk-YEIH7P7D.js} +3 -3
- package/dist/{connected-teams-ZCOU6KCD.js → connected-teams-4A2AXDNN.js} +3 -1
- package/dist/{connected-teams-O6D4PM7A.js → connected-teams-IJUJQ7R6.js} +3 -1
- package/dist/{coordinator-prompt-SPJSGAQS.js → coordinator-prompt-DXHEDRRN.js} +3 -3
- package/dist/{coordinator-prompt-MEEUOEF2.js → coordinator-prompt-XF7LVOUN.js} +3 -3
- package/dist/{global-workspace-KNESLWKG.js → global-workspace-2LO5ATOH.js} +4 -4
- package/dist/{global-workspace-BMWP7YZC.js → global-workspace-MLQNULOU.js} +4 -4
- package/dist/index.cjs +1410 -425
- package/dist/index.d.cts +224 -16
- package/dist/index.d.ts +224 -16
- package/dist/index.js +461 -292
- package/dist/mcp/run-stdio.cjs +1261 -342
- package/dist/mcp/run-stdio.js +340 -209
- package/dist/oauth-A7XXI2TM.js +11 -0
- package/dist/oauth-YJBAURIJ.js +13 -0
- package/dist/{orchestrator-3ZSFTG72.js → orchestrator-KR5RA5B7.js} +10 -8
- package/dist/{orchestrator-G6DU3AF3.js → orchestrator-P2SYHVLS.js} +11 -9
- package/dist/{workspaces-O4QZOASB.js → workspaces-ARWPZG6F.js} +5 -5
- package/dist/{workspaces-HSYRYJYV.js → workspaces-MINB764G.js} +5 -5
- package/dist/{worktree-ERHC7Q4F.js → worktree-EBZMUUAL.js} +2 -2
- package/dist/{worktree-JVGSLSLJ.js → worktree-S7MKMYAT.js} +2 -2
- 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-
|
|
11
|
+
} from "./chunk-S42XV45P.js";
|
|
10
12
|
import {
|
|
11
13
|
isOrchestratorThread
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YEIH7P7D.js";
|
|
13
15
|
import {
|
|
14
16
|
codexUnattendedGitConfigArgs,
|
|
15
17
|
mergeAgentGitAuthEnv,
|
|
16
18
|
resolveAgentGitAuthEnv,
|
|
17
19
|
resolveCodexGitWritableRoots
|
|
18
|
-
} from "./chunk-
|
|
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-
|
|
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 (
|
|
357
|
-
if (opts.exitCode === 0) return "";
|
|
358
|
+
if (opts.exitCode === 0) return chat;
|
|
358
359
|
const detail = opts.detail.trim();
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
+
};
|