@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
@@ -17,6 +17,7 @@ import {
17
17
  fireSchedule,
18
18
  getOrchestrator,
19
19
  getSchedule,
20
+ isAccountWideIssueSource,
20
21
  issueNeedsWorkspacePick,
21
22
  listLinearIssues,
22
23
  listSchedules,
@@ -31,23 +32,35 @@ import {
31
32
  slackTokenFor,
32
33
  syncBoardPins,
33
34
  updateSchedule
34
- } from "../chunk-SMWJAE74.js";
35
+ } from "../chunk-VSON7EJ6.js";
35
36
  import {
36
37
  listModelsForAgent,
37
38
  sideboardMcpProfile
38
- } from "../chunk-PK4PQUPU.js";
39
- import "../chunk-LLX3OMHU.js";
39
+ } from "../chunk-AK7SWE2U.js";
40
+ import "../chunk-ED4UPEJX.js";
41
+ import "../chunk-S42XV45P.js";
42
+ import "../chunk-Y645NFYN.js";
43
+ import {
44
+ createAbleTimeTask,
45
+ ensureAbleTimeTask,
46
+ getAbleTimeOrientation,
47
+ getAbleTimeTask,
48
+ listAbleTimeAssignedIssues,
49
+ listAbleTimeProjects,
50
+ listAbleTimeTasks,
51
+ searchAbleTimeTasks,
52
+ toAbleTimeIssueInfo
53
+ } from "../chunk-2XXAAIN3.js";
40
54
  import {
41
55
  httpFetch
42
- } from "../chunk-R7FGPBKL.js";
43
- import "../chunk-SYLTXDRF.js";
56
+ } from "../chunk-6GN5WPYZ.js";
44
57
  import "../chunk-N5PM7HGQ.js";
45
58
  import "../chunk-KLBOWJ74.js";
46
59
  import {
47
60
  GLOBAL_WORKSPACE_ID,
48
61
  isCloudCoordinatorThread
49
- } from "../chunk-MNL4FSKY.js";
50
- import "../chunk-RSFCB23A.js";
62
+ } from "../chunk-YEIH7P7D.js";
63
+ import "../chunk-6CRKPD4C.js";
51
64
  import {
52
65
  canonicalizeRepoPath,
53
66
  listBranches,
@@ -55,7 +68,7 @@ import {
55
68
  resolveGithubRepoSlug,
56
69
  resolveRepoRoot,
57
70
  warmGithubAgentAuth
58
- } from "../chunk-HRSBZIXS.js";
71
+ } from "../chunk-Q6XQXCOM.js";
59
72
  import "../chunk-B3SJXYIJ.js";
60
73
  import "../chunk-G6X6UFJO.js";
