@sideboard-ai/core 0.1.132 → 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 (49) hide show
  1. package/dist/abletime-DDTV7WO3.js +36 -0
  2. package/dist/abletime-VTP4JR63.js +37 -0
  3. package/dist/{agents-LGJLZMM3.js → agents-NHS6P25H.js} +8 -7
  4. package/dist/{agents-CI6NGKRO.js → agents-SOU5GPWI.js} +8 -7
  5. package/dist/{app-settings-6RPALX4J.js → app-settings-U5RZKUXG.js} +11 -1
  6. package/dist/{app-settings-E7NEQY7D.js → app-settings-YQ7YX2CC.js} +11 -1
  7. package/dist/chunk-2XXAAIN3.js +463 -0
  8. package/dist/{chunk-VE22NWBD.js → chunk-3UVVWNHF.js} +2 -2
  9. package/dist/{chunk-MGYCQAZ6.js → chunk-4YAVXWFR.js} +6 -6
  10. package/dist/{chunk-RSFCB23A.js → chunk-6CRKPD4C.js} +4 -3
  11. package/dist/chunk-6GN5WPYZ.js +31 -0
  12. package/dist/{chunk-PK4PQUPU.js → chunk-AK7SWE2U.js} +6 -6
  13. package/dist/chunk-AT346U23.js +468 -0
  14. package/dist/{chunk-LLX3OMHU.js → chunk-ED4UPEJX.js} +1 -1
  15. package/dist/chunk-ELBYOGVA.js +32 -0
  16. package/dist/{chunk-HUYEU4GS.js → chunk-GJBHOJCZ.js} +83 -2
  17. package/dist/{chunk-ZND6XV6J.js → chunk-GKKHXZCT.js} +83 -2
  18. package/dist/{chunk-HZPO3SSZ.js → chunk-IM36S4HZ.js} +4 -3
  19. package/dist/{chunk-SIPDL62B.js → chunk-KBWND62T.js} +6 -6
  20. package/dist/{chunk-HULESWLI.js → chunk-LTP7GHIF.js} +3 -3
  21. package/dist/{chunk-AQESMTCZ.js → chunk-M267JPEA.js} +3 -29
  22. package/dist/{chunk-HRSBZIXS.js → chunk-Q6XQXCOM.js} +1 -1
  23. package/dist/{chunk-R7FGPBKL.js → chunk-S42XV45P.js} +3 -25
  24. package/dist/{chunk-VFXZTSAD.js → chunk-TG7YU2KG.js} +53 -29
  25. package/dist/{chunk-SMWJAE74.js → chunk-VSON7EJ6.js} +53 -29
  26. package/dist/{chunk-UDQI3N47.js → chunk-WEU5M7BW.js} +1 -1
  27. package/dist/{chunk-SYLTXDRF.js → chunk-Y645NFYN.js} +2 -2
  28. package/dist/{chunk-MNL4FSKY.js → chunk-YEIH7P7D.js} +3 -3
  29. package/dist/{connected-teams-S6E3D5C7.js → connected-teams-4A2AXDNN.js} +3 -2
  30. package/dist/{connected-teams-3BWNJIRY.js → connected-teams-IJUJQ7R6.js} +3 -2
  31. package/dist/{coordinator-prompt-SPJSGAQS.js → coordinator-prompt-DXHEDRRN.js} +3 -3
  32. package/dist/{coordinator-prompt-MEEUOEF2.js → coordinator-prompt-XF7LVOUN.js} +3 -3
  33. package/dist/{global-workspace-KNESLWKG.js → global-workspace-2LO5ATOH.js} +4 -4
  34. package/dist/{global-workspace-BMWP7YZC.js → global-workspace-MLQNULOU.js} +4 -4
  35. package/dist/index.cjs +1105 -340
  36. package/dist/index.d.cts +171 -13
  37. package/dist/index.d.ts +171 -13
  38. package/dist/index.js +432 -239
  39. package/dist/mcp/run-stdio.cjs +998 -287
  40. package/dist/mcp/run-stdio.js +338 -186
  41. package/dist/{oauth-FT7EL2PY.js → oauth-A7XXI2TM.js} +2 -1
  42. package/dist/{oauth-43NNH6C6.js → oauth-YJBAURIJ.js} +2 -1
  43. package/dist/{orchestrator-JA65XKB6.js → orchestrator-KR5RA5B7.js} +10 -9
  44. package/dist/{orchestrator-5TN2DJWO.js → orchestrator-P2SYHVLS.js} +10 -9
  45. package/dist/{workspaces-O4QZOASB.js → workspaces-ARWPZG6F.js} +5 -5
  46. package/dist/{workspaces-HSYRYJYV.js → workspaces-MINB764G.js} +5 -5
  47. package/dist/{worktree-ERHC7Q4F.js → worktree-EBZMUUAL.js} +2 -2
  48. package/dist/{worktree-JVGSLSLJ.js → worktree-S7MKMYAT.js} +2 -2
  49. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -107,6 +107,7 @@ import {
107
107
  initStackFromThread,
108
108
  initializeGitRepository,
109
109
  inspectGitWorktree,
110
+ isAccountWideIssueSource,
110
111
  isCowboyThread,
111
112
  isDesktopHostAlive,
112
113
  isHomeBoardThread,
@@ -186,7 +187,7 @@ import {
186
187
  worktreeCleanupSettings,
187
188
  wrapReviewSkillMarkdown,
188
189
  writeWorktreeFile
189
- } from "./chunk-VFXZTSAD.js";
190
+ } from "./chunk-TG7YU2KG.js";
190
191
  import {
191
192
  BRIGHTSY_MCP_ALLOWED_TOOLS,
192
193
  CLAUDE_MODEL_CATALOG,
@@ -265,7 +266,7 @@ import {
265
266
  withEventParentId,
266
267
  withEventsParentId,
267
268
  writeInjectedMcpConfig
268
- } from "./chunk-MGYCQAZ6.js";
269
+ } from "./chunk-4YAVXWFR.js";
269
270
  import {
270
271
  brightsyMcpServerName,
271
272
  connectBrightsyTeam,
@@ -275,25 +276,22 @@ import {
275
276
  listBrightsyAccounts,
276
277
  listConnectedBrightsyTeams,
277
278
  switchBrightsyAccount
278
- } from "./chunk-SIPDL62B.js";
279
+ } from "./chunk-KBWND62T.js";
279
280
  import {
280
281
  brightsyAccessTokenNeedsRefresh,
281
282
  brightsyConfigPath,
282
283
  ensureBrightsyLocalConfigFresh,
283
- formatFetchError,
284
- httpFetch,
285
284
  loadBrightsyConfig,
286
285
  refreshBrightsyAccessToken,
287
- saveBrightsyConfig,
288
- setHttpFetchImpl
289
- } from "./chunk-AQESMTCZ.js";
286
+ saveBrightsyConfig
287
+ } from "./chunk-M267JPEA.js";
290
288
  import {
291
289
  addWorkspace,
292
290
  ensureWorkspace,
293
291
  listWorkspaces,
294
292
  removeWorkspace,
295
293
  syncWorkspacesFromThreads
296
- } from "./chunk-VE22NWBD.js";
294
+ } from "./chunk-3UVVWNHF.js";
297
295
  import {
298
296
  CLOUD_COORDINATOR_BUSY_REPLY,
299
297
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -321,7 +319,7 @@ import {
321
319
  parseForceStopMessage,
322
320
  slackCoordinatorSourceRef,
323
321
  takenTeamSlugsForOrchestration
324
- } from "./chunk-HULESWLI.js";
322
+ } from "./chunk-LTP7GHIF.js";
325
323
  import {
326
324
  COORDINATOR_TOOL_PLAYBOOK,
327
325
  SLACK_REPLY_FORMATTING,
@@ -330,7 +328,34 @@ import {
330
328
  enrichWorkspacesWithGithub,
331
329
  ensureGlobalCoordinatorCwd,
332
330
  formatWorkspaceInventory
333
- } from "./chunk-HZPO3SSZ.js";
331
+ } from "./chunk-IM36S4HZ.js";
332
+ import {
333
+ ABLETIME_MCP_PATH,
334
+ DEFAULT_ABLETIME_HOST,
335
+ abletimeMcpRequest,
336
+ abletimeMcpUrl,
337
+ callAbleTimeTool,
338
+ createAbleTimeTask,
339
+ ensureAbleTimeTask,
340
+ getAbleTimeOrientation,
341
+ getAbleTimeTask,
342
+ issueAttachmentForAbleTimeTask,
343
+ listAbleTimeAssignedIssues,
344
+ listAbleTimeProjects,
345
+ listAbleTimeTasks,
346
+ mapAbleTimeTask,
347
+ normalizeAbleTimeHost,
348
+ rewriteAbleTimeError,
349
+ searchAbleTimeTasks,
350
+ taskUrl,
351
+ toAbleTimeIssueInfo,
352
+ verifyAbleTimeConnection
353
+ } from "./chunk-AT346U23.js";
354
+ import {
355
+ formatFetchError,
356
+ httpFetch,
357
+ setHttpFetchImpl
358
+ } from "./chunk-ELBYOGVA.js";
334
359
  import {
335
360
  LEGACY_PLAN_FILE_REL,
336
361
  PLAN_FILE_NAME,
@@ -440,7 +465,7 @@ import {
440
465
  worktreeDisplayLabel,
441
466
  worktreeDisplayLabelForGroup,
442
467
  worktreeNameFromPath
443
- } from "./chunk-UDQI3N47.js";
468
+ } from "./chunk-WEU5M7BW.js";
444
469
  import {
445
470
  ATTACHMENTS_DIR,
446
471
  LEGACY_ATTACHMENTS_DIR,
@@ -469,8 +494,11 @@ import {
469
494
  claudeUserSettingsPath,
470
495
  cowboyModeEnabled,
471
496
  deleteBranchOnPurgeEnabled,
497
+ disconnectAbleTimeConnection,
472
498
  disconnectLinearConnection,
473
499
  ensureSlackDeviceIdentity,
500
+ getAbleTimeAccessToken,
501
+ getAbleTimeHost,
474
502
  getDefaultAgent,
475
503
  getDefaultEffort,
476
504
  getDefaultFast,
@@ -480,6 +508,7 @@ import {
480
508
  getIssueSource,
481
509
  getLinearApiKey,
482
510
  harnessEnvKey,
511
+ isAbleTimeConnected,
483
512
  isIssueSourceConnected,
484
513
  isLinearConnected,
485
514
  issueSourceLabel,
@@ -495,6 +524,7 @@ import {
495
524
  resolveNewThreadOptions,
496
525
  resolveThreadDefaults,
497
526
  resolveVaultKey,
527
+ saveAbleTimeConnection,
498
528
  saveAppSettings,
499
529
  saveLinearOAuth,
500
530
  secureFileUnlocksWith,
@@ -512,7 +542,7 @@ import {
512
542
  updateDefaultsSettings,
513
543
  updateIntegrationsSettings,
514
544
  updateOpencodeSettings
515
- } from "./chunk-HUYEU4GS.js";
545
+ } from "./chunk-GJBHOJCZ.js";
516
546
  import {
517
547
  stripNestedElectronEnv
518
548
  } from "./chunk-4TR3HZFT.js";
@@ -608,9 +638,9 @@ async function getGitHubStatus() {
608
638
  };
609
639
  }
610
640
  const status = await run("gh", ["auth", "status"], { reject: false });
611
- const text4 = `${status.stdout}
641
+ const text5 = `${status.stdout}
612
642
  ${status.stderr}`;
613
- const loginMatch = text4.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text4.match(/Logged in to ([^\s]+) as (\S+)/i);
643
+ const loginMatch = text5.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text5.match(/Logged in to ([^\s]+) as (\S+)/i);
614
644
  if (loginMatch) {
615
645
  return {
616
646
  connected: true,
@@ -1311,6 +1341,7 @@ async function listIssues(repoPath) {
1311
1341
  const settings = loadAppSettings();
1312
1342
  const preferredSource = settings.integrations.issueSource ?? "github";
1313
1343
  const linearConnected = isLinearConnected(settings);
1344
+ const abletimeConnected = isAbleTimeConnected(settings);
1314
1345
  const source = resolveEffectiveIssueSource(settings);
1315
1346
  if (source === "linear") {
1316
1347
  const listed = await listLinearAssignedIssues();
@@ -1318,6 +1349,21 @@ async function listIssues(repoPath) {
1318
1349
  source,
1319
1350
  preferredSource,
1320
1351
  linearConnected,
1352
+ abletimeConnected,
1353
+ issues: listed.issues,
1354
+ viewer: {
1355
+ login: listed.viewer.name,
1356
+ name: listed.viewer.name
1357
+ }
1358
+ };
1359
+ }
1360
+ if (source === "abletime") {
1361
+ const listed = await listAbleTimeAssignedIssues();
1362
+ return {
1363
+ source,
1364
+ preferredSource,
1365
+ linearConnected,
1366
+ abletimeConnected,
1321
1367
  issues: listed.issues,
1322
1368
  viewer: {
1323
1369
  login: listed.viewer.name,
@@ -1333,6 +1379,7 @@ async function listIssues(repoPath) {
1333
1379
  source,
1334
1380
  preferredSource,
1335
1381
  linearConnected,
1382
+ abletimeConnected,
1336
1383
  issues,
1337
1384
  viewer: login ? { login } : void 0
1338
1385
  };
@@ -1392,16 +1439,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
1392
1439
  var PASTE_ATTACH_MIN_LINES = 15;
1393
1440
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
1394
1441
  var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
1395
- function pastedTextStats(text4) {
1396
- const chars = text4.length;
1442
+ function pastedTextStats(text5) {
1443
+ const chars = text5.length;
1397
1444
  if (chars === 0) return { chars: 0, lines: 0 };
1398
- const lines = text4.split(/\r\n|\r|\n/).length;
1445
+ const lines = text5.split(/\r\n|\r|\n/).length;
1399
1446
  return { chars, lines };
1400
1447
  }
1401
- function shouldAttachPastedText(text4) {
1402
- const trimmed = text4.trim();
1448
+ function shouldAttachPastedText(text5) {
1449
+ const trimmed = text5.trim();
1403
1450
  if (!trimmed) return false;
1404
- const { chars, lines } = pastedTextStats(text4);
1451
+ const { chars, lines } = pastedTextStats(text5);
1405
1452
  return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
1406
1453
  }
1407
1454
  function nextPastedTextName(existing) {
@@ -1412,13 +1459,13 @@ function nextPastedTextName(existing) {
1412
1459
  }
1413
1460
  return `Pasted text #${max + 1}.txt`;
1414
1461
  }
1415
- function buildPastedTextAttachment(text4, opts) {
1462
+ function buildPastedTextAttachment(text5, opts) {
1416
1463
  return {
1417
1464
  id: opts?.id ?? randomUUID(),
1418
1465
  name: opts?.name ?? "Pasted text #1.txt",
1419
1466
  kind: "file",
1420
1467
  path: opts?.path,
1421
- content: text4
1468
+ content: text5
1422
1469
  };
1423
1470
  }
1424
1471
 
@@ -1539,7 +1586,7 @@ function formatPlanQuestionsForChat(questions) {
1539
1586
  // src/mcp/server.ts
1540
1587
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1541
1588
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1542
- import { z as z4 } from "zod";
1589
+ import { z as z5 } from "zod";
1543
1590
  import { randomUUID as randomUUID3 } from "crypto";
1544
1591
  import { basename } from "path";
1545
1592
 
@@ -1781,8 +1828,8 @@ function labelGithubUrl(url) {
1781
1828
  }
1782
1829
  return { kind: "other", label: "GitHub", url: raw };
1783
1830
  }
1784
- function appendGithubLink(text4, githubUrl) {
1785
- const body = text4.trimEnd();
1831
+ function appendGithubLink(text5, githubUrl) {
1832
+ const body = text5.trimEnd();
1786
1833
  if (!githubUrl?.trim()) return body;
1787
1834
  const labeled = labelGithubUrl(githubUrl);
1788
1835
  if (!labeled) {
@@ -2065,7 +2112,7 @@ function registerSlackTools(server) {
2065
2112
  );
2066
2113
  }
2067
2114
 
2068
- // src/mcp/linear-tools.ts
2115
+ // src/mcp/abletime-tools.ts
2069
2116
  import { z as z2 } from "zod";
2070
2117
  function text2(payload, isError = false) {
2071
2118
  return {
@@ -2079,7 +2126,127 @@ function fail2(err) {
2079
2126
  true
2080
2127
  );
2081
2128
  }
2082
- var prioritySchema = z2.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
2129
+ function registerAbleTimeTools(server) {
2130
+ server.tool(
2131
+ "abletime_orientation",
2132
+ "Call first when using AbleTime. Returns who the Account token is, open drafts, active tasks, and current projects. Requires Agent access (MCP) enabled in AbleTime.",
2133
+ {},
2134
+ async () => {
2135
+ try {
2136
+ const orientation = await getAbleTimeOrientation();
2137
+ return text2({
2138
+ viewer: orientation.viewer,
2139
+ projects: orientation.projects,
2140
+ tasks: orientation.tasks.map(toAbleTimeIssueInfo)
2141
+ });
2142
+ } catch (err) {
2143
+ return fail2(err);
2144
+ }
2145
+ }
2146
+ );
2147
+ server.tool(
2148
+ "abletime_list_projects",
2149
+ "List AbleTime projects (and categories) the Account token can see. Use a project id on abletime_create_task / abletime_ensure_task.",
2150
+ {},
2151
+ async () => {
2152
+ try {
2153
+ return text2({ projects: await listAbleTimeProjects() });
2154
+ } catch (err) {
2155
+ return fail2(err);
2156
+ }
2157
+ }
2158
+ );
2159
+ server.tool(
2160
+ "abletime_list_tasks",
2161
+ "List open AbleTime tasks (excludes done/archived). Optional projectId filter.",
2162
+ { projectId: z2.string().optional() },
2163
+ async ({ projectId }) => {
2164
+ try {
2165
+ const tasks = await listAbleTimeTasks({ projectId });
2166
+ return text2({ issues: tasks.map(toAbleTimeIssueInfo) });
2167
+ } catch (err) {
2168
+ return fail2(err);
2169
+ }
2170
+ }
2171
+ );
2172
+ server.tool(
2173
+ "abletime_search_tasks",
2174
+ "Search AbleTime tasks by text, reference, or tag.",
2175
+ { query: z2.string() },
2176
+ async ({ query }) => {
2177
+ try {
2178
+ const tasks = await searchAbleTimeTasks(query);
2179
+ return text2({ issues: tasks.map(toAbleTimeIssueInfo) });
2180
+ } catch (err) {
2181
+ return fail2(err);
2182
+ }
2183
+ }
2184
+ );
2185
+ server.tool(
2186
+ "abletime_get_task",
2187
+ "Get one AbleTime task by id or reference (e.g. CRM-232).",
2188
+ { id: z2.string() },
2189
+ async ({ id }) => {
2190
+ try {
2191
+ return text2(toAbleTimeIssueInfo(await getAbleTimeTask(id)));
2192
+ } catch (err) {
2193
+ return fail2(err);
2194
+ }
2195
+ }
2196
+ );
2197
+ server.tool(
2198
+ "abletime_create_task",
2199
+ "Create an AbleTime task in a project. Call abletime_list_projects if you do not have a project id. New tasks start in todo (or backlog).",
2200
+ {
2201
+ title: z2.string(),
2202
+ description: z2.string().optional(),
2203
+ projectId: z2.string().optional(),
2204
+ categoryId: z2.string().optional(),
2205
+ state: z2.enum(["backlog", "todo"]).optional()
2206
+ },
2207
+ async (args) => {
2208
+ try {
2209
+ return text2(toAbleTimeIssueInfo(await createAbleTimeTask(args)));
2210
+ } catch (err) {
2211
+ return fail2(err);
2212
+ }
2213
+ }
2214
+ );
2215
+ server.tool(
2216
+ "abletime_ensure_task",
2217
+ "Find an open AbleTime task for this work, or create one to track against. Matches identifier or exact title before creating. Prefer this when starting work that has no ticket yet, then create_thread with sourceType=ticket and the returned identifier.",
2218
+ {
2219
+ title: z2.string(),
2220
+ description: z2.string().optional(),
2221
+ projectId: z2.string().optional(),
2222
+ categoryId: z2.string().optional()
2223
+ },
2224
+ async (args) => {
2225
+ try {
2226
+ const task = await ensureAbleTimeTask(args);
2227
+ return text2({ ...toAbleTimeIssueInfo(task), created: task.created });
2228
+ } catch (err) {
2229
+ return fail2(err);
2230
+ }
2231
+ }
2232
+ );
2233
+ }
2234
+
2235
+ // src/mcp/linear-tools.ts
2236
+ import { z as z3 } from "zod";
2237
+ function text3(payload, isError = false) {
2238
+ return {
2239
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
2240
+ ...isError ? { isError: true } : {}
2241
+ };
2242
+ }
2243
+ function fail3(err) {
2244
+ return text3(
2245
+ { error: err instanceof Error ? err.message : String(err) },
2246
+ true
2247
+ );
2248
+ }
2249
+ var prioritySchema = z3.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
2083
2250
  function registerLinearTools(server) {
2084
2251
  server.tool(
2085
2252
  "linear_list_teams",
@@ -2087,21 +2254,21 @@ function registerLinearTools(server) {
2087
2254
  {},
2088
2255
  async () => {
2089
2256
  try {
2090
- return text2(await listLinearTeams());
2257
+ return text3(await listLinearTeams());
2091
2258
  } catch (err) {
2092
- return fail2(err);
2259
+ return fail3(err);
2093
2260
  }
2094
2261
  }
2095
2262
  );
2096
2263
  server.tool(
2097
2264
  "linear_get_issue",
2098
2265
  "Get a Linear issue by uuid or identifier (ENG-123).",
2099
- { id: z2.string() },
2266
+ { id: z3.string() },
2100
2267
  async ({ id }) => {
2101
2268
  try {
2102
- return text2(await getLinearIssue(id));
2269
+ return text3(await getLinearIssue(id));
2103
2270
  } catch (err) {
2104
- return fail2(err);
2271
+ return fail3(err);
2105
2272
  }
2106
2273
  }
2107
2274
  );
@@ -2109,18 +2276,18 @@ function registerLinearTools(server) {
2109
2276
  "linear_create_issue",
2110
2277
  'Create a Linear issue. Call linear_list_teams first. team is id, key, or name. state is name, type (unstarted/started/completed/canceled/backlog), or id. assignee is "me" or a user id.',
2111
2278
  {
2112
- team: z2.string(),
2113
- title: z2.string(),
2114
- description: z2.string().optional(),
2115
- state: z2.string().optional(),
2116
- assignee: z2.string().nullable().optional(),
2279
+ team: z3.string(),
2280
+ title: z3.string(),
2281
+ description: z3.string().optional(),
2282
+ state: z3.string().optional(),
2283
+ assignee: z3.string().nullable().optional(),
2117
2284
  priority: prioritySchema
2118
2285
  },
2119
2286
  async (args) => {
2120
2287
  try {
2121
- return text2(await createLinearIssue(args));
2288
+ return text3(await createLinearIssue(args));
2122
2289
  } catch (err) {
2123
- return fail2(err);
2290
+ return fail3(err);
2124
2291
  }
2125
2292
  }
2126
2293
  );
@@ -2128,18 +2295,18 @@ function registerLinearTools(server) {
2128
2295
  "linear_update_issue",
2129
2296
  'Update a Linear issue (uuid or ENG-123). Pass at least one of title, description, state, assignee, priority. state is name, type, or id. assignee is "me", a user id, or null to unassign.',
2130
2297
  {
2131
- id: z2.string(),
2132
- title: z2.string().optional(),
2133
- description: z2.string().optional(),
2134
- state: z2.string().optional(),
2135
- assignee: z2.string().nullable().optional(),
2298
+ id: z3.string(),
2299
+ title: z3.string().optional(),
2300
+ description: z3.string().optional(),
2301
+ state: z3.string().optional(),
2302
+ assignee: z3.string().nullable().optional(),
2136
2303
  priority: prioritySchema
2137
2304
  },
2138
2305
  async (args) => {
2139
2306
  try {
2140
- return text2(await updateLinearIssue(args));
2307
+ return text3(await updateLinearIssue(args));
2141
2308
  } catch (err) {
2142
- return fail2(err);
2309
+ return fail3(err);
2143
2310
  }
2144
2311
  }
2145
2312
  );
@@ -2147,29 +2314,29 @@ function registerLinearTools(server) {
2147
2314
  "linear_comment",
2148
2315
  "Add a markdown comment on a Linear issue (uuid or ENG-123).",
2149
2316
  {
2150
- id: z2.string(),
2151
- body: z2.string()
2317
+ id: z3.string(),
2318
+ body: z3.string()
2152
2319
  },
2153
2320
  async (args) => {
2154
2321
  try {
2155
- return text2(await commentLinearIssue(args));
2322
+ return text3(await commentLinearIssue(args));
2156
2323
  } catch (err) {
2157
- return fail2(err);
2324
+ return fail3(err);
2158
2325
  }
2159
2326
  }
2160
2327
  );
2161
2328
  }
2162
2329
 
2163
2330
  // src/mcp/schedule-tools.ts
2164
- import { z as z3 } from "zod";
2165
- function text3(payload, isError = false) {
2331
+ import { z as z4 } from "zod";
2332
+ function text4(payload, isError = false) {
2166
2333
  return {
2167
2334
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
2168
2335
  ...isError ? { isError: true } : {}
2169
2336
  };
2170
2337
  }
2171
- function fail3(err) {
2172
- return text3(
2338
+ function fail4(err) {
2339
+ return text4(
2173
2340
  { error: err instanceof Error ? err.message : String(err) },
2174
2341
  true
2175
2342
  );
@@ -2194,9 +2361,9 @@ function registerScheduleTools(server) {
2194
2361
  {},
2195
2362
  async () => {
2196
2363
  try {
2197
- return text3({ schedules: listSchedules() });
2364
+ return text4({ schedules: listSchedules() });
2198
2365
  } catch (err) {
2199
- return fail3(err);
2366
+ return fail4(err);
2200
2367
  }
2201
2368
  }
2202
2369
  );
@@ -2204,24 +2371,24 @@ function registerScheduleTools(server) {
2204
2371
  "create_schedule",
2205
2372
  "Create a local schedule that, when due, sends a prompt to an orchestration chat (threadId) or starts a new Global orchestration chat (omit threadId). Pass threadId=self to continue this coordinator. Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without threadId open a new chat each run. Overnight/unattended runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate. Sideboard.app must be running for the job to fire.",
2206
2373
  {
2207
- prompt: z3.string().describe("User message / goal queued when the schedule fires"),
2208
- name: z3.string().optional(),
2209
- at: z3.string().optional().describe("ISO datetime for a one-shot"),
2210
- every: z3.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
2211
- cron: z3.string().optional().describe("5-field cron expression"),
2212
- tz: z3.string().optional().describe("IANA timezone for cron (default: system)"),
2213
- threadId: z3.string().optional().describe(
2374
+ prompt: z4.string().describe("User message / goal queued when the schedule fires"),
2375
+ name: z4.string().optional(),
2376
+ at: z4.string().optional().describe("ISO datetime for a one-shot"),
2377
+ every: z4.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
2378
+ cron: z4.string().optional().describe("5-field cron expression"),
2379
+ tz: z4.string().optional().describe("IANA timezone for cron (default: system)"),
2380
+ threadId: z4.string().optional().describe(
2214
2381
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
2215
2382
  ),
2216
- agent: z3.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
2217
- model: z3.string().optional()
2383
+ agent: z4.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
2384
+ model: z4.string().optional()
2218
2385
  },
2219
2386
  async (args) => {
2220
2387
  try {
2221
2388
  const when = parseWhen(args);
2222
2389
  const threadId = resolveScheduleThreadId(args.threadId);
2223
2390
  if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
2224
- return fail3(
2391
+ return fail4(
2225
2392
  new Error("threadId=self requires this turn to be an orchestration chat")
2226
2393
  );
2227
2394
  }
@@ -2234,12 +2401,12 @@ function registerScheduleTools(server) {
2234
2401
  model: args.model,
2235
2402
  createdBy: "mcp"
2236
2403
  });
2237
- return text3({
2404
+ return text4({
2238
2405
  schedule,
2239
2406
  hint: "Fires while Sideboard.app is running. Recurring jobs without threadId create a new orchestration chat each run. Overnight runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate."
2240
2407
  });
2241
2408
  } catch (err) {
2242
- return fail3(err);
2409
+ return fail4(err);
2243
2410
  }
2244
2411
  }
2245
2412
  );
@@ -2247,17 +2414,17 @@ function registerScheduleTools(server) {
2247
2414
  "update_schedule",
2248
2415
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
2249
2416
  {
2250
- id: z3.string(),
2251
- prompt: z3.string().optional(),
2252
- name: z3.string().optional(),
2253
- at: z3.string().optional(),
2254
- every: z3.string().optional(),
2255
- cron: z3.string().optional(),
2256
- tz: z3.string().optional(),
2257
- threadId: z3.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
2258
- agent: z3.enum(["claude", "cursor", "codex", "opencode"]).optional(),
2259
- model: z3.string().optional(),
2260
- enabled: z3.boolean().optional()
2417
+ id: z4.string(),
2418
+ prompt: z4.string().optional(),
2419
+ name: z4.string().optional(),
2420
+ at: z4.string().optional(),
2421
+ every: z4.string().optional(),
2422
+ cron: z4.string().optional(),
2423
+ tz: z4.string().optional(),
2424
+ threadId: z4.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
2425
+ agent: z4.enum(["claude", "cursor", "codex", "opencode"]).optional(),
2426
+ model: z4.string().optional(),
2427
+ enabled: z4.boolean().optional()
2261
2428
  },
2262
2429
  async (args) => {
2263
2430
  try {
@@ -2272,7 +2439,7 @@ function registerScheduleTools(server) {
2272
2439
  if (args.threadId !== void 0) {
2273
2440
  threadId = resolveScheduleThreadId(args.threadId);
2274
2441
  if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
2275
- return fail3(
2442
+ return fail4(
2276
2443
  new Error("threadId=self requires this turn to be an orchestration chat")
2277
2444
  );
2278
2445
  }
@@ -2286,38 +2453,38 @@ function registerScheduleTools(server) {
2286
2453
  enabled: args.enabled,
2287
2454
  model: args.model
2288
2455
  });
2289
- return text3({ schedule });
2456
+ return text4({ schedule });
2290
2457
  } catch (err) {
2291
- return fail3(err);
2458
+ return fail4(err);
2292
2459
  }
2293
2460
  }
2294
2461
  );
2295
2462
  server.tool(
2296
2463
  "delete_schedule",
2297
2464
  "Delete a local schedule. Pass id from list_schedules.",
2298
- { id: z3.string() },
2465
+ { id: z4.string() },
2299
2466
  async ({ id }) => {
2300
2467
  try {
2301
2468
  deleteSchedule(id);
2302
- return text3({ ok: true, id });
2469
+ return text4({ ok: true, id });
2303
2470
  } catch (err) {
2304
- return fail3(err);
2471
+ return fail4(err);
2305
2472
  }
2306
2473
  }
2307
2474
  );
2308
2475
  server.tool(
2309
2476
  "run_schedule",
2310
2477
  "Fire a schedule now (does not wait for nextRunAt). Queues the prompt or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
2311
- { id: z3.string() },
2478
+ { id: z4.string() },
2312
2479
  async ({ id }) => {
2313
2480
  try {
2314
2481
  if (!getSchedule(id)) {
2315
- return fail3(new Error(`Schedule not found: ${id}`));
2482
+ return fail4(new Error(`Schedule not found: ${id}`));
2316
2483
  }
2317
2484
  const schedule = await fireSchedule(id);
2318
- return text3({ schedule });
2485
+ return text4({ schedule });
2319
2486
  } catch (err) {
2320
- return fail3(err);
2487
+ return fail4(err);
2321
2488
  }
2322
2489
  }
2323
2490
  );
@@ -2490,7 +2657,7 @@ async function loadHomeBoardInputs(workspaces) {
2490
2657
  const first = await listIssues(paths[0]);
2491
2658
  issueSource = first.source;
2492
2659
  viewerLogin = first.viewer?.login || first.viewer?.name || void 0;
2493
- if (first.source === "linear") {
2660
+ if (isAccountWideIssueSource(first.source)) {
2494
2661
  const repoPath = paths[0] ?? "";
2495
2662
  issues = first.issues.map((issue) => ({
2496
2663
  ...issue,
@@ -2718,7 +2885,7 @@ async function startMcpServer() {
2718
2885
  );
2719
2886
  }
2720
2887
  try {
2721
- const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-6RPALX4J.js");
2888
+ const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-U5RZKUXG.js");
2722
2889
  orch.setMaxConcurrent(maxConcurrentAgents2());
2723
2890
  } catch {
2724
2891
  }
@@ -2776,13 +2943,13 @@ async function startMcpServer() {
2776
2943
  "list_board",
2777
2944
  "Home Kanban of worktrees (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chat tabs nest as inner cards. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), column, limit (default 40). create_thread adds a worktree (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
2778
2945
  {
2779
- query: z4.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
2780
- repoPath: z4.string().optional().describe("Limit to one workspace path from list_workspaces"),
2781
- kind: z4.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
2782
- column: z4.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
2946
+ query: z5.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
2947
+ repoPath: z5.string().optional().describe("Limit to one workspace path from list_workspaces"),
2948
+ kind: z5.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
2949
+ column: z5.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
2783
2950
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
2784
2951
  ),
2785
- limit: z4.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
2952
+ limit: z5.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
2786
2953
  },
2787
2954
  async ({ query, repoPath, kind, column, limit }) => {
2788
2955
  const workspaces = orch.listWorkspaces();
@@ -2820,7 +2987,7 @@ async function startMcpServer() {
2820
2987
  server.tool(
2821
2988
  "get_thread",
2822
2989
  "Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
2823
- { ref: z4.string() },
2990
+ { ref: z5.string() },
2824
2991
  async ({ ref }) => {
2825
2992
  const t = orch.getThread(ref);
2826
2993
  if (!t) {
@@ -2857,17 +3024,17 @@ async function startMcpServer() {
2857
3024
  "present_artifact",
2858
3025
  "Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
2859
3026
  {
2860
- title: z4.string().describe("Short title shown in the artifact pane header"),
2861
- type: z4.enum(["html", "svg", "markdown", "react", "log"]).describe(
3027
+ title: z5.string().describe("Short title shown in the artifact pane header"),
3028
+ type: z5.enum(["html", "svg", "markdown", "react", "log"]).describe(
2862
3029
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
2863
3030
  ),
2864
- content: z4.string().describe(
3031
+ content: z5.string().describe(
2865
3032
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
2866
3033
  ),
2867
- artifact_id: z4.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
2868
- status: z4.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
2869
- phase: z4.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
2870
- mode: z4.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
3034
+ artifact_id: z5.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
3035
+ status: z5.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
3036
+ phase: z5.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
3037
+ mode: z5.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
2871
3038
  },
2872
3039
  async ({ title, type, artifact_id, status, phase, mode }) => {
2873
3040
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -2888,15 +3055,15 @@ async function startMcpServer() {
2888
3055
  "ask_user",
2889
3056
  "Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
2890
3057
  {
2891
- questions: z4.array(
2892
- z4.object({
2893
- question: z4.string().describe("Full question text ending with ?"),
2894
- header: z4.string().max(24).optional().describe("Short label shown above the question"),
2895
- multiSelect: z4.boolean().optional().describe("Allow selecting multiple options"),
2896
- options: z4.array(
2897
- z4.object({
2898
- label: z4.string(),
2899
- description: z4.string().optional().describe("What this option means / when to choose it (strongly preferred)")
3058
+ questions: z5.array(
3059
+ z5.object({
3060
+ question: z5.string().describe("Full question text ending with ?"),
3061
+ header: z5.string().max(24).optional().describe("Short label shown above the question"),
3062
+ multiSelect: z5.boolean().optional().describe("Allow selecting multiple options"),
3063
+ options: z5.array(
3064
+ z5.object({
3065
+ label: z5.string(),
3066
+ description: z5.string().optional().describe("What this option means / when to choose it (strongly preferred)")
2900
3067
  })
2901
3068
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
2902
3069
  })
@@ -2915,9 +3082,9 @@ async function startMcpServer() {
2915
3082
  "present_plan",
2916
3083
  "Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
2917
3084
  {
2918
- title: z4.string().optional().describe("Short plan title (defaults to Plan)"),
2919
- content: z4.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
2920
- thread_id: z4.string().optional().describe("Sideboard thread id when cwd is not the worktree")
3085
+ title: z5.string().optional().describe("Short plan title (defaults to Plan)"),
3086
+ content: z5.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
3087
+ thread_id: z5.string().optional().describe("Sideboard thread id when cwd is not the worktree")
2921
3088
  },
2922
3089
  async ({ title, content, thread_id }) => {
2923
3090
  const { writePlanFile: writePlanFile2 } = await import("./plan-file-6O7G4VPQ.js");
@@ -2940,15 +3107,15 @@ async function startMcpServer() {
2940
3107
  "present_schema",
2941
3108
  "Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
2942
3109
  {
2943
- title: z4.string().describe("Pane title"),
2944
- mode: z4.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
2945
- datasource: z4.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
2946
- resource_id: z4.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
2947
- record_id: z4.string().optional().describe("Record id when opening form mode"),
2948
- resource: z4.record(z4.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
2949
- record: z4.record(z4.unknown()).optional().describe("Inline record { id, data, published_at? }"),
2950
- records: z4.array(z4.record(z4.unknown())).optional().describe("Inline records for table mode"),
2951
- pane_id: z4.string().optional().describe("Stable pane id across updates")
3110
+ title: z5.string().describe("Pane title"),
3111
+ mode: z5.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
3112
+ datasource: z5.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
3113
+ resource_id: z5.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
3114
+ record_id: z5.string().optional().describe("Record id when opening form mode"),
3115
+ resource: z5.record(z5.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
3116
+ record: z5.record(z5.unknown()).optional().describe("Inline record { id, data, published_at? }"),
3117
+ records: z5.array(z5.record(z5.unknown())).optional().describe("Inline records for table mode"),
3118
+ pane_id: z5.string().optional().describe("Stable pane id across updates")
2952
3119
  },
2953
3120
  async (args) => {
2954
3121
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -2969,10 +3136,10 @@ async function startMcpServer() {
2969
3136
  "present_files",
2970
3137
  "Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
2971
3138
  {
2972
- title: z4.string().optional().describe("Pane title (default: Files)"),
2973
- datasource: z4.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
2974
- path: z4.string().optional().describe("Initial folder path (e.g. public)"),
2975
- pane_id: z4.string().optional().describe("Stable pane id across updates")
3139
+ title: z5.string().optional().describe("Pane title (default: Files)"),
3140
+ datasource: z5.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
3141
+ path: z5.string().optional().describe("Initial folder path (e.g. public)"),
3142
+ pane_id: z5.string().optional().describe("Stable pane id across updates")
2976
3143
  },
2977
3144
  async (args) => {
2978
3145
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -2990,16 +3157,17 @@ async function startMcpServer() {
2990
3157
  if (!worktreeProfile) {
2991
3158
  registerSlackTools(server);
2992
3159
  registerLinearTools(server);
3160
+ registerAbleTimeTools(server);
2993
3161
  registerScheduleTools(server);
2994
3162
  const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-X3JNPR42.js");
2995
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-6RPALX4J.js");
3163
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-U5RZKUXG.js");
2996
3164
  const accountDefaults = resolveThreadDefaults2();
2997
3165
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
2998
3166
  server.tool(
2999
3167
  "set_caffeinate",
3000
3168
  "Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. Closing or archiving this orchestration chat also releases the hold. macOS only.",
3001
3169
  {
3002
- enabled: z4.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
3170
+ enabled: z5.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
3003
3171
  },
3004
3172
  async ({ enabled }) => {
3005
3173
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -3037,18 +3205,18 @@ async function startMcpServer() {
3037
3205
  "create_thread",
3038
3206
  `Create a worktree thread (chat) from branch, pr, or ticket. If a live worktree already matches that ticket, PR, or named branch, returns it (alreadyStarted=true) instead of a second checkout. Creating from the default branch still opens a new isolated worktree. Pass repoPath from list_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_thread to chat.`,
3039
3207
  {
3040
- sourceType: z4.enum(["branch", "pr", "ticket"]),
3041
- sourceRef: z4.string(),
3042
- agent: z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
3043
- model: z4.string().nullable().optional().describe(
3208
+ sourceType: z5.enum(["branch", "pr", "ticket"]),
3209
+ sourceRef: z5.string(),
3210
+ agent: z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
3211
+ model: z5.string().nullable().optional().describe(
3044
3212
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
3045
3213
  ),
3046
- repoPath: z4.string(),
3047
- title: z4.string().optional(),
3048
- cowboy: z4.boolean().optional().describe(
3214
+ repoPath: z5.string(),
3215
+ title: z5.string().optional(),
3216
+ cowboy: z5.boolean().optional().describe(
3049
3217
  "If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
3050
3218
  ),
3051
- parentThreadId: z4.string().optional().describe(
3219
+ parentThreadId: z5.string().optional().describe(
3052
3220
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
3053
3221
  )
3054
3222
  },
@@ -3064,10 +3232,10 @@ async function startMcpServer() {
3064
3232
  "start_board_card",
3065
3233
  "Same as create_thread for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Reuses the live worktree if one already matches. Then send_to_thread.",
3066
3234
  {
3067
- kind: z4.enum(["ticket", "pr", "branch"]),
3068
- ref: z4.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
3069
- repoPath: z4.string().describe("Workspace path from list_workspaces / list_board"),
3070
- title: z4.string().optional()
3235
+ kind: z5.enum(["ticket", "pr", "branch"]),
3236
+ ref: z5.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
3237
+ repoPath: z5.string().describe("Workspace path from list_workspaces / list_board"),
3238
+ title: z5.string().optional()
3071
3239
  },
3072
3240
  async ({ kind, ref, repoPath, title }) => {
3073
3241
  const root = await resolveRepoRoot(repoPath);
@@ -3171,9 +3339,9 @@ async function startMcpServer() {
3171
3339
  "send_to_thread",
3172
3340
  'Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.',
3173
3341
  {
3174
- ref: z4.string(),
3175
- prompt: z4.string(),
3176
- force_stop: z4.boolean().optional()
3342
+ ref: z5.string(),
3343
+ prompt: z5.string(),
3344
+ force_stop: z5.boolean().optional()
3177
3345
  },
3178
3346
  async ({ ref, prompt, force_stop }) => {
3179
3347
  if (force_stop) {
@@ -3202,8 +3370,8 @@ async function startMcpServer() {
3202
3370
  "wait_for_turn",
3203
3371
  "Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume a hang. On status error, lastError/text is the failure. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
3204
3372
  {
3205
- ref: z4.string(),
3206
- timeoutMs: z4.number().optional()
3373
+ ref: z5.string(),
3374
+ timeoutMs: z5.number().optional()
3207
3375
  },
3208
3376
  async ({ ref, timeoutMs }) => {
3209
3377
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -3232,7 +3400,7 @@ async function startMcpServer() {
3232
3400
  server.tool(
3233
3401
  "get_turn_result",
3234
3402
  "Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript. Includes usage for the last agent turn (tokens + costUsd when reported).",
3235
- { ref: z4.string() },
3403
+ { ref: z5.string() },
3236
3404
  async ({ ref }) => {
3237
3405
  const result = orch.getTurnResult(ref);
3238
3406
  return {
@@ -3252,8 +3420,8 @@ async function startMcpServer() {
3252
3420
  "stop_thread",
3253
3421
  "Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
3254
3422
  {
3255
- ref: z4.string(),
3256
- force: z4.boolean().optional()
3423
+ ref: z5.string(),
3424
+ force: z5.boolean().optional()
3257
3425
  },
3258
3426
  async ({ ref, force }) => {
3259
3427
  const t = orch.getThread(ref);
@@ -3283,7 +3451,7 @@ async function startMcpServer() {
3283
3451
  server.tool(
3284
3452
  "archive_thread",
3285
3453
  "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
3286
- { ref: z4.string() },
3454
+ { ref: z5.string() },
3287
3455
  async ({ ref }) => {
3288
3456
  const t = orch.getThread(ref);
3289
3457
  if (!t) {
@@ -3316,7 +3484,7 @@ async function startMcpServer() {
3316
3484
  server.tool(
3317
3485
  "restore_thread",
3318
3486
  "Restore an archived thread (recreates worktree from branch when needed)",
3319
- { ref: z4.string() },
3487
+ { ref: z5.string() },
3320
3488
  async ({ ref }) => {
3321
3489
  try {
3322
3490
  const restored = await orch.restore(ref);
@@ -3342,8 +3510,8 @@ async function startMcpServer() {
3342
3510
  "get_diff",
3343
3511
  "Compact diff summary (capped hunks, paginated)",
3344
3512
  {
3345
- ref: z4.string(),
3346
- maxFiles: z4.number().optional()
3513
+ ref: z5.string(),
3514
+ maxFiles: z5.number().optional()
3347
3515
  },
3348
3516
  async ({ ref, maxFiles }) => {
3349
3517
  const summary = await orch.diffSummary(ref);
@@ -3363,7 +3531,7 @@ async function startMcpServer() {
3363
3531
  server.tool(
3364
3532
  "request_review",
3365
3533
  'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .claude/skills/review/SKILL.md when present (else seeds that skill from .sideboard/review.md or the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
3366
- { ref: z4.string().describe("Worktree thread id/ref to review") },
3534
+ { ref: z5.string().describe("Worktree thread id/ref to review") },
3367
3535
  async ({ ref }) => {
3368
3536
  try {
3369
3537
  const tab = await orch.requestReview(ref);
@@ -3392,8 +3560,8 @@ async function startMcpServer() {
3392
3560
  "ask_git",
3393
3561
  "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn (loop while stillRunning). Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
3394
3562
  {
3395
- ref: z4.string().describe("Worktree thread id/ref"),
3396
- action: z4.enum(AGENT_GIT_ACTIONS).describe(
3563
+ ref: z5.string().describe("Worktree thread id/ref"),
3564
+ action: z5.enum(AGENT_GIT_ACTIONS).describe(
3397
3565
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
3398
3566
  )
3399
3567
  },
@@ -3420,7 +3588,7 @@ async function startMcpServer() {
3420
3588
  }
3421
3589
  }
3422
3590
  );
3423
- const agentEnum = z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
3591
+ const agentEnum = z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
3424
3592
  server.tool(
3425
3593
  "list_models",
3426
3594
  "List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
@@ -3443,11 +3611,11 @@ async function startMcpServer() {
3443
3611
  "fork_worktree",
3444
3612
  "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id.",
3445
3613
  {
3446
- ref: z4.string().describe("Worktree thread id/ref to fork"),
3447
- through_index: z4.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3614
+ ref: z5.string().describe("Worktree thread id/ref to fork"),
3615
+ through_index: z5.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3448
3616
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
3449
- model: z4.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3450
- title: z4.string().optional()
3617
+ model: z5.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3618
+ title: z5.string().optional()
3451
3619
  },
3452
3620
  async ({ ref, through_index, agent, model, title }) => {
3453
3621
  try {
@@ -3488,11 +3656,11 @@ async function startMcpServer() {
3488
3656
  "fork_chat",
3489
3657
  "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
3490
3658
  {
3491
- ref: z4.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
3492
- through_index: z4.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3659
+ ref: z5.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
3660
+ through_index: z5.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3493
3661
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
3494
- model: z4.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3495
- title: z4.string().optional()
3662
+ model: z5.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3663
+ title: z5.string().optional()
3496
3664
  },
3497
3665
  async ({ ref, through_index, agent, model, title }) => {
3498
3666
  try {
@@ -3538,8 +3706,8 @@ async function startMcpServer() {
3538
3706
  "run_dev_script",
3539
3707
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
3540
3708
  {
3541
- ref: z4.string(),
3542
- name: z4.string().optional()
3709
+ ref: z5.string(),
3710
+ name: z5.string().optional()
3543
3711
  },
3544
3712
  async ({ ref, name }) => {
3545
3713
  const result = await orch.startDev(ref, name);
@@ -3561,7 +3729,7 @@ async function startMcpServer() {
3561
3729
  server.tool(
3562
3730
  "list_run_scripts",
3563
3731
  "List named run scripts available for a thread",
3564
- { ref: z4.string() },
3732
+ { ref: z5.string() },
3565
3733
  async ({ ref }) => {
3566
3734
  const scripts = orch.listThreadRunScripts(ref);
3567
3735
  const active = orch.getActiveRuns(ref);
@@ -3579,8 +3747,8 @@ async function startMcpServer() {
3579
3747
  "stop_dev_script",
3580
3748
  "Stop a running script for a thread (all scripts if name omitted)",
3581
3749
  {
3582
- ref: z4.string(),
3583
- name: z4.string().optional()
3750
+ ref: z5.string(),
3751
+ name: z5.string().optional()
3584
3752
  },
3585
3753
  async ({ ref, name }) => {
3586
3754
  orch.stopDev(ref, name);
@@ -3590,7 +3758,7 @@ async function startMcpServer() {
3590
3758
  server.tool(
3591
3759
  "run_setup",
3592
3760
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
3593
- { ref: z4.string() },
3761
+ { ref: z5.string() },
3594
3762
  async ({ ref }) => {
3595
3763
  const result = await orch.runSetup(ref);
3596
3764
  return {
@@ -3601,7 +3769,7 @@ async function startMcpServer() {
3601
3769
  server.tool(
3602
3770
  "add_workspace",
3603
3771
  "Register a git repo as a Sideboard workspace",
3604
- { repoPath: z4.string() },
3772
+ { repoPath: z5.string() },
3605
3773
  async ({ repoPath }) => {
3606
3774
  const ws = await orch.addWorkspace(repoPath);
3607
3775
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -3610,7 +3778,7 @@ async function startMcpServer() {
3610
3778
  server.tool(
3611
3779
  "remove_workspace",
3612
3780
  "Unregister a Sideboard workspace (does not archive threads)",
3613
- { repoPath: z4.string() },
3781
+ { repoPath: z5.string() },
3614
3782
  async ({ repoPath }) => {
3615
3783
  orch.removeWorkspace(repoPath);
3616
3784
  return { content: [{ type: "text", text: "ok" }] };
@@ -3620,12 +3788,12 @@ async function startMcpServer() {
3620
3788
  "fanout",
3621
3789
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
3622
3790
  {
3623
- prompt: z4.string(),
3624
- agents: z4.array(z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
3625
- repoPath: z4.string(),
3626
- sourceType: z4.enum(["branch", "pr", "ticket"]).optional(),
3627
- sourceRef: z4.string().optional(),
3628
- title: z4.string().optional()
3791
+ prompt: z5.string(),
3792
+ agents: z5.array(z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
3793
+ repoPath: z5.string(),
3794
+ sourceType: z5.enum(["branch", "pr", "ticket"]).optional(),
3795
+ sourceRef: z5.string().optional(),
3796
+ title: z5.string().optional()
3629
3797
  },
3630
3798
  async (args) => {
3631
3799
  const threads = await orch.bestOfN(args);
@@ -3652,8 +3820,8 @@ async function startMcpServer() {
3652
3820
  "list_branches",
3653
3821
  "List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
3654
3822
  {
3655
- repoPath: z4.string(),
3656
- unmergedOnly: z4.boolean().optional()
3823
+ repoPath: z5.string(),
3824
+ unmergedOnly: z5.boolean().optional()
3657
3825
  },
3658
3826
  async ({ repoPath, unmergedOnly }) => {
3659
3827
  const root = await resolveRepoRoot(repoPath);
@@ -3673,7 +3841,7 @@ async function startMcpServer() {
3673
3841
  server.tool(
3674
3842
  "list_prs",
3675
3843
  "List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
3676
- { repoPath: z4.string() },
3844
+ { repoPath: z5.string() },
3677
3845
  async ({ repoPath }) => {
3678
3846
  const root = await resolveRepoRoot(repoPath);
3679
3847
  const prs = await listPrs(root);
@@ -3692,7 +3860,7 @@ async function startMcpServer() {
3692
3860
  server.tool(
3693
3861
  "get_pr_stack",
3694
3862
  "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
3695
- { ref: z4.string() },
3863
+ { ref: z5.string() },
3696
3864
  async ({ ref }) => {
3697
3865
  const stack = await orch.getPrStack(ref);
3698
3866
  return {
@@ -3704,8 +3872,8 @@ async function startMcpServer() {
3704
3872
  "open_pr_stack_layers",
3705
3873
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
3706
3874
  {
3707
- ref: z4.string(),
3708
- layer: z4.number().int().positive().optional()
3875
+ ref: z5.string(),
3876
+ layer: z5.number().int().positive().optional()
3709
3877
  },
3710
3878
  async ({ ref, layer }) => {
3711
3879
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -3739,9 +3907,9 @@ async function startMcpServer() {
3739
3907
  "add_stack_layer",
3740
3908
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
3741
3909
  {
3742
- ref: z4.string(),
3743
- branchName: z4.string(),
3744
- title: z4.string().optional()
3910
+ ref: z5.string(),
3911
+ branchName: z5.string(),
3912
+ title: z5.string().optional()
3745
3913
  },
3746
3914
  async ({ ref, branchName, title }) => {
3747
3915
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -3770,11 +3938,11 @@ async function startMcpServer() {
3770
3938
  "create_pr_stack",
3771
3939
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
3772
3940
  {
3773
- repoPath: z4.string(),
3774
- branches: z4.array(z4.string()).min(1),
3775
- agent: z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
3776
- base: z4.string().optional(),
3777
- title: z4.string().optional()
3941
+ repoPath: z5.string(),
3942
+ branches: z5.array(z5.string()).min(1),
3943
+ agent: z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
3944
+ base: z5.string().optional(),
3945
+ title: z5.string().optional()
3778
3946
  },
3779
3947
  async (args) => {
3780
3948
  const result = await orch.createPrStack({
@@ -3811,8 +3979,8 @@ async function startMcpServer() {
3811
3979
  );
3812
3980
  server.tool(
3813
3981
  "list_issues",
3814
- "List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Linear returns issues assigned to you (includes cycle). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Prefer list_board for Home columns + current-cycle filter. Then create_thread with sourceType=ticket.",
3815
- { repoPath: z4.string() },
3982
+ "List issues from Sideboard Account connections (Linear, AbleTime MCP, or GitHub Issues; falls back to GitHub when the preferred tracker is not connected). Linear/AbleTime are account-wide; GitHub Issues are scoped to repoPath from list_workspaces. When AbleTime is the issue source and work has no ticket, call abletime_ensure_task (or create_thread from the default branch \u2014 Sideboard auto-creates a task to track against). Prefer list_board for Home columns. Then create_thread with sourceType=ticket.",
3983
+ { repoPath: z5.string() },
3816
3984
  async ({ repoPath }) => {
3817
3985
  const root = await resolveRepoRoot(repoPath);
3818
3986
  const result = await listIssues(root);
@@ -3823,8 +3991,8 @@ async function startMcpServer() {
3823
3991
  "list_linear_issues",
3824
3992
  "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
3825
3993
  {
3826
- agent: z4.enum(["claude", "codex", "opencode"]),
3827
- repoPath: z4.string()
3994
+ agent: z5.enum(["claude", "codex", "opencode"]),
3995
+ repoPath: z5.string()
3828
3996
  },
3829
3997
  async ({ agent, repoPath }) => {
3830
3998
  const issues = await listLinearIssues(agent, repoPath);
@@ -3945,8 +4113,8 @@ var BrightsySideboardApi = class {
3945
4113
  };
3946
4114
  function taskMessageText(task) {
3947
4115
  const parts = task.message?.parts ?? [];
3948
- const text4 = parts.find((p) => p.kind === "text")?.text ?? "";
3949
- return text4.trim();
4116
+ const text5 = parts.find((p) => p.kind === "text")?.text ?? "";
4117
+ return text5.trim();
3950
4118
  }
3951
4119
 
3952
4120
  // src/brightsy/cloud-connect.ts
@@ -4488,11 +4656,11 @@ async function startSlackOAuth(opts) {
4488
4656
  import { WebSocket as WsWebSocket } from "ws";
4489
4657
  var BACKOFF_START_MS = 1e3;
4490
4658
  var BACKOFF_MAX_MS = 3e4;
4491
- function stripSlackMentions(text4) {
4492
- return text4.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
4659
+ function stripSlackMentions(text5) {
4660
+ return text5.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
4493
4661
  }
4494
- function isSlackStopCommand(text4) {
4495
- const t = stripSlackMentions(text4).toLowerCase();
4662
+ function isSlackStopCommand(text5) {
4663
+ const t = stripSlackMentions(text5).toLowerCase();
4496
4664
  return t === "stop" || t === "sideboard_force_stop";
4497
4665
  }
4498
4666
  function parseSlackSocketFrame(raw) {
@@ -4514,8 +4682,8 @@ function inboundFromSocketFrame(frame) {
4514
4682
  const ts = event.ts?.trim();
4515
4683
  if (!channelId || !ts) return null;
4516
4684
  const rawText = event.text?.trim() ?? "";
4517
- const text4 = stripSlackMentions(rawText);
4518
- if (!text4) return null;
4685
+ const text5 = stripSlackMentions(rawText);
4686
+ if (!text5) return null;
4519
4687
  const teamId = (frame.payload?.team_id || event.team || "").trim();
4520
4688
  if (!teamId) return null;
4521
4689
  const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
@@ -4527,7 +4695,7 @@ function inboundFromSocketFrame(frame) {
4527
4695
  ts,
4528
4696
  threadTs: event.thread_ts?.trim() || void 0,
4529
4697
  userId: event.user?.trim(),
4530
- text: text4,
4698
+ text: text5,
4531
4699
  kind: isMention ? "mention" : "dm"
4532
4700
  };
4533
4701
  }
@@ -5067,12 +5235,12 @@ function formatSlackInboundPrompt(msg) {
5067
5235
 
5068
5236
  ${msg.text}`;
5069
5237
  }
5070
- function isSlackInboundUserPrompt(text4) {
5071
- return text4.startsWith("Slack DM\n") || text4.startsWith("Slack @mention\n");
5238
+ function isSlackInboundUserPrompt(text5) {
5239
+ return text5.startsWith("Slack DM\n") || text5.startsWith("Slack @mention\n");
5072
5240
  }
5073
- function formatSlackSignedReply(deviceLabel, text4) {
5241
+ function formatSlackSignedReply(deviceLabel, text5) {
5074
5242
  const label = deviceLabel.trim();
5075
- const body = text4.trim();
5243
+ const body = text5.trim();
5076
5244
  if (!body) return body;
5077
5245
  if (!label) return body;
5078
5246
  const head = `${label}:`;
@@ -5081,8 +5249,8 @@ function formatSlackSignedReply(deviceLabel, text4) {
5081
5249
  }
5082
5250
  return `${label}: ${body}`;
5083
5251
  }
5084
- function signForThisMac(text4) {
5085
- return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text4);
5252
+ function signForThisMac(text5) {
5253
+ return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text5);
5086
5254
  }
5087
5255
  function slackReplyThreadTs(msg) {
5088
5256
  if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
@@ -5143,9 +5311,9 @@ function replyStub(target) {
5143
5311
  kind: "dm"
5144
5312
  };
5145
5313
  }
5146
- async function postSlackText(target, text4, opts) {
5314
+ async function postSlackText(target, text5, opts) {
5147
5315
  if (opts.postReply) {
5148
- const result = await opts.postReply(replyStub(target), text4);
5316
+ const result = await opts.postReply(replyStub(target), text5);
5149
5317
  const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
5150
5318
  return ts2 ? { ts: ts2 } : null;
5151
5319
  }
@@ -5155,7 +5323,7 @@ async function postSlackText(target, text4, opts) {
5155
5323
  "chat.postMessage",
5156
5324
  {
5157
5325
  channel: target.channelId,
5158
- text: text4,
5326
+ text: text5,
5159
5327
  thread_ts: target.threadTs
5160
5328
  },
5161
5329
  opts.fetchImpl
@@ -5163,9 +5331,9 @@ async function postSlackText(target, text4, opts) {
5163
5331
  const ts = json.ts?.trim();
5164
5332
  return ts ? { ts } : null;
5165
5333
  }
5166
- async function updateSlackText(target, ts, text4, opts) {
5334
+ async function updateSlackText(target, ts, text5, opts) {
5167
5335
  if (opts.updateReply) {
5168
- await opts.updateReply(replyStub(target), ts, text4);
5336
+ await opts.updateReply(replyStub(target), ts, text5);
5169
5337
  return;
5170
5338
  }
5171
5339
  const token = writeTokenForTeam(target.teamId);
@@ -5175,7 +5343,7 @@ async function updateSlackText(target, ts, text4, opts) {
5175
5343
  {
5176
5344
  channel: target.channelId,
5177
5345
  ts,
5178
- text: text4
5346
+ text: text5
5179
5347
  },
5180
5348
  opts.fetchImpl
5181
5349
  );
@@ -5203,8 +5371,8 @@ function replyTargetOf(msg) {
5203
5371
  threadTs: slackReplyThreadTs(msg)
5204
5372
  };
5205
5373
  }
5206
- async function postSlackReply(msg, text4, opts) {
5207
- await postSlackText(replyTargetOf(msg), signForThisMac(text4), opts);
5374
+ async function postSlackReply(msg, text5, opts) {
5375
+ await postSlackText(replyTargetOf(msg), signForThisMac(text5), opts);
5208
5376
  }
5209
5377
  function startSlackTurnProgress(msg, threadId, opts) {
5210
5378
  const log = opts.onLog ?? (() => void 0);
@@ -5287,10 +5455,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
5287
5455
  }
5288
5456
  });
5289
5457
  },
5290
- finishWith: (text4) => {
5458
+ finishWith: (text5) => {
5291
5459
  stop();
5292
5460
  return run2(async () => {
5293
- const signed = signForThisMac(text4.trim());
5461
+ const signed = signForThisMac(text5.trim());
5294
5462
  if (!signed) {
5295
5463
  if (!postedTs) return;
5296
5464
  const ts = postedTs;
@@ -5318,11 +5486,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
5318
5486
  };
5319
5487
  }
5320
5488
  var lastRelayed = /* @__PURE__ */ new Map();
5321
- function markRelayed(threadId, text4) {
5322
- lastRelayed.set(threadId, `${threadId}:${text4}`);
5489
+ function markRelayed(threadId, text5) {
5490
+ lastRelayed.set(threadId, `${threadId}:${text5}`);
5323
5491
  }
5324
- function alreadyRelayed(threadId, text4) {
5325
- return lastRelayed.get(threadId) === `${threadId}:${text4}`;
5492
+ function alreadyRelayed(threadId, text5) {
5493
+ return lastRelayed.get(threadId) === `${threadId}:${text5}`;
5326
5494
  }
5327
5495
  async function relayCoordinatorReplyToSlack(threadId, opts) {
5328
5496
  const target = getSlackReplyTarget(threadId);
@@ -5331,14 +5499,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
5331
5499
  const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
5332
5500
  if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
5333
5501
  const result = getOrchestrator().getTurnResult(threadId);
5334
- const text4 = result.text.trim();
5335
- if (!text4) return;
5336
- if (alreadyRelayed(threadId, text4)) return;
5337
- markRelayed(threadId, text4);
5502
+ const text5 = result.text.trim();
5503
+ if (!text5) return;
5504
+ if (alreadyRelayed(threadId, text5)) return;
5505
+ markRelayed(threadId, text5);
5338
5506
  const log = opts.onLog ?? (() => void 0);
5339
5507
  try {
5340
- await postSlackText(target, signForThisMac(text4), opts);
5341
- log(`replied ${target.threadTs ?? target.channelId} (${text4.length} chars)`);
5508
+ await postSlackText(target, signForThisMac(text5), opts);
5509
+ log(`replied ${target.threadTs ?? target.channelId} (${text5.length} chars)`);
5342
5510
  } catch (err) {
5343
5511
  lastRelayed.delete(threadId);
5344
5512
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -5586,10 +5754,10 @@ function resolveSlackListenMode(opts) {
5586
5754
  }
5587
5755
 
5588
5756
  // src/slack/relay-hub.ts
5589
- function parseSlackDeviceDestination(text4) {
5590
- const m = text4.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
5591
- if (!m) return { label: null, rest: text4 };
5592
- return { label: m[1], rest: text4.slice(m[0].length) };
5757
+ function parseSlackDeviceDestination(text5) {
5758
+ const m = text5.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
5759
+ if (!m) return { label: null, rest: text5 };
5760
+ return { label: m[1], rest: text5.slice(m[0].length) };
5593
5761
  }
5594
5762
  var SlackRelayHub = class {
5595
5763
  sessions = /* @__PURE__ */ new Map();
@@ -6110,6 +6278,7 @@ async function startSlackRelayServer(opts) {
6110
6278
  };
6111
6279
  }
6112
6280
  export {
6281
+ ABLETIME_MCP_PATH,
6113
6282
  AGENT_GIT_ACTIONS,
6114
6283
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
6115
6284
  ATTACHMENTS_DIR,
@@ -6127,6 +6296,7 @@ export {
6127
6296
  CONTEXT_MIN_MESSAGES,
6128
6297
  CONVENTION_SETUP_RELPATHS,
6129
6298
  COORDINATOR_TOOL_PLAYBOOK,
6299
+ DEFAULT_ABLETIME_HOST,
6130
6300
  DEFAULT_WORKTREE_SORT,
6131
6301
  FAMOUS_SOCCER_TEAMS,
6132
6302
  GITHUB_GIT_AUTH_MODES,
@@ -6174,6 +6344,8 @@ export {
6174
6344
  SlackRelayHub,
6175
6345
  THINKING_EFFORTS,
6176
6346
  WORKTREE_MCP_TOOLS,
6347
+ abletimeMcpRequest,
6348
+ abletimeMcpUrl,
6177
6349
  ackSlackInboundSeen,
6178
6350
  addBoardPin,
6179
6351
  addPrStackLayer,
@@ -6230,6 +6402,7 @@ export {
6230
6402
  caffeinateWhileRunningEnabled,
6231
6403
  caffeinateWhileSchedulesEnabled,
6232
6404
  caffeinateWhileSlackListenEnabled,
6405
+ callAbleTimeTool,
6233
6406
  canonicalizeRepoPath,
6234
6407
  captureLoginEnv,
6235
6408
  captureTurnBaseline,
@@ -6263,6 +6436,7 @@ export {
6263
6436
  copyConfiguredFiles,
6264
6437
  countCacheControlBlocks,
6265
6438
  cowboyModeEnabled,
6439
+ createAbleTimeTask,
6266
6440
  createChatTab,
6267
6441
  createEmptyThread,
6268
6442
  createExistingBranchWorktree,
@@ -6286,6 +6460,7 @@ export {
6286
6460
  detectAgents,
6287
6461
  detectGhStack,
6288
6462
  detectLocalMergeConflicts,
6463
+ disconnectAbleTimeConnection,
6289
6464
  disconnectBrightsyTeam,
6290
6465
  disconnectLinear,
6291
6466
  disconnectLinearConnection,
@@ -6295,6 +6470,7 @@ export {
6295
6470
  encodeBrightsyTarget,
6296
6471
  enrichPathWithNpmGlobalBin,
6297
6472
  enrichWorkspacesWithGithub,
6473
+ ensureAbleTimeTask,
6298
6474
  ensureAgentPath,
6299
6475
  ensureBrightsyLocalConfigFresh,
6300
6476
  ensureCloudCoordinator,
@@ -6355,6 +6531,10 @@ export {
6355
6531
  formatWorktreeDirective,
6356
6532
  formatWorktreeReminder,
6357
6533
  fromInclusiveInputUsage,
6534
+ getAbleTimeAccessToken,
6535
+ getAbleTimeHost,
6536
+ getAbleTimeOrientation,
6537
+ getAbleTimeTask,
6358
6538
  getAdapter,
6359
6539
  getAgentSetupInfo,
6360
6540
  getBrightsySession,
@@ -6413,6 +6593,7 @@ export {
6413
6593
  inspectGitWorktree,
6414
6594
  installAgent,
6415
6595
  interruptSlackCoordinatorForInbound,
6596
+ isAbleTimeConnected,
6416
6597
  isAskUserToolName,
6417
6598
  isBrightsyConnected,
6418
6599
  isBrightsyNdjsonLine,
@@ -6453,6 +6634,7 @@ export {
6453
6634
  isThreadCaffeinated,
6454
6635
  isThreadRecordFile,
6455
6636
  isWorkspaceScratchPath,
6637
+ issueAttachmentForAbleTimeTask,
6456
6638
  issueSourceLabel,
6457
6639
  lastRequestOccupancy,
6458
6640
  latestPendingPlanQuestions,
@@ -6461,6 +6643,9 @@ export {
6461
6643
  linearGraphql,
6462
6644
  linearOAuthAuthorizeUrl,
6463
6645
  linearOAuthCredentials,
6646
+ listAbleTimeAssignedIssues,
6647
+ listAbleTimeProjects,
6648
+ listAbleTimeTasks,
6464
6649
  listAgentSetupInfo,
6465
6650
  listBoardPins,
6466
6651
  listBranchCommits,
@@ -6500,6 +6685,7 @@ export {
6500
6685
  locksDir,
6501
6686
  loginAgent,
6502
6687
  lookupSoccerTeam,
6688
+ mapAbleTimeTask,
6503
6689
  maxConcurrentAgents,
6504
6690
  maybeCompactContext,
6505
6691
  mcpAllowTools,
@@ -6513,6 +6699,7 @@ export {
6513
6699
  nextPastedTextName,
6514
6700
  nextThinkingEffort,
6515
6701
  nonInteractiveGitProcessEnv,
6702
+ normalizeAbleTimeHost,
6516
6703
  normalizeParseResult,
6517
6704
  normalizeThinkingEffort,
6518
6705
  normalizeThread,
@@ -6606,6 +6793,7 @@ export {
6606
6793
  resolveThreadEffort,
6607
6794
  resolveVaultKey,
6608
6795
  resolveWorktreeStartPoint,
6796
+ rewriteAbleTimeError,
6609
6797
  rewriteLinearError,
6610
6798
  run,
6611
6799
  runArchiveScript,
@@ -6618,10 +6806,12 @@ export {
6618
6806
  runWorkspaceSetup,
6619
6807
  sameWorktreePath,
6620
6808
  sanitizeMcpServerName,
6809
+ saveAbleTimeConnection,
6621
6810
  saveAppSettings,
6622
6811
  saveLinearOAuth,
6623
6812
  schedulesPath,
6624
6813
  scrubGithubTokensFromChildEnv,
6814
+ searchAbleTimeTasks,
6625
6815
  secureFileUnlocksWith,
6626
6816
  setCaffeinateHold,
6627
6817
  setHttpFetchImpl,
@@ -6672,6 +6862,7 @@ export {
6672
6862
  takenTeamSlugsForChatTab,
6673
6863
  takenTeamSlugsForOrchestration,
6674
6864
  taskMessageText,
6865
+ taskUrl,
6675
6866
  thinkingEffortBars,
6676
6867
  thinkingEffortLabel,
6677
6868
  thisProcessShouldDrainAgentQueues,
@@ -6682,6 +6873,7 @@ export {
6682
6873
  threadRequestsBrightsyMcp,
6683
6874
  threadsDir,
6684
6875
  threadsSharingWorktree,
6876
+ toAbleTimeIssueInfo,
6685
6877
  toPublicAppSettings,
6686
6878
  toolActivityLine,
6687
6879
  toolDescription,
@@ -6704,6 +6896,7 @@ export {
6704
6896
  userClaudeMcpConfigPath,
6705
6897
  userCursorMcpConfigPath,
6706
6898
  validateLinearApiKey,
6899
+ verifyAbleTimeConnection,
6707
6900
  waitForPidExit,
6708
6901
  warmGithubAgentAuth,
6709
6902
  withAgentInstructions,