61
74
  import {
@@ -64,11 +77,12 @@ import {
64
77
  import {
65
78
  disconnectLinearConnection,
66
79
  dropNestedElectronEnvFromProcess,
80
+ isAbleTimeConnected,
67
81
  isLinearConnected,
68
82
  loadAppSettings,
69
83
  resolveEffectiveIssueSource,
70
84
  saveLinearOAuth
71
- } from "../chunk-ZND6XV6J.js";
85
+ } from "../chunk-GKKHXZCT.js";
72
86
  import "../chunk-NSTQ6QKD.js";
73
87
  import "../chunk-KGZBYWZ3.js";
74
88
  import {
@@ -78,7 +92,7 @@ import {
78
92
  // src/mcp/server.ts
79
93
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
80
94
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
81
- import { z as z4 } from "zod";
95
+ import { z as z5 } from "zod";
82
96
  import { randomUUID as randomUUID2 } from "crypto";
83
97
  import { basename } from "path";
84
98
 
@@ -576,6 +590,7 @@ async function listIssues(repoPath) {
576
590
  const settings = loadAppSettings();
577
591
  const preferredSource = settings.integrations.issueSource ?? "github";
578
592
  const linearConnected = isLinearConnected(settings);
593
+ const abletimeConnected = isAbleTimeConnected(settings);
579
594
  const source = resolveEffectiveIssueSource(settings);
580
595
  if (source === "linear") {
581
596
  const listed = await listLinearAssignedIssues();
@@ -583,6 +598,21 @@ async function listIssues(repoPath) {
583
598
  source,
584
599
  preferredSource,
585
600
  linearConnected,
601
+ abletimeConnected,
602
+ issues: listed.issues,
603
+ viewer: {
604
+ login: listed.viewer.name,
605
+ name: listed.viewer.name
606
+ }
607
+ };
608
+ }
609
+ if (source === "abletime") {
610
+ const listed = await listAbleTimeAssignedIssues();
611
+ return {
612
+ source,
613
+ preferredSource,
614
+ linearConnected,
615
+ abletimeConnected,
586
616
  issues: listed.issues,
587
617
  viewer: {
588
618
  login: listed.viewer.name,
@@ -598,6 +628,7 @@ async function listIssues(repoPath) {
598
628
  source,
599
629
  preferredSource,
600
630
  linearConnected,
631
+ abletimeConnected,
601
632
  issues,
602
633
  viewer: login ? { login } : void 0
603
634
  };
@@ -841,8 +872,8 @@ function labelGithubUrl(url) {
841
872
  }
842
873
  return { kind: "other", label: "GitHub", url: raw };
843
874
  }
844
- function appendGithubLink(text4, githubUrl) {
845
- const body = text4.trimEnd();
875
+ function appendGithubLink(text5, githubUrl) {
876
+ const body = text5.trimEnd();
846
877
  if (!githubUrl?.trim()) return body;
847
878
  const labeled = labelGithubUrl(githubUrl);
848
879
  if (!labeled) {
@@ -1125,7 +1156,7 @@ function registerSlackTools(server) {
1125
1156
  );
1126
1157
  }
1127
1158
 
1128
- // src/mcp/linear-tools.ts
1159
+ // src/mcp/abletime-tools.ts
1129
1160
  import { z as z2 } from "zod";
1130
1161
  function text2(payload, isError = false) {
1131
1162
  return {
@@ -1139,7 +1170,127 @@ function fail2(err) {
1139
1170
  true
1140
1171
  );
1141
1172
  }
1142
- var prioritySchema = z2.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
1173
+ function registerAbleTimeTools(server) {
1174
+ server.tool(
1175
+ "abletime_orientation",
1176
+ "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.",
1177
+ {},
1178
+ async () => {
1179
+ try {
1180
+ const orientation = await getAbleTimeOrientation();
1181
+ return text2({
1182
+ viewer: orientation.viewer,
1183
+ projects: orientation.projects,
1184
+ tasks: orientation.tasks.map(toAbleTimeIssueInfo)
1185
+ });
1186
+ } catch (err) {
1187
+ return fail2(err);
1188
+ }
1189
+ }
1190
+ );
1191
+ server.tool(
1192
+ "abletime_list_projects",
1193
+ "List AbleTime projects (and categories) the Account token can see. Use a project id on abletime_create_task / abletime_ensure_task.",
1194
+ {},
1195
+ async () => {
1196
+ try {
1197
+ return text2({ projects: await listAbleTimeProjects() });
1198
+ } catch (err) {
1199
+ return fail2(err);
1200
+ }
1201
+ }
1202
+ );
1203
+ server.tool(
1204
+ "abletime_list_tasks",
1205
+ "List open AbleTime tasks (excludes done/archived). Optional projectId filter.",
1206
+ { projectId: z2.string().optional() },
1207
+ async ({ projectId }) => {
1208
+ try {
1209
+ const tasks = await listAbleTimeTasks({ projectId });
1210
+ return text2({ issues: tasks.map(toAbleTimeIssueInfo) });
1211
+ } catch (err) {
1212
+ return fail2(err);
1213
+ }
1214
+ }
1215
+ );
1216
+ server.tool(
1217
+ "abletime_search_tasks",
1218
+ "Search AbleTime tasks by text, reference, or tag.",
1219
+ { query: z2.string() },
1220
+ async ({ query }) => {
1221
+ try {
1222
+ const tasks = await searchAbleTimeTasks(query);
1223
+ return text2({ issues: tasks.map(toAbleTimeIssueInfo) });
1224
+ } catch (err) {
1225
+ return fail2(err);
1226
+ }
1227
+ }
1228
+ );
1229
+ server.tool(
1230
+ "abletime_get_task",
1231
+ "Get one AbleTime task by id or reference (e.g. CRM-232).",
1232
+ { id: z2.string() },
1233
+ async ({ id }) => {
1234
+ try {
1235
+ return text2(toAbleTimeIssueInfo(await getAbleTimeTask(id)));
1236
+ } catch (err) {
1237
+ return fail2(err);
1238
+ }
1239
+ }
1240
+ );
1241
+ server.tool(
1242
+ "abletime_create_task",
1243
+ "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).",
1244
+ {
1245
+ title: z2.string(),
1246
+ description: z2.string().optional(),
1247
+ projectId: z2.string().optional(),
1248
+ categoryId: z2.string().optional(),
1249
+ state: z2.enum(["backlog", "todo"]).optional()
1250
+ },
1251
+ async (args) => {
1252
+ try {
1253
+ return text2(toAbleTimeIssueInfo(await createAbleTimeTask(args)));
1254
+ } catch (err) {
1255
+ return fail2(err);
1256
+ }
1257
+ }
1258
+ );
1259
+ server.tool(
1260
+ "abletime_ensure_task",
1261
+ "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.",
1262
+ {
1263
+ title: z2.string(),
1264
+ description: z2.string().optional(),
1265
+ projectId: z2.string().optional(),
1266
+ categoryId: z2.string().optional()
1267
+ },
1268
+ async (args) => {
1269
+ try {
1270
+ const task = await ensureAbleTimeTask(args);
1271
+ return text2({ ...toAbleTimeIssueInfo(task), created: task.created });
1272
+ } catch (err) {
1273
+ return fail2(err);
1274
+ }
1275
+ }
1276
+ );
1277
+ }
1278
+
1279
+ // src/mcp/linear-tools.ts
1280
+ import { z as z3 } from "zod";
1281
+ function text3(payload, isError = false) {
1282
+ return {
1283
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
1284
+ ...isError ? { isError: true } : {}
1285
+ };
1286
+ }
1287
+ function fail3(err) {
1288
+ return text3(
1289
+ { error: err instanceof Error ? err.message : String(err) },
1290
+ true
1291
+ );
1292
+ }
1293
+ var prioritySchema = z3.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
1143
1294
  function registerLinearTools(server) {
1144
1295
  server.tool(
1145
1296
  "linear_list_teams",
@@ -1147,21 +1298,21 @@ function registerLinearTools(server) {
1147
1298
  {},
1148
1299
  async () => {
1149
1300
  try {
1150
- return text2(await listLinearTeams());
1301
+ return text3(await listLinearTeams());
1151
1302
  } catch (err) {
1152
- return fail2(err);
1303
+ return fail3(err);
1153
1304
  }
1154
1305
  }
1155
1306
  );
1156
1307
  server.tool(
1157
1308
  "linear_get_issue",
1158
1309
  "Get a Linear issue by uuid or identifier (ENG-123).",
1159
- { id: z2.string() },
1310
+ { id: z3.string() },
1160
1311
  async ({ id }) => {
1161
1312
  try {
1162
- return text2(await getLinearIssue(id));
1313
+ return text3(await getLinearIssue(id));
1163
1314
  } catch (err) {
1164
- return fail2(err);
1315
+ return fail3(err);
1165
1316
  }
1166
1317
  }
1167
1318
  );
@@ -1169,18 +1320,18 @@ function registerLinearTools(server) {
1169
1320
  "linear_create_issue",
1170
1321
  '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.',
1171
1322
  {
1172
- team: z2.string(),
1173
- title: z2.string(),
1174
- description: z2.string().optional(),
1175
- state: z2.string().optional(),
1176
- assignee: z2.string().nullable().optional(),
1323
+ team: z3.string(),
1324
+ title: z3.string(),
1325
+ description: z3.string().optional(),
1326
+ state: z3.string().optional(),
1327
+ assignee: z3.string().nullable().optional(),
1177
1328
  priority: prioritySchema
1178
1329
  },
1179
1330
  async (args) => {
1180
1331
  try {
1181
- return text2(await createLinearIssue(args));
1332
+ return text3(await createLinearIssue(args));
1182
1333
  } catch (err) {
1183
- return fail2(err);
1334
+ return fail3(err);
1184
1335
  }
1185
1336
  }
1186
1337
  );
@@ -1188,18 +1339,18 @@ function registerLinearTools(server) {
1188
1339
  "linear_update_issue",
1189
1340
  '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.',
1190
1341
  {
1191
- id: z2.string(),
1192
- title: z2.string().optional(),
1193
- description: z2.string().optional(),
1194
- state: z2.string().optional(),
1195
- assignee: z2.string().nullable().optional(),
1342
+ id: z3.string(),
1343
+ title: z3.string().optional(),
1344
+ description: z3.string().optional(),
1345
+ state: z3.string().optional(),
1346
+ assignee: z3.string().nullable().optional(),
1196
1347
  priority: prioritySchema
1197
1348
  },
1198
1349
  async (args) => {
1199
1350
  try {
1200
- return text2(await updateLinearIssue(args));
1351
+ return text3(await updateLinearIssue(args));
1201
1352
  } catch (err) {
1202
- return fail2(err);
1353
+ return fail3(err);
1203
1354
  }
1204
1355
  }
1205
1356
  );
@@ -1207,29 +1358,29 @@ function registerLinearTools(server) {
1207
1358
  "linear_comment",
1208
1359
  "Add a markdown comment on a Linear issue (uuid or ENG-123).",
1209
1360
  {
1210
- id: z2.string(),
1211
- body: z2.string()
1361
+ id: z3.string(),
1362
+ body: z3.string()
1212
1363
  },
1213
1364
  async (args) => {
1214
1365
  try {
1215
- return text2(await commentLinearIssue(args));
1366
+ return text3(await commentLinearIssue(args));
1216
1367
  } catch (err) {
1217
- return fail2(err);
1368
+ return fail3(err);
1218
1369
  }
1219
1370
  }
1220
1371
  );
1221
1372
  }
1222
1373
 
1223
1374
  // src/mcp/schedule-tools.ts
1224
- import { z as z3 } from "zod";
1225
- function text3(payload, isError = false) {
1375
+ import { z as z4 } from "zod";
1376
+ function text4(payload, isError = false) {
1226
1377
  return {
1227
1378
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
1228
1379
  ...isError ? { isError: true } : {}
1229
1380
  };
1230
1381
  }
1231
- function fail3(err) {
1232
- return text3(
1382
+ function fail4(err) {
1383
+ return text4(
1233
1384
  { error: err instanceof Error ? err.message : String(err) },
1234
1385
  true
1235
1386
  );
@@ -1254,9 +1405,9 @@ function registerScheduleTools(server) {
1254
1405
  {},
1255
1406
  async () => {
1256
1407
  try {
1257
- return text3({ schedules: listSchedules() });
1408
+ return text4({ schedules: listSchedules() });
1258
1409
  } catch (err) {
1259
- return fail3(err);
1410
+ return fail4(err);
1260
1411
  }
1261
1412
  }
1262
1413
  );
@@ -1264,24 +1415,24 @@ function registerScheduleTools(server) {
1264
1415
  "create_schedule",
1265
1416
  "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.",
1266
1417
  {
1267
- prompt: z3.string().describe("User message / goal queued when the schedule fires"),
1268
- name: z3.string().optional(),
1269
- at: z3.string().optional().describe("ISO datetime for a one-shot"),
1270
- every: z3.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
1271
- cron: z3.string().optional().describe("5-field cron expression"),
1272
- tz: z3.string().optional().describe("IANA timezone for cron (default: system)"),
1273
- threadId: z3.string().optional().describe(
1418
+ prompt: z4.string().describe("User message / goal queued when the schedule fires"),
1419
+ name: z4.string().optional(),
1420
+ at: z4.string().optional().describe("ISO datetime for a one-shot"),
1421
+ every: z4.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
1422
+ cron: z4.string().optional().describe("5-field cron expression"),
1423
+ tz: z4.string().optional().describe("IANA timezone for cron (default: system)"),
1424
+ threadId: z4.string().optional().describe(
1274
1425
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
1275
1426
  ),
1276
- agent: z3.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
1277
- model: z3.string().optional()
1427
+ agent: z4.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
1428
+ model: z4.string().optional()
1278
1429
  },
1279
1430
  async (args) => {
1280
1431
  try {
1281
1432
  const when = parseWhen(args);
1282
1433
  const threadId = resolveScheduleThreadId(args.threadId);
1283
1434
  if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
1284
- return fail3(
1435
+ return fail4(
1285
1436
  new Error("threadId=self requires this turn to be an orchestration chat")
1286
1437
  );
1287
1438
  }
@@ -1294,12 +1445,12 @@ function registerScheduleTools(server) {
1294
1445
  model: args.model,
1295
1446
  createdBy: "mcp"
1296
1447
  });
1297
- return text3({
1448
+ return text4({
1298
1449
  schedule,
1299
1450
  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."
1300
1451
  });
1301
1452
  } catch (err) {
1302
- return fail3(err);
1453
+ return fail4(err);
1303
1454
  }
1304
1455
  }
1305
1456
  );
@@ -1307,17 +1458,17 @@ function registerScheduleTools(server) {
1307
1458
  "update_schedule",
1308
1459
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
1309
1460
  {
1310
- id: z3.string(),
1311
- prompt: z3.string().optional(),
1312
- name: z3.string().optional(),
1313
- at: z3.string().optional(),
1314
- every: z3.string().optional(),
1315
- cron: z3.string().optional(),
1316
- tz: z3.string().optional(),
1317
- threadId: z3.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
1318
- agent: z3.enum(["claude", "cursor", "codex", "opencode"]).optional(),
1319
- model: z3.string().optional(),
1320
- enabled: z3.boolean().optional()
1461
+ id: z4.string(),
1462
+ prompt: z4.string().optional(),
1463
+ name: z4.string().optional(),
1464
+ at: z4.string().optional(),
1465
+ every: z4.string().optional(),
1466
+ cron: z4.string().optional(),
1467
+ tz: z4.string().optional(),
1468
+ threadId: z4.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
1469
+ agent: z4.enum(["claude", "cursor", "codex", "opencode"]).optional(),
1470
+ model: z4.string().optional(),
1471
+ enabled: z4.boolean().optional()
1321
1472
  },
1322
1473
  async (args) => {
1323
1474
  try {
@@ -1332,7 +1483,7 @@ function registerScheduleTools(server) {
1332
1483
  if (args.threadId !== void 0) {
1333
1484
  threadId = resolveScheduleThreadId(args.threadId);
1334
1485
  if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
1335
- return fail3(
1486
+ return fail4(
1336
1487
  new Error("threadId=self requires this turn to be an orchestration chat")
1337
1488
  );
1338
1489
  }
@@ -1346,38 +1497,38 @@ function registerScheduleTools(server) {
1346
1497
  enabled: args.enabled,
1347
1498
  model: args.model
1348
1499
  });
1349
- return text3({ schedule });
1500
+ return text4({ schedule });
1350
1501
  } catch (err) {
1351
- return fail3(err);
1502
+ return fail4(err);
1352
1503
  }
1353
1504
  }
1354
1505
  );
1355
1506
  server.tool(
1356
1507
  "delete_schedule",
1357
1508
  "Delete a local schedule. Pass id from list_schedules.",
1358
- { id: z3.string() },
1509
+ { id: z4.string() },
1359
1510
  async ({ id }) => {
1360
1511
  try {
1361
1512
  deleteSchedule(id);
1362
- return text3({ ok: true, id });
1513
+ return text4({ ok: true, id });
1363
1514
  } catch (err) {
1364
- return fail3(err);
1515
+ return fail4(err);
1365
1516
  }
1366
1517
  }
1367
1518
  );
1368
1519
  server.tool(
1369
1520
  "run_schedule",
1370
1521
  "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.",
1371
- { id: z3.string() },
1522
+ { id: z4.string() },
1372
1523
  async ({ id }) => {
1373
1524
  try {
1374
1525
  if (!getSchedule(id)) {
1375
- return fail3(new Error(`Schedule not found: ${id}`));
1526
+ return fail4(new Error(`Schedule not found: ${id}`));
1376
1527
  }
1377
1528
  const schedule = await fireSchedule(id);
1378
- return text3({ schedule });
1529
+ return text4({ schedule });
1379
1530
  } catch (err) {
1380
- return fail3(err);
1531
+ return fail4(err);
1381
1532
  }
1382
1533
  }
1383
1534
  );
@@ -1488,7 +1639,7 @@ async function loadHomeBoardInputs(workspaces) {
1488
1639
  const first = await listIssues(paths[0]);
1489
1640
  issueSource = first.source;
1490
1641
  viewerLogin = first.viewer?.login || first.viewer?.name || void 0;
1491
- if (first.source === "linear") {
1642
+ if (isAccountWideIssueSource(first.source)) {
1492
1643
  const repoPath = paths[0] ?? "";
1493
1644
  issues = first.issues.map((issue) => ({
1494
1645
  ...issue,
@@ -1716,7 +1867,7 @@ async function startMcpServer() {
1716
1867
  );
1717
1868
  }
1718
1869
  try {
1719
- const { maxConcurrentAgents } = await import("../app-settings-E7NEQY7D.js");
1870
+ const { maxConcurrentAgents } = await import("../app-settings-YQ7YX2CC.js");
1720
1871
  orch.setMaxConcurrent(maxConcurrentAgents());
1721
1872
  } catch {
1722
1873
  }
@@ -1774,13 +1925,13 @@ async function startMcpServer() {
1774
1925
  "list_board",
1775
1926
  "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.",
1776
1927
  {
1777
- query: z4.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
1778
- repoPath: z4.string().optional().describe("Limit to one workspace path from list_workspaces"),
1779
- kind: z4.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
1780
- column: z4.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
1928
+ query: z5.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
1929
+ repoPath: z5.string().optional().describe("Limit to one workspace path from list_workspaces"),
1930
+ kind: z5.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
1931
+ column: z5.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
1781
1932
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
1782
1933
  ),
1783
- limit: z4.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
1934
+ limit: z5.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
1784
1935
  },
1785
1936
  async ({ query, repoPath, kind, column, limit }) => {
1786
1937
  const workspaces = orch.listWorkspaces();
@@ -1818,7 +1969,7 @@ async function startMcpServer() {
1818
1969
  server.tool(
1819
1970
  "get_thread",
1820
1971
  "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.",
1821
- { ref: z4.string() },
1972
+ { ref: z5.string() },
1822
1973
  async ({ ref }) => {
1823
1974
  const t = orch.getThread(ref);
1824
1975
  if (!t) {
@@ -1855,17 +2006,17 @@ async function startMcpServer() {
1855
2006
  "present_artifact",
1856
2007
  "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.",
1857
2008
  {
1858
- title: z4.string().describe("Short title shown in the artifact pane header"),
1859
- type: z4.enum(["html", "svg", "markdown", "react", "log"]).describe(
2009
+ title: z5.string().describe("Short title shown in the artifact pane header"),
2010
+ type: z5.enum(["html", "svg", "markdown", "react", "log"]).describe(
1860
2011
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
1861
2012
  ),
1862
- content: z4.string().describe(
2013
+ content: z5.string().describe(
1863
2014
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
1864
2015
  ),
1865
- artifact_id: z4.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
1866
- status: z4.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
1867
- phase: z4.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
1868
- mode: z4.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
2016
+ artifact_id: z5.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
2017
+ status: z5.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
2018
+ phase: z5.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
2019
+ mode: z5.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
1869
2020
  },
1870
2021
  async ({ title, type, artifact_id, status, phase, mode }) => {
1871
2022
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -1886,15 +2037,15 @@ async function startMcpServer() {
1886
2037
  "ask_user",
1887
2038
  "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.",
1888
2039
  {
1889
- questions: z4.array(
1890
- z4.object({
1891
- question: z4.string().describe("Full question text ending with ?"),
1892
- header: z4.string().max(24).optional().describe("Short label shown above the question"),
1893
- multiSelect: z4.boolean().optional().describe("Allow selecting multiple options"),
1894
- options: z4.array(
1895
- z4.object({
1896
- label: z4.string(),
1897
- description: z4.string().optional().describe("What this option means / when to choose it (strongly preferred)")
2040
+ questions: z5.array(
2041
+ z5.object({
2042
+ question: z5.string().describe("Full question text ending with ?"),
2043
+ header: z5.string().max(24).optional().describe("Short label shown above the question"),
2044
+ multiSelect: z5.boolean().optional().describe("Allow selecting multiple options"),
2045
+ options: z5.array(
2046
+ z5.object({
2047
+ label: z5.string(),
2048
+ description: z5.string().optional().describe("What this option means / when to choose it (strongly preferred)")
1898
2049
  })
1899
2050
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
1900
2051
  })
@@ -1913,9 +2064,9 @@ async function startMcpServer() {
1913
2064
  "present_plan",
1914
2065
  "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.",
1915
2066
  {
1916
- title: z4.string().optional().describe("Short plan title (defaults to Plan)"),
1917
- content: z4.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
1918
- thread_id: z4.string().optional().describe("Sideboard thread id when cwd is not the worktree")
2067
+ title: z5.string().optional().describe("Short plan title (defaults to Plan)"),
2068
+ content: z5.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
2069
+ thread_id: z5.string().optional().describe("Sideboard thread id when cwd is not the worktree")
1919
2070
  },
1920
2071
  async ({ title, content, thread_id }) => {
1921
2072
  const { writePlanFile } = await import("../plan-file-PHVKUAEE.js");
@@ -1938,15 +2089,15 @@ async function startMcpServer() {
1938
2089
  "present_schema",
1939
2090
  "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.",
1940
2091
  {
1941
- title: z4.string().describe("Pane title"),
1942
- mode: z4.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
1943
- datasource: z4.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
1944
- resource_id: z4.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
1945
- record_id: z4.string().optional().describe("Record id when opening form mode"),
1946
- resource: z4.record(z4.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
1947
- record: z4.record(z4.unknown()).optional().describe("Inline record { id, data, published_at? }"),
1948
- records: z4.array(z4.record(z4.unknown())).optional().describe("Inline records for table mode"),
1949
- pane_id: z4.string().optional().describe("Stable pane id across updates")
2092
+ title: z5.string().describe("Pane title"),
2093
+ mode: z5.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
2094
+ datasource: z5.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
2095
+ resource_id: z5.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
2096
+ record_id: z5.string().optional().describe("Record id when opening form mode"),
2097
+ resource: z5.record(z5.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
2098
+ record: z5.record(z5.unknown()).optional().describe("Inline record { id, data, published_at? }"),
2099
+ records: z5.array(z5.record(z5.unknown())).optional().describe("Inline records for table mode"),
2100
+ pane_id: z5.string().optional().describe("Stable pane id across updates")
1950
2101
  },
1951
2102
  async (args) => {
1952
2103
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -1967,10 +2118,10 @@ async function startMcpServer() {
1967
2118
  "present_files",
1968
2119
  "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.",
1969
2120
  {
1970
- title: z4.string().optional().describe("Pane title (default: Files)"),
1971
- datasource: z4.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
1972
- path: z4.string().optional().describe("Initial folder path (e.g. public)"),
1973
- pane_id: z4.string().optional().describe("Stable pane id across updates")
2121
+ title: z5.string().optional().describe("Pane title (default: Files)"),
2122
+ datasource: z5.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
2123
+ path: z5.string().optional().describe("Initial folder path (e.g. public)"),
2124
+ pane_id: z5.string().optional().describe("Stable pane id across updates")
1974
2125
  },
1975
2126
  async (args) => {
1976
2127
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -1988,16 +2139,17 @@ async function startMcpServer() {
1988
2139
  if (!worktreeProfile) {
1989
2140
  registerSlackTools(server);
1990
2141
  registerLinearTools(server);
2142
+ registerAbleTimeTools(server);
1991
2143
  registerScheduleTools(server);
1992
2144
  const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-NBRUWYXO.js");
1993
- const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-E7NEQY7D.js");
2145
+ const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-YQ7YX2CC.js");
1994
2146
  const accountDefaults = resolveThreadDefaults();
1995
2147
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
1996
2148
  server.tool(
1997
2149
  "set_caffeinate",
1998
2150
  "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.",
1999
2151
  {
2000
- enabled: z4.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
2152
+ enabled: z5.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
2001
2153
  },
2002
2154
  async ({ enabled }) => {
2003
2155
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -2035,18 +2187,18 @@ async function startMcpServer() {
2035
2187
  "create_thread",
2036
2188
  `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.`,
2037
2189
  {
2038
- sourceType: z4.enum(["branch", "pr", "ticket"]),
2039
- sourceRef: z4.string(),
2040
- agent: z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
2041
- model: z4.string().nullable().optional().describe(
2190
+ sourceType: z5.enum(["branch", "pr", "ticket"]),
2191
+ sourceRef: z5.string(),
2192
+ agent: z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
2193
+ model: z5.string().nullable().optional().describe(
2042
2194
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
2043
2195
  ),
2044
- repoPath: z4.string(),
2045
- title: z4.string().optional(),
2046
- cowboy: z4.boolean().optional().describe(
2196
+ repoPath: z5.string(),
2197
+ title: z5.string().optional(),
2198
+ cowboy: z5.boolean().optional().describe(
2047
2199
  "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."
2048
2200
  ),
2049
- parentThreadId: z4.string().optional().describe(
2201
+ parentThreadId: z5.string().optional().describe(
2050
2202
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
2051
2203
  )
2052
2204
  },
@@ -2062,10 +2214,10 @@ async function startMcpServer() {
2062
2214
  "start_board_card",
2063
2215
  "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.",
2064
2216
  {
2065
- kind: z4.enum(["ticket", "pr", "branch"]),
2066
- ref: z4.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
2067
- repoPath: z4.string().describe("Workspace path from list_workspaces / list_board"),
2068
- title: z4.string().optional()
2217
+ kind: z5.enum(["ticket", "pr", "branch"]),
2218
+ ref: z5.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
2219
+ repoPath: z5.string().describe("Workspace path from list_workspaces / list_board"),
2220
+ title: z5.string().optional()
2069
2221
  },
2070
2222
  async ({ kind, ref, repoPath, title }) => {
2071
2223
  const root = await resolveRepoRoot(repoPath);
@@ -2169,9 +2321,9 @@ async function startMcpServer() {
2169
2321
  "send_to_thread",
2170
2322
  '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.',
2171
2323
  {
2172
- ref: z4.string(),
2173
- prompt: z4.string(),
2174
- force_stop: z4.boolean().optional()
2324
+ ref: z5.string(),
2325
+ prompt: z5.string(),
2326
+ force_stop: z5.boolean().optional()
2175
2327
  },
2176
2328
  async ({ ref, prompt, force_stop }) => {
2177
2329
  if (force_stop) {
@@ -2200,8 +2352,8 @@ async function startMcpServer() {
2200
2352
  "wait_for_turn",
2201
2353
  "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).",
2202
2354
  {
2203
- ref: z4.string(),
2204
- timeoutMs: z4.number().optional()
2355
+ ref: z5.string(),
2356
+ timeoutMs: z5.number().optional()
2205
2357
  },
2206
2358
  async ({ ref, timeoutMs }) => {
2207
2359
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -2230,7 +2382,7 @@ async function startMcpServer() {
2230
2382
  server.tool(
2231
2383
  "get_turn_result",
2232
2384
  "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).",
2233
- { ref: z4.string() },
2385
+ { ref: z5.string() },
2234
2386
  async ({ ref }) => {
2235
2387
  const result = orch.getTurnResult(ref);
2236
2388
  return {
@@ -2250,8 +2402,8 @@ async function startMcpServer() {
2250
2402
  "stop_thread",
2251
2403
  "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.",
2252
2404
  {
2253
- ref: z4.string(),
2254
- force: z4.boolean().optional()
2405
+ ref: z5.string(),
2406
+ force: z5.boolean().optional()
2255
2407
  },
2256
2408
  async ({ ref, force }) => {
2257
2409
  const t = orch.getThread(ref);
@@ -2281,7 +2433,7 @@ async function startMcpServer() {
2281
2433
  server.tool(
2282
2434
  "archive_thread",
2283
2435
  "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.",
2284
- { ref: z4.string() },
2436
+ { ref: z5.string() },
2285
2437
  async ({ ref }) => {
2286
2438
  const t = orch.getThread(ref);
2287
2439
  if (!t) {
@@ -2314,7 +2466,7 @@ async function startMcpServer() {
2314
2466
  server.tool(
2315
2467
  "restore_thread",
2316
2468
  "Restore an archived thread (recreates worktree from branch when needed)",
2317
- { ref: z4.string() },
2469
+ { ref: z5.string() },
2318
2470
  async ({ ref }) => {
2319
2471
  try {
2320
2472
  const restored = await orch.restore(ref);
@@ -2340,8 +2492,8 @@ async function startMcpServer() {
2340
2492
  "get_diff",
2341
2493
  "Compact diff summary (capped hunks, paginated)",
2342
2494
  {
2343
- ref: z4.string(),
2344
- maxFiles: z4.number().optional()
2495
+ ref: z5.string(),
2496
+ maxFiles: z5.number().optional()
2345
2497
  },
2346
2498
  async ({ ref, maxFiles }) => {
2347
2499
  const summary = await orch.diffSummary(ref);
@@ -2361,7 +2513,7 @@ async function startMcpServer() {
2361
2513
  server.tool(
2362
2514
  "request_review",
2363
2515
  '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.',
2364
- { ref: z4.string().describe("Worktree thread id/ref to review") },
2516
+ { ref: z5.string().describe("Worktree thread id/ref to review") },
2365
2517
  async ({ ref }) => {
2366
2518
  try {
2367
2519
  const tab = await orch.requestReview(ref);
@@ -2390,8 +2542,8 @@ async function startMcpServer() {
2390
2542
  "ask_git",
2391
2543
  "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.",
2392
2544
  {
2393
- ref: z4.string().describe("Worktree thread id/ref"),
2394
- action: z4.enum(AGENT_GIT_ACTIONS).describe(
2545
+ ref: z5.string().describe("Worktree thread id/ref"),
2546
+ action: z5.enum(AGENT_GIT_ACTIONS).describe(
2395
2547
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
2396
2548
  )
2397
2549
  },
@@ -2418,7 +2570,7 @@ async function startMcpServer() {
2418
2570
  }
2419
2571
  }
2420
2572
  );
2421
- const agentEnum = z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
2573
+ const agentEnum = z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
2422
2574
  server.tool(
2423
2575
  "list_models",
2424
2576
  "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.",
@@ -2441,11 +2593,11 @@ async function startMcpServer() {
2441
2593
  "fork_worktree",
2442
2594
  "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.",
2443
2595
  {
2444
- ref: z4.string().describe("Worktree thread id/ref to fork"),
2445
- through_index: z4.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
2596
+ ref: z5.string().describe("Worktree thread id/ref to fork"),
2597
+ through_index: z5.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
2446
2598
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
2447
- model: z4.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
2448
- title: z4.string().optional()
2599
+ model: z5.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
2600
+ title: z5.string().optional()
2449
2601
  },
2450
2602
  async ({ ref, through_index, agent, model, title }) => {
2451
2603
  try {
@@ -2486,11 +2638,11 @@ async function startMcpServer() {
2486
2638
  "fork_chat",
2487
2639
  "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.",
2488
2640
  {
2489
- ref: z4.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
2490
- through_index: z4.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
2641
+ ref: z5.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
2642
+ through_index: z5.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
2491
2643
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
2492
- model: z4.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
2493
- title: z4.string().optional()
2644
+ model: z5.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
2645
+ title: z5.string().optional()
2494
2646
  },
2495
2647
  async ({ ref, through_index, agent, model, title }) => {
2496
2648
  try {
@@ -2536,8 +2688,8 @@ async function startMcpServer() {
2536
2688
  "run_dev_script",
2537
2689
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
2538
2690
  {
2539
- ref: z4.string(),
2540
- name: z4.string().optional()
2691
+ ref: z5.string(),
2692
+ name: z5.string().optional()
2541
2693
  },
2542
2694
  async ({ ref, name }) => {
2543
2695
  const result = await orch.startDev(ref, name);
@@ -2559,7 +2711,7 @@ async function startMcpServer() {
2559
2711
  server.tool(
2560
2712
  "list_run_scripts",
2561
2713
  "List named run scripts available for a thread",
2562
- { ref: z4.string() },
2714
+ { ref: z5.string() },
2563
2715
  async ({ ref }) => {
2564
2716
  const scripts = orch.listThreadRunScripts(ref);
2565
2717
  const active = orch.getActiveRuns(ref);
@@ -2577,8 +2729,8 @@ async function startMcpServer() {
2577
2729
  "stop_dev_script",
2578
2730
  "Stop a running script for a thread (all scripts if name omitted)",
2579
2731
  {
2580
- ref: z4.string(),
2581
- name: z4.string().optional()
2732
+ ref: z5.string(),
2733
+ name: z5.string().optional()
2582
2734
  },
2583
2735
  async ({ ref, name }) => {
2584
2736
  orch.stopDev(ref, name);
@@ -2588,7 +2740,7 @@ async function startMcpServer() {
2588
2740
  server.tool(
2589
2741
  "run_setup",
2590
2742
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
2591
- { ref: z4.string() },
2743
+ { ref: z5.string() },
2592
2744
  async ({ ref }) => {
2593
2745
  const result = await orch.runSetup(ref);
2594
2746
  return {
@@ -2599,7 +2751,7 @@ async function startMcpServer() {
2599
2751
  server.tool(
2600
2752
  "add_workspace",
2601
2753
  "Register a git repo as a Sideboard workspace",
2602
- { repoPath: z4.string() },
2754
+ { repoPath: z5.string() },
2603
2755
  async ({ repoPath }) => {
2604
2756
  const ws = await orch.addWorkspace(repoPath);
2605
2757
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -2608,7 +2760,7 @@ async function startMcpServer() {
2608
2760
  server.tool(
2609
2761
  "remove_workspace",
2610
2762
  "Unregister a Sideboard workspace (does not archive threads)",
2611
- { repoPath: z4.string() },
2763
+ { repoPath: z5.string() },
2612
2764
  async ({ repoPath }) => {
2613
2765
  orch.removeWorkspace(repoPath);
2614
2766
  return { content: [{ type: "text", text: "ok" }] };
@@ -2618,12 +2770,12 @@ async function startMcpServer() {
2618
2770
  "fanout",
2619
2771
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
2620
2772
  {
2621
- prompt: z4.string(),
2622
- agents: z4.array(z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
2623
- repoPath: z4.string(),
2624
- sourceType: z4.enum(["branch", "pr", "ticket"]).optional(),
2625
- sourceRef: z4.string().optional(),
2626
- title: z4.string().optional()
2773
+ prompt: z5.string(),
2774
+ agents: z5.array(z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
2775
+ repoPath: z5.string(),
2776
+ sourceType: z5.enum(["branch", "pr", "ticket"]).optional(),
2777
+ sourceRef: z5.string().optional(),
2778
+ title: z5.string().optional()
2627
2779
  },
2628
2780
  async (args) => {
2629
2781
  const threads = await orch.bestOfN(args);
@@ -2650,8 +2802,8 @@ async function startMcpServer() {
2650
2802
  "list_branches",
2651
2803
  "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).",
2652
2804
  {
2653
- repoPath: z4.string(),
2654
- unmergedOnly: z4.boolean().optional()
2805
+ repoPath: z5.string(),
2806
+ unmergedOnly: z5.boolean().optional()
2655
2807
  },
2656
2808
  async ({ repoPath, unmergedOnly }) => {
2657
2809
  const root = await resolveRepoRoot(repoPath);
@@ -2671,7 +2823,7 @@ async function startMcpServer() {
2671
2823
  server.tool(
2672
2824
  "list_prs",
2673
2825
  "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.",
2674
- { repoPath: z4.string() },
2826
+ { repoPath: z5.string() },
2675
2827
  async ({ repoPath }) => {
2676
2828
  const root = await resolveRepoRoot(repoPath);
2677
2829
  const prs = await listPrs(root);
@@ -2690,7 +2842,7 @@ async function startMcpServer() {
2690
2842
  server.tool(
2691
2843
  "get_pr_stack",
2692
2844
  "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).",
2693
- { ref: z4.string() },
2845
+ { ref: z5.string() },
2694
2846
  async ({ ref }) => {
2695
2847
  const stack = await orch.getPrStack(ref);
2696
2848
  return {
@@ -2702,8 +2854,8 @@ async function startMcpServer() {
2702
2854
  "open_pr_stack_layers",
2703
2855
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
2704
2856
  {
2705
- ref: z4.string(),
2706
- layer: z4.number().int().positive().optional()
2857
+ ref: z5.string(),
2858
+ layer: z5.number().int().positive().optional()
2707
2859
  },
2708
2860
  async ({ ref, layer }) => {
2709
2861
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -2737,9 +2889,9 @@ async function startMcpServer() {
2737
2889
  "add_stack_layer",
2738
2890
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
2739
2891
  {
2740
- ref: z4.string(),
2741
- branchName: z4.string(),
2742
- title: z4.string().optional()
2892
+ ref: z5.string(),
2893
+ branchName: z5.string(),
2894
+ title: z5.string().optional()
2743
2895
  },
2744
2896
  async ({ ref, branchName, title }) => {
2745
2897
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -2768,11 +2920,11 @@ async function startMcpServer() {
2768
2920
  "create_pr_stack",
2769
2921
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
2770
2922
  {
2771
- repoPath: z4.string(),
2772
- branches: z4.array(z4.string()).min(1),
2773
- agent: z4.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
2774
- base: z4.string().optional(),
2775
- title: z4.string().optional()
2923
+ repoPath: z5.string(),
2924
+ branches: z5.array(z5.string()).min(1),
2925
+ agent: z5.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
2926
+ base: z5.string().optional(),
2927
+ title: z5.string().optional()
2776
2928
  },
2777
2929
  async (args) => {
2778
2930
  const result = await orch.createPrStack({
@@ -2809,8 +2961,8 @@ async function startMcpServer() {
2809
2961
  );
2810
2962
  server.tool(
2811
2963
  "list_issues",
2812
- "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.",
2813
- { repoPath: z4.string() },
2964
+ "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.",
2965
+ { repoPath: z5.string() },
2814
2966
  async ({ repoPath }) => {
2815
2967
  const root = await resolveRepoRoot(repoPath);
2816
2968
  const result = await listIssues(root);
@@ -2821,8 +2973,8 @@ async function startMcpServer() {
2821
2973
  "list_linear_issues",
2822
2974
  "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
2823
2975
  {
2824
- agent: z4.enum(["claude", "codex", "opencode"]),
2825
- repoPath: z4.string()
2976
+ agent: z5.enum(["claude", "codex", "opencode"]),
2977
+ repoPath: z5.string()
2826
2978
  },
2827
2979
  async ({ agent, repoPath }) => {
2828
2980
  const issues = await listLinearIssues(agent, repoPath);